Where should I handle collision?


(artanis) #1

Overall, I’m pretty new to Flashpunk and programming in general. I have made a Megaman Clone that works pretty well, but I grew tired of the way I was making the maps. I was using 2d arrays. All the platforms/walls were entities that the Player class would check collision with based on type.

So I’ve been checking out Ogmo. I followed a tutorial by Zachary Lewis and it is pretty different to what I was doing. Grids/Masks are new to me. His game seems simpler than a Megaman game. Ogmo 2 with Flashpunk Tutorial

No trouble getting it to work, but he handles everything in his World class (input, collision, ect). I’d prefer to stick with my method of the Player class handling the input and its own collision (maybe I shouldn’t???). I cannot figure out how to interact with the grid (walls) from the Ogmo file, from the Player class. The grid doesn’t seem to have any type I can check against. Am I going about this wrong?


(David Williams) #2

I always create a dedicated entity that handles the tilemap and grid. I use the tilemap as the graphic, and the grid as the mask. This way, you can just set the type and treat it like a normal entity.


(Jacob Albano) #3

I always structure things so that entities are responsible for handling their own collision when they move. Same with input. If you’re working on a small game for demonstration purposes, those things can go in the World without a problem, but it gets obnoxious. I learned my lesson after my first big project wound up with an 800 line main file. :wink:

You can add the grid to the the world with the addMask() function to allow other entities to check collision against it with a type string.


(Matt L) #4

I make my map into an entity. A grid (which FP calls “masks”) can be added to an entity, and then the grid will be used to collide with other objects. then just set the map’s type to something memorable, like “solid”. Then in your player, use the type “solid” anywhere you want to check if you’re hitting the world. You can use any type you want, like “solid”, “world”, or “banana”.

The gist is this.

Your map (as in the environment the player can interact with) is just another entity, no different than the player himself, a bullet, or an enemy. Make the tilemap you divined out of the ogmo file the graphic, and make the grid you got the “mask”. Mask just means collision gird. You don’t need a special class made up, just a default empty entity will work fine.

// make the world entity
map = new Entity(0, 0, mapImage, mapGrid);

// give it the type "solid" and unique name "map"
map.type = "solid";
map.name = "map";

// add it to the world
add(map);

The map will be placed at 0,0, with the tilemap image as it’s graphic, and the grid from the ogmo as it’s collider/mask/grid. Also wise to set it’s name to something easy to use, so you can single it out for collision checks separate from other “solid” objects.

As to if you should handle input in your player class or the world, really it depends, but in almost all cases it’s better to handle it in the world, and use states to decide where input goes. What about if you show a menu? Does it make sense to have the player entity control that? What about if the player dies and is removed from the world, or if you have a scene where the player isn’t present? You’d need to scatter input checks into some other object to handle it in his absence.

If you’d like to keep it compartmentalized somewhat you could make a separate class that extends flashpunk.world, and then have your worlds extend that class, and have your input handling done there, which would keep you having to redefine the input in every world type, like if you had a separate world class for cutscenes, menus, credits, etc.


(Jacob Albano) #5

I can’t think of a single case where it’s actually better to handle input at the top level in a World class. To use your example above, when the player dies:

  • Handling in World.update(), we have to make sure the player object isn’t null, and that he exists in the world before sending him input messages. In addition, we have to keep a reference to him in the world class.
  • Handling in Player.update(), our input checking code will only run if the player has been added to the world and is set to active. If he dies, set active to false. The player can exist autonomously in the world without being directly referred to by any other entity, which helps avoid bugs related to his being accessed and modified at the wrong time.

In the case of a menu, your approach makes even less sense. Why do this:

for each (var button:Button in buttons)
{
    if (button.collidePoint(button.x, button.y, mouseX, mouseY))
    {
        button.activate();
    }
}

when you can get the exact same behavior with this:

override public function update():void
{
    if (collidePoint(x, y, world.mouseX, world.mouseY))
    {
        activate();
    }
}

The whole point of the World class is to separate game states; your Player entity never needs to be added to a Menu world instance, and your Button entities never need to be added to your Game World instance. By allowing each entity to handle its own input, you separate game logic into the domain that it is uniquely responsible for.

My team has a strict rule against globally mutable data in our projects. This means no non-constant static objects, and no direct access to other entities from a top-level domain. It can be tough to get used to, but it’s helped us drastically the frequency with which complicated bugs crop up.


(artanis) #6

Thank you all for the input. Apparently I had been doing it correctly the whole time, but was making a stupid mistake with the collide function (putting in the wrong x,y…D’OH).


(Matt L) #8

Not hating, by the way, direct class based input is simpler/easier to work with than dispatched input, but if I walk up and talk to a shopkeeper and a buy/sell menu pops up, how do you stop moving the player and move the menu cursor? Do you check to see if a menu is open on the player? Do you set the player inactive and set the menu active? There are still times when the input is going to need to be dispatched somehow in this case.

What is wrong with setting a game state between values like “PLAYERMOVE”, “MAINMENU”, “SHOPMENU”, “BATTLE”. You don’t have to check between if the player is alive, if they exist, if a menu is up, you just have those events set a world-wide state variable and use different input handling functions in the world’s update depending on what state is active.


(Jacob Albano) #9

Here’s how I would handle a pause menu.

// PauseListener
// a small entity class that listens for a pause input and dispatches a menu

override public function update():void
{
    if (Input.pressed("pause"))
    {
        // take a screenshot of the current world and pass it to the menu
        // along with the current world instance
        FP.world = new PauseMenu(FP.screen.capture(), world);
    }
}

// PauseMenu
// a world class that adds a series of buttons, etc when it is created

public function PauseMenu(screenshot:Image, lastWorld:World)
{
    this.lastWorld = lastWorld;

    // we add a screenshot of the last world behind to make it feel cohesive
    // since the last world isn't active any more, it won't update or render
    addGraphic(screenshot);

    // load a menu file or add buttons here
}

override public function update():void
{
    if (Input.pressed("pause"))
    {
        // set the current world to the last active world
        FP.world = lastWorld;
    }
}

While this World class does indeed do some input handling, it’s only managing itself rather than all the entities in the world.


(Matt L) #10

Hadn’t thought about faking it with a screenshot, which is pretty clever.


(Jacob Albano) #11

In my experience, keeping state management in a top-level domain ultimately leads to code bloat as the project gets larger and more systems are added. I’ve worked on projects where we set a global state variable to determine various behaviors and it ended up being extremely hard to debug due to the fact that that state could be set from any number of places. It was experiences like that which led me to institute our rule against globally mutable data, and it’s been very helpful.

Honestly, I haven’t yet had the need to implement functionality like the shop interface in your example, so I don’t know how I would handle it at the moment. I have a number of helper classes that allow me to send messages to entities in a way similar to Flash events/event listeners, so I’d probably do something with those. My whole workflow at this point is based around building autonomous entities and loading them from data files, so I rarely write any code at all in a World class.

Don’t worry, I don’t feel like you’re being a hater and I hope you aren’t feeling that I am. It’s obvious we both have different approaches that work for us; it’s just that I used to use your approach and it doesn’t scale well to large projects with multiple people, in my experience.


(Matt L) #12

I do have an approach that works for me right now, but I’m always looking for approaches that work better in the future. I’ve had to tangle with the issues you’re talking about as well, so I don’t doubt it gets hairy with a lot of people/lots of objects mucking with the state.


(Zachary Lewis) #13

I feel the exact opposite way! :blush:

If I’m in my main menu, I want my world to know how to do menu stuff with input. If I’m in the game, I want my world to know how to do player move and pause stuff with my input. If I’m in replay world, I just want to pass in fake input that I previously recorded and ignore all user input.

What about multiplayer?

public class VersusWorld extends World
{
  private var player1:Player;
  private var player2:Player;

  override public function begin():void
  {
    player1 = Player(add(new Player()));
    player2 = Player(add(new Player()));
  }

  override public function update():void
  {
    // Why do my mans always move by what player 1 types?
  }
}

Sure, it may make your worlds a bit larger, but you have access over so much more without having to write weird code where the world updates an entity so the entity can call functions in that world. That’s zany.


(Jacob Albano) #14

So, with my data-oriented approach, I would do something like this:

In Ogmo, my Player entity would have an id property (distinct from the id property that Ogmo adds by default). I’d add two of them, each with a different ID.

In my assets folder, I’d have two files that looked something like this:

Player1.xml

<input>
    <up>W</up>
    <down>S</down>
    <left>A</left>
    <right>D</right>
<input>

Player2.xml

<input>
    <up>UP</up>
    <down>DOWN</down>
    <left>LEFT</left>
    <right>RIGHT</right>
<input>

And in my Player class, I’d load these files like so:

private var up:int;
private var down:int;
private var left:int;
private var right:int;

// ...

// load() is a function on my Entity extension class
// it automatically sets class properties based on Ogmo entity properties
override public function load(xml:XML):void
{
    super.load(xml);

    // my Library class loads assets dynamically if I'm in debug mode
    // this means I don't have to embed any assets manually
    var keys:XML = Library.getXML("Player" + id + ".xml");

    up = Key[String(keys.up)];
    down = Key[String(keys.down)];
    left = Key[String(keys.left)];
    right = Key[String(keys.right)];
}

Now each Player instance has its own group of keys to check against without doing something silly like making Player1 and Player2 classes, and I get the added benefit that I can edit an input file, switch back to the game, and hit a button to instantly reload all values so I can see my changes without closing the swf and rerunning/recompiling it.


(Zachary Lewis) #15

Ooh, I like that data-oriented approach.

How would you handle replays? :grin:


(Jacob Albano) #16

Off the top of my head, I would probably do something like this, with my message passing helper library:

Setup

  • Create a small ReplayManager class that listens for a “trigger relay” message.
  • Create a Player class that listens for a “start relay” message.
  • Implement an input system in Player that collects inputs and keeps track of the time at which each input was received. In replay mode, actions will be based on inputs read from this collection.

Practice

  • When a replay is triggered, the ReplayManager instance broadcasts a “start replay” message, passing itself in as a payload.
  • Each Player instance will respond to this message by setting itself to replay mode and adding itself to the ReplayManager passed to the message.
  • ReplayManager takes care of setting up the level and adding the Player instances it received.

I’ve never done anything like this before, so I’m sure it wouldn’t be as simple, but that’s the basic idea.