Module: Rubygame::Hotspot

Defined in:
lib/rubygame/hotspot.rb

Overview

NOTE: you must require ‘rubygame/hotspot’ manually to gain access to Rubygame::Hotspot. It is not imported with Rubygame by default!

Hotspot is a mixin module to extend an object with “hotspots”: custom, named, relative position offsets. Hotspots can be defined relative to the origin, to another hotspot, or to the results of a method (via a ‘smart’ hotspot).

There are two types of hotspots, simple and ‘smart’.

Simple hotspots are an Array of three values, an x offset, a y offset, and the label of another hotspot relative to which this hotspot is defined. If the last argument is omitted or nil, the hotspot is defined relative to the true origin (i.e. (0,0), the top-left corner of the Screen). See #new_hotspot.

Smart hotspots, or ‘smartspots’ for short, act as a proxy to the object’s methods. Each time a smartspot is evaluated, it calls the object’s method of the same name as the smartspot, and uses the results of the method as x and y offsets. Therefore, smartspots only work for methods which:

1. take no arguments
2. return an Array with 2 Numeric values (or something else that responds
   to #[]

By adding a smartspot to a Rect, for example, you could define simple hotspots relative to its Rect#center; then, even if the Rect moves or changes size, the smartspot will always to evaluate to its true center. See #new_smartspot.

– ((Old documentation/brainstorming)) As an example, consider an object which represents a person’s face: eyes, ears, nose, mouth, etc. You might make a face and define several hotspots like so:

myface = Face.new()                # Create a new face, with no hotspots.
myface.extend(Hotspot)             # Extend myface with hotspot ability.
myface.new_hotspot \               # Define some new hotspots: ...
 :nose => [10,5, :center],        # the nose, relative to face's center,
 :left_eye => [-5,-2, :nose],     # the left eye, left and above the nose,
 :left_brow => [0,-5, :left_eye], # the left eye-brow, above the left eye.

Please note that :center is a “virtual” hotspot. When the coordinates of :center are requested, myface‘s #center method is called* and the results used as the coordinates. (* Technically, myface is sent :center, which is not exactly the same as calling #center.)

Now, suppose we want to find out where :left_brow is, in absolute coordinates (i.e. relative to the origin). We can do this, even if myface has moved, or the hotspots have been changed:

myface.left_brow                   # => [5,-2]
myface.move(20,-12)                # moves the face right and up
myface.left_brow                   # => [25,-14]
myface.new_hotspot \
  :left_eye => [-10,3]             # redefine the left_eye hotspot
myface.left_brow                   # => [20,-9]

Where do [5,-2], [25,-24], and [20,-9] come from? They are the vector sums of each hotspot in the chain: left_brow, left_eye, nose, and center. See #hotspot for more information. ++

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *args) ⇒ Object



257
258
259
260
261
262
# File 'lib/rubygame/hotspot.rb', line 257

def method_missing(symbol,*args)
  if have_hotspot?(symbol) 
    hotspot(symbol)
  else old_method_missing(symbol,*args)
  end
end

Instance Method Details

#def_hotspot(dfn) ⇒ Object

:call-seq: def_hotspot label => [x,y,parent]

Define label as a simple hotspot, a custom reference coordinate point x pixels to the right and y pixels below the hotspot whose label is parent. You may omit parent, in which case the hotspot will evaluate relative to the origin, i.e. the top-left corner of the Screen.

See also #def_smartspot to create a ‘smart hotspot’.

label must be usable as a key in a Hash table. Additionally, if you want myobject.{label} to work like myobject.hotspot({label}), label must be a :symbol.

IMPORTANT: Do NOT create circular hotspot chains (e.g. a -> b -> a). Doing so will raise SystemStackError when #hotspot is asked to evaluate any hotspot in that chain. Hotspots are not yet smart enough to detect circular chains.

Hotspots can be defined in any order, as long as you define all the hotspots in a chain before that chain is evaluated with #hotspot.

You may define multiple hotspots simultaneously by separating the definitions by commas. For example:

def_hotspot label => [x,y,parent], label2 => [x,y,parent]

Users of the Rake library will recognize this style of syntax. It is simply constructing a Hash object and passing it as the only argument to #new_hotspot. The above code is equivalent to:

def_hotspot( { label => [x,y,parent], label2 => [x,y,parent] } )


120
121
122
123
124
125
126
127
128
# File 'lib/rubygame/hotspot.rb', line 120

def def_hotspot(dfn)
  @hotspots.update(dfn)
rescue NoMethodError => e
  unless defined? @hotspots
    @hotspots = Hash.new
    retry
  else raise e
  end
end

#def_smartspot(*labels) ⇒ Object

Define each label in *labels as a smartspot (‘smart hotspot’).

To prevent outside objects from abusing hotspots to call arbitrary methods, a smartspot must be defined for each method before it can be used as a parent to a hotspot.

The label must be a :symbol, and it must be identical to the name of the method to be called.



196
197
198
199
200
201
202
203
204
205
# File 'lib/rubygame/hotspot.rb', line 196

def def_smartspot(*labels)
  @smartspots += labels.flatten
  @smartspots.uniq!
rescue NoMethodError => e
  unless defined? @smartspots
    @smartspots = Array.new
    retry
  else raise e
  end
end

#defined_hotspot?(label) ⇒ Boolean

True if label has been defined as a simple hotspot.

Returns:

  • (Boolean)


138
139
140
141
142
143
144
145
# File 'lib/rubygame/hotspot.rb', line 138

def defined_hotspot?(label)
  @hotspots.include? label
rescue NoMethodError => e
  unless defined? @hotspots
    false
  else raise e
  end
end

#defined_smartspot?(label) ⇒ Boolean

True if label has been defined as a smartspot.

Returns:

  • (Boolean)


213
214
215
216
217
218
219
220
# File 'lib/rubygame/hotspot.rb', line 213

def defined_smartspot?(label)
  @smartspots.include? label
rescue NoMethodError => e
  unless defined? @smartspots
    false
  else raise e
  end
end

#hotspot(label, x = 0, y = 0) ⇒ Object

:call-seq: hotspot(label)

Returns the absolute coordinates represented by the hotspot label. Will return nil if the hotspot (or one of its ancestors) does not exist.

This method will recursively evaluate the hotspot, it’s parent hotspot (if any), and so on, until a parent-less hotspot or a smartspot is found.

(NOTE: this means that a circular chains (e.g. a -> b -> a) will keep going around and around until the ruby interpreter raises SystemStackError!)

The final value returned by this method will be the vector component sum of all the hotspots in the chain. For example, if you have this chain:

:a => [1, 2, :b]
:b => [4, 8, :c]
:c => [16,32]

the value returned for :a would be [21,42], i.e. [1+4+16, 2+8+32] – The x and y arguments are used for recursive accumulation, although I suppose you could use them to create a temporary offset by giving something besides zero when you look up a hotspot. ++



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/rubygame/hotspot.rb', line 172

def hotspot(label,x=0,y=0)
  a = @hotspots[label]
  if a[2].nil?              # has no parent
    [x+a[0],y+a[1]]
  else                      # has a parent
    hotspot(a[2],x+a[0],y+a[1])
  end
rescue NoMethodError => e
  if not(defined? @hotspots)
    return nil
  elsif a.nil?
    smartspot(label,x,y)
  else raise e
  end
end

#old_method_missingObject

TODO: Methods for changing the position of the object indirectly, by saying where the hotspot should be. You could do this: my_object.foot = [5,3], and if foot was defined relative to, say, #center, it would set center = [5-x_offset, 3-y_offset]

It should be recursive to work with chains of hotspots too.

++



255
# File 'lib/rubygame/hotspot.rb', line 255

alias :old_method_missing :method_missing

#smartspot(label, x = 0, y = 0) ⇒ Object

:call-seq: smartspot(label)

Evaluate the smartspot label, calling the method of the same name as label. Will return nil if no such smartspot has been defined. – The x and y arguments are used for recursive accumulation. ++



229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/rubygame/hotspot.rb', line 229

def smartspot(label,x=0,y=0)
  if @smartspots.include? label
    a = self.send(label)
    [x+a[0],y+a[1]]
  else
    nil
  end
rescue NoMethodError => e
  unless defined? @smartspots
    nil
  else raise e
  end
end

#undef_hotspot(*labels) ⇒ Object

Remove all simple hotspots whose label is included in *labels.



131
132
133
134
135
# File 'lib/rubygame/hotspot.rb', line 131

def undef_hotspot(*labels)
  labels.flatten.each do |l|
    @hotspots.delete(l)
  end
end

#undef_smartspot(*labels) ⇒ Object

Remove all smartspots whose label is included in *labels.



208
209
210
# File 'lib/rubygame/hotspot.rb', line 208

def undef_smartspot(*labels)
  @smartspots -= labels.flatten
end