Entity-Component game programming using JRuby and libGDX – part 8

Introduction

Our Lunar Lander game is somewhat playable by this point but it still lacks some key features. After all, it would be nice if we could detect collisions and determine if the lander has safely landed on the pad. Let’s see how our flexible Entity-Component system permits us to expand our game with minimal fuss.

Collision Detection

First, a frank disclaimer: the following collision detection algorithm is entirely inefficient. It’s kept simple for our basic teaching purposes here but is probably undesirable in a game of any scale. But that’s OK: E-C will permit you to swap in a much more advanced collision detection system when you’re ready. 🙂

Desired feature: we want to know if our lander has collided with the ground. By now you have been doing enough E-C with me to anticipate what we need: a new component that describes the “collideable” attribute. The lander itself is collideable, as is the ground.

class PolygonCollidable < Component
  attr_accessor :bounding_polygon

  def marshal_dump
    [@id]
  end

  def marshal_load(array)
    @id = array
  end
end

Let’s imagine that any entity with a “collideable” component gains a bounding polygon: a simple, invisible, rectangular box that fits around the texture. If the texture rotates, so does the bounding polygon. And when two bounding polygons intersect, that defines a collision. The libGDX library provides us with some very nice polygon-computation methods, so we don’t need to write those on our own.

  def update_bounding_polygons(entity_mgr, entities)
    entities.each do |e|
      spatial_component    = entity_mgr.get_component_of_type(e, SpatialState)
      renderable_component = entity_mgr.get_component_of_type(e, Renderable)
      collidable_component = entity_mgr.get_component_of_type(e, PolygonCollidable)

      collidable_component.bounding_polygon =
                   make_polygon(spatial_component.x,
                                renderable_component.width,
                                spatial_component.y,
                                renderable_component.height,
                                renderable_component.rotation,
                                renderable_component.scale)
    end
  end

Now that we have the polygons computed, one way to accomplish the collision detection is via a wholly naive, O(n^2) algorithm where we loop over all “collideable” entities and compare each one with every other “collideable” entity.

  def process_one_game_tick(delta, entity_mgr)
    collidable_entities=[]

    polygon_entities = entity_mgr.get_all_entities_with_component_of_type(PolygonCollidable)
    update_bounding_polygons(entity_mgr, polygon_entities)
    collidable_entities += polygon_entities

    bounding_areas={}
    collidable_entities.each do |e|
      bounding_areas[e]=entity_mgr.get_component_of_type(e, PolygonCollidable).bounding_polygon
    end

    # Naive O(n^2)
    bounding_areas.each_key do |entity|
      bounding_areas.each_key do |other|
        next if entity==other

        if Intersector.overlapConvexPolygons(bounding_areas[entity], bounding_areas[other])
          if entity_mgr.get_tag(entity)=='p1_lander' || entity_mgr.get_tag(other)=='p1_lander'
            #puts "Intersection!"
            return true
          end
        end
      end
    end

    return false
  end

[Aside: the O(n^2) notation — pronounced “big-oh of n squared” — describes the computational efficiency of an algorithm. In this case the function scales per the square of the arguments. That’s pretty bad.]

Again, libGDX gave us a hand by providing an intersection-detection method.

Landing Detection

Another desired feature is knowing when the lander has safely touched down on the pad. In conversational terms, that means at least half of the lander is on the pad, and it doesn’t touch down too fast. (Later, perhaps, we could improve that to include a maximum rotation: say, no more than 15 degrees from the vertical. But we’ll keep things simple for now.)

As before, you probably already know what we need: two new components, “landable” and “pad”. Landable is assigned to an entity can be landed; pad is assigned to an entity that can be landed upon. Here again you can anticipate E-C’s flexibility: we could have multiple players — each “landable” — as well as multiple places to land on — each with “pad”. Neither of these needs any data.

class Landable < Component
end
class Pad < Component end 

Our system logic then becomes a matter of looping over the appropriate entities and doing a little swift calculation to see if the lander is located within the margin of safety. If so, a proper landing has occurred:

   def process_one_game_tick(delta, entity_mgr)     landable_entities = entity_mgr.get_all_entities_with_component_of_type(Landable)     pad_entities      = entity_mgr.get_all_entities_with_component_of_type(Pad)     landable_entities.each do |entity|       location_component   = entity_mgr.get_component_of_type(entity, SpatialState)       renderable_component = entity_mgr.get_component_of_type(entity, Renderable)       bl_x = location_component.x       bl_y = location_component.y        bc_x = bl_x + (renderable_component.width/2)       br_x = bl_x + renderable_component.width       br_y = bl_y       pad_entities.each do |pad|         pad_loc_component = entity_mgr.get_component_of_type(pad, SpatialState)         pad_rend_component = entity_mgr.get_component_of_type(pad, Renderable)         ul_x = pad_loc_component.x         ul_y = pad_loc_component.y+pad_rend_component.height         #puts "lander x: #{bc_x} y: #{bl_y} / Pad x: #{ul_x} y: #{ul_y}"         ur_x = ul_x+pad_rend_component.width         ur_y = ul_y         if (bl_y>=ul_y-PIXEL_FUDGE && bl_y <= ul_y+PIXEL_FUDGE) &&              ( bc_x>=ul_x && bc_x <= ur_x) &&
            ( location_component.dy <= MAX_SPEED)
          return true
        end
      end

      return false
    end
  end

Avoid the Asteroids!

Let’s conclude this post in a fun way. We have all the pieces in place — rendering, collision detection, and so forth — so let’s leverage the flexibility of E-C to throw a wholly unplanned feature into the mix. Let’s program an asteroid system that randomly generates asteroids off-screen and hurls them across the player’s game window. The player, naturally, must avoid colliding with these.

  def generate_new_asteroids(delta, entity_mgr)
    if rand(50)==0
      starting_x = -100
      starting_y = rand(500) - 150
      starting_dx = rand(15) + 2
      starting_dy = rand(20) - 10
      asteroid = entity_mgr.create_tagged_entity('asteroid')
      entity_mgr.add_component asteroid, SpatialState.new(starting_x, starting_y, starting_dx, starting_dy)
      entity_mgr.add_component asteroid, Renderable.new(RELATIVE_ROOT + "res/images/asteroid.png", 1.0, 0)
      entity_mgr.add_component asteroid, PolygonCollidable.new
      entity_mgr.add_component asteroid, Motion.new
    end
  end

Naturally we don’t want to bog down the machine with asteroids that have left the playing field, so after they have left the visible area we can clean them up:

  def cleanup_asteroids(delta, entity_mgr)
    asteroid_entities = entity_mgr.get_all_entities_with_tag('asteroid') || []

    asteroid_entities.each do |a|
      spatial_component = entity_mgr.get_component_of_type(a, SpatialState)
      if spatial_component.x > 640
        entity_mgr.kill_entity(a)
      end
    end
  end
  def process_one_game_tick(delta, entity_mgr)
    generate_new_asteroids(delta, entity_mgr)
    cleanup_asteroids(delta, entity_mgr)
  end

We didn’t have to refactor or re-think a single part of our existing codebase; we merely leveraged our existing spatial, renderable, collideable and motion components. We easily exploited E-C’s inherent flexibility to adapt our code to a wholly new, unanticipated feature.

Conclusion

Don’t forget, the full source code for this series is on Github.

I hope you enjoyed this series on Entity-Component programming as much as I enjoyed writing it. If you have questions, please get in touch. Thanks for reading.

Advertisements

2 thoughts on “Entity-Component game programming using JRuby and libGDX – part 8

  1. Very very good. I enjoyed to learn a new design pattern.

    I made 2 games in java + slick2D (space invaders + snake) without any knowledge about game design : I failled in every place where E-C win….. so thank you a lot.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s