Not logged inGosu Forums
Forum back to libgosu.org Help Search Register Login
Up Topic Gosu / Gosu Exchange / Game code layout
- - By allcaps Date 2013-07-26 19:21
I'm starting to get better at making games using Ruby and Gosu, and I think I'm coming up on a wall I'd like advice on getting over.  I think I need to improve the way I handle my objects, and how I get them to talk to each other.  Let me use an example:

In most of my games' main file, I notice myself doing this:

def update
  player.update
  bullets.update
  enemies.update
  hud.update
end

def draw
  player.draw
  bullets.draw
  enemies.draw
  hud.draw
end


Seems reasonable, but when it comes time for these objects to interact, what should handle what?  Take the bullets class I mention above.  It has a function called fire, that I use to spawn a new shot object, which is stored in an array, and then all the bullets in the array are looped over and drawn/moved with each draw/update cycle.  Works great.  But who should check if a bullet hits an enemy?  Should each bullet check to see if it's hit an enemy? To do that I'll need to pass the enemies array to the bullets class, so that each bullet can iterate over the enemies and tell if it's hit one, then mark itself as spent and destroy that enemy.  Sounds good, works well.  What about when the enemy goes to shoot back?  I can't reuse the bullets class, because I had to pass enemies to the bullets class, so the enemies class is already created.  I have thought of a few approaches that might solve my problem.

I could use a lazy linking method, like this

@enemies = Enemies.new
@bullets = Bullets(@enemies)
@enemies.link(@bullets)


That would give enemies access to bullets.  This seems really stupid, and makes it look like I'm totally defeating the point of objects.  I feel like I end up passing all my objects to nearly all the other objects.  I'm worried I'm missing an easier way.

What seems more sensible, and just struck me as I was writing this, is to make a less specific bullets class, and make two classes that extend it, one for the player, and one for enemies.  Each enemy wouldn't need it's own, since the way my bullets class works bullets can be spawned from anywhere, and can head in any direction.  It would be enough to have one allied array of bullets and one hostile one.  Anyway, I digress.

How do you normally handle the structure of your game's logic?  Do you pack all of the logic into the objects the logic involves, do you use dedicated classes for controlling the game, or something I haven't thought of?  I love Ruby dearly, so I'm trying to learn to do this the sensible, Ruby way, instead of just a way that works.  Any advice?
Parent - By lol_o2 Date 2013-07-26 20:22
My solution was to group objects in few arrays. Structure looks like this @entities = [ [] , [] , [] ]
@entities is array of arrays of objects. Every object is pushed in the first array (@entities[0]) and all objects in this array are passed update and draw.
Then, we can push the same object in another array (like @entities[1]) grouping objects of the same type. Interacting is checking objects only from this group array, not the one where are objects are.

For bullets, my latest solutions was to use one Projectile class with all necessary logic etc. but giving it also something like @enemy instance variable.
Every bullet is being updated, making its logic the same way, but there's a condition on the end/beginning of bullets update method. It looks like this:
if @enemy
  ..check collision with player...
else
  ...check collision with enemies...
end

So you can use the same class for player and enemy bullets and the key is this condition.

My whole system is more complex (like using inheritance for basic management methods for each object), all you can find here: https://github.com/KoBeWi/Sapphire Sorry, but for now it may be difficult to use for newbies.

Hope it helps. Somehow.
Parent - - By jlnr (dev) Date 2013-07-26 21:06 Edited 2013-07-26 21:14
I've always stuck to my standard recipe for Ruby games, and it's certainly sufficient for smaller games… :)

• The game is divided into several "states", which have a similar interface to the Gosu::Window class. The window delegates all callbacks to the current state.
• There is a GameState class which has an attr_reader :map and attr_reader :objects. The latter is an array of all objects in the game.
• All game object classes are a subclass of GameObject. GameObject has a constructor that takes and saves a reference to a GameState, so that all game objects can access the map and the object list via self.game.…
* The game object also introduces a deleted? flag that can be set to mark that an object should be discarded ASAP
Window::update calls GameState::draw, which basically does: @map.draw; @objects.each &:draw; each object knows how to draw itself.
Window::update calls GameState::update, which then does @objects.each &:update; @objects.reject! &:deleted? (it removes all objects from the list that should be deleted)
• Objects interact by calling callbacks on all other objects; for example, a Bomb class can apply it's damage by iterating over the array as such: self.game.objects.each { |obj| obj.bomb_damage_at(self.x, self.y) }; the GameObject class defines a standard implementation for each callback to avoid MethodErrors

So basically all my objects are created equal :) Exception: For objects that cannot work in a general way, e.g. objects that actively follow the (only) player, I usually keep a reference to the player in the GameState class. Enemies would then follow self.game.player.
Parent - By allcaps Date 2013-07-26 21:46
So your approach is to have all objects in one array, and then be specific in your method calls on those objects, instead of using general method names on many specialized arrays of objects.  This seems pretty simple.  I have not tried using any kind of gamestates yet at all, so this might be a good time to look into that as well.  I also haven't tried extending general classes (gameobject) into more specific classes (the player, enemies, etc).  I think the gameobject approach is pretty good.

I remember considering things like this when I was reading about Chingu, as it implements some of them, but thinking I needed to get some experience before I tried to use something similar.  I think I've gotten to that point.  Time to experiment I guess!

Thanks a bunch!
Parent - - By allcaps Date 2013-07-28 15:51
This approach is actually working pretty well.  I've also taken a page from Unity3D and given each gameobject subclass an array call "tags" that I use to help when I do my method calls.  Like I tag any "Shot < GameObject" that a player makes as "playerfire" and any an enemy makes as "enemyfire", I can also tag enemy bullets and ships as "hurt", and then when I check for collisions, I can just check all the objects, if if I encounter one with the tag "hurt", I know to take damage.  Working much better than my old approach.

One thing I'm still kind of confused about is how to cleanly forward button_down? data to the current gamestate.  button_down is easy, but to forward button_down? I've had to just make a function in each gamestate for all of the buttons I use, and then reassign them that way.

in Game < Window

def update
if button_down? blah
  @gamestate.blah
end


then in my gamestate

def blah
puts 'foo'
end
Parent - By jlnr (dev) Date 2013-07-28 18:40
I usually forward this to the window and use the chance to translate "logical" buttons into physical ones; for example


class Game
  ..
  def button_down? btn
    case btn
    when :jump
      self.window.button_down? KbUp or self.window.button_down? GpUp
    ..
  end
  ..
end
Parent - By RavensKrag Date 2013-08-01 18:25
You may want to consider taking advantage of Ruby's dynamic features instead of using a tagging system.   Tagging is Unity's only option, because it relies on static typing, but there are other options.

Rather than have a "hurt" tag, you can simply define a method #hurt? and check based on that. More of a duck-typing approach.

For example:
class GameObject
  def initialize
    @health = 5
  end
 
  def hurt?
    return true if @health <= 0
  end
end

g = GameObject.new
if g.hurt?
  # Do whatever is necessary with the gameobject
end


If not all objects are going to implement #hurt?, you can check if that method exists, similarly to how you can check if an object is of a particular class.  To carry on the previous example, you could use
g.respond_to? :hurt?
Up Topic Gosu / Gosu Exchange / Game code layout

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill