Handling Multiple Objects
Submitted:|| 9th January, 2009
This article will discuss my favourite methods for handling multiple objects. This can be a real bind, most commonly for the following reasons:
We often use a separate collision mask (i.e. a bounding box) for most enemies, so that things like capes, pony tails, swords, etc don't all cause a collision when they come into contact with walls. This means that almost every enemy will have two parts - its visible sprite, and its invisible counterpart.
Ever the economists that we are, we try and create multiple instances of enemies we have already programmed, rather than creating a complete clone for every badguy.
Following on from the above, we try and avoid repeating code. So for instance, if our player will bounce on the heads of bad guys kinda Mario-stylee, we don't want to have to recode that event for every enemy type we create.
These can be problematic as evidenced by the abundance of queries and questions posted regularly by newbies and oldbies alike. To boot, MMF's knack for simplifying complex things and complicating simple things tends to hinder more than it helps.
So, while not offering a difinitive guide, this article is intended to set out a method that I use to communicate between multiple objects (e.g. a collision detector and its visible sprite) and to handle multiple instances of the same object in a cost-effective way.
As always, please don't forget that articles can have comments, so do read them, as I'm sure many users have alternative ways to deal with these problems, which may well be better than my own.
(This article's coming over really business-like isn't it?)
Scenario: You have a computer-controlled ally working with you. He's made up of a collision mask and a visible sprite.
Problem: Communicating between the two. For instance, suppose the player is moving. If his detector is against the floor, then he must be walking/running. If it isn't, he may well be jumping/falling/flying/swimming. If he's walking against a wall, this may mean he's trying to push it.
In each situation, the DETECTOR is telling us about the character's current state. But it's the SPRITE which must represent these via animations (e.g. walking, running, swimming, jumping, pushing, etc).
What's more, if we have multiple instances of this character, we could accidentally interfere with other copies. This commonly results in situations where all enemies of a certain type start walking or jumping when only one should.
Solution: The commonest solution is to spread an ID value in each enemy and then carefully loop through each one. This is NOT my solution.
I have found that the best method is to create an Alterable String inside the SPRITE and the DETECTOR, and in both cases name it something like "Action".
Most commonly, the Sprite is a slave of the Detector (in other words, the detector is the real enemy, and the sprite is simply its visible appearance. The detector moves, the sprite follows). This means we can get away with one-way communication.
So ALWAYS set ACTION of Sprite to ACTION of Detector
MMF's inbuilt object pairing will naturally pair each Sprite with its Detector and copy the action values across.
So if our detector is jumping, we can set the Action value to 'JUMPING'. If it's running, we set it to 'RUNNING' and so on and so forth.
Then, once we've copied the value across to the sprites, we just do something like this:
ACTION of Sprite = "RUNNING"
--- Sprite: Set animation to 'Running'
Simple! And much quicker than brutally fastlooping every character. With a little care, it can also become a two-way communication (e.g. detector needs to know when the 'Throwing Rocks' animation has finished, so that it can restore the player's normal controls).
HANDLING MULTIPLE INSTANCES
Scenario: We have multiple copies of the same enemy type dotted throughout the level.
Problem: When sending actions to one enemy, it sometimes affects many of them.
Solution: Use a system of States and Responses.
Once again, the key is alterable values, as alterable values work well with MMF's built-in object scope.
We first build up a picture of our enemies' environments. So we may have some alterable strings such as:
Is Standing On Floor
Has Obstacle Left
Has Obstacle Right
Has Obstacle Above
Player In Focal View
Can See Player
You can add as many as you think you'll need.
So before our enemies do ANYTHING else, we want to write up some events to test for these conditions. So we may have one line which checks if the enemy is within, say, 100px of the window's edge. If so, then 'Is Onscreen' should be YES, otherwise NO.
The same goes for the other 'senses' we are coding.
Once ALL senses have been evaluated, we can then go on to RESPONSES.
So you may do an event like this:
ENEMY: Is Standing On Floor = "YES"
ENEMY: Can See Player = "YES"
--- ENEMY: Action = "CHARGE"
Then finally, you code what should happen for any enemies whose Action = "CHARGE".
This should keep your enemies as individuals.
MOVEMENT FOR MULTIPLE INSTANCES
Scenario: There are many copies of an enemy. All must move using the same code.
Problem: They seem to fall through the floor, or when just one collides with an obstacle, they all stop.
Solution: Use the 'Move Safely 2' object. It's beyond the scope of this article to go into all the details, but there are some great examples out there. The gist of it is thus:
Give Move Safely an alterable string called 'Used For'. This will contain a string to let us know what object type it's being used for right now (the player, bees, foot soldiers, guards, whatever).
Every time we assign some objects to the Move Safely object, we PRECEDE this action with a 'Release All Objects' action. This lets us recycle the MS2 object for more than one object type. Make sure to assign a 'Used For' value at this point. This lets us perform certain actions during the obstacle-detect which only apply to these kinds of objects.
Most custom movements involve keeping a decimal-precise copy of the object's location in the alterable values. Now, if our object has been pushed out of an obstacle, it's actual position will now differ to that recorded in its alterable values. This can cause problems.
The best solution I've found is to add a response (using the Senses/Responses technique above) which will check the 'Is Standing On Floor', 'Has Obstacle Above/Left/Right' etc values, and if they read true, take that opportunity to compare the actual position to the internal position and update it if the difference is more than a few decimal points. This is based on the principle that if there's a downward difference, but the object is standing on the floor, the higher coordinate must be correct.
That bit would really need an example, and there are many around.
Top tip just quickly. We're all familiar with object groups, but if you always maintain the first Alterable String as an 'Action' string, you can do stuff like this:
PLAYER: Overlaps Group.Enemies
--- Group.Enemies: Alterable String A (Action) = "HURT"
GUARD: Action = "HURT"
--- GUARD: Subtract 1 from Health
ASSASSIN: Action = "HURT"
--- ASSASSIN: Subtract 3 from Health
--- SOUND: Play sample "I'll Get You.wav"
In this way you can trigger a fundamental truth like 'is hurt', but the details are still defined locally within each enemy type
I'm now hungry, but I hope this has helped someone! BYEEE!!
Best Article WriterRegistered