Class: Entity

Inherits:
Object
  • Object
show all
Extended by:
ClassMethods
Includes:
ClassMethods, EntityConstants, Registerable, Serializable, Transparency
Defined in:
lib/game_2d/entity.rb,
lib/game_2d/entity/block.rb,
lib/game_2d/entity/pellet.rb,
lib/game_2d/entity/titanium.rb,
lib/game_2d/entity/teleporter.rb,
lib/game_2d/entity/destination.rb,
lib/game_2d/entity/owned_entity.rb

Direct Known Subclasses

OwnedEntity, Teleporter, Titanium, Player, Wall

Defined Under Namespace

Modules: ClassMethods Classes: Block, Destination, OwnedEntity, Pellet, Teleporter, Titanium

Constant Summary

Constants included from EntityConstants

EntityConstants::CELL_WIDTH_IN_PIXELS, EntityConstants::MAX_VELOCITY, EntityConstants::PIXEL_WIDTH, EntityConstants::WIDTH

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ClassMethods

bottom_cell_y_at, constrain_velocity, left_cell_x_at, right_cell_x_at, top_cell_y_at

Methods included from Transparency

#transparent?

Methods included from Registerable

#nullsafe_registry_id, #registry_id, #registry_id=, #registry_id?, #registry_id_safe

Methods included from Serializable

#<=>, #==, as_json, #eql?, from_json, #hash, #to_json

Constructor Details

#initialize(x = 0, y = 0, a = 0, x_vel = 0, y_vel = 0) ⇒ Entity

space: the game space x, y: position in sub-pixels of the upper-left corner a: angle, with 0 = up, 90 = right x_vel, y_vel: velocity in sub-pixels



53
54
55
56
57
58
# File 'lib/game_2d/entity.rb', line 53

def initialize(x = 0, y = 0, a = 0, x_vel = 0, y_vel = 0)
  @x, @y, self.a = x, y, a
  self.x_vel, self.y_vel = x_vel, y_vel
  @moving = true
  @grabbed = false
end

Instance Attribute Details

#aObject

Returns the value of attribute a.



47
48
49
# File 'lib/game_2d/entity.rb', line 47

def a
  @a
end

#movingObject

X and Y position of the top-left corner



45
46
47
# File 'lib/game_2d/entity.rb', line 45

def moving
  @moving
end

#spaceObject

X and Y position of the top-left corner



45
46
47
# File 'lib/game_2d/entity.rb', line 45

def space
  @space
end

#xObject

X and Y position of the top-left corner



45
46
47
# File 'lib/game_2d/entity.rb', line 45

def x
  @x
end

#x_velObject

Returns the value of attribute x_vel.



47
48
49
# File 'lib/game_2d/entity.rb', line 47

def x_vel
  @x_vel
end

#yObject

X and Y position of the top-left corner



45
46
47
# File 'lib/game_2d/entity.rb', line 45

def y
  @y
end

#y_velObject

Returns the value of attribute y_vel.



47
48
49
# File 'lib/game_2d/entity.rb', line 47

def y_vel
  @y_vel
end

Instance Method Details

#accelerate(x_accel, y_accel) ⇒ Object

Apply acceleration



118
119
120
121
# File 'lib/game_2d/entity.rb', line 118

def accelerate(x_accel, y_accel)
  self.x_vel = @x_vel + x_accel
  self.y_vel = @y_vel + y_accel
end

#all_stateObject



384
385
386
# File 'lib/game_2d/entity.rb', line 384

def all_state
  [registry_id_safe, @x, @y, @a, @x_vel, @y_vel, @moving]
end

#angle_to_vector(angle, amplitude = 1) ⇒ Object



251
252
253
254
255
256
257
258
259
# File 'lib/game_2d/entity.rb', line 251

def angle_to_vector(angle, amplitude=1)
  case angle % 360
  when 0 then [0, -amplitude]
  when 90 then [amplitude, 0]
  when 180 then [0, amplitude]
  when 270 then [-amplitude, 0]
  else raise "Trig unimplemented"
  end
end

#as_jsonObject



336
337
338
339
340
341
342
343
344
345
# File 'lib/game_2d/entity.rb', line 336

def as_json
  Serializable.as_json(self).merge!(
    :class => self.class.to_s,
    :registry_id => registry_id,
    :position => [ self.x, self.y ],
    :velocity => [ self.x_vel, self.y_vel ],
    :angle => self.a,
    :moving => self.moving?
  )
end

#bottom_cell_y(y = @y) ⇒ Object



107
# File 'lib/game_2d/entity.rb', line 107

def bottom_cell_y(y = @y); bottom_cell_y_at(y); end

#destroy!Object

Give this entity a chance to perform clean-up upon destruction



97
# File 'lib/game_2d/entity.rb', line 97

def destroy!; end

#direction_to(other_x, other_y) ⇒ Object

Is the other entity basically above us, below us, or on the left or the right? Returns the angle we should face if we want to face that entity.



285
286
287
# File 'lib/game_2d/entity.rb', line 285

def direction_to(other_x, other_y)
  vector_to_angle(*drop_diagonal(other_x - @x, other_y - @y))
end

#doomed?Boolean

Returns:

  • (Boolean)


72
# File 'lib/game_2d/entity.rb', line 72

def doomed?; @space.doomed?(self); end

#draw(window) ⇒ Object



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/game_2d/entity.rb', line 363

def draw(window)
  anim = window.animation[window.media(image_filename)]
  img = anim[Gosu::milliseconds / 100 % anim.size]
  # Entity's pixel_x/pixel_y is the location of the upper-left corner
  # draw_rot wants us to specify the point around which rotation occurs
  # That should be the center
  img.draw_rot(
    self.pixel_x + CELL_WIDTH_IN_PIXELS / 2,
    self.pixel_y + CELL_WIDTH_IN_PIXELS / 2,
    draw_zorder, draw_angle)
  # 0.5, 0.5, # rotate around the center
  # 1, 1, # scaling factor
  # @color, # modify color
  # :add) # draw additively
end

#draw_angleObject

0.5, 0.5, # rotate around the center 1, 1, # scaling factor @color, # modify color :add) # draw additively



378
# File 'lib/game_2d/entity.rb', line 378

def draw_angle; a; end

#draw_zorderObject



361
# File 'lib/game_2d/entity.rb', line 361

def draw_zorder; ZOrder::Objects end

#drop_diagonal(x_vel, y_vel) ⇒ Object

Given a vector with a diagonal, drop the smaller component, returning a vector that is strictly either horizontal or vertical.



279
280
281
# File 'lib/game_2d/entity.rb', line 279

def drop_diagonal(x_vel, y_vel)
  (y_vel.abs > x_vel.abs) ? [0, y_vel] : [x_vel, 0]
end

#empty_above?Boolean

Returns:

  • (Boolean)


249
# File 'lib/game_2d/entity.rb', line 249

def empty_above?; opaque(next_to(0)).empty?; end

#empty_on_left?Boolean

Returns:

  • (Boolean)


247
# File 'lib/game_2d/entity.rb', line 247

def empty_on_left?; opaque(next_to(270)).empty?; end

#empty_on_right?Boolean

Returns:

  • (Boolean)


248
# File 'lib/game_2d/entity.rb', line 248

def empty_on_right?; opaque(next_to(90)).empty?; end

#empty_underneath?Boolean

Returns:

  • (Boolean)


246
# File 'lib/game_2d/entity.rb', line 246

def empty_underneath?; opaque(next_to(180)).empty?; end

#entities_obstructing(new_x, new_y) ⇒ Object

Wrapper around @space.entities_overlapping Allows us to remove any entities that are transparent to us



130
131
132
133
# File 'lib/game_2d/entity.rb', line 130

def entities_obstructing(new_x, new_y)
  fail "No @space set!" unless @space
  opaque(@space.entities_overlapping(new_x, new_y))
end

#going_past_entity(other_x, other_y) ⇒ Object

Given our current position and velocity (and only if our velocity is not on a diagonal), are we about to move past the entity at the specified coordinates? If so, returns:

1) The X/Y position of the empty space just past the entity. Assuming the other entity is adjacent to us, this spot touches corners with the other entity.

2) How far we’d go to reach that point.

3) How far past that spot we would go.

4) Which way we’d have to turn (delta angle) if moving around the other entity. Either +90 or -90.



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/game_2d/entity.rb', line 303

def going_past_entity(other_x, other_y)
  return if @x_vel == 0 && @y_vel == 0
  return if @x_vel != 0 && @y_vel != 0

  if @x_vel.zero?
    # Moving vertically.  Find target height
    y_pos = (@y_vel > 0) ? other_y + HEIGHT : other_y - HEIGHT
    distance = (@y - y_pos).abs
    overshoot = @y_vel.abs - distance
    turn = if @y_vel > 0
      # Going down: Turn left if it's on our right
      direction_to(other_x, other_y) == 90 ? -90 : 90
    else
      # Going up: Turn right if it's on our right
      direction_to(other_x, other_y) == 90 ? 90 : -90
    end
    return [[@x, y_pos], distance, overshoot, turn] if overshoot >= 0
  else
    # Moving horizontally.  Find target column
    x_pos = (@x_vel > 0) ? other_x + WIDTH : other_x - WIDTH
    distance = (@x - x_pos).abs
    overshoot = @x_vel.abs - distance
    turn = if @x_vel > 0
      # Going right: Turn right if it's below us
      direction_to(other_x, other_y) == 180 ? 90 : -90
    else
      # Going left: Turn left if it's below us
      direction_to(other_x, other_y) == 180 ? -90 : 90
    end
    return [[x_pos, @y], distance, overshoot, turn] if overshoot >= 0
  end
end

#grab!Object

Entity is under direct control by a player This is transitory state (not persisted or copied)



92
# File 'lib/game_2d/entity.rb', line 92

def grab!; @grabbed = true; end

#grabbed?Boolean

Returns:

  • (Boolean)


94
# File 'lib/game_2d/entity.rb', line 94

def grabbed?; @grabbed; end

#harmed_by(other) ⇒ Object



228
# File 'lib/game_2d/entity.rb', line 228

def harmed_by(other); end

#i_hit(other) ⇒ Object



223
224
225
226
# File 'lib/game_2d/entity.rb', line 223

def i_hit(other)
  # TODO
  puts "#{self} hit #{other.inspect}"
end

#image_filenameObject



357
358
359
# File 'lib/game_2d/entity.rb', line 357

def image_filename
  raise "No image filename defined"
end

#left_cell_x(x = @x) ⇒ Object



104
# File 'lib/game_2d/entity.rb', line 104

def left_cell_x(x = @x); left_cell_x_at(x); end

#moveObject

Process one tick of motion. Only called when moving? is true



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/game_2d/entity.rb', line 186

def move
  # Force evaluation of both update_x and update_y (no short-circuit)
  # If we're moving faster horizontally, do that first
  # Otherwise do the vertical move first
  moved = @space.process_moving_entity(self) do
    if @x_vel.abs > @y_vel.abs then move_x; move_y
    else move_y; move_x
    end
  end

  # Didn't move?  Might be time to go to sleep
  @moving = false if !moved && sleep_now?

  moved
end

#move_xObject

Process one tick of motion, horizontally only



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/game_2d/entity.rb', line 136

def move_x
  return if doomed?
  return if @x_vel.zero?
  new_x = @x + @x_vel
  impacts = entities_obstructing(new_x, @y)
  if impacts.empty?
    @x = new_x
    return
  end
  @x = if @x_vel > 0 # moving right
    # X position of leftmost candidate(s)
    impact_at_x = impacts.collect(&:x).min
    impacts.delete_if {|e| e.x > impact_at_x }
    impact_at_x - WIDTH
  else # moving left
    # X position of rightmost candidate(s)
    impact_at_x = impacts.collect(&:x).max
    impacts.delete_if {|e| e.x < impact_at_x }
    impact_at_x + WIDTH
  end
  self.x_vel = 0
  i_hit(impacts)
end

#move_yObject

Process one tick of motion, vertically only



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/game_2d/entity.rb', line 161

def move_y
  return if doomed?
  return if @y_vel.zero?
  new_y = @y + @y_vel
  impacts = entities_obstructing(@x, new_y)
  if impacts.empty?
    @y = new_y
    return
  end
  @y = if @y_vel > 0 # moving down
    # Y position of highest candidate(s)
    impact_at_y = impacts.collect(&:y).min
    impacts.delete_if {|e| e.y > impact_at_y }
    impact_at_y - HEIGHT
  else # moving up
    # Y position of lowest candidate(s)
    impact_at_y = impacts.collect(&:y).max
    impacts.delete_if {|e| e.y < impact_at_y }
    impact_at_y + HEIGHT
  end
  self.y_vel = 0
  i_hit(impacts)
end

#moving?Boolean

True if we need to update this entity

Returns:

  • (Boolean)


70
# File 'lib/game_2d/entity.rb', line 70

def moving?; @moving; end

#next_to(angle, x = @x, y = @y) ⇒ Object

Return any entities adjacent to this one in the specified direction



231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/game_2d/entity.rb', line 231

def next_to(angle, x=@x, y=@y)
  points = case angle % 360
  when 0 then
    [[x, y - 1], [x + WIDTH - 1, y - 1]]
  when 90 then
    [[x + WIDTH, y], [x + WIDTH, y + HEIGHT - 1]]
  when 180 then
    [[x, y + HEIGHT], [x + WIDTH - 1, y + HEIGHT]]
  when 270 then
    [[x - 1, y], [x - 1, y + HEIGHT - 1]]
  else puts "Trig unimplemented"; []
  end
  @space.entities_at_points(points)
end

#occupied_cells(x = @x, y = @y) ⇒ Object

Returns an array of one, two, or four cell-coordinate tuples E.g. [[4, 5], [4, 6], [5, 5], [5, 6]]



111
112
113
114
115
# File 'lib/game_2d/entity.rb', line 111

def occupied_cells(x = @x, y = @y)
  x_array = (left_cell_x(x) .. right_cell_x(x)).to_a
  y_array = (top_cell_y(y) .. bottom_cell_y(y)).to_a
  x_array.product(y_array)
end

#opaque(others) ⇒ Object



123
124
125
# File 'lib/game_2d/entity.rb', line 123

def opaque(others)
  others.delete_if {|obj| obj.equal?(self) || transparent?(self, obj)}
end

#pixel_xObject

X positions near this entity’s Position in pixels of the upper-left corner



101
# File 'lib/game_2d/entity.rb', line 101

def pixel_x; @x / PIXEL_WIDTH; end

#pixel_yObject



102
# File 'lib/game_2d/entity.rb', line 102

def pixel_y; @y / PIXEL_WIDTH; end

#release!Object



93
# File 'lib/game_2d/entity.rb', line 93

def release!; @grabbed = false; end

#right_cell_x(x = @x) ⇒ Object



105
# File 'lib/game_2d/entity.rb', line 105

def right_cell_x(x = @x); right_cell_x_at(x); end

#should_fall?Boolean

Returns:

  • (Boolean)


81
82
83
# File 'lib/game_2d/entity.rb', line 81

def should_fall?
  raise "should_fall? undefined"
end

#sleep_now?Boolean

True if this entity can go to sleep now Only called if update() fails to produce any motion Default: Sleep if we’re not moving and not falling

Returns:

  • (Boolean)


77
78
79
# File 'lib/game_2d/entity.rb', line 77

def sleep_now?
  self.x_vel == 0 && self.y_vel == 0 && !should_fall?
end

#to_sObject



380
381
382
# File 'lib/game_2d/entity.rb', line 380

def to_s
  "#{self.class} (#{registry_id_safe}) at #{x}x#{y}"
end

#top_cell_y(y = @y) ⇒ Object



106
# File 'lib/game_2d/entity.rb', line 106

def top_cell_y(y = @y); top_cell_y_at(y); end

#updateObject

Handle any behavior specific to this entity Default: Accelerate downward if the subclass says we should fall



204
205
206
207
# File 'lib/game_2d/entity.rb', line 204

def update
  accelerate(0, 1) if should_fall?
  move
end

#update_from_json(json) ⇒ Object



347
348
349
350
351
352
353
354
355
# File 'lib/game_2d/entity.rb', line 347

def update_from_json(json)
  new_x, new_y = json[:position]
  new_x_vel, new_y_vel = json[:velocity]
  new_angle = json[:angle]
  new_moving = json[:moving]

  warp(new_x, new_y, new_x_vel, new_y_vel, new_angle, new_moving)
  self
end

#vector_to_angle(x_vel = @x_vel, y_vel = @y_vel) ⇒ Object

Convert x/y to an angle



262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/game_2d/entity.rb', line 262

def vector_to_angle(x_vel=@x_vel, y_vel=@y_vel)
  if x_vel == 0 && y_vel == 0
    return puts "Zero velocity, no angle"
  end
  if x_vel != 0 && y_vel != 0
    return puts "Diagonal velocity (#{x_vel}x#{y_vel}), no angle"
  end

  if x_vel.zero?
    (y_vel > 0) ? 180 : 0
  else
    (x_vel > 0) ? 90 : 270
  end
end

#wake!Object

Notify this entity that it must take action



86
87
88
# File 'lib/game_2d/entity.rb', line 86

def wake!
  @moving = true
end

#warp(x, y, x_vel = nil, y_vel = nil, angle = nil, moving = nil) ⇒ Object

Update position/velocity/angle data, and tell the space about it



210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/game_2d/entity.rb', line 210

def warp(x, y, x_vel=nil, y_vel=nil, angle=nil, moving=nil)
  blk = proc do
    @x, @y, self.x_vel, self.y_vel, self.a, @moving =
      (x || @x), (y || @y), (x_vel || @x_vel), (y_vel || @y_vel), (angle || @a),
      (moving.nil? ? @moving : moving)
  end
  if @space
    @space.process_moving_entity(self, &blk)
  else
    blk.call
  end
end