Battle System Tutorial by MetalMac

From Spheriki

Jump to: navigation, search


This tutorial series is an attempt at helping people, new to this area of coding, create their own battle systems. Maybe, and hopefully, you, who is reading this, somehow benefits and gains something outta this. You should be able to, after fully reading and applying what you learned, create your own full battle system from dirt up. Somewhat-Experienced users should be able to comprehend what is occurring here, if not please inform.

Contents

NEWB BATTLE SYSTEM TUTORIAL SERIES, PART I: CREATING TURN BASED BATTLES

In this part of the tutorial series, I, MetalMac, will teach you how to create a basic turn-based battle system. To teach you, I'll chop up each part of what a battle system of this type usually consists of, using the simplest battle system with the least amount of resources I could possibly make, and explain each step in detail (to the best of my extent).

Step 1: Before You Get Started

Before you begin coding your first turn based battle system, you’ll need to know a few essential things already. These things are:

(Rest Optional:)

Step 2: To the Drawing Board

To start off, you’ll first need to list things you think a battle system needs, things that battle system, in games you know, commonly have. Some things I can identify off the top of my head are:

  1. Stats (weapons, items, party members, enemies, and etc.)
  2. A Menu System (mainly which includes mathematical ways of players battling the enemy)
  3. The Battle System itself (which calculates damage and such)
  4. A System that draws the Battle on Screen
  5. A Health Bar System (which shows progression of the battle)

(rest is all optional, basically it’s a way of organizing yourself, skip to step 3 if you do not whish to learn it)

Next, you’ll have to identify the steps in which to code your script. Basically, this is helpfully for you to know what is next, down the line of the script, of which you need to code. An example:

  • 1st comes the scripts that are needed to be called for the battle to work
  • 2nd comes the stats
  • 3rd comes the battle system
  • 4th will come the battle menu
  • 5th comes the player’s/enemy’s attack routine
  • 6th comes drawing the battle itself
  • 7th comes the health bar system
  • 8th and finally comes the setup of the game(); function

After you’ve identified the process, then you may wish to identify the structure of the script, which is basically a more defined summary of which was previously listed. An example would be: Scripts & Vars > Color Coding > Weapon & Item Stats > Player Stats > Enemy Stats > Battle System > Battle Menu System > Player & Enemy’s Attack Routine > Drawing of The Battle > Health Bar System > Game Function Setup

  • note - We don’t always code our global variables first in our script, but mainly only place them as first in the script, because we figure them out along the way when coding the script itself. Another thing to remember is that calling some functions before others may affect the script entirely. For example: if the item & weapon functions are called after the player and enemy stats, then the entire script may not work. Usually, this is cause most player stats require their attack to be defined by whatever weapon they have equipped plus their level, and since JavaScript is read top to bottom, then the players stats would naturally come out undefined, that’s of course only if weapon’s stats is called after the player’s.

Step 3: Coding Stats

The first step people take when making their battle system is usually the statistics of the weapons, items, the party members, and the player’s enemies. Mainly cause they’re naturally easy to code and are a core part of the battle system. Remember though, the first thing in a script should always be the other scripts that are needed to be called and your globals, stats are normally the next step after that.

To begin creating stats, start by creating a function, which will contain variables that a player needs in a battle system. The variables needed range from name, to attack level, to the player’s maximum HP. There are other things that’ll naturally come to mind that a person may think that is needed in the framework of the battle system but truly isn’t needed at all yet. An example of things not needed yet are experience. Experience is calculated at the end of battles, you’ll have to create the battle itself first before even concerning yourself with an leveling up system. Things you might want to include in the player’s stats are thing like weapons and armor, but normally, they’re located in a function of their own, so it depends on the fact you would whether want to write less or more (in this tutorial, I won‘t include weapons and items in the stats, because we‘re only wanting to create a frame work for right now). In the end, the function should come out looking something similar to this:

function Stats (Name, Hp, Str, Sta)
{
  this.Name   = Name;
  this.Hp     = Hp;    	//HP (Health Points)
  this.MaxHp  = Hp;  	//max HP
  this.Str    = Str;	//strength (attack power)
  this.Sta    = Sta;	//stamina (defense)
  this.Atk    = 10;  	// str + weapon atk
  this.Def    = 10;  	// sta + armor/helmet/sheild def
  this.Level  = 1;  	// The players current level
  this.Exp    = 0;	//exp levels
  this.Active = 0;      //is member in Party (stops members from being drawn on screen)
}

After you’ve coded a stats function, you’ll need to go further by creating arrays. You’ll need an array of each character/player/party member’s stats, and if you included weapons/items in the stats function, you’ll need separate arrays for those as well. These arrays should contain certain stuff, just as the stats should contain variable like name, these arrays should define those variables created in the stats function. For example, if you have created a attack level variable inside the stats function, the array should define a certain character’s attack level. Here’s an example of arrays defining party member’s name, attack, level and so on:

var Person = new Array();

Person[0] = new Stats ("Mac", 100, 12, 10);
Person[0].Active = 1;
Person[0].Def = Person[0].Sta; 	 //constructs the player's defense.
Person[0].Atk = Person[0].Str; 	//constructs the player's attack

After you’ve defined the stats, you’ll then need to create an array which list the party members. Something similar to this:

//-A List of Current Party Members
var Party = new Array();
Party[0] = 0;
Party[1] = 1;
Party[2] = 2;
Party[3] = 3;

Next needed is the coding of the enemy stats. You can’t have a battle with enemies right? Start by creating a function containing variables that’ll be then defined in an array, it’s basically similar to how you defined the player’s stats. Remember that you will need a blank enemy (an enemy with 0 stats) to vary up the enemy’s line up a bit (it’ll be used when the enemy’s party only has a set of 3, instead of a full party set of 4). This step would look something like this:

function Enemy (Name, Hp)    //Enemy statistics
{
  this.Name = Name;
  this.Hp = Hp;
  this.MaxHp = Hp;
  this.Atk = 0;
  this.Def = 0;
}

//-Enemy Parties

var Enemies = new Array();
Enemies[0] = new Enemy (" ", 0); //Blank Enemy for smaller parties
Enemies[0].Atk = 0;
Enemies[0].Def = 0;

Enemies[1] = new Enemy ("Thief", 50);
Enemies[1].Atk = 15;
Enemies[1].Def = 20;

Now, the final thing in creating stats, is you’ll need to make something like when you coded your party’ line up, except this one will be more defined. You will need to create an array for a line-up of your enemies now. You will also need an array that will be needed in battle sequences to fill each battle with new enemies, instead of the same ones you already killed, this array needs to consist of an for-loop which includes blank enemy stats. An example of this part:

//-Enemy's Team --> 	Lists combinations of enemies encountered in battle

function EnemysTeam (E1, E2, E3, E4)
{
  this.E1 = E1;
  this.E2 = E2;
  this.E3 = E3;
  this.E4 = E4;
}
var EnemyTeam = new Array();
EnemyTeam[0] = new EnemysTeam(1,1,2,2); //Contains Thieves, Guards, and Knights
EnemyTeam[1] = new EnemysTeam(1,1,0,3); //setting an enemy to 0 makes empty spot

//this is the actual array that is used in the battle sequences, at the beginning 
//of every battle it is filled with a new set of Enemies from the 'EnemyTeam' lists
var Horde = new Array();
for (var i = 0; i <= 3; i++)
{
	Horde[i] = new Enemy ("blank", 0);
	Horde[i].Atk = 0;
	Horde[i].Def = 0;
	Horde[i].Accu = 0;
}

Step 4: Coding the Battle System

First step in making this battle system is creating a variable that reads as an asking of the system “Is Battle Over?” which will be defined as false. It’ll look like this:

BattleIsOver = false;

The next step, is you want create another variable to decide whether if it’s enemy’s turn or not. You can do this by simply making a local “Eturn = o;” and then define it in other functions. You will also need one more local that is used to calculate the size of the enemy’s team. After this step, you need to start things off by creating an array that’ll be used in a for-loop for transferring the Enemy’s ID from enemy team into whatever you named the function that consist of the array for getting your party’s line-up, which I named horde in my script. You should be coming out with something that looks like this right now:

Eturn = 0;	// <--just a lil variable to decide if enemy's turn
HordeSize = 0;	// <--another variable for calculating size of enemy's team

//used to send enemy's party into the For-Loop to create the number of the horde in battle
var ETeam = new Array();	 // <--temporary array for transferring Enemy ID's from EnemyTeam to the Horde
ETeam[0] = EnemyTeam[ET].E1;
ETeam[1] = EnemyTeam[ET].E2;
ETeam[2] = EnemyTeam[ET].E3;
ETeam[3] = EnemyTeam[ET].E4;	

After that, you’ll need to make a for-loop which defines the array for getting your enemy’s party line-up’s HP and etc. with the temporary array for transferring enemy’s ID. Then, after you’ve defined that, next inside of the for-loop needs an if-then statement that will return every enemy that is alive and add them to the size of the enemy’s team. An example:

//For-Loop the gets the number of enemies, which defines how big the Horde party is
for (var i = 0; i <= 3; i++) // <--populates the horde with enemies listed in enemyteam
{
	Horde[i].Name = Enemies[ETeam[i]].Name;
	Horde[i].Hp = Enemies[ETeam[i]].Hp;
	Horde[i].Atk = Enemies[ETeam[i]].Atk;
	Horde[i].Def = Enemies[ETeam[i]].Def;
	
	if (Horde[i].Hp > 0)
	{
		HordeSize ++;
	}
}

Next, after the for-loop you’ve just defined, you’ll need another for-loop that finds the first living party member and sets them as the first attacker. To do this, you’ll need a variable, inside of the for-loop, that is defined as an equation using the largest/maximum number of party members you can have minus one (In normal occasions, that number would be 3 since normally parties‘ largest size are 4), then subtracted by “i”.

Then, still inside of that for-loop, needs an if-then, that is defined as being: “if a person in the party is (whatever the variable in the for-loop is defined as being) greater than zero, than person’s turn equals (that variable). By now, that for-loop should look something like this:

for (var i = 0; i <= 3; i++)
{
	U = 3 - i;
	if ( Person[Party[U]].Hp > 0)
	{
		Turn = U;
	}
}

Now is where IsBattleOver comes into play. To start off, you’re going to need a while-loop that says: “while battle is over == false, then do this”. First to go inside of the loop is the function that’ll draw your battle (we haven’t coded it yet, just make sure you remember the name of the function you call here). After that, we need a variable that’ll decide between whose turn it is, enemy’s or player’s. Since there are only two choices to choose between, the best way for us to do this is to make a local like: “Side = 1 - Side;” If the variable is 0, then it’s player’s turn, if 1, then it’s the enemy’s. By now, it should be looking like this:

//The Battle
while (BattleIsOver == false)  // <--basically says: "while battle is not over, do this"
{
	DrawBattle();   	// <--draws actors & background for battles
	Side = 1 - Side;  //flips between 0 and 1 to choose hero's or Enemy's turn

After the variable, we need an if-then to decide if it’s the player’s turn or not. We’ll use 0 as to determine whether if it’s the player’s turn. We do this by “if (side == 0) then do this”. Inside the do this part of the if then, we need to define a variable that’ll identify living party members, do this by defining that variable equal to zero. Then, we need a for-loop inside of the if-then statement, then an if-then defined inside of that for-loop. The if-then, inside of the for-loop, should read: “if any party member whose HP is greater than zero, then add one to the variable inside the first if-then defined as zero, and define another variable as TurnLimit being equal to I.” And lastly, inside of the first if-then, you need to define another if-then as “if (the variable that is defined to identify living party members) is greater than zero, then activate the battle menu function (our battle menu is yet to be defined so remember what the name of the function you’ve called here). An example of this should look like this:

	if (Side == 0) 	//Player's turn
	{
		Living = 0;
		for (var i = 0; i <= 3; i++)
		{
			if ( Person[Party[i]].Hp > 0) // <--finds any person that's still alive
			{
				Living ++;  // <--if living = 0 then no-one alive
				TurnLimit = i;       //last living party member in list
			}
		}
		if (Living > 0)
		{
			Battle_Menu();// <--draws battle menu (attack selected from there)
		}
	}

Now, after that long if-then, we’re going to need an “else” which will define the enemy’s turn. Inside of it, we need a variable that’ll identify the living enemies. We do the same as what we did with the player’s, and define this variable as being zero. Next, inside the “else” we need a for-loop that’ll find any enemy that is still alive. To do this, we need an if-then statement, same as in the player’s, that says: “if (horde’s HP is greater than 0) then add one to (the variable that identifies living enemies) and define a new variable as enemy’s limit being equal to “i”.

After that for-loop, but still inside the “else”, we need a while-loop that says: “while (enemy’s, with turn, HP is equal to 0), then add one to turn limit, and if (enemy’s turn is greater than its limit) then enemy’s turn equals zero. After the if-then, but still inside the “else” we need another if-then that says: “if (enemies lving equals 0) then activate enemy’s attack routine”.

Finally, after the “else“, we need a FlipScreen so that the “Draw Battle” function will show, and we’ll also add a Delay function of 800 miliseconds to give a pause that’ll normally be caused by attack animations on screen. And, that’s it for the Battle system code, next is the Battle system menu. An example of what’s been previously said, should look similar to this:

	else	// <--Now the Enemy's turn
	{
		Eliving = 0;
		for (var i = 0; i <= 3; i++)
		{
			if ( Horde[i].Hp > 0);  // <--finds any enemy thats still alive
			{
				Eliving ++;	//if Eliving = 0 then no-one alive 
				Elimit = i;     //last living party member in list
			}
		}
		while (Horde[Eturn].Hp == 0)    // <--skips any dead enemies
		{
			Eturn ++;
			if (Eturn > Elimit) Eturn = 0;
		}
		if (Eliving > 0) EnemyAtk();  	// <--enemy's turn to attack
		FlipScreen();	//Draws everything on screen
		Delay(800);  	//Because there's no animations, this gives a similar delay 	
	}
} // <-- ends the while

Step 5: Coding the Battle Menu

(For right now, we’ll only use Sphere’s system menu, that’ll leave out the trouble of creating our own entirely battle menu, but all the same, it’ll still go where we place this menu.) First thing we need to do is now create a function entitled something similar to “Battle Menu” (remember: has to be the same title you have as inside of the player’s turn part of the Main Battle Code). Inside of this function we need to define a variable that is defined as a new Menu. All we need to do next is add an item (“Attack”, and name of the Select Enemy function) then define the menu’s execute, which calculates the position of the menu and its size.


function Battle_Menu()	// <--Uses Sphere's system menu
{   
  var menu = new Menu();	// <--Lists choices to select from when it's player's turn
  menu.addItem ("Attack", SelectEnemy); // <--if selected, instantly starts selectenemy() function
							         
  menu.execute (20, 320, 90,142); 	 //Calculates the position of the battle menu 
}

Next, we need another new function that’ll select the enemy. To start off, inside this function, we need to call the function which will draw our battle, then we create a new menu (preferably still using the system’s menu) to create a list of enemies to select from. We’ll need to create a for-loop, then proceed to create an if-then inside of the for-loop which states if horde (the enemy’s) hp is greater than 0, then add item to menu, that includes horde’s name and when selected, the player attacks that horde (enemy). And finally, we define the menu’s position and size. By now, you should have something similar to this:


function SelectEnemy()   	  // <--gives the player a choice in which enemy to attack            
{
  DrawBattle();
 var menu = new Menu(); 	// <--puts the selectenemy() function on entirely new menu
  for (var i = 0; i <= 3; i++)	// <--Creates a list of enemies to attack
  {
    if (Horde[i].Hp > 0)  
    {
      menu.addItem (Horde[i].Name, new Function("PlayerAtk("+i+")") ); 
    }
  }
  menu.execute (20, 320, 90,142); 		 //calculates poistion of menu 
}

Step 6: Player and Enemy's Attack routines

Now, we need to start creating the player’s and enemy’s attack routines. First, we’ll create the player’s, then the enemy’s. First off, inside of the parenthesis, when naming the function, you’re going to need a variable that’ll be defined in the select enemy function (that’s the reason why there’s a “+i+” inside the PlayerAtk of the SelectEnemy function, in case you noticed), we’ll call the variable Sel (as in enemy selected for relevance reasons, at least I will call it that, you can call it whatever you want).

Next, We’re going to need two variables (preferably locally, inside of the function we’re creating) known as X and Y, for placement purposes (for damage and stuff like that). Then, We need locals that calculate positions for the enemy’s sprites and text. By now you should have something looking similar to this:


function PlayerAtk(Sel)
{	
	Xeffect = 0; //variables
	Yeffect = 0;
	
	DrawBattle();  		 // <--Draws everything on screen
	
	HordePos = 320 / HordeSize;	  //Calculates positions for enemy sprites/text
	Xeffect = 550;										
	Yeffect = 40 + (Sel * HordePos);
	

We’ll then need to define the amount of player’s attack and the enemy’s defense (for the purpose of calculating damage). We can define the player’s attacks simply as the player’s, with turn, attack (though usually, this is added to which weapon the player has plus player’s attack level and other stuff). We can just define the enemy’s defense, as just that, the enemy’s defense (though this is added to stuff like shields and all that). Next, we must define damage as being math.round and inside the parenthesis we’ll put attack minus the defense. An example of this would be:


Attack = Person[Party[Turn]].Atk; // <--The amount of person's, with turn, attack
Defend = Horde[Sel].Def;	 // <--The amount of selected enemy's defense
Damage = Math.round(Attack - Defend); 

Next, We must make conditionals, in case damage becomes a negative number and things such as that. The first conditional we can make is if damage is less than zero, make it equal to zero. Then, after we made that conditional, we need to make the selected horde’s hp, subtracted by the damage (which basically deals damage to selected enemy) and if damage equals zero, then draw (using main font) the word “Miss”, else (in other words: if damage does not equal zero) then blit the damage amount. An example of this should look something like this:

	
if (Damage < 0) //if damage is less than zero
{
	Damage = 0;	//Prevents damage from being a negative number 
}		
										
Horde[Sel].Hp -= Damage;   // <--Deals damage
if (Damage == 0)
{
	Main_Font.drawText (Xeffect, Yeffect, "Miss");
}
	
else //if damage does not equal zero or less, it blits how much damage was made
{
	Main_Font.drawText (Xeffect, Yeffect, Damage);
}

Now, we can make a conditional to prevent the enemy’s hp from falling under zero by placing the selected enemy, who has less than zero hp, then his hp equals zero. We then define a variable of living enemies as zero. We then can create a for-loop that says if enemy’s hp is greater than zero, then we add one to the enemies that are living variable. Next, say if enemies living does equal zero, then the battle is over. To say the battle is over, we can simply make the variable (that was place in the beginning of the main battle code) as being equal true, and then we resume the game. Now, to finish this attack routine, we’ll create a while-loop that says while person’s, in party with turn, hp equals zero, then add one to turn, and if the turn is greater than turn limit, then turn is equal to zero. By now you should have something that reads like this:


if (Horde[Sel].Hp < 0)  //Prevents enemy's hp faling below 0 
{
	Horde[Sel].Hp = 0;
}		
	
Eliving = 0; //variable
for (var i = 0; i <= 3; i++)
{
	if ( Horde[i].Hp > 0)  //looks for enemies with any life
	{
		Eliving ++;	//if Eliving = 0 then no-one alive 
	}
}
	
if (Eliving == 0)  //if no enemies alive then
{
	BattleIsOver = true;   //Player wins the battle
	Resume();
}

Turn ++;			         	//sets the next player active
if (Turn > TurnLimit)
{
	Turn = 0;
}

while ( Person[Party[Turn]].Hp == 0)  //skips any dead players	
{
	Turn ++ ;
	if (Turn > TurnLimit)
	{
		Turn = 0;
	}
}	

} //End of Player's Turn

To make a simple enemy’s attack routine is as simple as to a basic edit of the player’s, keeping all in mind the stuff that an enemy’s routine needs and replace it inside the player’s (for example, change up things like selected enemy would now be just a randomly selected person of the party). In more complicated games though, instead of your usual basic outline, you’ll start from this same basic idea, but in the end, the enemy’s attack routine should not be able to be recognizable in comparison to the player’s.


Step 7: Battle Graphics

Now is where we begin to code the function that will draw the battle. Inside this function, you should create a for-loop that includes several things:

* Battle Background * Battle Sprites’ Positions * Health Bar System

After the for-loop is created, first thing to do inside of it is to blit the battle ground. If you don’t have a battle ground, don’t worry cause it’s not important (you can always come back and put one there if you leave room). Next to come is a variable that’ll define party members’ index number (we can just call the variable “index”). Then, inside the for-loop should be a battle position variable (which basically is an equation used to place sprites). After you have your equation, then you need to make use of it using if-then statements to place sprites correctly on screen. And, after all that is defined, you’ll need to call your function which draws your health bars and such. An example of a “Drawing Battle Graphics” function:

function DrawBattle()
{

for (var i = 0; i <= 3; i++)
	{
		
		// -->draw battle background here<--
		Indx = Party[i];
		BattlePos = 50 + (i * 80);  // <--an equation used to place sprites and etc.
		if (Person[Indx].Active > 0)
		{		
			if (Person[Indx].Hp > 0)
			{
				Main_Font.drawText (50, BattlePos, Person[Indx].Name );   
				
			}
			else
			{
				Main_Font.drawText (50, BattlePos, Person[Indx].Name );   
				Main_Font.drawText (90, BattlePos, "  KO  " );
			}
		}
		HordePos = 320 / HordeSize;
		EmyPos = 50 + (i * HordePos);   //Draws enemies evenly across screen
		if (Horde[i].Hp > 0)
		{
			Main_Font.drawText (550, EmyPos, Horde[i].Name);   
		}
	}
	Bars(430);  //Draws the health bars
}

Step 8: Health Bars

To create a health bar function, basically the whole thing is set inside a for-loop. So, after you’ve named the function itself, then create a for-loop, which inside of that we’ll define all the things to a health bar system. First off, it’ll be easier for us if we made a local that identifies the player’s index number as just an variable called index. Next, we need to make an if-then that says “If person[index] is greater than 0, then do this.” Then, inside the then-part, we code all the parts of a health bar system we need. An example of this should look similar to:

function Bars(offset)	
{
		
	for (var i = 0; i <= 3; i++)
	{
		Indx = Party[i];
		if (Person[Indx].Active > 0)
		{			
			MultiHp = Person[Indx].MaxHp / 100;             //Generates a scale factor
			Hpbar = Math.floor(Person[Indx].Hp / MultiHp);   //Converts the HP into a percentage (always 0-100)
			Hpcol = Colorbar[Math.floor(Hpbar / 10)];	     //Generates a number from hp betweem 0 & 10 for the bar colour array
				
				//Poisitions of Health Bar
				Ybar = 295 + (i*45);		
				Xbar= 250;  						
				Bartext = Xbar - 55;
					
			
			Main_Font.drawText (Bartext - 65, Ybar + 15, Person[Indx].Name);
			Main_Font.setColorMask(Yellow);
			Main_Font.drawText (Bartext - 65, Ybar + 30,"Level " + Person[Indx].Level);
			Main_Font.setColorMask(White);
			Main_Font.drawText (Bartext, Ybar+15, "HP " + Person[Indx].Hp);
			Rectangle(Xbar, Ybar+15, Hpbar, 10, Hpcol);  //Draws HP bar
		}
	}
}

Step 9: Finishing Up TBBS

All that’s left is for you to sum things up with the game function, a trigger to make the battle start, a map engine, and whatever else you might think this thing needs. Something like this:

function game()
{

	// -->Game Start Screen Goes Here<--
	
	// -->the main game code would go here<--
	
	
	Main_Font.drawText (120, 240, "Press Enter to Start");	
	FlipScreen();
	GetKey("KEY_ENTER");
	BindKey (KEY_ENTER, 'BattleCode(0)', 'None()');	// <--Triggers battle
	
	
  MapEngine("test.rmp", 60); //blank map with no tileset    
}



function None()  		//blank function - does nothing
{
}



function Resume()   //Keeps the game going even after battle is won
{
  Main_Font.drawText (320, 240, "You Won");
  FlipScreen();
  GetKey(); 
}



function GameOver()   //Returns to start screen after battle is lost
{
  Main_Font.drawText (200, 240, "Game Over");  //Game over - restarts the game
  FlipScreen();
  Delay(5000);	
  RestartGame();
}

And that’s it for this part of the Battle System tutorial series, brought to you by, yours truly, MetalMac…

Personal tools