Class: Chingu::GameObjectMap

Inherits:
Object
  • Object
show all
Defined in:
lib/chingu/game_object_map.rb

Overview

** This class is under heavy development, API will most likely change! **

GameObjectMap can map any set of game objects into a 2D-array for fast lookup. You can choose gridsize with the :grid-parameter, defaults to [32,32]. The smaller the grid the more memory GameObjectMap will eat.

The game objects sent to GameObjectMap must respond to #bb (as provided by trait :bounding_box) This is needed to calcuate what cells in the grid each game object covers.

Basic usage:

@map = GameObjectMap.new(:game_objects => TerrainObject.all, :grid => [32, 32])
@map.at(100, 100)         # returns one TerrainObject at x/y: 100/100
@map.game_object(player)  # returns one TerrainObject which collides with player.bounding_box

A GameObjectMap is ment to be used for static non-moving objects, where a map can be calculated once and then used for fast lookups. This makes GameObjectMap very well suited for terrain for a player to walk on / collide with.

One cell in the GameObjectMap can only be occupied by one game object. If you need many objects at the same cell, use 2 GameObjectMaps, something like:

@terrain = GameObjectMap.new(:game_objects => Terrain.all)
@mines = GameObjectMap.new(:game_objects => Mine.all)

@player.stop_falling  if @terrain.at(@player.x, @player)
@player.die           if @mine.at(@player.x, @player)

Take note, since there can be only 1 game object per cell a huge game object could very well “cover out” another smaller game objects occupying the same cells.

** This class is under heavy development, API will most likely change! **

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ GameObjectMap

Returns a new instance of GameObjectMap



57
58
59
60
61
62
# File 'lib/chingu/game_object_map.rb', line 57

def initialize(options = {})
  @game_objects = options[:game_objects]
  @grid = options[:grid] || [32,32]
  @debug = options[:debug]
  create_map
end

Instance Attribute Details

#game_object_positionsObject (readonly)

Returns the value of attribute game_object_positions



55
56
57
# File 'lib/chingu/game_object_map.rb', line 55

def game_object_positions
  @game_object_positions
end

#mapObject (readonly)

Returns the value of attribute map



55
56
57
# File 'lib/chingu/game_object_map.rb', line 55

def map
  @map
end

Instance Method Details

#at(x, y) ⇒ Object

Gets game object from map that resides on real world coordinates x/y



126
127
128
129
130
# File 'lib/chingu/game_object_map.rb', line 126

def at(x, y)
  lookup_x = (x / @grid[0]).to_i
  lookup_y = (y / @grid[1]).to_i
  @map[lookup_x][lookup_y]  rescue nil  # Benchmark this against @map[lookup_x] && @map[lookup_x][lookup_y] => prob faster
end

#clear_at(x, y) ⇒ Object

Clear the game object residing in the cell given by real world coordinates x/y



117
118
119
120
121
# File 'lib/chingu/game_object_map.rb', line 117

def clear_at(x, y)
  lookup_x = (x / @grid[0]).to_i
  lookup_y = (y / @grid[1]).to_i
  @map[lookup_x][lookup_y] = nil
end

#collisions_with(game_object) ⇒ Object

Returns an array of GameObjects in this grid map that collide with the given GameObject (which is not on the grid).



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

def collisions_with(game_object)
  start_x = (game_object.bb.left / @grid[0]).to_i
  stop_x =  (game_object.bb.right / @grid[0]).to_i

  objects = []
  (start_x ... stop_x).each do |x|
    start_y = (game_object.bb.top / @grid[1]).to_i
    stop_y =  (game_object.bb.bottom / @grid[1]).to_i

    (start_y ... stop_y).each do |y|
      obj = game_object_at(x, y)
      objects << @map[x][y] if obj and obj != game_object  # Don't yield collisions with itself
    end
  end
  objects
end

#create_mapObject

Creates a “tilemap” of game objects using @grid and @game_objects Useful for faster collision detection on a grid-based freeform map created with the Edit game state.



68
69
70
71
72
73
74
75
76
# File 'lib/chingu/game_object_map.rb', line 68

def create_map
  @map = []
  @game_object_positions = {}
        
  @game_objects.each do |game_object|
    puts "#{game_object.class} @ #{game_object.x}/#{game_object.y} - #{game_object.bb}" if @debug
    insert(game_object)                
  end
end

#delete(game_object) ⇒ Object Also known as: clear_game_object

Removes a specific game object from the map, replace the cell-value with nil



102
103
104
105
106
107
108
109
110
111
# File 'lib/chingu/game_object_map.rb', line 102

def delete(game_object)
  range_x, range_y = @game_object_positions[game_object]
  return unless range_x && range_y
  
  range_x.each do |x|
    range_y.each do |y|
      @map[x][y] = nil
    end
  end
end

#each_collision(game_object) ⇒ Object

Yields all game objects occupying any of the cells that given 'game_object' covers



154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/chingu/game_object_map.rb', line 154

def each_collision(game_object)
  start_x = (game_object.bb.left / @grid[0]).to_i
  stop_x =  (game_object.bb.right / @grid[0]).to_i
  
  (start_x ... stop_x).each do |x|
    start_y = (game_object.bb.top / @grid[1]).to_i
    stop_y =  (game_object.bb.bottom / @grid[1]).to_i
      
    (start_y ... stop_y).each do |y|
      yield @map[x][y]   if @map[x] && @map[x][y] && @map[x][y] != game_object  # Don't yield collisions with itself
    end
  end
end

#each_game_object_between(origin, dest) ⇒ Object

Yields to the block each GameObject in this map's grid which lies between two GameObjects: origin and dest.



193
194
195
196
197
198
# File 'lib/chingu/game_object_map.rb', line 193

def each_game_object_between(origin, dest)
  grid_spaces_between(origin, dest) do |x, y|
    obj = game_object_at(x, y)
    yield if obj and obj != origin and obj != dest
  end
end

#from_game_object(game_object) ⇒ Object

Return the first game object occupying any of the cells that given 'game_object' covers



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/chingu/game_object_map.rb', line 135

def from_game_object(game_object)
  start_x = (game_object.bb.left / @grid[0]).to_i
  stop_x =  (game_object.bb.right / @grid[0]).to_i
  
  (start_x .. stop_x).each do |x|
    start_y = (game_object.bb.top / @grid[1]).to_i
    stop_y =  (game_object.bb.bottom / @grid[1]).to_i
      
    (start_y .. stop_y).each do |y|
      return @map[x][y]   if @map[x] && @map[x][y]
    end
    
  end
  return nil
end

#game_object_at(x, y) ⇒ Object

Return the GameObject at the grid coordinates (not pixel coordinates) x and

  1. If there is no object there, return nil.



237
238
239
240
# File 'lib/chingu/game_object_map.rb', line 237

def game_object_at(x, y)
  return @map[x][y] if @map[x] and @map[x][y]
  return nil
end

#game_object_between?(origin, dest, options = {}) ⇒ Boolean

Options can contain the keys :target and :only.

Returns true if GameObject options is between GameObjects origin and dest.

If the target is nil, returns true if any GameObject in this map's grid lies between origin and dest.

If options is set, return true only if the matched object is_a? options.

This can be used to find line-of-sight between two objects, for example:

player.sees_enemy if game_object_map.game_object_between?(player, enemy, :only => Wall) # Walls block vision

Returns:

  • (Boolean)


215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/chingu/game_object_map.rb', line 215

def game_object_between?(origin, dest, options={})
  grid_spaces_between(origin, dest) do |x, y|
    if options[:target]
      x_pixels = x * @grid[0]
      y_pixels = y * @grid[1]
      return true if options[:target].collision_at?(x_pixels, y_pixels) 
    else
      obj = game_object_at(x, y)
      if options[:only]
        return true if obj and obj != origin and obj != dest and obj.is_a? options[:only]
      else
        return true if obj and obj != origin and obj != dest
      end
    end
  end
  return false
end

#grid_spaces_between(origin, dest) ⇒ Object

Returns an array of [x, y] grid coordinate pairs in this map's grid between the GameObjects origin and dest.

If a block is given, the method will yield x, y to the block for each grid square.



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/chingu/game_object_map.rb', line 249

def grid_spaces_between(origin, dest)
  # Note: x and y here are a Grid location, not pixel coordinates.
  raise "Expected GameObject as origin, got #{origin.class}" unless origin.is_a? Chingu::GameObject
  raise "Expected GameObject as dest, got #{dest.class}" unless dest.is_a? Chingu::GameObject
  start_x = (origin.bb.x/ @grid[0]).to_i
  stop_x =  (dest.bb.x/ @grid[0]).to_i
  start_y = (origin.bb.y/ @grid[1]).to_i
  stop_y =  (dest.bb.y/ @grid[1]).to_i
  diff_x = (start_x - stop_x).abs
  diff_y = (start_y - stop_y).abs

  x = start_x
  y = start_y
  n = 1 + diff_x + diff_y
  x_inc = 1 #FIXME: do fewer checks
  x_inc = -1 if start_x > stop_x
  y_inc = 1
  y_inc = -1 if start_y > stop_y
  error = diff_x - diff_y
  diff_x *= 2
  diff_y *= 2


  checked_squares = []
  checked_squares << [start_x,start_y]
  checked_squares << [stop_x,stop_y]
  n.times do

    checked_squares << [x,y]
    yield(x, y) if block_given?

    if error > 0
      x += x_inc
      error -= diff_y
    else
      y += y_inc
      error += diff_x
    end

  end

  return checked_squares
end

#insert(game_object) ⇒ Object

Insert game_object into the map



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/chingu/game_object_map.rb', line 81

def insert(game_object)
  start_x = ( game_object.bb.left / @grid[0] ).to_i
  stop_x =  ( game_object.bb.right / @grid[0] ).to_i
  
  (start_x ... stop_x).each do |x|
    start_y = ( game_object.bb.top / @grid[1] ).to_i
    stop_y =  ( game_object.bb.bottom / @grid[1] ).to_i
      
    @game_object_positions[game_object] = [(start_x ... stop_x), (start_y ... stop_y)]
      
    @map[x] ||= []
    (start_y ... stop_y).each do |y|
      @map[x][y] = game_object
      puts "#{game_object.class} => map[#{x}][#{y}]" if @debug
    end
  end      
end