Difference between revisions of "Battle System Tutorial by NeoLogiX"
(created from http://web.archive.org/web/20120829064120/http://www.spheredev.org/wiki/Battle_System_Tutorial_by_NeoLogiX) |
(No difference)
|
Latest revision as of 23:32, 7 June 2013
One of the most common questions for Sphere I've found is "How can I make a battle system for my game?" Well, if you follow the theory presented below, you'll be whipping them out in no time (well, maybe some time).
This theory-based tutorial may not contain actual code, but that's because each person may have his or her own unique coding style. It's up to you, the user, to come up with a way to accomplish what I demonstrate. With that said, let's begin.
Creating a skeleton
You'll most likely want a Battle object that you can use to keep track of things common to each battle, like a background pic, enemies, etc. Below is a list of the most likely things your battle system should contain:
- Graphics; a background image, combatant sprites/images/face pics, the little things like player readygauges, any/all the stuff you plan on blitting during the battle
- A list of the combatants; you can separate it into two lists, one for enemies, one for players, or have one superlist; combatants usually have stats that determine variation in the effectiveness of certain actions
- An array to keep track of combatants's turns (from here on referred to as the turn queue)
- A list of events that may happen during the battle (ie you want a boss to execute an attack that brings everyone down to 1 HP after 5 mins in a battle)
- A timer, in case you want to make a battle timed
Ok, now you should have a basic battle object ready. A battle usually runs on a loop. That loop should keep going until the battle ends, and the battle ending is a condition that you can determine. Take a look at the following:
Battle system pseudo-skeleton
BATTLE:
- initialize battle & combatant vars
- start timer
- MAIN LOOP:
- blit bg & under-elements
- blit combatants & main elements
- draw displays & over-elements
- FlipScreen()
- HANDLE EVENTS:
- process turn queue (do action of who's up next)
- process new events (if enemy's ready, have "AI" determine next action & add them to the queue; if **:player's ready, bring up menu & process menu action)
- process constant events (update the readygauge, update the counter for any effects like glowing or death sentence, update the timer, etc)
- END HANDLE EVENTS
- HANDLE COMBATANTS:
- process actions
- process status changes
- END HANDLE COMBATANTS
- check for battle end conditions
- if conditions are not met, continue
- else END MAIN LOOP
- stop timer
- resolve battle's end (give out spoils if won, else handle battle lost/game overs)
- reset battle info (heal enemies, remove temporary statuses, reset timers, etc)
- END BATTLE
That's basically the process a battle goes through. Keep in mind this is pseudocode and implementing it to your game's programming style is the tricky part. In my opinion the easiest part is deciding where to put all the graphic elements. The hard part is handling the actions. This skeleton may be edited/updated in the future to include other battle system types, such as the active battle system style found in Secret of Mana, the Legend of Zelda series, and the Kingdom Hearts series. Further explanation of each phase of the battle follows.
Dissecting the skeleton
Here we'll discuss the above phases at length.
Initialize phase
- Initialize battle vars
- There are usually no visible effects when these particular initializations take place. Such variables include HUD placement vars, user-triggered flags such as "show cursor," etc.
- Initialize combatant vars
- If there are any battle-only status effects, activate them now. If a chara has equipment on, apply any innate enhancements or detriments now if they haven't yet been applied (including statistic modifiers).
- Start timers
- It's recommended that there's always at least one timer running in the background - the main battle timer. This particular timer will be used for turn calculations, timed battle events, and whatnot. Other timers you can use include ones that keep track of remaining air (eg the underwater battle against Emerald Weapon in Final Fantasy 7), time remaining until an area's self-destruction (eg every Metroid game in existence), or even time remaining until a boss delivers a finishing blow (eg several bosses throughout the Final Fantasy series).
Main loop
For the purposes of this tutorial, a battle's main loop consists of four stages or phases: a blit phase, an event-handling phase, an action-handling phase, and a phase to check for battle-ending conditions. They are described below.
Blit phase
A battle would be pretty boring if it was just text-based or graphically uninteresting. The blit phase should be used to do nothing but draw all your graphical elements to the screen. It is highly recommended you separate your blit phase into the following three functions:
- Blit backgrounds & underlays
- Even battles in RPGs as old as Final Fantasy and Phantasy Star had a "dynamic background" somehow; it didn't necessarily animate as some later games do, but it did change depending on where in the world the battle took place - thorny vines and purple mist usually signified a swamp or dark forest, while a grey stone wall usually meant inside a castle or fortress, etc. Draw your background elements here (if you set your code up properly, you can even have animated elements in your background such as moving clouds that run along a timer), as well as any underlaid elements. Underlays include windowstyles for character stat heads-up displays and other such elements, but make sure background elements are drawn first.
- Blit combatants & primary elements
- Where is everybody? Here. Draw the player's party and enemy party in this stage. Also include any passive battle elements like a wall the party needs to break down before your charas can damage any enemy. Draw chara status elements here as well; the screenshot at the top shows revolving particles as an example status effect that would be drawn in this stage.
- This stage is also where you'd draw the battle animations for actions such as attacking.
- Blit displays & overlays
- For most battle systems, all that's left to draw are heads-up displays and any overlaid elements not yet handled. Heads-up displays include those showing chara stats (the bottom half of the screenshot at the top), and overlays include a target pointer over a potential attack target. Some systems prefer to have all elements of heads-up displays drawn (including those such as windowstyles mentioned earlier); that's dependent on the design specs of the system.
- FlipScreen()
- Always remember to flip the screen after something is drawn, otherwise you won't be able to see it. The above structure of a battle system does all of its visual rendering before any action processing, though design specs can allow for mingling the two processes.
Event-handling phase
Well-written design specs for a battle system should have the render phase separated from action phases. I say "phases" because the above skeleton breaks the action phase into two pieces - one that executes events already queued to take place, and one that handles keystrokes and such actions that result in queuing an event to take place.
- Process turn queue
- The turn queue is essentially an array that keeps track of who's ready to act and in what order. The system should be able to use this array to process the proper actions in the proper order.
- There are a few schools of thought as to the format of the turn queue. It's really dependent on the requirements of the design specs, but usually used is an array of objects in the format
{ ctype, cid, caction }
, where ctype is the combatant type (usually "party" or "enemy" with other values as needed), cid is the index of the queued combatant relative to the combatant type ('0' is the first enemy or party member, '1' is the second, etc.), and caction is the queued action. caction is usually an object itself, the specs of which were presumably determined long before the battle system was coded. - Process new events
- Get ready to add to the queue. If an enemy is ready to act, have the previously written logic determine the enemy's next action and it to the queue. If a player is ready, usually you bring up a menu and once an action is chosen, add that action to the queue for processing. The design specs should also include whether timers continue during this menu processing or not.
- Process constant events
- Automatic events are handled here - properly updating combatants' readygauge counters each turn, updating counters for any effects like spriteset glowing to signify status effects or countdowns for "death sentence" type status effects, updating battle timers, etc. Update them here.
A note about queuing turns
The above queuing method was originally intended as a prototype for simple queuing - just the current turn, and possibly the next turn. More complex battle systems (such as those found in Final Fantasy X and Final Fantasy Tactics) use a more advanced form of queuing, where up to three turns later are calculated. The above explanation assumes the use of separate member vars that keep track of a combatant's readygauge counter; during each turn you add x to the counter, where x can be affected by battle speed, the combatant's net speed (combatant's base speed+speed of combatant's total equipment), actual time passed, and any speed modifiers such as haste, slow, stop (reduces x to zero), and/or berserk (as you can tell, I'm using FF for status examples).
Once the readygauge counter is full (value of your choice; I usually use 10000), add that chara to the queue. If you want a few turns ahead (this is the "advanced" turn queuing referred to previously), the simplest way to accomplish this is to reset the counter to zero and start over, but keeping the initial turn in the queue. The queue as specified in the above example is an array of turns, where each turn represents the combatant and action at that turn. There may or may not be examples of this advanced turn queuing in this article in the future, though I have successfully helped another Sphere user add such advanced turn queuing to a conditional turn-based battle system, so it's possible we may see such examples eventually here.
Action-handling phase
Here is where you handle those actions that affect the event queue, usually adding to it.
- Process actions
- Run your checks for errant keypresses, mouse clicks, and such here. Adding actions to the queue should really be here, but the above skeleton was originally written rather quickly, so there are a few items that could be reorganized into better places. Your design specs should ultimately determine the proper organization of battle system procedures.
- Process status changes
- If there's a change in status due to a previous action, process it here. This is where you kill a combatant or revive a "dead" combatant, though your design specs should determine whether certain combatants can be revived or not and under what circumstances such limitations may or may not occur.
End phase (aka "Is the main loop done?")
We have to end the battle somehow. Check for it here.
- Check for battle end conditions
- If the battle's end conditions are not met, continue the battle as detailed above and/or as detailed in your design specs.
- Else, if the battle's end conditions are met, end the main loop.
End conditions are as simple as "the party has no living members or the enemy party has no living members;" end conditions are usually defined per-battle if special circumstances are necessary, and such per-battle circumstances should be defined in your design specs. Special end conditions include a countdown timer reaching zero or a certain number of turns having elapsed.
Resolve phase
Once main loop has completed, everything about the battle needs to be resolved. Do that here.
- Stop timers
- If there are any running timers dependent on the actual battle loop, stop them. Most games are kind enough to stop any global countdown timers while victory music plays and victory messages are displayed, though they usually continue the timers after a short fade to black then a switch back to your pre-battle map position.
- Resolve battle's end
- If you won the battle, you usually receive spoils of war. These spoils can include anything from experience points to items to money to even a new character joining your party. You can make the receipt of spoils as simple or complex an event as the game's design requires.
- If you lost the battle, however, you usually receive a game over screen. There are some instances in some games where you lose a battle and the game continues. Usually this is a result of a game making a battle so difficult that you are required to lose the battle to trigger an event and continue the story.
- Running away is the combination of winning and losing - you don't receive a game over screen like losing, but you don't receive spoils like winning. Running away is still the end of battle, however, unless a game is programmed to have enemies chase you if you do so.
- Reset battle info
- There are generally two ways to create a battle: use one battle scenario over and over (many games do this), or copy an existing scenario (some other games do this). If you program your game to copy an existing scenario, you only have to keep track of the copy, and once the battle is over you can trash that copy. If you program your game to use a scenario over and over, however, you need to account for that in this phase. Enemies will usually have zero HP, sometimes have less MP than their max MP, sometimes a status effect, so you need to restore their HP and MP and remove status effects inflicted during that battle. This will in effect reset the enemies so that the next time you encounter that particular battle scenario it would be as if you never fought this particular group of enemies before (though in fact you will have).
- Your party may be inflicted with status effects that should only remain active during this battle. Remember to have your code remove these temporary statuses.
- Timers may have been running during the battle. Reset any timers to zero that are temporary for this battle. If a global timer is running, have it continue.
- Return to the map
- It is recommended to provide a graphical transition to play so that the change from battle screen to map screen isn't sudden and "unnatural." This transition can be a fade out+fade in, a screen wipe, a pixelation, etc. It doesn't have to be stylish or complicated, though other than my own N-Trans library there seems to be a lack of graphical screen transition functions readily available in Sphere.
An example battle system
Here would be where I would put an example of a battle system using the skeleton procedure. As of this writing (27 June 2006) my example battle system (FF6-style TBS) blits a background image, party & enemy sprites (represented by colored boxes), a heads-up display positioned on the bottom which displays party's stats, what enemies there are, and each character's readystate (my custom variable keeping track of a chara's wait gauge), and keeps a timer of the battle, which is blitted on a corner. The battle ends after five seconds, then returns to the map. The battle is triggered by stepping on a tile with a trigger entity and upon triggering one of my screen transitions (mosaic out/in) takes place before the battle is blitted.
In closing
If there are any questions, you can ask at the forums or the Talk. Happy coding!