Class: Entity
- Inherits:
-
Object
- Object
- Entity
- Includes:
- EntityConstants, Registerable, Serializable
- 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/owned_entity.rb
Direct Known Subclasses
Defined Under Namespace
Classes: Block, OwnedEntity, Pellet, Titanium
Constant Summary
Constants included from EntityConstants
EntityConstants::CELL_WIDTH_IN_PIXELS, EntityConstants::MAX_VELOCITY, EntityConstants::PIXEL_WIDTH, EntityConstants::WIDTH
Instance Attribute Summary collapse
-
#a ⇒ Object
Returns the value of attribute a.
-
#moving ⇒ Object
X and Y position of the top-left corner.
-
#space ⇒ Object
X and Y position of the top-left corner.
-
#x ⇒ Object
X and Y position of the top-left corner.
-
#x_vel ⇒ Object
Returns the value of attribute x_vel.
-
#y ⇒ Object
X and Y position of the top-left corner.
-
#y_vel ⇒ Object
Returns the value of attribute y_vel.
Class Method Summary collapse
-
.bottom_cell_y_at(y) ⇒ Object
Bottom-most cell Y position occupied.
-
.left_cell_x_at(x) ⇒ Object
Left-most cell X position occupied.
-
.right_cell_x_at(x) ⇒ Object
Right-most cell X position occupied If we’re exactly within a column (@x is an exact multiple of WIDTH), then this equals left_cell_x.
-
.top_cell_y_at(y) ⇒ Object
Top-most cell Y position occupied.
Instance Method Summary collapse
-
#accelerate(x_accel, y_accel) ⇒ Object
Apply acceleration.
- #all_state ⇒ Object
- #angle_to_vector(angle, amplitude = 1) ⇒ Object
- #as_json ⇒ Object
- #bottom_cell_y(y = @y) ⇒ Object
-
#destroy! ⇒ Object
Give this entity a chance to perform clean-up upon destruction.
-
#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.
- #doomed? ⇒ Boolean
- #draw(window) ⇒ Object
- #draw_zorder ⇒ Object
-
#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.
- #empty_above? ⇒ Boolean
- #empty_on_left? ⇒ Boolean
- #empty_on_right? ⇒ Boolean
- #empty_underneath? ⇒ Boolean
-
#entities_obstructing(new_x, new_y) ⇒ Object
Wrapper around @space.entities_overlapping Allows us to remove any entities that are transparent to us.
-
#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:.
- #harmed_by(other) ⇒ Object
- #i_hit(other) ⇒ Object
- #image_filename ⇒ Object
-
#initialize(x = 0, y = 0, a = 0, x_vel = 0, y_vel = 0) ⇒ Entity
constructor
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.
-
#left_cell_x(x = @x) ⇒ Object
TODO: Find a more elegant way to call class methods in Ruby…
-
#move ⇒ Object
Process one tick of motion.
-
#move_x ⇒ Object
Process one tick of motion, horizontally only.
-
#move_y ⇒ Object
Process one tick of motion, vertically only.
-
#moving? ⇒ Boolean
True if we need to update this entity.
-
#next_to(angle, x = @x, y = @y) ⇒ Object
Return any entities adjacent to this one in the specified direction.
-
#occupied_cells(x = @x, y = @y) ⇒ Object
Returns an array of one, two, or four cell-coordinate tuples E.g.
- #opaque(others) ⇒ Object
-
#pixel_x ⇒ Object
X positions near this entity’s Position in pixels of the upper-left corner.
- #pixel_y ⇒ Object
- #right_cell_x(x = @x) ⇒ Object
- #should_fall? ⇒ Boolean
-
#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.
- #to_s ⇒ Object
- #top_cell_y(y = @y) ⇒ Object
-
#transparent_to_me?(other) ⇒ Boolean
Override to make particular entities transparent to each other.
-
#update ⇒ Object
Handle any behavior specific to this entity Default: Accelerate downward if the subclass says we should fall.
- #update_from_json(json) ⇒ Object
-
#vector_to_angle(x_vel = @x_vel, y_vel = @y_vel) ⇒ Object
Convert x/y to an angle.
-
#wake! ⇒ Object
Notify this entity that it must take action.
-
#warp(x, y, x_vel, y_vel, angle = self.a, moving = @moving) ⇒ Object
Update position/velocity/angle data, and tell the space about it.
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
26 27 28 29 30 |
# File 'lib/game_2d/entity.rb', line 26 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 end |
Instance Attribute Details
#a ⇒ Object
Returns the value of attribute a.
20 21 22 |
# File 'lib/game_2d/entity.rb', line 20 def a @a end |
#moving ⇒ Object
X and Y position of the top-left corner
18 19 20 |
# File 'lib/game_2d/entity.rb', line 18 def moving @moving end |
#space ⇒ Object
X and Y position of the top-left corner
18 19 20 |
# File 'lib/game_2d/entity.rb', line 18 def space @space end |
#x ⇒ Object
X and Y position of the top-left corner
18 19 20 |
# File 'lib/game_2d/entity.rb', line 18 def x @x end |
#x_vel ⇒ Object
Returns the value of attribute x_vel.
20 21 22 |
# File 'lib/game_2d/entity.rb', line 20 def x_vel @x_vel end |
#y ⇒ Object
X and Y position of the top-left corner
18 19 20 |
# File 'lib/game_2d/entity.rb', line 18 def y @y end |
#y_vel ⇒ Object
Returns the value of attribute y_vel.
20 21 22 |
# File 'lib/game_2d/entity.rb', line 20 def y_vel @y_vel end |
Class Method Details
.bottom_cell_y_at(y) ⇒ Object
Bottom-most cell Y position occupied
87 |
# File 'lib/game_2d/entity.rb', line 87 def self.bottom_cell_y_at(y); (y + HEIGHT - 1) / HEIGHT; end |
.left_cell_x_at(x) ⇒ Object
Left-most cell X position occupied
72 |
# File 'lib/game_2d/entity.rb', line 72 def self.left_cell_x_at(x); x / WIDTH; end |
.right_cell_x_at(x) ⇒ Object
Right-most cell X position occupied If we’re exactly within a column (@x is an exact multiple of WIDTH), then this equals left_cell_x. Otherwise, it’s one higher
79 |
# File 'lib/game_2d/entity.rb', line 79 def self.right_cell_x_at(x); (x + WIDTH - 1) / WIDTH; end |
.top_cell_y_at(y) ⇒ Object
Top-most cell Y position occupied
83 |
# File 'lib/game_2d/entity.rb', line 83 def self.top_cell_y_at(y); y / HEIGHT; end |
Instance Method Details
#accelerate(x_accel, y_accel) ⇒ Object
Apply acceleration
99 100 101 102 |
# File 'lib/game_2d/entity.rb', line 99 def accelerate(x_accel, y_accel) self.x_vel = @x_vel + x_accel self.y_vel = @y_vel + y_accel end |
#all_state ⇒ Object
368 369 370 |
# File 'lib/game_2d/entity.rb', line 368 def all_state [registry_id_safe, @x, @y, @a, @x_vel, @y_vel, @moving] end |
#angle_to_vector(angle, amplitude = 1) ⇒ Object
236 237 238 239 240 241 242 243 244 |
# File 'lib/game_2d/entity.rb', line 236 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 ⇒ Object
321 322 323 324 325 326 327 328 329 330 |
# File 'lib/game_2d/entity.rb', line 321 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
88 |
# File 'lib/game_2d/entity.rb', line 88 def bottom_cell_y(y = @y); self.class.bottom_cell_y_at(y); end |
#destroy! ⇒ Object
Give this entity a chance to perform clean-up upon destruction
64 |
# File 'lib/game_2d/entity.rb', line 64 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.
270 271 272 |
# File 'lib/game_2d/entity.rb', line 270 def direction_to(other_x, other_y) vector_to_angle(*drop_diagonal(other_x - @x, other_y - @y)) end |
#doomed? ⇒ Boolean
45 |
# File 'lib/game_2d/entity.rb', line 45 def doomed?; @space.doomed?(self); end |
#draw(window) ⇒ Object
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 |
# File 'lib/game_2d/entity.rb', line 348 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, self.a) # 0.5, 0.5, # rotate around the center # 1, 1, # scaling factor # @color, # modify color # :add) # draw additively end |
#draw_zorder ⇒ Object
346 |
# File 'lib/game_2d/entity.rb', line 346 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.
264 265 266 |
# File 'lib/game_2d/entity.rb', line 264 def drop_diagonal(x_vel, y_vel) (y_vel.abs > x_vel.abs) ? [0, y_vel] : [x_vel, 0] end |
#empty_above? ⇒ Boolean
234 |
# File 'lib/game_2d/entity.rb', line 234 def empty_above?; opaque(next_to(0)).empty?; end |
#empty_on_left? ⇒ Boolean
232 |
# File 'lib/game_2d/entity.rb', line 232 def empty_on_left?; opaque(next_to(270)).empty?; end |
#empty_on_right? ⇒ Boolean
233 |
# File 'lib/game_2d/entity.rb', line 233 def empty_on_right?; opaque(next_to(90)).empty?; end |
#empty_underneath? ⇒ Boolean
231 |
# File 'lib/game_2d/entity.rb', line 231 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
116 117 118 119 |
# File 'lib/game_2d/entity.rb', line 116 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.
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/game_2d/entity.rb', line 288 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 |
#harmed_by(other) ⇒ Object
213 |
# File 'lib/game_2d/entity.rb', line 213 def harmed_by(other); end |
#i_hit(other) ⇒ Object
208 209 210 211 |
# File 'lib/game_2d/entity.rb', line 208 def i_hit(other) # TODO puts "#{self} hit #{other.inspect}" end |
#image_filename ⇒ Object
342 343 344 |
# File 'lib/game_2d/entity.rb', line 342 def image_filename raise "No image filename defined" end |
#left_cell_x(x = @x) ⇒ Object
TODO: Find a more elegant way to call class methods in Ruby…
74 |
# File 'lib/game_2d/entity.rb', line 74 def left_cell_x(x = @x); self.class.left_cell_x_at(x); end |
#move ⇒ Object
Process one tick of motion. Only called when moving? is true
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/game_2d/entity.rb', line 172 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 ⇒ Object
Process one tick of motion, horizontally only
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/game_2d/entity.rb', line 122 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_y ⇒ Object
Process one tick of motion, vertically only
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/game_2d/entity.rb', line 147 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
43 |
# File 'lib/game_2d/entity.rb', line 43 def moving?; @moving; end |
#next_to(angle, x = @x, y = @y) ⇒ Object
Return any entities adjacent to this one in the specified direction
216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/game_2d/entity.rb', line 216 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]]
92 93 94 95 96 |
# File 'lib/game_2d/entity.rb', line 92 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
109 110 111 |
# File 'lib/game_2d/entity.rb', line 109 def opaque(others) others.delete_if {|obj| transparent_to_me?(obj)} end |
#pixel_x ⇒ Object
X positions near this entity’s Position in pixels of the upper-left corner
68 |
# File 'lib/game_2d/entity.rb', line 68 def pixel_x; @x / PIXEL_WIDTH; end |
#pixel_y ⇒ Object
69 |
# File 'lib/game_2d/entity.rb', line 69 def pixel_y; @y / PIXEL_WIDTH; end |
#right_cell_x(x = @x) ⇒ Object
80 |
# File 'lib/game_2d/entity.rb', line 80 def right_cell_x(x = @x); self.class.right_cell_x_at(x); end |
#should_fall? ⇒ Boolean
54 55 56 |
# File 'lib/game_2d/entity.rb', line 54 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
50 51 52 |
# File 'lib/game_2d/entity.rb', line 50 def sleep_now? self.x_vel == 0 && self.y_vel == 0 && !should_fall? end |
#to_s ⇒ Object
364 365 366 |
# File 'lib/game_2d/entity.rb', line 364 def to_s "#{self.class} (#{registry_id_safe}) at #{x}x#{y}" end |
#top_cell_y(y = @y) ⇒ Object
84 |
# File 'lib/game_2d/entity.rb', line 84 def top_cell_y(y = @y); self.class.top_cell_y_at(y); end |
#transparent_to_me?(other) ⇒ Boolean
Override to make particular entities transparent to each other
105 106 107 |
# File 'lib/game_2d/entity.rb', line 105 def transparent_to_me?(other) other == self end |
#update ⇒ Object
Handle any behavior specific to this entity Default: Accelerate downward if the subclass says we should fall
190 191 192 193 |
# File 'lib/game_2d/entity.rb', line 190 def update accelerate(0, 1) if should_fall? move end |
#update_from_json(json) ⇒ Object
332 333 334 335 336 337 338 339 340 |
# File 'lib/game_2d/entity.rb', line 332 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
247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/game_2d/entity.rb', line 247 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
59 60 61 |
# File 'lib/game_2d/entity.rb', line 59 def wake! @moving = true end |
#warp(x, y, x_vel, y_vel, angle = self.a, moving = @moving) ⇒ Object
Update position/velocity/angle data, and tell the space about it
196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/game_2d/entity.rb', line 196 def warp(x, y, x_vel, y_vel, angle=self.a, moving=@moving) blk = proc do @x, @y, self.x_vel, self.y_vel, self.a, @moving = x, y, x_vel, y_vel, angle, moving end if @space @space.process_moving_entity(self, &blk) else blk.call end end |