Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

Scripter's corner : Snippets, Classes, Tips and more

Zeriab

Sponsor

If the player press F12 during a transition the transition will reset and start again.
If F12 is kept pressed the game will feel like it has been paused. The FPS registered will typically be registered as higher although game playing will not change. Nor will it if it's Graphics.update being cancelled. The shown timer will however run faster.
Here is a snippet where it feels like the game simply pauses when pressing F12, if that is what one prefer:

Code:
#=============================================================================
# ** Reset class (because it won't be defined until F12 is pressed otherwise)
#=============================================================================
class Reset < Exception
  
end
#=============================================================================
# ** Module Graphics
#=============================================================================
module Graphics
  class << self
    #-------------------------------------------------------------------------
    # * Aliases Graphics.update and Graphics.transition
    #-------------------------------------------------------------------------
    unless self.method_defined?(:zeriab_f12_removal_update)
      alias_method(:zeriab_f12_removal_update, :update)
      alias_method(:zeriab_f12_removal_transition, :transition)
    end
    def update(*args)
      done = false
      # Keep trying to do the update
      while !done
        begin
          zeriab_f12_removal_update(*args)
          done = true
        rescue Reset
          # Do nothing
        end
      end
    end
    def transition(*args)
      done = false
      # Keep trying to do the transition
      while !done
        begin
          zeriab_f12_removal_transition(*args)
          done = true
        rescue Reset
          # Do nothing
        end
      end
    end
  end
end
 

boon

Sponsor

That now seems to make the game take 2-3 times the time to load as it normally does.

EDIT: It's all to do with the arrangement of the scripts. I moved it up to below Scene_Debug and it loads the same speed as before I Installed it. It's very useful. The Speed-Up one kindof nulled having to get running shoes, as the FPS made the character walk very fast.
 
I've edited my first post in this thread with what I feel is an improvement upon the older code.  The method is now much easier to call without having to input unnecessary arguments.
 
Multidimensional Array and Hash.

makes automatic n-dimensional without a size limit.

Code:
class MultiArray
  attr :data
	def initialize
		@data = []
	end
	
	def [](*args)
		temp = @data
		args.each { |id|
			temp[id] = [] if temp[id].nil?
			temp = temp[id]
		}
		return temp
	end
	
	def []=(*args)
		value = args.pop
		temp = @data
		args.each_index { |i|
      id=args[i]
      if (i+1) == args.size
        temp[id] = value
      else
        temp[id] = [] if temp[id].nil?
        temp[id] = temp[id].to_a unless temp[id].is_a?(Array)
      end
			temp = temp[id]
		}
	end
end

class MultiHash
  attr :data
	def initialize
		@data = {}
	end
	
	def [](*args)
		temp = @data
		args.each { |id|
			temp[id] = {} if temp[id].nil?
			temp = temp[id]
		}
		return temp
	end
	
	def []=(*args)
		value = args.pop
		temp = @data
		args.each_index { |i|
      id=args[i]
      if (i+1) == args.size
        temp[id] = value
      else
        temp[id] = {} if temp[id].nil?
        temp[id] = {temp[id] => {}} unless temp[id].is_a?(Hash)
      end
			temp = temp[id]
		}
	end
end

call this with:

Code:
test = MultiArray.new
test[1,2,3,4]=5
p test[1,2,3,4] #=> 5


PS: this is in development.

Bugs:
-this array is not homogen. -> have not equal size
-without a sizelimit, you can make a space overflow
-has no each or include method. -> so you can't search


i hope you gus help me.
 
It's been a while since someone posted here. Well, I have something new for you!
That's a class_defined? method for the Object class.
Code:
#==============================================================================
# ** Object
#==============================================================================

class Object
  #--------------------------------------------------------------------------
  # * Class Defined?
  #--------------------------------------------------------------------------
  def class_defined?(class_name)
    mod = Class
    class_name.split("::").each { |c|
      mod.constants.include?(c) or return false
      mod = mod.const_get c
    }
    return true
  end
end
call this like that:
Code:
if class_defined?(class_name)
...
end
 
I sure hope that 20 days isn't considered necroposting...

I just wanted to compliment khmp on that "chunks" addition.  I tried it out and it looked quite spiffy.  Though, I did have to write Math::distance to make it work.

I'm going to make an addition to this place soon, something that as I understand it SephirothSpawn likes enough to put in the SDK once I'm finished debugging and rewriting some of the classes to use it.
 
Alright...nobody likes to post.  This place is far slower than I remember it, although the last time I was really active was on .net right before it went under...

This is a Scene_Base which is capable of layering scenes on top of each other.  What that means is, the new scene will act as an instance variable to the parent scene, causing some of the parent scene to cease updating while the layer is active, but some of the parent scene will still update.  To put it less abstractly, you will not only have the spriteset for the map under the menus, you will also see the events working behind the menu as well.

New scenes are called by new_scene(object_name[, *args]).  If this is called from $scene, which is the root scene and has no parents, then it will end up calling $scene = object_name.new(nil, *args) (that nil represents the parent of the object, which doesn't exist because $scene is the root scene).  If called from a layered scene, it will replace that layer with whatever scene is being called, or, if the object is the same as the parent, it will simply remove that layer and return operation to the parent scene (this is also accomplished with new_scene(nil))

New layers are called by new_layer(object_name[, *args]).  As soon as the layer is called, it is completely initialized (all of the main_ classes from main_variable to main_transition) and def update of the parent class ceases to be processed (though def update_background will always be processed).  The layer creates a Graphics.freeze and Graphics.transition to fade into the new "scene."  The function basically calls @layers.push(object_name.new(self, *args).

Each scene has its own array of layers, rather than just one, and each layer has an @active variable.  If a layer is inactive, it will not call its own update functions (SephirothSpawn's auto_update will still update anything that isn't a Window_Selectable), nor will it prevent its parent from updating.  This would allow the creation of intentionally inactive layers which simply add windows or sprites on top of an existing scene, like Scene_Map.  A HUD, for example, could be easily created and disposed of as a layer, with a call script something like $scene.new_layer(Scene_HUD) and $scene.clear_layers (which, OMG, clears all layers of that scene!)

On top of that, this class contains standardized methods for animating the entry and exit of any and every scene.  main_animation_loop is called every frame until main_animation_loop_done? returns true (it always does so by default).  When the scene is disposed of for any reason, it starts to call main_out_animation_loop every frame until main_out_animation_loop_done? (which is also set true by default), at which point it disposes (def dispose calls main_out_transition, main_dispose, and main_end, among other internal things).

This was designed to fit into the SDK, so its class name is SDK::Scene_Base.  If you want to change it to something else, make sure to run a Ctrl + H replace on all instances of the name as the script checks to make sure that all layers and parents are based on that class.  Additionally, it comes with alterations to SDK parts III and IV which provide the new functionality by ensuring that a) Scene_Map has its update divided between background processes and those which aren't updated if a layer is present, b) All def initialize functions have "parent" as their first value, which they super() to Scene_Base, and c) All new scenes and layers are called with the new_scene and new_layer functions.  If there is demand I may look into porting this to VX.

SDK::Scene_Base
SDK Part III Scene Class Modifications
SDK Part IV Scene Class Modifications
 
Simple syntax tips:

Math, Power by:
Code:
# Instead of writing

var * var = var**2
var * var * var = var**3
# and so on.

use "unless", instead of "if not"

Lighten your code: include your modules into classes
Code:
# for example instead to have to write Math::...... 

class foo
  include Math
  
  def foo_method
     return sqrt(2*PI)
  end
end

Use constants over Global Variables if they don't have to change.
If a constant isn't defined into a class, then it's accessible from everywhere.

you can also use Class variables, to act as constant, or as uniq variables, for a class.

Basically use Global variables if you don't have any other choice.

You don't have to write parenthesis to pass an argument for a method. just a space is enough, though it might improve readability.

Special Method name:
how do  -1 works? or -variable ?

well there is a method defined like this: 
Code:
def  -@
   return 0-self
end
Pretty usefull to know when you're scripting math related class. ^^


Rotate arrays, can be usefull sometimes :p
Code:
class Array
   def rotate!(n,dir=0) #n number of items; 0 to the right(1st member goes last), else to the left
      if dir.zero?
         n.times {|| self.push self.shift  }
      else
        n.times {|| self.unshift self.pop }
      end
   end
end
 
I've updated the Scene_Base again, and I'm going to give you an example of how easy it is to create useful layers with it.  First you need to install the SDK, including part 3.  Then install my SDK::Scene_Base and the modifications to SDK part 3.  You're all set to begin.

Put in this very small and very simple script:
Code:
#==============================================================================
# ** Layer_HUD
#------------------------------------------------------------------------------
#  Designed to layer over Scene_Map and provide debug information
#==============================================================================
class Layer_HUD < SDK::Scene_Base
  def main_variable
    super
    @active = false
  end
  def main_window
    super
    @hud_window = Window_Base.new(0, 0, 128, 64)
    @hud_window.contents = Bitmap.new(96, 32)
    @hud_window.contents.font.size = 12
  end
  def update_background
    super
    @hud_window.contents.clear
    @hud_window.contents.draw_text(0, 0, 96, 16, "Display X:" + $game_map.display_x.to_s)
    @hud_window.contents.draw_text(0, 16, 96, 16, "Display y:" + $game_map.display_y.to_s)
  end
end
Now call this script from the map:
Code:
$scene.new_layer(Layer_HUD)
You've got a little window there now to tell you what the map's display X and display Y are!  This isn't necessarily an important feature, but you can fill your HUD with whatever you want.  The important part is @active = false, as this tells Scene_Base that this layer isn't important enough to stop the processing of Scene_Map.  The window will stay there until you call a script that says:
Code:
$scene.delete_layer(Layer_HUD)
Alternatively, you could simply delete all layers:
Code:
$scene.clear_layers

EDIT: I feel like I'm not up with the spirit of this thread all the way.  Maybe this is for smaller tips, not full-blown scripts like this.  It might be better to split this post and my last one into a new thread entitled "Scene_Base with Layering Capabilities" for RMXP.
 

Zeriab

Sponsor

Here's a little something if you want to be able to print out the contents of a Table:
Code:
class Table
  ##
  # Gives a special string representation. (For debugging purposes mostly)
  #
  def to_s
    if ysize == 1
      begin
        return to_s_1d
      rescue # In the rare case where you have a 2- or 3-dimensional 
      end    # table with ysize = 1
    end
    if zsize == 1
      begin
        return to_s_2d
      rescue # In the rare case where you have a 3-dimensional table
      end    # with zsize = 1
    end
    return to_s_3d
  end
  
  ##
  # Returns a representation of the 1-dimensional table as a string
  # Assumes that the table is 1-dimensional. Will throw an error otherwise
  #
  def to_s_1d
    str = '['
    for i in 0...(xsize-1)
      str += self[i].to_s + ', '
    end
    str += self[xsize-1].to_s + ']'
    return str
  end
  
  ##
  # Returns a representation of the 2-dimensional table as a string
  # Assumes that the table is 2-dimensional. Will throw an error otherwise
  #
  def to_s_2d
    str = '['
    for j in 0...ysize
      str += "\n["
      for i in 0...(xsize-1)
        str += self[i,j].to_s + ', '
      end
      str += self[xsize-1,j].to_s + ']'
    end
    str += "\n]"
    return str
  end
  
  ##
  # Returns a representation of the 3-dimensional table as a string
  # Assumes that the table is 3-dimensional. Will throw an error otherwise
  #
  def to_s_3d
    str = '{'
    for k in 0...zsize
      str += '['
      for j in 0...ysize
        str += "\n["
        for i in 0...(xsize-1)
          str += self[i,j,k].to_s + ', '
        end
        str += self[xsize-1,j,k].to_s + ']'
      end
      str += "\n]"
    end
    str += '}'
    return str
  end
end

You can simply use p table after inserting this.

*hugs*
- Zeriab
 
I sure could have used that when I was trying to build my own Tilemap class. That was a loooong time ago, though...I would have made my own, too, if I didn't feel like building the autotile INDEX was so horribly tedious. Nice work, very simple and it seems to have pretty good stability, looking at the amount of error checking.
 

Zeriab

Sponsor

That's funny, I posted this script because BlueScope was messing around with the Tilemap class XD
I am glad you like, but you can't assume stability from the amount of error checking. I am using an unsafe method to check how many dimensions the table has. I know there are special cases which I should guard against.

I actually made this script over a year ago. I just forgot to post it ._.
 

Zeriab

Sponsor

I found a little snippet I made some time ago which can help with debugging:
Code:
class StandardError < Exception
  def to_s
    b = self.backtrace
    b.delete_at(0)
    return super + "\n\nBacktrace:\n" + b.join("\n")
  end
end

I found it in an unfinished combo system.
Here is the back-bone of a combo system I once were working on.
I can't remember why I dropped it, but I release it free for anyone to use any part of it for both non-commercial and commercial purposes.
No guarantee that it works. No guarantees at all. Use at your own risk :x
Code:
class Key_Combo
  def initialize
    @timeline = {}
    @running = []
    @actions = []
    @ticks = 0
    @total_ticks = 0
  end
  
  ##
  # Put an action into the timeline
  #
  def put(action, start)
    @timeline[start] = [] if @timeline[start].nil?
    @timeline[start] << action
    @actions << action
    # Update the total ticks if necessary
    total_ticks = start + action.total_ticks
    if total_ticks > @total_ticks
      @total_ticks = total_ticks
    end
  end
  
  ##
  # Resets the timeline and all the actions
  #
  def reset
    @ticks = 0
    @running.clear
    for action in @actions
      action.reset
    end
  end
  
  ##
  # Updates the combo
  #
  # Returns
  # nil - Not completed yet
  # false - The action failed
  # true - The action succeeded
  #
  def update
    # Add new actions
    new_actions = @timeline[@ticks]
    unless new_actions.nil?
      for action in new_actions
        @running << action
      end
    end
    # Check and update the running actions
    for action in @running
      # Update the action
      result = action.update
      # Check if the action still is running
      unless result.nil?
        if result
          # It was successful
          @running.delete(action)
        else
          # It was not successful
          return false
        end
      end
    end
    # Tick
    @ticks += 1
    # Check if all ticks has been used
    if @ticks >= @total_ticks
      # No failures => success
      return true
    end
    return nil
  end
end

class Key_Action
  attr_reader :ticks
  attr_reader :total_ticks
  def reset
    raise NotImplementedError.new(self.class.to_s + ".reset")
  end
  def update
    raise NotImplementedError.new(self.class.to_s + ".update")
  end
end

class Conjunction_Key_Action < Key_Action
  ##
  # Initialization
  # condition - The condition that must be fulfilled
  # *actions - The actions to check if they in conjunction succeed
  #            The first action entered is the first to be executed
  #            The second action entered is the next and so on
  #
  #    action1 ^ action2 ^ action3 ^ ... ^ actionN
  #
  # Note: Automatically succeeds if no actions are entered unless condition is
  #       false
  #
  def initialize(condition, *actions)
    @total_ticks = 0
    @condition = condition
    @actions = []
    for action in actions
      if action.is_a?(Key_Action)
        @actions << action
        @total_ticks += action.total_ticks
      else
        raise IllegalArgumentError.new("On of the arguments in " + self.class.to_s +
                " is not a Key_Action, it is a " + condition.class.to_s)
      end
    end
    @index = 0
    @ticks = 0
  end
  
  ##
  # Reset the action so it can be used again
  #
  def reset
    @index = 0
    @ticks = 0
    for action in @actions
      action.reset
    end
  end
  
  ##
  # Returns
  # nil - Not completed yet
  # false - The action failed
  # true - The action succeeded
  #
  def update
    # Check if the condition is fulfilled
    unless @condition.checkCondition
      return false
    end
    # Check if the correct keys have been triggered
    action = @actions[@index]
    if action.nil?
      return true
    end
    # Get the result from the action update
    @ticks += 1
    result = action.update
    # If the action has failed
    if result == false
      return false
    # If the action has succeeded
    elsif result == true
      # Move to next action
      @index += 1
      # If no more actions
      if @actions.size <= @index
        return true
      end
    end
    return nil
  end
end

class Disjunction_Key_Action < Key_Action
  ##
  # Initialization
  # condition - The condition that must be fulfilled
  # *actions - The actions to check if they in disjunction succeed
  #            The first action entered is the first to be executed
  #            The second action entered is the next and so on
  #
  #    action1 v action2 v action3 v ... v actionN
  #
  # Note: Automatically fails if no actions are entered.
  #
  def initialize(condition, *actions)
    @total_ticks = 0
    @condition = condition
    @actions = []
    for action in actions
      if action.is_a?(Key_Action)
        @actions << action
        @total_ticks += action.total_ticks
      else
        raise IllegalArgumentError.new("On of the arguments in " + self.class.to_s +
                " is not a Key_Action, it is a " + condition.class.to_s)
      end
    end
    @index = 0
    @ticks = 0
  end
  
  ##
  # Reset the action so it can be used again
  #
  def reset
    @index = 0
    @ticks = 0
    for action in @actions
      action.reset
    end
  end
  
  ##
  # Returns
  # nil - Not completed yet
  # false - The action failed
  # true - The action succeeded
  #
  def update
    # Check if the condition is fulfilled
    unless @condition.checkCondition
      return false
    end
    # Check if the correct keys have been triggered
    action = @actions[@index]
    if action.nil?
      return false
    end
    # Get the result from the action update
    @ticks += 1
    result = action.update
    # If the action has failed
    if result == false
      # Move to next action
      @index += 1
      # If no more actions
      if @actions.size <= @index
        return false
      end
    # If the action has succeeded
    elsif result == true
      return true
    end
    return nil
  end
end

class Trigger_Key_Action < Key_Action
  ##
  # Initialization
  # key_press - The keys to press
  # condition - The condition that must be fulfilled
  # ticks - The number of ticks to press the key
  #
  def initialize(key_press, condition, ticks)
    @key_press = key_press
    @condition = condition
    @total_ticks = ticks
    @ticks = 0
  end
  
  ##
  # Reset the action so it can be used again
  #
  def reset
    @ticks = @total_ticks
  end
  
  ##
  # Returns
  # nil - Not completed yet
  # false - The action failed
  # true - The action succeeded
  #
  def update
    # Check if the condition is fulfilled
    unless @condition.checkCondition
      return false
    end
    # Check if the correct keys have been triggered
    if @key_press.trigger?
      return true
    end
    # Removes the amount of ticks for the action
    @ticks += 1
    # Check if all ticks has been used
    if @ticks >= @total_ticks
      return false
    end
    return nil
  end
end

class Release_Key_Action < Key_Action
  ##
  # Initialization
  # key_press - The keys to press
  # condition - The condition that must be fulfilled
  # ticks - The number of ticks to hold the key pressed
  # delta - The amounts of ticks before and after the right tick which are
  #         considered as being timely release
  #
  def initialize(key_press, condition, ticks, delta)
    @key_press = key_press
    @condition = condition
    @total_ticks = ticks + delta
    @ticks = 0
    @delta = delta
  end
  
  ##
  # Reset the action so it can be used again
  #
  def reset
    @ticks = 0
  end
  
  ##
  # Returns
  # nil - Not completed yet
  # false - The action failed
  # true - The action succeeded
  #
  def update
    # Check if the condition is fulfilled
    unless @condition.checkCondition
      return false
    end
    # Check if the correct keys is still pressed
    unless @key_press.press?
      if @total_ticks - @delta * 2 <= @ticks && @total_ticks >= @ticks
        return true
      else
        return false
      end
    end
    # Removes the amount of ticks for the action
    @ticks += 1
    # Check if all ticks has been used
    if @ticks >= @total_ticks
      return false
    end
    return nil
  end
end

##
# Manages a serie of keys and whether they are pressed, triggered and repeated
# Basically the same as the Input module except that it can check an arbitrary
# number of keys instead of 1 key
#
class Key_Press
  @keys = []
  ##
  # Initialization
  # *args - integer values of the keys to press. (Refer to Input constants)
  def initialize(*args)
    @keys = args.flatten.compact
  end
  ##
  # Check if all keys are triggered
  #
  def trigger?
    for key in @keys
      unless Input.trigger?(key)
        return false
      end
    end
    return true
  end
  ##
  # Check if all keys are pressed
  #
  def press?
    for key in @keys
      unless Input.press?(key)
        return false
      end
    end
    return true
  end
  ##
  # Check if all keys are repeated
  #
  def repeat?
    for key in @keys
      unless Input.repeat?(key)
        return false
      end
    end
    return true
  end
end

class IllegalArgumentError < StandardError
end

class StandardError < Exception
  def to_s
    b =  self.backtrace
    b.delete_at(0)
    return super + "\n\nBacktrace:\n" + b.join("\n")
  end
end


class Condition
  def checkCondition(*args)
    raise NotImplementedError.new(self.class.to_s + ".checkCondition")
  end
end

class ConjunctionCondition < Condition
  def initialize(*conditions)
    @conditions = []
    for condition in conditions
      if condition.is_a?(Condition)
        @conditions << condition
      else
        raise IllegalArgumentError.new("On of the arguments in " + self.class.to_s +
                " is not a Condition, it is a " + condition.class.to_s)
      end
    end
  end
  def checkCondition(*args)
    for condition in @conditions
      return false unless condition.checkCondition(*args)
    end
    return true
  end
end

class DisjunctionCondition < Condition
  def initialize(*conditions)
    @conditions = []
    for condition in conditions
      if condition.is_a?(Condition)
        @conditions << condition
      else
        raise IllegalArgumentError.new("On of the arguments in " + self.class.to_s +
                " is not a Condition, it is a " + condition.class.to_s)
      end
    end
  end
  def checkCondition(*args)
    for condition in @conditions
      return true if condition.checkCondition(*args)
    end
    return false
  end
end

class NotCondition < Condition
  def initialize(condition)
    if condition.is_a?(Condition)
      @condition = condition
    else
      raise IllegalArgumentError.new("Given argument in " + self.class.to_s + 
                " is not a condition, it is a " + condition.class.to_s)
    end
  end
  def checkCondition(*args)
    return condition.checkCondition(*args)
  end
end

class TrueCondition < Condition
  def checkCondition(*args)
    return true
  end
end

An example usage:
Code:
class
  ##
  # Press A within the first 20 frames
  # Press L + R between frame 20 inclusive and frame 40 exclusive
  # Press C between frame 40 inclusive and frame 60 exclusive
  # Release C between frame 70 inclusive and frame 90 exclusive
  # Press A + B between frame 90 inclusive and frame 90 exclusive
  #
  def self.test_combo_start
    kp_ab = Key_Press.new(Input::A, Input::B)
    kp_a = Key_Press.new(Input::A)
    kp_c = Key_Press.new(Input::C)
    kp_lr = Key_Press.new(Input::L, Input::R)
    true_condition = TrueCondition.new
    tka_a = Trigger_Key_Action.new(kp_a, true_condition, 20)
    tka_lr = Trigger_Key_Action.new(kp_lr, true_condition, 20)
    tka_c = Trigger_Key_Action.new(kp_c, true_condition, 20)
    rka_c = Release_Key_Action.new(kp_c, true_condition, 20, 10)
    tka_ab = Trigger_Key_Action.new(kp_ab, true_condition, 20)
    combo = Key_Combo.new
    combo.put(tka_a, 0)
    combo.put(tka_lr, 20)
    combo.put(tka_c, 40)
    combo.put(rka_c, 60)
    combo.put(tka_ab, 90)
    @@combo = combo
    @@counter = 0
  end
  
  def self.key_combo
    res = @@combo.update
    @@counter += 1
    if res.nil?
      return false
    end
    p res, @@counter
    return true
  end
end

*hugs*
- Zeriab
 

Zeriab

Sponsor

Here is an update to the StandardError extension:
Code:
class StandardError < Exception
  def to_s
    b = self.backtrace[1..-1]
    k = self.backtrace[0]
    message = super + "\n\nBacktrace:\n" + b.join("\n")
    if $DEBUG
      $DEBUG = false
      write_to_log(message, k) 
    end
    return message
  end
  def to_log(message, k)
    k.gsub(/Section(\d*):(\d*)/) {}
    string = "Script '#{$RGSS_SCRIPTS[$1.to_i][1]}' line #{$2.to_i}: #{self.class} occured"
    return string + "\n\n" + message
  end
  def write_to_log(message, k)
    file = File.open('ErrorLog.log', File::CREAT|File::APPEND|File::WRONLY)
    file.print to_log(message, k)
    file.print "\n==============================\n"
    file.close
  end
end

This time it appends the error to an error log if $DEBUG is true. (I.e. test run)
I like this approach since the editor appears to go to the right place like normally ^_^

*hugs*
- Zeriab
 
I like it. I think it would also be nice to add simple error checking to Scene_Base, and with that error log you've implemented it even makes sense to try and keep going even when you reach an error.

I'm just thinking it should be something simple like this:

Code:
def Exception.fatal?
  return true
end

def StandardError.fatal?
  return false
end

class Scene_Base
  def main_loop
    [...]
  rescue
    raise $! if $!.fatal?
    p $!.to_s
  end
end

This way, errors don't destroy the program, and it's even possible to make certain errors fatal so they DO destroy the program. Just an addition to your debugging tool, I think I'll put these into my scripting bay to help debugging and make it less apt to quit.

EDIT: I've got it working, doing exactly what I want. It's a bit more complicated, but it points the script editor to the right place like the original intent. It prints all errors to the screen, but ignores them as long as an error didn't happen the last frame (indicating that the error's going to happen every frame).

Code:
class Object
  #--------------------------------------------------------------------------
  # * Advanced error checking for methods - provide method as a string for eval
  #--------------------------------------------------------------------------
  def error_wrapper(symbol)
    # Create the record of whether there's been an error last time checked
    if @_error == nil
      @_error = {}
      @_error.default = false
    end
    # If there was an error last time we ran this method
    if @_error[symbol]
      # Run the method and, after getting no errors, reset error check
      send symbol
      @_error[symbol] = false
    else # If there was no error last time we ran this method
      begin
        send symbol
      rescue
        # Catch errors and provide feedback; set checker for each-frame errors
        print $!.full_message if $DEBUG or $!.fatal?
        $!.write_to_log
        exit(1) if $!.fatal?
        @_error[symbol] = true
      end
    end
  end
end

This uses some modifications to your original work:

Code:
class Exception
  # prevents infinite loops by aliasing the original message
  alias to_string :to_s
  #--------------------------------------------------------------------------
  # * Only called from internal error checking; writes log and adds backtrace
  #--------------------------------------------------------------------------
  def to_s
    write_to_log # Note that the log calls :full_message
    return message_with_backtrace
  end
  #--------------------------------------------------------------------------
  # * Returns the error message with accompanying backtrace
  #--------------------------------------------------------------------------
  def message_with_backtrace
    return to_string + backtrace_string(1...backtrace.length)
  end
  #--------------------------------------------------------------------------
  # * Returns what raising an error displays
  #--------------------------------------------------------------------------
  def full_message
    # Finds script and line number
    error = "Script " + backtrace_line(0) + ": #{self.class} occured.\n\n"
    return error + self.message_with_backtrace
  end
  #--------------------------------------------------------------------------
  # * Generate the backtrace string from a range of its values
  #--------------------------------------------------------------------------
  def backtrace_string(range = 0...backtrace.length)
    output = "\n\nBacktrace:\n"
    for i in range
      output += backtrace_line(i, true) + "\n"
    end
    return output
  end
  #--------------------------------------------------------------------------
  # * Generate a string from an individual item in the backtrace
  #--------------------------------------------------------------------------
  def backtrace_line(i, show_method = false)
    method = backtrace[i].gsub(/Section(\d*):(\d*)/) {}
    output = "'%-20s' line %d" % [$RGSS_SCRIPTS[$1.to_i][1], $2]
    output += method.sub(':', ' ') if show_method
    return output
  end
  #--------------------------------------------------------------------------
  # * Write the error to the log file
  #--------------------------------------------------------------------------
  def write_to_log
    return if @written # Only write log once
    file = File.open('ErrorLog.log', File::CREAT|File::APPEND|File::WRONLY)
    file.print full_message
    file.print "\n========================================================\n"
    file.close
    @written = true
  end
  #--------------------------------------------------------------------------
  # * Is this error fatal? (redefine for other exceptions)
  #--------------------------------------------------------------------------
  def fatal?
    return true
  end
end

class StandardError
  def fatal? ; return false ; end
end

All that needs to be done to catch simple errors that don't occur every frame is to replace a call to main_loop with a call to error_wrapper:)main_loop) (within objects)
 

Zeriab

Sponsor

That is an good idea and I like your execution meustrus ^_^

I would consider only printing non-fatal errors out on the screen if $DEBUG == true.
It really depends on how you want to test the game since a silent ONLY_WRITE_TO_LOG mode gives a better feeling of how the game works. If the error message is displayed though you know when exactly it occurs. Something to consider.

Nice work anyway ^^
 
I like it, I'll edit the post to include that...where it says "print $!.full_message" it's changed to "print $!.full_message if $DEBUG or $!.fatal?"
 
This might be helpful even though it's kinda simple, a method that finds the angle from 0,0 to x,y and returns it (in degrees)

[rgss]module Math
  def self.angle(x,y)
    deg = (Math.asin(y/((x**2 + y**2)**(0.5))))
    # Convert to degrees
    deg = ((deg)/(2 * Math::PI)) * 360
    # Round to 4 decimals
    deg = ((deg * 1000).ceil)/1000
    deg = 180 - deg if x < 0
    deg = 360 + deg if x >= 0 and y < 0
    return deg
  end
end
[/rgss]
 
Get the X first / last items of an array !

Some people might have been used to : my_array[0,3] for example to get the first 3 items...
or my_array[-X,X] to get the last X items of an array...

Well there is actually something much simpler :

my_array.first(X)
and
my_array.last(X)

Will return a new array containing the first/last X items ^^, if you don't specify X, it'll simplify return the 1st or last item directly, not as an array.
 
Here's something I would have made earlier if I knew how. @_@
It's a C dll that (very quickly) recolors a bitmap.
http://www.bwdyeti.com/bitmap.rar

It recolors all instances of a given color with another given color. Use it with:
[rgss]class Bitmap
  def recolor_c(color_to,color_from)
    raise RGSSError.new("Disposed bitmap") if disposed?
    func=Win32API.new("bitmap.dll","Bitmap_Recolor","lll","")
    func.call(self.__id__,toColor(color_to),toColor(color_from))
  end
 
  def toColor(c)
    r=c.red.to_i
    g=c.green.to_i
    b=c.blue.to_i
    a=c.alpha.to_i
    return b|(g<<8)|(r<<16)|(a<<24)
  end
end
[/rgss]
[rgss]bitmap.recolor_c(color1, color2)
[/rgss]

It can recolor a 640x480 bitmap every frame at 60fps while similar Ruby code using get_pixel/set_pixel would fall to 1fps .-.

I might do different things with it like recoloring by hue (something Seph suggested when I asked about recoloring in the past), but this might be helpful to someone else now.

Thanks goes to poccil for the the basis I worked from with his bitmap effects in C (viewtopic.php?f=11&t=39324)
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top