[XP] Heretic's Collision Optimizer

Started by Heretic86, April 29, 2015, 08:10:01 pm

Previous topic - Next topic

Heretic86

April 29, 2015, 08:10:01 pm Last Edit: April 21, 2016, 05:37:27 am by Heretic86
Collision Optimizer XP
Authors: Heretic
Version: 1.01
Type: Custom Movement System
Key Term: Custom Movement System



Introduction

The way the default code handles Collision is extremely inefficient.  This script cleans that up by only checking for Events at a specific location instead of every single Event every time any Event moves.

This script also doubles as a Framework for much faster checking of Events at locations, which can allow for additional scripts to use Events for many other things that would normally cause to much lag to be worth while.


Features


  • Exponentially increases Collision and Trigger Performance

  • Checks Collisions with Events that use Decimal @X @y Coordinates




Screenshots

No screenshots.


Demo

http://downloads.chaos-project.com/heretic86/MP/CollisionOptimizer.exe


Script

Place below Modular Passable (Required)
Place above Loop Maps (Optional)

Spoiler: ShowHide
#===============================================================================
#
#           HERETIC'S MODULAR COLLISION OPTIMIZER [XP]
#           Version 1.01
#           Monday, April 11th, 2016
#
#===============================================================================
#
# ---  Requirements and Installation  ---
#
# *** REQUIRES HERETICS MODULAR PASSABLE SCRIPT ***
#
#  This script should be placed DIRECTLY below Heretic's Modular Passable
#  and ABOVE all other Modular Passable Dependant Scripts.
#
#
# -----  Features  -----
#
# - Exponentially increases Collision and Trigger Performance
# - Checks Collisions with Events that use Decimal @x @y Coordinates
#
#
# -----  History  -----
# Version 1.0 - Saturday, November 29th, 2014
#  * Initial Release
# Version 1.01 - Monday, April 11th, 2016
#  * Fixed a bug that would cause the game to Crash when a save game was loaded
#    if erased did not have a :reader, :accessor, or other method.
#  * Added a method to check the status of @erased
#
#
# -----  Overview  -----
#
#  The more Events you have on a Map, the worse your Performance partly because
#  of Collisions and Trigger checking.  This script works similarly to Self
#  Switches by creating a Hash that uses X and Y coordinates as Keys.  The
#  Keys that access the Data Hash will cause the return of an Array of Events
#  whose position match the Keys.  As a result, the Collision and Trigger
#  checks use Events at that Location.  The performance increase is exponential
#  because each Event checks every other event on every frame, several times
#  in fact because events are again checked for Triggers.  Thus, for 100
#  Events on a Map, with each Event being checked for a coordinate match, there
#  ends up being 100 x 300 = 30,000 checks made every single frame.  This
#  script reduces that number to a mere 300 checks made every frame, thus
#  the performance increase is by orders of magnitude or exponentially better.
#
#  Lag is also caused by the updating of Sprites.  Processing Graphics requires
#  much more processing than just Event Collision and Trigger checking.  This
#  script will help in performance, but does not do anything else for Sprites.
#  Some Anti Lag Scripts will prevent processing Sprites if they are not
#  visible on screen.  Those style of Anti Lag scripts can compliment this
#  script if they are compatible.
#
#  This script is intended for Optimizing Collisions and Triggers.  You can
#  write scripts that build on the functionality of this script where extra
#  Collision or Data processing takes place.  It should also be possible to
#  write scripts that utilize the functionality of this script to use Events
#  with Tile Graphics to also check for Terrain Tags, Bush Flags, Counter Flags
#  or other types of data that would result from conditions of Events with
#  no significant loss in performance.  An additional cycle of scanning
#  Events would result in a Map with 100 Events being checked 400 times, which
#  is still exponentially less than the 30,000 checks made by default.  If
#  the functionality of this script is excluded, checking Events would increase
#  the number of checks made per frame for 100 Map Events from 30,00 to 40,000
#  and that would significantly comprimise performance.  Thus, that is what
#  this script is good for when used as a Scripters Tool.
#
#  -----  Performance Increases  ------
#
#  Lets be serious.  This is what you REALLY want.  Don't be afraid to test
#  it out!  Build a map with a ton of Moving Events that are all right next
#  to each other and box them in so they all only have one or two spots they
#  can move.  Then hit F2 and check your framerate.  Try it again without
#  the script by adding __END__ to the very top, and check your framerate
#  again.  I did this test and the performance increase was probably four
#  times faster than without this script.  Seriously, it really helps
#  that much!  But don't just take my word for it, try this for yourself
#  and see how much you can squeeze out of this script!
#
#
#  -----  Options  -----
#
#  This script has no configurable options. 

# Check for Modular Passable Script - REQUIRED - DO NOT EDIT
unless $Modular_Passable
  print "Fatal Error: Heretic's Modular Collision Optimizer\n",
        "requires Heretics Modular Passable Script!\n\n",
        "Modular Passable is Not Available or is below this script.\n",
        "Modular Passable MUST be above this script.\n\n",
        "The Game must now Exit"
  exit
end

#==============================================================================
# ** Game_Events_XY
#==============================================================================
class Game_Events_XY
  #--------------------------------------------------------------------------
  # * Object Initialization - Game_Events_XY
  #  - Similar to Game_SelfSwitches
  #--------------------------------------------------------------------------
  def initialize
    @data = {}
  end
  #--------------------------------------------------------------------------
  # * Get Events at XY - Game_Events_XY
  #  - Returns Events stored in @data[key] Array
  #     key : [Map X, Map Y]
  #--------------------------------------------------------------------------
  def [](key)
    return (@data[key] ? @data[key] : [])
  end
  #--------------------------------------------------------------------------
  # * Store Event XY - Game_Events_XY
  #     key   : [Map X, Map Y]
  #     event : Game Event
  #--------------------------------------------------------------------------
  def []=(key, event)
    @data[key] = (@data[key]) ? @data[key] << event : [event]
  end
  #--------------------------------------------------------------------------
  # * Delete - Game_Events_XY
  #  - Deletes Event from old Key Value Pair and Garbage Cleanup
  #     key   : [Map X, Map Y]
  #     event : Game_Event to Delete from old Key Value Pair Array
  #--------------------------------------------------------------------------
  def delete(key, event)
    if @data[key]
      # Delete the Event from the Data Key Array
      @data[key].delete(event)
      # Delete Data by Key if No Events are contained within Data Key Array
      @data.delete(key) if @data[key].size == 0
    end
  end
  #--------------------------------------------------------------------------
  # * Data - Game_Events_XY
  #  - Hash Map of Event Locations accessed with Key[x,y] to Array of Events
  #--------------------------------------------------------------------------
  def data
    return @data
  end
end

#==============================================================================
# ** Game_System
#==============================================================================
class Game_System
  #--------------------------------------------------------------------------
  # * Collision Optimizer Version - Constant - Game_System
  #  - Returns the version number
  #--------------------------------------------------------------------------
  Collision_Optimizer_Version = 1.01 
end

#==============================================================================
# ** Game_Map
#==============================================================================
class Game_Map
  #--------------------------------------------------------------------------
  # * Public Instance Variables - Game_Map
  #--------------------------------------------------------------------------
  attr_writer :events_hash_xy  # Hash of Map Events similar to SelfSwitches
  #--------------------------------------------------------------------------
  # * Setup - Game_Map
  #  - Creates a Data Hash in $game_map that works similar to SelfSwitches
  #  - Exponentially faster than iterating through every Map Event as Hash
  #    only returns Events at Location instead of ever single Game_Event
  #      map_id : Map ID
  #--------------------------------------------------------------------------
  alias game_map_events_hash_xy_setup setup unless $@
  def setup(map_id)
    # Current Map
    current_map_id = @map_id
    # If Changing to a New Valid Map
    if map_id > 0 and map_id != @map_id
      # Create or Clear Game_Events_XY
      @events_hash_xy = Game_Events_XY.new
    end
    # Call Original or other Aliases to Initialize and create Events
    game_map_events_hash_xy_setup(map_id)
  end
  #--------------------------------------------------------------------------
  # * Events Hash XY - Game_Map
  #  - Reader method
  #  - Creates New Game_Events_XY Object if value is nil
  #--------------------------------------------------------------------------
  def events_hash_xy
    return @events_hash_xy ||= Game_Events_XY.new
  end
  #--------------------------------------------------------------------------
  # * Create Events Hash - Game_Map
  #  - Creates an Events Hash XY for Save Games when file is loaded
  #--------------------------------------------------------------------------
  def create_events_hash
    # Create or Clear Game_Events_XY
    @events_hash_xy = Game_Events_XY.new
  end
  #--------------------------------------------------------------------------
  # * Make Map Passable List - Game_Map
  #  - Generates an Array of Events to check in Game_Map Passable?
  #  - Only uses X, Y, Results and ignores other available arguments
  #      x, y    : Logical Coordinates on Map
  #      results : Used by other Aliases of this Method
  #--------------------------------------------------------------------------
  alias hash_map_make_map_passable_list make_map_passable_list unless $@
  def make_map_passable_list(x, y, d, bit, self_event = nil, results = nil)
    # Use X and Y as Keys for Array as Value if no other Results
    results = get_events_at_xy(x, y) if results.nil?
    # Call Original or other Aliases
    hash_map_make_map_passable_list(x, y, d, bit, self_event, results)
  end
  #--------------------------------------------------------------------------
  # * Get Events at XY - Game_Map
  #  - Returns Array of stored in Data Hash using X, Y as Keys
  #      x, y : Logical Coordinates on Map used as Has Data[key]
  #-------------------------------------------------------------------------- 
  def get_events_at_xy(x, y)
    # Returns Array stored in Data Hash using X and Y as Data Hash Keys
    return @events_hash_xy[[x,y]]
  end
end

#==============================================================================
# ** Game_Character
#==============================================================================
class Game_Character
  #--------------------------------------------------------------------------
  # * Public Instance Variables - Game_Character
  #--------------------------------------------------------------------------
  attr_accessor   :map_hash_x  # Logical Coordinate X Key in Data Hash[key]
  attr_accessor   :map_hash_y  # Logical Coordinate Y Key in Data Hash[key]
  #--------------------------------------------------------------------------
  # * Make Passable List - Game_Character
  #  - Generates an Array of Events to check in Game_Character Passable?
  #  - Only uses X, Y, Results and ignores other available arguments
  #      new_x, new_y    : Logical Coordinates on Map as Data[key]
  #      results         : Used by other Aliases of this Method
  #--------------------------------------------------------------------------
  alias hash_map_make_passable_list make_passable_list unless $@
  def make_passable_list(x, y, d, new_x, new_y, results = nil)
    # Use New X and New Y as Keys for Array as Value
    results = $game_map.get_events_at_xy(new_x, new_y)
    # Call Original or other Aliases
    hash_map_make_passable_list(x, y, d, new_x, new_y, results)
  end
  #--------------------------------------------------------------------------
  # * Make Events List - Game_Character
  #  - Similar to Make Passable List
  #  - Intended for use in Non Passable determining methods in other Scripts
  #  - Intended to be aliased where X and Y values can be adjusted as needed
  #  - Excludes other Arguments needed by Make Passable List
  #  - Used by other Scripts with Triggers when X or Y have no arguments to fix
  #     x          : x-coordinate
  #     y          : y-coordinate
  #     results    : Array generated by an Alias of this method elsewhere
  #--------------------------------------------------------------------------
  alias hash_map_make_events_list make_events_list unless $@
  def make_events_list(x, y, results = nil)
    # If an Alias has declared an Array of Events, use the Alias result instead
    if results.nil?
      # Use Get Events to create a smaller Array of Events at Coordinates
      results = $game_map.get_events_at_xy(x, y)
    end
    # Call Original or other Aliases with Adjusted Arguments
    hash_map_make_events_list(x, y, results)
  end
  #--------------------------------------------------------------------------
  # * Make New XY Hash Conditions? - Game_Character
  #  - Determines if Event does not match the Stored Hash Keys
  #  - Can be Aliased or Replaced for Looping Maps
  #--------------------------------------------------------------------------
  def make_new_xy_hash_conditions?(x, y)
    (@map_hash_x != x or @map_hash_y != y)
  end
  #--------------------------------------------------------------------------
  # * Make New XY Hash Keys - Game_Character
  #  - Creates X and Y Hash Keys using Rounded X and Y Values
  #  - Can be Aliased or Replaced for Looping Maps
  #--------------------------------------------------------------------------
  def make_new_xy_hash_keys
    return [@x.round, @y.round]
  end
  #--------------------------------------------------------------------------
  # * Store Map Hash Keys - Game_Character
  #  - Corrects the Events Hash Data and stores X, Y Keys to access Data
  #--------------------------------------------------------------------------
  def store_map_hash_keys
    # If Event has Moved
    if make_new_xy_hash_conditions?(@x, @y) and self.is_a?(Game_Event)
      # If Hash Key is Stored
      if @map_hash_x and @map_hash_y
        # Delete Event from Value of Map Hash Key Array
        $game_map.events_hash_xy.delete([@map_hash_x, @map_hash_y], self)
      end
      # Make the New X and Y Hash Keys for Event Location
      x_key, y_key = make_new_xy_hash_keys
      # Add Event to Map Hash -> Array using New Key from Changed Coordinates
      $game_map.events_hash_xy[[x_key, y_key]] = self unless @erased
      # Store Map Hash Keys as Coordinates
      @map_hash_x = x_key
      @map_hash_y = y_key
    end
  end
  #--------------------------------------------------------------------------
  # * Move To - Game_Character
  #  - Called when Event is Initialized to place on Map
  #  - Some Framerate Optimizations exclude Update so Storing the Keys here
  #    will ensure that the Events Hash always has proper Event Coordinates
  #    even when Events are not updated
  #--------------------------------------------------------------------------
  alias map_hash_moveto moveto unless $@
  def moveto(x, y)
    # Call Original or other Aliases
    map_hash_moveto(x, y)
    # Update the Events Hash Map Coordinates for Key -> Value
    store_map_hash_keys
  end
  #--------------------------------------------------------------------------
  # * Update (Frame) - Game_Character
  #  - Monitors for changes to Logical Coordinates and Stores correct Keys
  #--------------------------------------------------------------------------
  alias map_hash_update update unless $@
  def update
    # Call Original or other Aliases
    map_hash_update
    # Update the Events Hash Map Coordinates for Key -> Value
    store_map_hash_keys
  end
end

#==============================================================================
# ** Game_Player
#==============================================================================
class Game_Player
  #----------------------------------------------------------------------------
  # * Check Event Trigger Here - Game_Player (FULL REDEFINITION)
  #  - Same Position Starting Determinant
  #  - Ignores Coordinate Match since List already matched coordinates
  #  - Fully Redefines method Check Event Trigger Here
  #     triggers : Array of @trigger[0,1,2] Action Button, Player / Event Touch
  #----------------------------------------------------------------------------
  def check_event_trigger_here(triggers)
    result = false
    # If event is running
    if $game_system.map_interpreter.running?
      return result
    end
    # Generate a List of Map Events to loop through
    event_list = make_events_list(@x, @y)
    # Loop Events in List
    for event in event_list
      # If Triggers argument is consistent with Event's Trigger
      if triggers.include?(event.trigger)
        # If starting determinant is same position event (other than jumping)
        if not event.jumping? and event.over_trigger?
          event.start
          result = true
        end
      end
    end
    return result
  end
  #----------------------------------------------------------------------------
  # * Check Event Trigger There - Game_Player (FULL REDEFINITION)
  #  - Front Event Starting Determinant
  #     triggers : Array of @trigger[0,1,2] Action Button, Player / Event Touch
  #----------------------------------------------------------------------------
  def check_event_trigger_there(triggers)
    result = false
    # If event is running
    if $game_system.map_interpreter.running?
      return result
    end
    # Calculate front event coordinates
    new_x, new_y = make_new_xy(@x, @y, @direction)
    # Generate a List of Map Events to loop through
    event_list = make_events_list(new_x, new_y)
    # Loop Events in List
    for event in event_list
      # If triggers are consistent
      if triggers.include?(event.trigger)
        # If starting determinant is front event (other than jumping)
        if not event.jumping? and not event.over_trigger?
          event.start
          result = true
        end
      end
    end
    # If fitting event is not found
    if result == false
      # If front tile is a counter
      if $game_map.counter?(new_x, new_y)
        # Calculate 1 tile inside coordinates
        new_x, new_y = make_new_xy(new_x, new_y, @direction)
        # Generate a List of Map Events to loop through
        event_list = make_events_list(new_x, new_y)
        # Loop Events in List
        for event in event_list
          # If triggers are consistent
          if triggers.include?(event.trigger)
            # If starting determinant is front event (other than jumping)
            if not event.jumping? and not event.over_trigger?
              event.start
              result = true
            end
          end
        end
      end
    end
    return result
  end
  #----------------------------------------------------------------------------
  # * Check Event Trigger Touch - Game_Player (FULL REDEFINITION)
  #  - Touch Event Starting Determinant
  #     triggers : Array of @trigger[1,2] Player Touch, Event Touch 
  #----------------------------------------------------------------------------
  def check_event_trigger_touch(x, y)
    result = false
    # If event is running
    if $game_system.map_interpreter.running?
      return result
    end
    # Generate a List of Map Events to loop through
    event_list = make_events_list(x, y)
    # Loop Events in List
    for event in event_list
      # If triggers are consistent
      if [1,2].include?(event.trigger)
        # If starting determinant is front event (other than jumping)
        if not event.jumping? and not event.over_trigger?
          event.start
          result = true
        end
      end
    end
    return result
  end 
end

#==============================================================================
# ** Scene_Load
#==============================================================================
class Scene_Load < Scene_File
  #--------------------------------------------------------------------------
  # * Read Save Data - Scene_Load
  #  - Rebuilds entire Events Data Hash when Game is Loaded
  #--------------------------------------------------------------------------
  alias map_hash_read_save_data read_save_data unless $@
  def read_save_data(file)
    # Call Original or other Aliases
    map_hash_read_save_data(file)
    # Create or Replace the Hash
    $game_map.create_events_hash
    # Rebuild the Map Data Hash
    for event in $game_map.events.values
      # If Event is not Erased
      unless event.hmco_erased?
        # Force the Event to Update the Map Keys
        $game_map.events_hash_xy[[event.x.round, event.y.round]] = event
      end
    end
  end
end

#==============================================================================
# ** Game_Event - Class
#==============================================================================
class Game_Event < Game_Character
  #--------------------------------------------------------------------------
  # * HMCO Erased? - Scene_Load
  #  - Method to check value of Erased
  #--------------------------------------------------------------------------
  def hmco_erased?
    return @erased
  end
end



Instructions

There is nothing for you to configure in this script.  If you are a Scripter and are interested in the functionality of this script, just read the Documentation in the Script itself.


Compatibility

Severe Lactose Intolerance.


Credits and Thanks


  • I'd like to thank Arby's for making tasty sandwiches!




Author's Notes

This script requires Modular Passable.

The way this Script works is to store the Locations of all Events in a Hash and uses rounded @x @y coordinates as a Key for Hash Values.  This is far more efficient than to have every single Event to compare values to every other Event on the entire Map every single frame.  By scanning every Event every time, 10 Events causes 100 coordinate comparisons to be made.  When you have 100 Events, 10000 comparisons are made each and every frame.  Thus, performance increase is Exponential, especially when there are a lot of moving Events on a Map.
Current Scripts:
Heretic's Moving Platforms

Current Demos:
Collection of Art and 100% Compatible Scripts

(Script Demos are all still available in the Collection link above.  I lost some individual demos due to a server crash.)