class Entity

A player object that can stick to walls and slide around corners Calling update() causes a move to be dequeued and executed, applying forces to the game object

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

Attributes

a[R]
moving[RW]

X and Y position of the top-left corner

space[RW]

X and Y position of the top-left corner

x[RW]

X and Y position of the top-left corner

x_vel[R]
y[RW]

X and Y position of the top-left corner

y_vel[R]

Public Class Methods

new(x = 0, y = 0, a = 0, x_vel = 0, y_vel = 0) click to toggle source

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

# 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

Public Instance Methods

a=(angle) click to toggle source
# File lib/game_2d/entity.rb, line 60
def a=(angle); @a = (angle || 0) % 360; end
above() click to toggle source
# File lib/game_2d/entity.rb, line 275
def above; opaque(next_to(0)); end
accelerate(x_accel, y_accel, max=MAX_VELOCITY) click to toggle source

Apply acceleration

# 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_state() click to toggle source
# 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) click to toggle source
# 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_json() click to toggle source
# 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
beneath() click to toggle source
# File lib/game_2d/entity.rb, line 272
def beneath; opaque(next_to(180)); end
bottom_cell_y(y = @y) click to toggle source
# File lib/game_2d/entity.rb, line 110
def bottom_cell_y(y = @y); bottom_cell_y_at(y); end
cx() click to toggle source
# File lib/game_2d/entity.rb, line 69
def cx; x + WIDTH/2; end
cy() click to toggle source
# File lib/game_2d/entity.rb, line 70
def cy; y + HEIGHT/2; end
destroy!() click to toggle source

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

# File lib/game_2d/entity.rb, line 100
def destroy!; end
direction() click to toggle source

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

# 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) click to toggle source

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.

# 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?() click to toggle source
# File lib/game_2d/entity.rb, line 75
def doomed?; @space.doomed?(self); end
draw(window) click to toggle source
# 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_angle() click to toggle source
# File lib/game_2d/entity.rb, line 461
def draw_angle; a; end
draw_animation(window) click to toggle source
# File lib/game_2d/entity.rb, line 438
def draw_animation(window)
  window.animation[window.media(image_filename)]
end
draw_image(anim) click to toggle source
# File lib/game_2d/entity.rb, line 442
def draw_image(anim)
  anim[Gosu::milliseconds / 100 % anim.size]
end
draw_zorder() click to toggle source
# File lib/game_2d/entity.rb, line 436
def draw_zorder; ZOrder::Objects end
drop_diagonal(x_vel=@x_vel, y_vel=@y_vel) click to toggle source

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

# 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?() click to toggle source
# File lib/game_2d/entity.rb, line 279
def empty_above?; above.empty?; end
empty_on_left?() click to toggle source
# File lib/game_2d/entity.rb, line 277
def empty_on_left?; on_left.empty?; end
empty_on_right?() click to toggle source
# File lib/game_2d/entity.rb, line 278
def empty_on_right?; on_right.empty?; end
empty_underneath?() click to toggle source
# File lib/game_2d/entity.rb, line 276
def empty_underneath?; beneath.empty?; end
entities_obstructing(new_x, new_y) click to toggle source

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

# 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) click to toggle source

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.

# 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!() click to toggle source

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

# File lib/game_2d/entity.rb, line 95
def grab!; @grabbed = true; end
grabbed?() click to toggle source
# File lib/game_2d/entity.rb, line 97
def grabbed?; @grabbed; end
harmed_by(other, damage=1) click to toggle source
# File lib/game_2d/entity.rb, line 250
def harmed_by(other, damage=1); end
i_hit(others, velocity) click to toggle source

‘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

# File lib/game_2d/entity.rb, line 248
def i_hit(others, velocity); end
image_filename() click to toggle source
# File lib/game_2d/entity.rb, line 432
def image_filename
  raise "No image filename defined"
end
left_cell_x(x = @x) click to toggle source
# File lib/game_2d/entity.rb, line 107
def left_cell_x(x = @x); left_cell_x_at(x); end
move() click to toggle source

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

# 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_x() click to toggle source

Process one tick of motion, horizontally only

# 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_y() click to toggle source

Process one tick of motion, vertically only

# 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?() click to toggle source

True if we need to update this entity

# File lib/game_2d/entity.rb, line 73
def moving?; @moving; end
next_to(angle, x=@x, y=@y) click to toggle source

Return any entities adjacent to this one in the specified direction

# 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) click to toggle source

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

# 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_left() click to toggle source
# File lib/game_2d/entity.rb, line 273
def on_left; opaque(next_to(270)); end
on_right() click to toggle source
# File lib/game_2d/entity.rb, line 274
def on_right; opaque(next_to(90)); end
opaque(others) click to toggle source
# File lib/game_2d/entity.rb, line 126
def opaque(others)
  others.delete_if {|obj| obj.equal?(self) || transparent?(self, obj)}
end
pixel_x() click to toggle source

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

# File lib/game_2d/entity.rb, line 104
def pixel_x; @x / PIXEL_WIDTH; end
pixel_y() click to toggle source
# File lib/game_2d/entity.rb, line 105
def pixel_y; @y / PIXEL_WIDTH; end
release!() click to toggle source
# File lib/game_2d/entity.rb, line 96
def release!; @grabbed = false; end
right_cell_x(x = @x) click to toggle source
# File lib/game_2d/entity.rb, line 108
def right_cell_x(x = @x); right_cell_x_at(x); end
should_fall?() click to toggle source
# File lib/game_2d/entity.rb, line 84
def should_fall?
  raise "should_fall? undefined"
end
sleep_now?() click to toggle source

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

# 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) click to toggle source

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)

# 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) click to toggle source
# 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) click to toggle source
# 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?() click to toggle source

Most entities can be teleported, but not when grabbed

# File lib/game_2d/entity.rb, line 241
def teleportable?
  !grabbed?
end
to_s() click to toggle source
# 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) click to toggle source
# File lib/game_2d/entity.rb, line 109
def top_cell_y(y = @y); top_cell_y_at(y); end
underfoot() click to toggle source
# File lib/game_2d/entity.rb, line 268
def underfoot
  opaque(next_to(self.a + 180))
end
update() click to toggle source

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

# File lib/game_2d/entity.rb, line 221
def update
  space.fall(self) if should_fall?
  move
end
update_from_json(json) click to toggle source
# 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) click to toggle source

Convert x/y to an angle

# 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!() click to toggle source

Notify this entity that it must take action

# 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) click to toggle source

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

# 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
x_vel=(xv) click to toggle source
# File lib/game_2d/entity.rb, line 62
def x_vel=(xv)
  @x_vel = constrain_velocity xv
end
y_vel=(yv) click to toggle source
# File lib/game_2d/entity.rb, line 65
def y_vel=(yv)
  @y_vel = constrain_velocity yv
end