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

I feel special, and you should too! I've written a Table class which is designed specifically to replace the built-in Table class, right down to the ability to Marshal.load the data that came out of RMXP or anyplace else that only knew about the built-in class. I would like to thank my hex editor (0xED for Mac) for making this possible.

[rgss]#===============================================================================
# ** Table
#-------------------------------------------------------------------------------
#    Rewrites the Table class. This is slower than RGSS but more portable.
#===============================================================================
 
class Table < Hash
  #-----------------------------------------------------------------------------
  # * Initialize the table with variable length arguments
  #-----------------------------------------------------------------------------
  def initialize(*args)
    super(0) # 0 is default value
    @sizes = []
    resize(*args)
  end
  #-----------------------------------------------------------------------------
  # * Set the size of (and number of) the dimensions
  #-----------------------------------------------------------------------------
  def resize(*args)
    args.pop while args.last == 0 # Remove 0 values from end of args
    if args.include?(0)
      raise ArgumentError, 'Size of part of a Table cannot be zero.'
    end
    self.clear unless @sizes.length == args.length
    @sizes = args
  end
  #-----------------------------------------------------------------------------
  # * Returns the size at a specific depth, or nil if out of range
  #-----------------------------------------------------------------------------
  def size(depth)
    @sizes[depth]
  end
  def xsize ; @sizes[0] ; end
  def ysize ; @sizes[1] ; end
  def zsize ; @sizes[2] ; end
  #-----------------------------------------------------------------------------
  # * Clear hash (reset integer-only flag as well)
  #-----------------------------------------------------------------------------
  def clear
    super
    @ints_only = true
  end
  #-----------------------------------------------------------------------------
  # * Retrieve data
  #-----------------------------------------------------------------------------
  def [](*args)
    # Return nil if out of range
    return nil unless check_indeces(args)
    super(args) # Notice the variable arguments is passed as a single array
  end
  #-----------------------------------------------------------------------------
  # * Set data
  #-----------------------------------------------------------------------------
  def []=(*args)
    # Remove the last argument, which is what the data will be set to
    operand = args.pop
    # Set @ints_only flag
    @ints_only &= operand.is_a? Integer
    # Return nil if out of range
    return nil unless check_indeces(args)
    super(args, operand)
  end
  #-----------------------------------------------------------------------------
  # * Check arguments for size and within range
  #-----------------------------------------------------------------------------
  def check_indeces(args)
    # Prevent different number of arguments than at creation
    if args.size != @sizes.size
      raise ArgumentError, "Table has #{@sizes.size} dimensions, but " +
                           "received #{args.size} arguments."
    end
    # Check if index is within range
    args.each_with_index {|i, depth| return false if i < 0 or i > @sizes[depth]}
    true
  end
  #-----------------------------------------------------------------------------
  # * Dump to String (compatible with RGSS Table dump format for Integers)
  #-----------------------------------------------------------------------------
  def _dump(marshal_depth = -1)
    integer_format = (@ints_only && @sizes.size <= 3)
    # First, store information about the size
    size_info = [@sizes.length, *@sizes]
    size_info << 0x01 while size_info.length < 4
    output = size_info.pack('I*')
    # Then, store whether this is integer format or not
    output += [integer_format ? 0xD2 : 0x00].pack('I')
    if integer_format
      # All values are integers, so store them as such
      self.each { |int| output += [int].pack('s') }
    else
      # Copy self into a plain hash to dump
      output += Marshal.dump(Hash.new.merge!(self), marshal_depth)
    end
    output
  end
  #-----------------------------------------------------------------------------
  # * Load from String (compatible with RGSS Table dump format for Integers)
  #-----------------------------------------------------------------------------
  def self._load(data)
    # Get depth of the Table
    depth = data.slice!(0...4).unpack('I')[0]
    # Get size of each dimension
    sizes = data.slice!(0...[12, depth * 4].max).unpack('I*')[0...depth]
    output = Table.new(*sizes)
    # Non-integer format: load the rest as a plain Hash
    if data.slice!(0...4).unpack('I')[0] == 0x00
      return output.merge!(Marshal.load(data))
    end
    # Integer format: load each piece of data in the correct order
    output.each_index { |*i| output[*i] = data.slice!(0...2).unpack('s')[0] }
    return output
  rescue RangeError
    # Catches failed attempts to slice existing data due to out of range
    raise RuntimeError, 'Load data for Table is not as long as specified. ' +
                        'The file may be corrupted.'
  end
  #-----------------------------------------------------------------------------
  # * Process through each element in order
  #-----------------------------------------------------------------------------
  def each
    self.each_index { |*index| yield self[*index] }
  end
  #-----------------------------------------------------------------------------
  # * Process through each element in order, with index
  #-----------------------------------------------------------------------------
  def each_with_index
    self.each_index { |*index| yield self[*index], *index }
  end
  #-----------------------------------------------------------------------------
  # * Process a block for each in range from 0...arg, permutated thru all args
  #-----------------------------------------------------------------------------
  def each_index(&block) ; Table.each_index(*@sizes.dup, &block) ; end
  def self.each_index(*sizes, &block)
    range = 0...sizes.shift
    if sizes.empty?
      range.each(&block)
    else
      range.each { |i| self.each_index(*sizes) { |*index| yield i, *index } }
    end
  end
end
 
[/rgss]

As a plus, it works as a table for non-integers as well, in which case its Marshal dumping and loading go back somewhat to normal in order to accommodate non-integers.

Note that in the _dump function, 0x01 and 0xD2 are inserted in the size section. These are values that were part of the result of Table's original dump function, though I could not discern their purpose besides filling space. 0x01 is just filler. I'm using the space where 0xD2 is to determine the load type, so all that's important is that that value is NOT 0x00.
 
Oh, so you're saying it would have gotten done anyway? I guess I've come to expect that certain things like that I have to do myself if I want them done, especially given that most of the best scripters aren't around anymore...
 
It's always better to do things yourself rather than waiting on someone. Its why I learned to script lol. And we still have a great community of talented scripters. :cheers:
 
I came up with some pretty cool method modifications for the Hash class that allows you to read and set multi-dimensioned hashes without the work it had before. It also allows you to use classes with [] and []= methods.

Code:
class Hash

  alias_method :seph_mdhash_elem, :[]

  def [](*keys)

    next_object = self

    while keys.size > 0

      key = keys.shift

      if next_object.is_a?(Hash)

        next_object = next_object.seph_mdhash_elem(key)

      else

        next_object = next_object[key]

      end

    end

    return next_object

  end

  alias_method :seph_mdhash_setelem, :[]=

  def []=(*args)

    if args.size > 2

      value = args.pop

      keys = args.dup

      next_object = self

      while keys.size > 0

        if keys.size == 1

          if next_object.is_a?(Hash)

            next_object.seph_mdhash_setelem(keys[0], value)

          else

            next_object[key] = value

          end

          break

        end

        key = keys.shift

        next_object[key] = {} unless next_object.has_key?(key)

        next_object = next_object[key]

      end

    else

      seph_mdhash_setelem(*args)

    end

  end

  alias_method :seph_mdhash_hk?, :has_key?

  def has_key?(*keys)

    current_hash = self

    while keys.size > 0

      key = keys.shift

      if current_hash.seph_mdhash_hk?(key)

        current_hash = current_hash[key]

        next

      end

      return false

    end

    return true

  end

end

# Reading m-d hashes
a = {1={2=>{3=>1, 4=>2}, 1=2}, 2=>{3 => 4}}

a -> {1={2=>{3=>1, 4=>2}, 1=2}, 2=>{3 => 4}}
a[1] -> {2=>{3=>1, 4=>2}, 1=2}
a[1, 2] -> {3=>1, 4=>2}
a[1, 2, 3] -> 1
a[1, 2, 4] -> 2
a[1, 1] -> 2
a[2] -> {3 => 4}
a[2][3] -> 4

# Setting m-d hashes (if dimension is not found, creates hash)

a = {}
a[1, 2] = 4
a[1, 3] = 2
a[2, 4] = 6

a -> {1 => {2 => 4, 3 => 2}, 2 => {4 => 6}}

# Checking if m-d has a key

a = {1 => {2 => 4, 3 => 2}, 2 => {4 => 6}}

a.has_key?(1, 2) -> true
a.has_key?(1, 4) -> false
a.has_key?(2) -> true
a.has_key?(2, 4) -> true
a.has_key?(2, 3) -> false


Why I haven't come up with this modification years ago when every script I created had m-d hashes for setup is beyond me, but oh well. No more

Code:
if hash.has_key?(key)

  if hash[key].has_key?(key2)

    return hash[key][key2]

  end

end

 

# Now

if has.has_key?(key, key2)

  return hash[key, key2]

end
 
Seems to be useful.

BTW, here's a simple non GUI dice rolling script.

As a class...
[rgss]class QtyError < Exception
  def to_s; 'Wrong number of dice, must be above 0' end
end
 
class SidesError < Exception
  def to_s; 'Wrong number of sides, must be above 3' end
end
 
class Dice
  def roll(qty, sides)
    raise QtyError   if qty <= 0
    raise SidesError if sides < 4
    dice = []
    qty.times {dice << (rand sides)+1}
    return dice
  end
end
[/rgss]

Then you call it like this...

@dice = Dice.new # Returns #<Dice:0x85ee24c>
@dice.roll 2, 6 # Possibly returns [2, 5]

As a Module...
[rgss]class QtyError < Exception
  def to_s; 'Wrong number of dice, must be above 0' end
end
 
class SidesError < Exception
  def to_s; 'Wrong number of sides, must be above 3' end
end
 
module Dice
  def self.roll(qty, sides)
    raise QtyError   if qty <= 0
    raise SidesError if sides < 4
    dice = []
    qty.times {dice << (rand sides)+1}
    return dice
  end
end
[/rgss]

Then you call it like this...

Dice.roll 2, 6 # Possibly returns [2, 5]
 
Here's my second add-on for the Array class. It might look kinda repetitive, but you may chose to use just some of these method if you like.

Warning: include_one? method won't work in Ruby 1.8.1, the one included in RGSS 1 and 2. You'd need to create a one? method of your own.

[rgss]class Array
  def include_all?(*args)
    result = *args
    return result.all? {|arg| self.include?(arg)}
  end
 
  def include_one?(*args)
    result = *args
    return result.one? {|arg| self.include?(arg)}
  end
 
  def include_some?(*args)
    result = []
    args.each {|arg| result << self.include?(arg)}
    result.each {|arg| result.delete arg if !arg}
    return result.size.between?(2,args.size-1)
  end
end
[/rgss]
 
I like the Dice roller. Perhaps a more general name for it could be used for generating random numbers vs. just dice rolls. It could serve more purposes than dice rolling.

--------------

While working on the mouse module, I decided to make a little Button class that basically uses proc objects to perform actions when the mouse if hovering over an area (or not hovering), when the mouse is clicked (left or right) over an object. Infinitely useful for games with Mouse as part of their system.

Code:
class Mouse::Button

  attr_accessor :active

  attr_accessor :on_hover

  attr_accessor :off_hover

  attr_accessor :left_trigger

  attr_accessor :right_trigger

  def initialize(active = true)

    @active = active

    @on_hover = nil

    @off_hover = nil

    @left_trigger = nil

    @right_trigger = nil

    @hovering = false

  end

  def active=(active)

    if @active != active

      @active = active

      self.hovering = false unless active

    end

  end

  def self.hovering=(hovering)

    if @hovering != hovering

      @hovering = hovering

      if hovering

        @on_hover.call unless @on_hover == nil

      else

        @off_hover.call unless @off_hover == nil

      end

    end

  end

  def update

    return unless self.active

    self.hovering = hovering?

    if @hovering

      if Mouse.trigger?(0)

        @left_trigger.call unless @left_trigger == nil

      end

      if Mouse.trigger?(1)

        @right_trigger.call unless @right_trigger == nil

      end

    end

  end

end

 

class Mouse::Button::Rect < Mouse::Button

  attr_accessor :rect

  def initialize(rect, active = true)

    @rect = rect

    super(active)

  end

  def hovering?

    return @rect.in_rect?(*Mouse.position)

  end

end

 

class Mouse::Button::Circ < Mouse::Button

  attr_accessor :circ

  def initialize(circ, active = true)

    @circ = circ

    super(active)

  end

  def hovering?

    return @circ.in_circ?(*Mouse.position)

  end

end

 

class Mouse::Button::Bitmap < Mouse::Button::Rect

  attr_accessor :x, :y, :bitmap

  def initialize(x, y, bitmap, active = true)

    @x, @y = x, y

    @bitmap = bitmap

    super(Rect.new(x, y, bitmap.width, bitmap.height), active)

  end

  def x=(x)

    if @x != x

      @x = x

      @rect.set(@x, @rect.y, @rect.width, @rect.height)

    end

  end

  def y=(y)

    if @y != y

      @y = y

      @rect.set(@rect.x, @y, @rect.width, @rect.height)

    end

  end

  def bitmap=(bitmap)

    if @bitmap != bitmap

      @bitmap = bitmap

      @rect.set(@rect.x, @rect.y, @bitmap.width, @bitmap.height)

    end

  end

  def hovering?(x, y)

    return false unless super(x, y)

    mx, my = *Mouse.position

    return @bitmap.get_pixel(mx - @x, my - @y).alpha != 0

  end

end
Required classes:
Code:
class Circle

  attr_accessor :center_x, :center_y, :radius

  def initialize(cx, cy, rad)

    @center_x = cx

    @center_y = cy

    @radius   = rad

  end

  def diameter

    return @radius * 2

  end

  def dia ; return diameter ; end

  def circumference

    return Math::PI * dia

  end

  def circ ; return circumference ; end

  def perimeter ; return circumference ; end

  def per ; return circumference ; end

  def area

    return Math::PI * @radius ** 2

  end

  def in_circ?(x, y)

    return Math.hypot((x - @x).abs, (y - @y).abs) < @radius

  end

end

 

class Rect

  def in_rect?(x, y)

    return x.between?(@x, @x + @width) && y.between?(@y + @height)

  end

end

Right now you can create 3 types of buttons; Rectangle, Circle or Bitmap; which basically just define a region the mouse must be within. The first two are pretty straight forward and just use some geometry. The Bitmap button lets you set a bitmap object that it uses to check pixels. The mouse must be over a pixel with a alpha value over 0 to be considered hovering. Its also uses the Rect button as a parent to first check if the mouse is within the bitmaps area.

Testing them out now making a Sphere Break minigame.
 

Gust

Member

SephirothSpawn":2i0o3h5o said:
I like the Dice roller. Perhaps a more general name for it could be used for generating random numbers vs. just dice rolls. It could serve more purposes than dice rolling.

--------------

While working on the mouse module, I decided to make a little Button class that basically uses proc objects to perform actions when the mouse if hovering over an area (or not hovering), when the mouse is clicked (left or right) over an object. Infinitely useful for games with Mouse as part of their system.

Code:
class Mouse::Button

  attr_accessor :active

  attr_accessor :on_hover

  attr_accessor :off_hover

  attr_accessor :left_trigger

  attr_accessor :right_trigger

  def initialize(active = true)

    @active = active

    @on_hover = nil

    @off_hover = nil

    @left_trigger = nil

    @right_trigger = nil

    @hovering = false

  end

  def active=(active)

    if @active != active

      @active = active

      self.hovering = false unless active

    end

  end

  def self.hovering=(hovering)

    if @hovering != hovering

      @hovering = hovering

      if hovering

        @on_hover.call unless @on_hover == nil

      else

        @off_hover.call unless @off_hover == nil

      end

    end

  end

  def update

    return unless self.active

    self.hovering = hovering?

    if @hovering

      if Mouse.trigger?(0)

        @left_trigger.call unless @left_trigger == nil

      end

      if Mouse.trigger?(1)

        @right_trigger.call unless @right_trigger == nil

      end

    end

  end

end

 

class Mouse::Button::Rect < Mouse::Button

  attr_accessor :rect

  def initialize(rect, active = true)

    @rect = rect

    super(active)

  end

  def hovering?

    return @rect.in_rect?(*Mouse.position)

  end

end

 

class Mouse::Button::Circ < Mouse::Button

  attr_accessor :circ

  def initialize(circ, active = true)

    @circ = circ

    super(active)

  end

  def hovering?

    return @circ.in_circ?(*Mouse.position)

  end

end

 

class Mouse::Button::Bitmap < Mouse::Button::Rect

  attr_accessor :x, :y, :bitmap

  def initialize(x, y, bitmap, active = true)

    @x, @y = x, y

    @bitmap = bitmap

    super(Rect.new(x, y, bitmap.width, bitmap.height), active)

  end

  def x=(x)

    if @x != x

      @x = x

      @rect.set(@x, @rect.y, @rect.width, @rect.height)

    end

  end

  def y=(y)

    if @y != y

      @y = y

      @rect.set(@rect.x, @y, @rect.width, @rect.height)

    end

  end

  def bitmap=(bitmap)

    if @bitmap != bitmap

      @bitmap = bitmap

      @rect.set(@rect.x, @rect.y, @bitmap.width, @bitmap.height)

    end

  end

  def hovering?(x, y)

    return false unless super(x, y)

    mx, my = *Mouse.position

    return @bitmap.get_pixel(mx - @x, my - @y).alpha != 0

  end

end
Required classes:
Code:
class Circle

  attr_accessor :center_x, :center_y, :radius

  def initialize(cx, cy, rad)

    @center_x = cx

    @center_y = cy

    @radius   = rad

  end

  def diameter

    return @radius * 2

  end

  def dia ; return diameter ; end

  def circumference

    return Math::PI * dia

  end

  def circ ; return circumference ; end

  def perimeter ; return circumference ; end

  def per ; return circumference ; end

  def area

    return Math::PI * @radius ** 2

  end

  def in_circ?(x, y)

    return Math.hypot((x - @x).abs, (y - @y).abs) < @radius

  end

end

 

class Rect

  def in_rect?(x, y)

    return x.between?(@x, @x + @width) && y.between?(@y + @height)

  end

end

Right now you can create 3 types of buttons; Rectangle, Circle or Bitmap; which basically just define a region the mouse must be within. The first two are pretty straight forward and just use some geometry. The Bitmap button lets you set a bitmap object that it uses to check pixels. The mouse must be over a pixel with a alpha value over 0 to be considered hovering. Its also uses the Rect button as a parent to first check if the mouse is within the bitmaps area.

Testing them out now making a Sphere Break minigame.

I think a better approach than subclassing for each type is creating an "area" attr that can be set to a Bitmap or to an object with method "inside?(x,y)". If the area is a bitmap, you treat it as a bitmap, otherwise just call the inside? method. That way you can change the button "type" anytime.
 
Not a bad idea, never thought of it.

Code:
class Mouse::Button

  attr_reader   :area

  attr_reader   :bitmap

  attr_reader   :active

  attr_accessor :on_hover

  attr_accessor :off_hover

  attr_accessor :left_trigger

  attr_accessor :right_trigger

  def initialize(area, active = true, procs = {})

    @area = area

    @bitmap = nil

    @active = active

    instances = [@on_hover, @off_hover, @left_trigger, @right_trigger]

    keys = ['on_hov', 'off_hov', 'l_trig', 'r_trig']

    for i in 0...instances.size

      instances[i] = procs.has_key?(keys[i]) ? procs[keys[i]] : nil

    end

    @hovering = false

  end

  def area=(area)

    if @area != area

      @area = area

      if @bitmap != nil && @area.is_a?(Rect)

        @area.set(@area.x, @area.y, @bitmap.width, @bitmap.height)

      end

    end

    self.hovering = hovering?

  end

  def bitmap=(bitmap)

    if @bitmap != bitmap

      @bitmap = bitmap

      unless @area.is_a?(Rect)

        @area = Rect.new(0, 0, @bitmap.width, @bitmap.height)

      else

        @area.set(@area.x, @area.y, @bitmap.width, @bitmap.height)

      end

    end

    self.hovering = hovering?

  end

  def active=(active)

    if @active != active

      @active = active

      self.hovering = false unless active

    end

  end

  def self.hovering=(hovering)

    if @hovering != hovering

      @hovering = hovering

      if hovering

        @on_hover.call unless @on_hover == nil

      else

        @off_hover.call unless @off_hover == nil

      end

    end

  end

  def hovering?

    mx, my = *Mouse.position

    if @area.respond_to(:inside?)

      return false unless @area.inside?(mx, my)

    end

    if @bitmap != nil

      if @area.respond_to?(:x) && @area.repond_to?(:y)

        ax, ay = @area.x, @area.y

      else

        ax, ay = 0, 0

      end

      return false unless @bitmap.get_pixel(mx - ax, my - ay).alpha > 0

    end

    return true

  end

  def update

    return unless self.active

    self.hovering = hovering?

    if @hovering

      if Mouse.trigger?(0)

        @left_trigger.call unless @left_trigger == nil

      end

      if Mouse.trigger?(1)

        @right_trigger.call unless @right_trigger == nil

      end

    end

  end

end

I like it.
 

Gust

Member

Hope you won't mind, I remade that Button class, I think my version is easier to use.. you just set the "area" attribute with something that has and "inside?" method, or "x" and "y" methods and a "get_pixel" or "width" and "height", or even anything with a "bitmap" attr that can be used to get the pixels.
That is, the area could be a Sprite, a Bitmap with extra "x" and "y" methods, a Rect... or if you have another kind of shape that should use a specific algorithm, you just define the "inside?(x,y)" method for it.

Code:
 

#==============================================================================

# ** Mouse::Button

#------------------------------------------------------------------------------

# Author: Gustavo Bicalho

# Date: 2010-04-10

# MACL Mouse Module Required

#==============================================================================

 

class Mouse::Button

  attr_accessor :area

  attr_accessor :active

 

  attr_accessor :on_hover

  attr_accessor :on_leave

  attr_accessor :on_left_click

  attr_accessor :on_right_click

 

  def initialize(area, active = true)

    raise "area is #{area}" if !area

    @area = area

    @active = active

    @hover = hover?

  end

 

  def hover?

    return false unless self.active

    mx, my = *Mouse.position

 

    if @area.respond_to?(:inside?)

      return @area.inside?(mx, my)

    end

 

    if !(@area.respond_to?(:x) && @area.respond_to?(:y))

      return false # Can't calculate position

    end

 

    ax, ay = [email=mx-@area.x]mx-@area.x[/email], [email=my-@area.y]my-@area.y[/email] # relative mouse position

 

    bitmap = nil

    if @area.respond_to?(:get_pixel)

      bitmap = @area # ok, we'll use the get_pixel method of the area

    elsif @area.respond_to?(:bitmap) && @area.bitmap.respond_to?(:get_pixel)

      bitmap = @area.bitmap # we'll use the get_pixel of the bitmap of the area

    end

 

    return bitmap.get_pixel(ax, ay).alpha > 0 if bitmap != nil

    

    # here we couldn't find any "get_pixel" method, so we try width and height

    if @area.respond_to?(:width) && @area.respond_to?(:height)

      return ax < @area.width && ay < @area.height

    end

    

    # here, the area is completely useless. So we return false

    return false

  end

 

  def update

    if !self.active

      return if !@hover

      @on_leave.call if @on_leave

      @hover = false

      return

    end

 

    h = hover?

    

    # updates hovering status

    if @hover != h

      if @hover

        @on_leave.call if @on_leave

      else

        @on_hover.call if @on_hover

      end

      @hover = h

    end

 

    # checks clicks

    if h

      if Mouse.trigger?(0)

        @on_left_click.call unless @on_left_click == nil

      end

      if Mouse.trigger?(1)

        @on_right_click.call unless @on_right_click == nil

      end

    end

  end

 

end

 

And here's a little test scene for it:
Code:
 

class Scene_Test

 

  def initialize

  end

 

  def start_up

    @b_normal = Bitmap.new("Graphics/btn.png")

    @b_hover = Bitmap.new("Graphics/btn_hov.png")

 

    @sprite = Sprite.new

    @sprite.x, @sprite.y = 40, 50

    @sprite.bitmap = @b_normal

    

    @button = Mouse::Button.new(@sprite)

    @button.on_hover = Proc.new { @sprite.bitmap = @b_hover }

    @button.on_leave = Proc.new { @sprite.bitmap = @b_normal }

    @button.on_left_click = Proc.new { $scene = nil }

    @button.on_right_click = Proc.new {

      @sprite.x, @sprite.y = rand(200)+100, rand(200)+100

    }

  end

 

  def clean_up

    @b_normal.dispose

    @b_hover.dispose

    @sprite.dispose

  end

 

  def update

    Mouse.update

    pos = Mouse.position

    @button.update

  end

 

  def main

    start_up

    Graphics.transition

    loop do

      Graphics.update

      Input.update

      update

      break if $scene != self

    end

    Graphics.freeze

    clean_up

  end

 

end

 
Just put something that looks like a button at the Graphics dir with the name "btn.png" and another (different) one with the name "btn_hov.png" to see the hover effect. There's no cursor sprite so you can't test it properly in fullscreen, but hey, it's only an example :P

If you want to add this to the MACL or that HBGE project of yours, I'd be honored :biggrin:

PS: would you review my RGSS Web Kit script, please? XD
 
Hmm... The only thing is that Bitmaps don't have a x or y instance, and that's only the thing.

The extra methods and differences I have are all control. If you turn a button off, you shouldn't still be hovering and it should call the off_hover method. Which called for the method for setting the hovering instance, to avoid that being in both the update and active= methods. I also kept the area and bitmap different for a speed thing. An extra instance, but relied more on the inside? method in the Rect class instead of checking the mouse position within the bitmaps area. This also allowed Buttons with a bitmap to have a :x and :y instance by having a Rect area property. Slight differences, but I'll work on taking the best of the two.

I am also working on making a Mixin for this, so you don't have to initialize button objects. But instead Sprites with include a button mixin (will have to also rely on the src_rect) but will work without having to create sprites and buttons.

And sure I will take a look over your web kit. Mind you I don't know tons about web-related Ruby, but I can look and do some research while I look over it. At the least I can do is go over your coding practices and formatting. I'll take a look tonight sometime.
 

Gust

Member

SephirothSpawn":3tp37yfo said:
Hmm... The only thing is that Bitmaps don't have a x or y instance, and that's only the thing.

The extra methods and differences I have are all control. If you turn a button off, you shouldn't still be hovering and it should call the off_hover method. Which called for the method for setting the hovering instance, to avoid that being in both the update and active= methods. I also kept the area and bitmap different for a speed thing. An extra instance, but relied more on the inside? method in the Rect class instead of checking the mouse position within the bitmaps area. This also allowed Buttons with a bitmap to have a :x and :y instance by having a Rect area property. Slight differences, but I'll work on taking the best of the two.

I am also working on making a Mixin for this, so you don't have to initialize button objects. But instead Sprites with include a button mixin (will have to also rely on the src_rect) but will work without having to create sprites and buttons.

And sure I will take a look over your web kit. Mind you I don't know tons about web-related Ruby, but I can look and do some research while I look over it. At the least I can do is go over your coding practices and formatting. I'll take a look tonight sometime.

Well, anyone could add the x and y properties to Bitmaps, I thought someone could want to do that to avoid creating a Sprite =P But actually the main idea is to use Sprites.
And if you want a bitmap on the screen and have a button that reacts to an area of the bitmap (like <map> and <area> in html) you just put the bitmap on the screen using a Sprinte or whatever and use Rects (or classes implementing inside?(x,y), which by the way Rects don't) to define those areas. Of course, if you moved the Bitmap you'd need to move the areas together...

About running off_hover when deactivating the button, I just forgot that. Let's change the
Code:
 

  def update

    return unless self.active

    [...]

 
to
Code:
 

  def update

    if !self.active

      return if !@hover

      @on_leave.call if @on_leave

      @hover = false

      return

    end

    [...]

 

And change the hover? method to return false whenever self.active is false.
I'll make those changes in my original post.

The mixin-button idea sounds a bit weird to me... maybe because I'm used to static languages =P
 
here are some snippets of code:

Testing whether any key is pressed:
[ruby]module Input
  def self.all_keys?
    self.update
    self.constants.each{|i|return true if Input.trigger?(eval(i))}
    return false
  end
end
[/ruby]
loop{break if Input.all_keys?}

Opening the web browser:
[ruby]module Net
  def self.openurl(url)
    url='http://'<<url if url[0..6]!='http://'
    Thread.new{system("explorer",url)}
  end
end
[/ruby]
Net.openurl("http://www.hbgames.org")

Converting Array to Hash:
[ruby]class Array
  def to_hash(val=nil)
    return Hash[*self.collect{|v|[v,val]}.flatten]
  end
end
[/ruby]
val is the default value
["key1","key2","key3"].to_hash(1)
=> {"key1"=> 1, "key2"=> 1, "key3"=> 1}

This one probably already exists.
[ruby]class Float<Numeric
  alias :f_round :round unless $@
  def round(r=0)
    eval sprintf("%.#{r}f",self)
  end
end
[/ruby]
3,14159265.round(3)
=>3,141
 
Well, what happens when Arrays have multiples of the same value? I think a .to_hash function should be with the indexes as keys, and objects as values. Otherwise some of the information will get lost. Additionally, you aren't just populating a Hash with multiple keys and same value.

Code:
 

class Array

  def to_hash(include_nil = false)

    hash = {}

    for i in 0...size

      hash[i] = self[i] if self[i] != nil || include_nil

    end

    return hash

  end

end
 
Here I posted a few contributions.

[rgss]class Array
  def which_is?(bool=true)
    return 'No boolean argument provided' unless bool or !bool or bool.nil?
    results = []
    self.each {|v| results << self.index(v) if v == bool}
    return results.empty? ? nil : results
  end
end
 
class Game_Switches
  def [](switch_id)
    if switch_id.is_a? Integer and switch_id <= 5000 and @data[switch_id] != nil
      return @data[switch_id]
    elsif switch_id.is_a? Range and switch_id.first.between?(1, 5000) and
      switch_id.last.between?(1, 5000)
      ary = []
      switch_id.each {|id| ary << (@data[id].nil? ? false : @data[id]) }
      return ary
    end
    return false
  end
end
[/rgss]

To test it, use...

$game_switches[2] = true
p $game_switches[1..5]
p $game_switches[1..5].include? true
p $game_switches[1..5].which_is? true

You should see 3 popup windows with the results to those commands.


 
A while ago I made a small extension to the Module class to allow quick writing of getter/setter methods for class variables. I used eval to create the method, so I rewrote the functions to not use eval.

Code:
class Module

  private

  def class_accessor(*symbols)

    class_reader *symbols

    class_writer *symbols

  end

  def class_reader(*symbols)

    symbols.each do |sym|

      self.class.send(:define_method, sym) {class_variable_get("@@#{sym}")}

    end

    self

  end

  def class_writer(*symbols)

    symbols.each do |sym|

      self.class.send(:define_method, "#{sym}=") {|obj| class_variable_set("@@#{sym}", obj)}

    end

    self

  end

  def class_attr(symbol, writable=false)

    class_reader symbol

    class_writer symbol if writable

  end

end
 

regi

Sponsor

A small problem I noticed was that RMXP did not recognize terrain tags from events with tileset graphics. This may get in the way of some complicated event system, so I figured I might post this to help others out.

Starting on line 299 of Game_Map, I modified def terrain_tag to include event tile checks. Replace with the code below:

Code:
class Game_Map

 

  def terrain_tag(x, y)

    if @map_id != 0

      for i in [2, 1, 0]

        tile_id = data[x, y, i]

        if tile_id == nil

          return 0

        elsif @terrain_tags[tile_id] > 0

          for event in $game_map.events.values

            if event.x == x and event.y == y and event.through == false and event.tile_id != nil and @terrain_tags[event.tile_id] > 0

              return @terrain_tags[event.tile_id]

            end

          end

          return @terrain_tags[tile_id]

        end

      end

    end

    return 0

  end

 

end

There may be errors with priority (I haven't delved deep into that area) but for general layered tiles it should work fine. Hope this helps, and feel free to comment on more efficient ways to rewrite, as it's probably not perfect.
 
Here's a little snippet to allow seperators in instances of Window_Command, effectively skipping the highlighting. Now I know that this could've been solved better, adjusting the index accordingly and all that, but for what it does compared to the amount of code it'd need, this is the better solution.

[rgss]class Window_Command < Window_Selectable
  #--------------------------------------------------------------------------
  def cursor_down(wrap = false)
    if (@index < @item_max - @column_max) or (wrap and @column_max == 1)
      skip = (@commands[@index+1] == '') ? 1 : 0
      @index = (@index + skip + @column_max) % @item_max
    end
  end
  #--------------------------------------------------------------------------
  def cursor_up(wrap = false)
    if (@index >= @column_max) or (wrap and @column_max == 1)
      skip = (@commands[@index-1] == '') ? 1 : 0
      @index = (@index - skip - @column_max + @item_max) % @item_max
    end
  end
  #--------------------------------------------------------------------------
end
[/rgss]

Done in RMVX - no idea if it'll work in RMXP. For use without any credit whatsoever, of course.
 

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