Battle System Tutorial by Vakinox

From Spherical
Jump to: navigation, search

This tutorial is an attempt to encourage people, new to this area of coding, to create their own simple turnbased battle systems. It is preferred you have some experience in other areas of sphere and a fair amount in learning the API before you dive off into this tutorial without the proper background. The more you practice, the more you know.


Before You Begin

First, you will need to know a few essentials.

  • Good Grasp on Sphere [1]
  • How to make TextBoxes
  • How to problem solve
  • If-Then, For-Loop, and While-Loop conditionals [2]
  • An understanding of Global and Local Variables [3]

(Optional:)

  • Create full-functional menus [4]
  • Knowledge of Color Coding [5]
  • Familiarity with Sphere’s API [6]



Order of Importance

Every battle system must meet a list of certain criteria to operate:

1. Stats -(party members, items, enemies, and etc.)

2. Menu System - (to give the player choices)

3. The Battle System - (calculates damage, handles attack routines)

4. Graphics and Animations - (displays progress and visuals)

At the very basic level, the most important two functions are player stats and calculating the damage between the two opposing stats. However to make battle systems more entertaining, we add an interactive component (either menu choices or button smashing) and graphics to peak the player's interests. These are the fundamentals to turnbased battles, most everything else is optional.

Next, having order and structure in your coding process will help make writing scripts an easier exercise. You will always want to know what is the following item you want implement, or how to arrange everything so that it is easy to find. Having a checklist is a proper motivational tool to accomplish these things. Here is my personal order of importance:

  • 1st call necessary scripts
  • 2nd the game() function
  • 3rd the stats
  • 4th the battle system
  • 5th the battle menu (options)
  • 6th the attack routines
  • 7th drawing the battle (graphics)
  • 8th the health bars

Calling certain functions before others in the script may affect the program differently, this is why I insist the checklist to be in this order. For example, because player stats tend to reference items (equipment) you would want to define the items before you define player stats. Otherwise, defining player stats prematurely will cause the program to fail because it doesn't understand what items you are referencing. Remember: Javascript is read top-to-bottom.

Items and Statistics

After setting up the basics, you want to create statistics for everything with a numerical value attached to it and provides the battle system with some basis for calculations. Things such as weapons, health, or attack values will all have to be grouped into statistics and coded individually into the script. In a more reasonable approach and to be better identify certain types of statistics, I find it best to split and group them under separate functions, each established to identify the certain topic they associate with.

For example, player statistics will have its own function and set of arrays that associate with it.

Constructors

First what we will be doing is creating a function known as a 'Constructor', which will hold variables that we will later define through arrays. 'Arrays' are something we can group multiple values together in for various objectives. In the constructor (stats function) we need to establish variables which will then hold information for necessary things, such as player name or health, for your battle system to reference and use in calculations. The result should be structured similar to this:

 function Stats() { this.blah = value;	}

Here we identify the constructor as "Stats()" and inside the brackets we then create variables. Anything you want to be later defined in an array, you will want to bind to the constructor through the placement of "this." before each variable you create. You will need to replace "blah" as it is simply a placeholder for whatever variable you want to establish and give value to. For example, if you want your stats to include a variable for Health then you will want to edit as so:

 this.health = 0;

Any value you give a variable inside the constructor (stats function) will be the default value. We are currently establishing the default value as 0, however it will be changed in the Array that we create for the constructor. You will need to list more variables for whatever statistics you will want to be used in the battle system, such as attack value or current level.

The end result should look similar to this:

 function Stats (Name, MaxHp)
{
  this.Name   = Name;    // <-- The Character's Name
  this.Hp     = Hp;    	//  <-- The Character's Current Health value
  this.MaxHp  = Hp;  	//  <-- Maximum Health, for calculating percentages
  this.Atk    = 10;  	//  <-- The Character's Attack
  this.Def    = 10;  	//  <-- The Character's Defense
  this.Lvl    = 1;  	//  <-- The Character's Level
  this.Exp    = 0;	//  <-- For calculating the current experience
  this.ExpNL  = 0;	//  <-- The Goal to reach until the next Level Up
  this.Active = 0;      //  <-- Stops inactive members from being included in the Battle System
}

Within the parentheses, you can see where I placed both variables "Name" and "MaxHp" inside of them. This is a shortcut so that when we create the Arrays, we won't have to write out another line for defining both those variables. Instead we can just define both the variable's values inside of the parentheses whenever we are establishing our arrays.

Arrays

Upon when you finish listing the variables and finalizing the constructor (stats function) then you will need to create an array of each individual character or item's stats. Each individual character or item has their own array which defines their own individual names and attack values along with the other variables that were listed inside the constructor. To begin making arrays, you will first and only once need to declare a global variable as an array, like so:

 var blah = new Array();

Once that's established, this will allow you to create arrays branching off the variable "blah". An array will look similar to this:

blah[0] = new Stats ("Name", 10);   
blah[0].Atk = 10;

Within the new Array, we have defined several things. In the parentheses we establish the character's name in quotes, and we also established his MaxHp as a value of 10. And furthermore we also gave his Attack (Atk) a value of 10.

Whenever creating separate stats for a new character, you must again create a new array as similar to the one shown in example above. See the 0 in the brackets next to "blah"? That number is known as the index.

Each array is given an index which gives them their own unique identity, separate from the others that may follow.

For each new statistic for the other characters that you create, that identifying numerical value (the index) is increased. An example:

blah[0] = new Stats ("Character-01", 10);   
blah[0].Atk = 10; 

blah[1] = new Stats ("Character-02", 10);   
blah[1].Atk = 10;

What separates each instance is the index, the numerical value between the brackets. Remember to always change that number when creating new stats for the next character, or else it would conflict with other arrays that has the same index.

You will want to create several different arrays for Equipment, Items, Characters, and Enemies attaching to them whatever you feel is necessary to be calculated and used in your battle system. You will want to separate them in a way that is easy to identify and reference so that your code won't become illegible or hard to read.

Parties

After doing so, although this is optional, you may also want to group player characters and enemies into their own individual party -- which is done through another separate array -- so that you can remove characters and reference them more easily in the battle system. This is done like so:

var Party = new Array();
Party[0] = 0;	
Party[1] = 1;
Party[2] = 2;
Party[3] = 3;

Each array's index signifies the order in which characters will be chosen in the battle system. Since arrays are referenced in Javascript, the values attached to them are always modifiable meaning you can add or subtract their values to remove or replace a character. Always remember the values to the right represent the index of the arrays made for character statistics, and that by changing the values you are changing the characters.

Enemies

For enemies you may want to randomize the character order, so then you would want to format the array in a different way than how we done with player characters.

First thing is create another constructor and then inside the properties (brackets) list variables, which we will later assign the index number of the enemies that are to be randomly chosen for battle.

function EnemysTeam (E1, E2, E3, E4)    
{
  this.E1 = E1;      
  this.E2 = E2;  
  this.E3 = E3;   
  this.E4 = E4;
}

The variables inside the parentheses of EnemysTeam represents each individual enemy's index, which they are assigned when you create arrays for their statistics. Next, you want to establish a party. The values to the right are the index number to identify certain enemies. You should change those values to the index number of the enemies you want to be in the party, usually depending on the player's current environment or placement on a map for most turnbased games.

var enemies = [];
enemies[0] = 0;
enemies[1] = 1;
enemies[2] = 2;
enemies[3] = 3;

Now to randomize the lineup of the enemies participating in battle, we must figure out a way to use Math.random to vary it.

This is done first by setting up another array, using our constructor and defining the variables to associate with the enemies established in the current party.

 var team[0] = new EnemysTeam(0, 1, 2, 3);

However, now the variables are defined. We want them randomized, so now we must create a function to replace those numbers in our new array and randomly produce a new lineup from the enemies already established in the party. To write out this function, we must recognize what we can use from sphere's API that will be necessary for this to happen. Several things should come to mind, which are: Math.floor, Math.random, and array.length. And with those three things we create this:

 function GetRandom(array) { return array[Math.floor(Math.random() * array.length)]; }

With this function, we are returning a random number out of an array. That random number is also the index of the enemies we established as party members. We get this result if we process our array for enemy party members through the function, then the results will be that of enemy's indexes being produced in random. If you remember the other array we setup to establish party members inside the constructor, using their indexes to define the 'E' variables, we now can replace those numbers with our newest function to produce randomized party members. Which we do so like this:

 var team[0] = new EnemysTeam(GetRandom(enemies), GetRandom(enemies), GetRandom(enemies), GetRandom(enemies));

And now you have randomized party members for the array team[0] which we will then use and reference in the battle system.

The Battle System

(its importance, what it does, what it handles)

First, you want to create the function and then establish a Local Variable that checks for an active battle. You can do this like so:

 IsTheBattleOver = false;   //<-- determines if the battle is over

After you've inserted that into your script, you're going want to add two Global Variables and another array which we will then establish locally inside the function.

Of these variables should be one for determining whether or not it is currently the enemies' turn, and attached to the variable is the answer.

It's easier to think of the variable in the form of a question: "Is it the enemies' turn?" Then our options would be "ONE for yes, ZERO for no."

Eturn = 0;   // <--decides if it's enemy's turn

To keep things more simple we will attach a number value to the variable, but you could also use true and false statements to signal an enemies' turn however it more simple to control the variables using mathematical operations.

Attached to these types of variables are known as 'Boolean Values' as any type of data or conditional statement with only two possible outcomes (yes or no) will always be recognize as Boolean data types.

The other variable should be for calculating the size of the enemies' party.

 ETeamSize = 0;	// <--determines size of enemy's team

This variable will be essential to the battle system, as it will signal when the enemies' turn is over when all enemies have taken their own each individual turns.

Next, we create the array ETeam is for transferring the enemies' indexes over to the battle system so we can properly reference them.

var ETeam = new Array(); // <--for transferring Enemy Indexes
ETeam[0] = team[0].E1;
ETeam[1] = team[0].E2;
ETeam[2] = team[0].E3;
ETeam[3] = team[0].E4;

This allows the character order to be properly referenced without the ability to predict which enemy will be included in the party, because of our randomization of their party members. Now we can choose and rotate between the lineup simply by referencing ETeam[i].

Start Cycle

When you have finished setting up, then we want to move onto beginning the player's turn cycle. In our turn cycle, we want to setup several things.

First, a For-Loop which signals the beginning of "Your Turn" and then inside that For-Loop we want to establish whose turn it is in the character order.

We can establish whose turn it is by creating a Local Variable inside the For-Loop, which is dependent on the For-Loop's determining variables which in this case is an i.

for (var i = 0; i <= 3; i++) // <--Begins the "Your Turn"
{ U = 3 - i; }

To explain the For-Loop inside its parentheses it's establishing the variable i as equal to ZERO. And as long i is less than or equal to THREE then it will keep adding a value of ONE to i.

  • Note: If you recall we only established THREE possible character slots in our party. That is what the THREE in this variable represents.

The Local Variable U is dependent on this For-Loop, as it determines which character is currently selected. This is done by taking the variable i and subtracting it from the largest known available character slots, which is THREE. As the variable i is increased in valued, it will move character selection to next person in the character order.

When we have that done, inside the For-Loop we also want to create an If-Then solution for finding the first living party member and to set them as the first attacker. You can do this easy by first, finding where you created the statistics array for each of your characters.

When I created the statistics array for my characters, I chose to create them under the array Person[i]. Now that you understand that, what you want to do is replace the index and reference your array for party members inside where the index goes for Person[i] or your statistics array. And then inside where you would place the index for the array for your party members, you would then reference the Local Variable U which will identify the character's index. The result should look similar to this:

 Person[Party[U]]

However to find the first living party member for the If-Then conditional we're creating, you want to reference their HP and then compare it to see if it were greater than ZERO.

 if ( Person[Party[U]].Hp > 0 )

This allows the first party member who has HP greater than ZERO, to then be the first person selected for an attack routine.

Now we need to produce a result for the If-Then conditional that will make the battle system recognize which character's turn it is.

We can do this simply through creating a Global Variable called Turn, and then making it equal to the Local Variable U, like so:

 Turn = U;

When you have that placed inside the If-Then statement, you can then close off the For-Loop and you now have the starting turn cycle finished.

What you should end with is a result similar to this:

for (var i = 0; i <= 3; i++) // <--Begins the "Your Turn"
{
 U = 3 - i;
 if ( Person[Party[U]].Hp > 0) // <--Finds the first living party member and set them as first attacker
 {
  Turn = U;
 }
}
  • Note: For coding a Preemptive Strike from the enemy, you could implement the For-Loop inside of a If-Then statement. Through creating a Global Variable which the If-Then will check for a Boolean Value, you can regulate whether the enemy attacks first or the player.

Turn Cycles

When that's done, you now have to regulate turn cycles.

To do this, we will have to put everything through a While-Loop then use another Global Variable with a Boolean Value to flip between the player and enemy's turn.

To start, simply create a While-Loop to check if the battle is occurring.

while (IsTheBattleOver == false){  }   // <--"while battle is not over, do this"

After that is placed inside the battle system function, we then can begin to start working inside the While-Loop and on our Turn Cycles.

First two things you want to do create a Global Variable to track whose turn it is, and also inside the While-Loop place a reference to a function for drawing battles.

We haven't made this function yet for drawing battles, but you want it to be the first thing inside the While-Loop so the battle graphics are shown properly when we do create the function.

When you have the Global Variable to track turns, which I call Side, assign it a value of ONE. After that, inside the While-Loop you want to make Global Variable equal ONE minus itself, like so:

while (BattleIsOver == false) {

 DrawBattle();   // <--draws actors & background for battles	
 Side = 1 - Side;
}

This is so when it's your turn, the variable will subtract itself to equal ZERO. When we define your turn cycle, we want ZERO to be your turn.

Since ZERO is our turn, we want to compare Global Variable Side to ZERO in an If-Then and see if it's our turn.

Side = 1 - Side;  //flips between 0 and 1 to choose hero's or Enemy's turn		
if (Side == 0) 	 //Player's turn

Now inside of the If-Then statement, we want to establish several things.

First thing we want to establish is a Local Variable to obtain how many living characters we have in our party, and then a For-Loop so we can count how many party members are still alive.

Side = 1 - Side;  //<--chooses hero or enemy's turn		
if (Side == 0)   //<--Player's turn
{
 Living = 0;
 for (var i = 0; i <= 3; i++) {  }

Since we're in a For-Loop, we want to add to the Local Variable Living as long as any party members with any HP over ZERO exists, until the For-Loop's limit has been reached.

The limit for the For-Loop should be the largest amount of character possible in your party, and your result so far should look similar to the below example.

Living = 0;   // <--if living = 0 then no one is alive 
for (var i = 0; i <= 3; i++)
{
 if ( Person[Party[i]].Hp > 0)  // <--finds any member still alive
 {
  Living ++;   // <-- counts living players
 }
}

Before we move on, we also need a cap to limit our turn. Something that will signal who is the last player able to be selected before it becomes the enemy's turn.

So to fix our problem, we create a Global Variable called TurnLimit then reference it locally in the If-Then under Living++;

Living = 0;
for (var i = 0; i <= 3; i++)
{
 if ( Person[Party[i]].Hp > 0) 
 {
  Living ++;		
  TurnLimit = i;   //<--last living party member in list
 }
}

We attach the variable i to the variable TurnLimit because doing so will stop our turn once all living players have had their turn.

Finally, we want to finish our player's turn cycle by checking if there are any living players, and if there are we want to access the Battle Menu which will gives us our options of attack.

To check if there are any living players left, we can simply use Local Variable Living and compare it to see if it's greater than ZERO.

 if (Living > 0) {  }

Then, inside the If-Then statement we want to reference the not-yet-created function for the Battle Menu.

 if (Living > 0) { Battle_Menu(); }   // <--draws the battle menu (attacks selected from there)

When that's done, we close up everything for the player's turn then begin working on the enemy's turn cycle.

Side = 1 - Side;	
if (Side == 0) 		
{
 
 Living = 0;
 for (var i = 0; i <= 3; i++)
 {
  if ( Person[Party[i]].Hp > 0) 
  {
   Living ++;			
   TurnLimit = i;    
  }
 }
			
 if (Living > 0) { Battle_Menu(); }
}

Your results should look similar to the above example.

Enemy's Turn Cycle:

After we've finished off that long If-Then for the player's turn cycle, we now have to do one for the enemy.

We are still inside the While-Loop checking if the batter is over, so to contrast ourselves from the player's If-Then turn cycle that started with if(Side ==0) we simply need an Else statement.

Inside this Else statement, we will need much of the similar things found in the player's turn cycle.

To start, we will need a Global Variable counting the number of our living party members and a For-Loop to add the living to the variable.

else   //<--Now the Enemy's turn
{
 Eliving = 0;  //<--if Eliving = 0 then enemies are dead
 for (var i = 0; i <= 3; i++)
 {
  if ( Enemies[i].Hp > 0);  // <--finds any enemy still alive
  {
   Eliving ++;	 //<--counts the living			
   Elimit = i;   //last living party member listed
  }
 }

The above should all seem similar because it's nearly an exact replica of the player's turn.

Next we need a While-Loop that is going to check for dead enemies, skip them, and then identify when the enemies' turn has reached its end.

We do this by first creating the While-Loop check if any enemies' HP is equal or less than ZERO.

 while (Enemies[Eturn].Hp == 0){  }   // <--checks for any dead enemies

The Global Variable Eturn, as an index of Enemies, identifies which individual enemy's turn it is.

Then inside the While-Loop we count those dead enemies.

Because the While-Loop only occurs when an enemy's HP is equal to or less than ZERO, then what's inside the While-Loop can only occur then, including the count.

While we are inside the While-Loop, we also want check if the enemies' turn has exceeded its limit and every enemy has gotten his turn yet.

while (Enemies[Eturn].Hp == 0)   // <--checks for any dead enemies
{
 Eturn ++;   //<--counts the dead enemies
 if (Eturn > Elimit)  //<--if enemies' turn is more than number of party members
 {
  Eturn = 0;   //<--then their turn is over
 }
}

Once we check to see if the enemies' turn is larger than their limit allows in an If-Then statement, we can then conclude it's the end of their turn.

After that's done, we can finish our enemies' turn cycle by checking for if their is any living and reference their attack routine like so:

if (Eliving > 0){ EnemyAtk(); }   // <--enemy's turn to attack

This is so that for any enemies left alive, they have their turn to attack.

When that is completed we can close up the enemies' turn cycle, but before we close the While-Loop checking if the battle is over, we need to insert a FlipScreen() so that everything will be drawn properly.

What you should end up with is a result similar to this:

while (BattleIsOver == false)  // <--"while battle is not over, do this"
{
 DrawBattle();  // <--draws the background and sprites

 Side = 1 - Side;  //<--checks for player or enemy's turn		
 if (Side == 0)   //<--player's turn
 {
  Living = 0;
  for (var i = 0; i <= 3; i++)
  {
   if ( Person[Party[i]].Hp > 0) // <--finds players still alive
   {
    Living ++;			   
    TurnLimit = i;      
   }
  }

  if (Living > 0){Battle_Menu();}  // <--draws the battle menu (attacks selected from there)
 }

 else	// <--the enemy's turn
 {
  Eliving = 0;
  for (var i = 0; i <= 3; i++)
  {
   if ( Enemies[i].Hp > 0);  // <--finds enemies still alive
   {
    Eliving ++;				
    Elimit = i;   
   }
  }

  while (Enemies[Eturn].Hp == 0)   // <--skips dead enemies
  {
   Eturn ++;
   if (Eturn > Elimit){Eturn = 0;}
  }

  if (Eliving > 0){EnemyAtk();}
 }
 FlipScreen();   //<--draws everything on screen
 Delay(800);   //<--where animations would go	
}

The Battle Menu

(it's importance, what it handles)

Our battle menu is referenced in the player's turn cycle. It will consist of options which the player can choose from to interact with the enemy or otherwise.

Attack Routines

The Graphics

Health Bars