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
X and Y position of the top-left corner
X and Y position of the top-left corner
X and Y position of the top-left corner
X and Y position of the top-left corner
Public Class Methods
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
# File lib/game_2d/entity.rb, line 60 def a=(angle); @a = (angle || 0) % 360; end
# File lib/game_2d/entity.rb, line 275 def above; opaque(next_to(0)); end
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
# File lib/game_2d/entity.rb, line 467 def all_state [registry_id_safe, @x, @y, @a, @x_vel, @y_vel, @moving] end
# 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
# 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
# File lib/game_2d/entity.rb, line 272 def beneath; opaque(next_to(180)); end
# File lib/game_2d/entity.rb, line 110 def bottom_cell_y(y = @y); bottom_cell_y_at(y); end
# File lib/game_2d/entity.rb, line 69 def cx; x + WIDTH/2; end
# File lib/game_2d/entity.rb, line 70 def cy; y + HEIGHT/2; end
Give this entity a chance to perform clean-up upon destruction
# File lib/game_2d/entity.rb, line 100 def destroy!; end
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
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
# File lib/game_2d/entity.rb, line 75 def doomed?; @space.doomed?(self); end
# 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
# File lib/game_2d/entity.rb, line 461 def draw_angle; a; end
# File lib/game_2d/entity.rb, line 438 def draw_animation(window) window.animation[window.media(image_filename)] end
# File lib/game_2d/entity.rb, line 442 def draw_image(anim) anim[Gosu::milliseconds / 100 % anim.size] end
# File lib/game_2d/entity.rb, line 436 def draw_zorder; ZOrder::Objects end
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
# File lib/game_2d/entity.rb, line 279 def empty_above?; above.empty?; end
# File lib/game_2d/entity.rb, line 277 def empty_on_left?; on_left.empty?; end
# File lib/game_2d/entity.rb, line 278 def empty_on_right?; on_right.empty?; end
# File lib/game_2d/entity.rb, line 276 def empty_underneath?; beneath.empty?; end
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
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
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
# File lib/game_2d/entity.rb, line 97 def grabbed?; @grabbed; end
# File lib/game_2d/entity.rb, line 250 def harmed_by(other, damage=1); end
# File lib/game_2d/entity.rb, line 432 def image_filename raise "No image filename defined" end
# File lib/game_2d/entity.rb, line 107 def left_cell_x(x = @x); left_cell_x_at(x); end
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
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
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
True if we need to update this entity
# File lib/game_2d/entity.rb, line 73 def moving?; @moving; end
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
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
# File lib/game_2d/entity.rb, line 273 def on_left; opaque(next_to(270)); end
# File lib/game_2d/entity.rb, line 274 def on_right; opaque(next_to(90)); end
# File lib/game_2d/entity.rb, line 126 def opaque(others) others.delete_if {|obj| obj.equal?(self) || transparent?(self, obj)} end
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
# File lib/game_2d/entity.rb, line 105 def pixel_y; @y / PIXEL_WIDTH; end
# File lib/game_2d/entity.rb, line 96 def release!; @grabbed = false; end
# File lib/game_2d/entity.rb, line 108 def right_cell_x(x = @x); right_cell_x_at(x); end
# File lib/game_2d/entity.rb, line 84 def should_fall? raise "should_fall? undefined" end
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
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
# 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
# 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
Most entities can be teleported, but not when grabbed
# File lib/game_2d/entity.rb, line 241 def teleportable? !grabbed? end
# File lib/game_2d/entity.rb, line 463 def to_s "#{self.class} (#{registry_id_safe}) at #{x}x#{y}" end
# File lib/game_2d/entity.rb, line 109 def top_cell_y(y = @y); top_cell_y_at(y); end
# File lib/game_2d/entity.rb, line 268 def underfoot opaque(next_to(self.a + 180)) end
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
# 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
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
Notify this entity that it must take action
# File lib/game_2d/entity.rb, line 89 def wake! @moving = true end
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
# File lib/game_2d/entity.rb, line 62 def x_vel=(xv) @x_vel = constrain_velocity xv end
# File lib/game_2d/entity.rb, line 65 def y_vel=(yv) @y_vel = constrain_velocity yv end