Class: Entity

Inherits:
Object
  • Object
show all
Extended by:
ClassMethods
Includes:
ClassMethods, EntityConstants, Registerable, Serializable, Transparency
Defined in:
lib/game_2d/entity/gecko.rb,
lib/game_2d/entity.rb,
lib/game_2d/entity/base.rb,
lib/game_2d/entity/hole.rb,
lib/game_2d/entity/block.rb,
lib/game_2d/entity/ghost.rb,
lib/game_2d/entity/slime.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

Overview

A player object that represents the player when between corporeal incarnations

Ghost can fly around and look at things, but can’t touch or affect anything

Direct Known Subclasses

Base, Gecko, Ghost, Hole, OwnedEntity, Slime, Teleporter, Titanium, Wall

Defined Under Namespace

Modules: ClassMethods Classes: Base, Block, Destination, Gecko, Ghost, Hole, OwnedEntity, Pellet, Slime, 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

#aboveObject



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

def above; opaque(next_to(0)); end

#accelerate(x_accel, y_accel, max = MAX_VELOCITY) ⇒ Object

Apply acceleration



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

def accelerate(x_accel, y_accel, max=MAX_VELOCITY)
  @x_vel = constrain_velocity(@x_vel + x_accel, max) if x_accel
  @y_vel = constrain_velocity(@y_vel + y_accel, max) if y_accel
end

#all_stateObject



467
468
469
# File 'lib/game_2d/entity.rb', line 467

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

#angle_to_vector(angle, amplitude = 1) ⇒ Object



281
282
283
284
285
286
287
288
289
# File 'lib/game_2d/entity.rb', line 281

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



411
412
413
414
415
416
417
418
419
420
# File 'lib/game_2d/entity.rb', line 411

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

#beneathObject



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

def beneath; opaque(next_to(180)); end

#bottom_cell_y(y = @y) ⇒ Object



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

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

#cxObject



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

def cx; x + WIDTH/2; end

#cyObject



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

def cy; y + HEIGHT/2; end

#destroy!Object

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



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

def destroy!; end

#directionObject

Roughly speaking, are we going left, right, up, or down?



314
315
316
317
# File 'lib/game_2d/entity.rb', line 314

def direction
  return nil if x_vel.zero? && y_vel.zero?
  vector_to_angle(*drop_diagonal)
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.



321
322
323
# File 'lib/game_2d/entity.rb', line 321

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

#doomed?Boolean

Returns:

  • (Boolean)


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

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

#draw(window) ⇒ Object



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/game_2d/entity.rb', line 446

def draw(window)
  img = draw_image(draw_animation(window))

  # 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



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

def draw_angle; a; end

#draw_animation(window) ⇒ Object



438
439
440
# File 'lib/game_2d/entity.rb', line 438

def draw_animation(window)
  window.animation[window.media(image_filename)]
end

#draw_image(anim) ⇒ Object



442
443
444
# File 'lib/game_2d/entity.rb', line 442

def draw_image(anim)
  anim[Gosu::milliseconds / 100 % anim.size]
end

#draw_zorderObject



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

def draw_zorder; ZOrder::Objects end

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

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



309
310
311
# File 'lib/game_2d/entity.rb', line 309

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

#empty_above?Boolean

Returns:

  • (Boolean)


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

def empty_above?; above.empty?; end

#empty_on_left?Boolean

Returns:

  • (Boolean)


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

def empty_on_left?; on_left.empty?; end

#empty_on_right?Boolean

Returns:

  • (Boolean)


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

def empty_on_right?; on_right.empty?; end

#empty_underneath?Boolean

Returns:

  • (Boolean)


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

def empty_underneath?; beneath.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



133
134
135
136
# File 'lib/game_2d/entity.rb', line 133

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.



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/game_2d/entity.rb', line 339

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)



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

def grab!; @grabbed = true; end

#grabbed?Boolean

Returns:

  • (Boolean)


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

def grabbed?; @grabbed; end

#harmed_by(other, damage = 1) ⇒ Object



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

def harmed_by(other, damage=1); end

#i_hit(others, velocity) ⇒ Object

‘others’ is an array of impacted entities ‘velocity’ is the absolute value of the x_vel or y_vel that was being applied when the hit occurred



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

def i_hit(others, velocity); end

#image_filenameObject



432
433
434
# File 'lib/game_2d/entity.rb', line 432

def image_filename
  raise "No image filename defined"
end

#left_cell_x(x = @x) ⇒ Object



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

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

#moveObject

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



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/game_2d/entity.rb', line 203

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



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/game_2d/entity.rb', line 153

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
  i_hit(impacts, @x_vel.abs)
  self.x_vel = 0
end

#move_yObject

Process one tick of motion, vertically only



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/game_2d/entity.rb', line 178

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
  i_hit(impacts, @y_vel.abs)
  self.y_vel = 0
end

#moving?Boolean

True if we need to update this entity

Returns:

  • (Boolean)


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

def moving?; @moving; end

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

Return any entities adjacent to this one in the specified direction



253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/game_2d/entity.rb', line 253

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]]



114
115
116
117
118
# File 'lib/game_2d/entity.rb', line 114

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

#on_leftObject



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

def on_left; opaque(next_to(270)); end

#on_rightObject



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

def on_right; opaque(next_to(90)); end

#opaque(others) ⇒ Object



126
127
128
# File 'lib/game_2d/entity.rb', line 126

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



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

def pixel_x; @x / PIXEL_WIDTH; end

#pixel_yObject



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

def pixel_y; @y / PIXEL_WIDTH; end

#release!Object



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

def release!; @grabbed = false; end

#right_cell_x(x = @x) ⇒ Object



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

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

#should_fall?Boolean

Returns:

  • (Boolean)


84
85
86
# File 'lib/game_2d/entity.rb', line 84

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)


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

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

#slide_around(other, apply_turn = true) ⇒ Object

Apply a move where this entity slides past another If it reaches the other entity’s corner, it will turn at right angles to go around that corner

apply_turn: true if this entity’s angle should be adjusted during the turn

Returns true if a corner was reached and we went around it, false if that didn’t happen (in which case, no move occurred)



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
# File 'lib/game_2d/entity.rb', line 381

def slide_around(other, apply_turn = true)
  # Figure out where corner is and whether we're about to reach or pass it
  corner, distance, overshoot, turn = going_past_entity(other.x, other.y)
  return false unless corner

  original_speed = @x_vel.abs + @y_vel.abs
  original_dir = vector_to_angle
  new_dir = original_dir + turn

  # Make sure nothing occupies any space we're about to move through
  return false unless opaque(
    @space.entities_overlapping(*corner) + next_to(new_dir, *corner)
  ).empty?

  # Move to the corner
  self.x_vel, self.y_vel = angle_to_vector(original_dir, distance)
  move

  # Turn and apply remaining velocity
  # Make sure we move at least one subpixel so we don't sit exactly at
  # the corner, and fall
  self.a += turn if apply_turn
  overshoot = 1 if overshoot.zero?
  self.x_vel, self.y_vel = angle_to_vector(new_dir, overshoot)
  move

  self.x_vel, self.y_vel = angle_to_vector(new_dir, original_speed)
  true
end

#slow_by(amount) ⇒ Object



138
139
140
141
142
143
144
# File 'lib/game_2d/entity.rb', line 138

def slow_by(amount)
  if @x_vel.zero?
    self.y_vel = slower_speed(@y_vel, amount)
  else
    self.x_vel = slower_speed(@x_vel, amount)
  end
end

#slower_speed(current, delta) ⇒ Object



146
147
148
149
150
# File 'lib/game_2d/entity.rb', line 146

def slower_speed(current, delta)
  return 0 if current.abs < delta
  sign = current <=> 0
  sign * (current.abs - delta)
end

#teleportable?Boolean

Most entities can be teleported, but not when grabbed

Returns:

  • (Boolean)


241
242
243
# File 'lib/game_2d/entity.rb', line 241

def teleportable?
  !grabbed?
end

#to_sObject



463
464
465
# File 'lib/game_2d/entity.rb', line 463

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

#top_cell_y(y = @y) ⇒ Object



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

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

#underfootObject



268
269
270
# File 'lib/game_2d/entity.rb', line 268

def underfoot
  opaque(next_to(self.a + 180))
end

#updateObject

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



221
222
223
224
# File 'lib/game_2d/entity.rb', line 221

def update
  space.fall(self) if should_fall?
  move
end

#update_from_json(json) ⇒ Object



422
423
424
425
426
427
428
429
430
# File 'lib/game_2d/entity.rb', line 422

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



292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/game_2d/entity.rb', line 292

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



89
90
91
# File 'lib/game_2d/entity.rb', line 89

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



227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/game_2d/entity.rb', line 227

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