Feeks easy battle system
From Spheriki
Welcome to this simple retro styled battle system that you can use in your game, as your learning how to make one and script. It will be in a 320*240 resolution so your pictures won't need to be so big. This is for those who know the language somewhat after reading through the basic material tutorials supplied by people like Davince. This version is complete and is reccomended for tutorial use. It is advised that you don't use this system for your own game unless you know what your doing...
Contents |
First Steps
First for a battle system, you need a style, players, and enemies. For this tutorial I'm doing a turn based one. Mainly in this tutorial your looking for explanations. I'm not doing it in pseudo code because if your ever stuck, then copy the code. It is rather important that you code it your own way based off the explanations or directly type in the code I made. Copying the code won't help you learn the language or give you the feel at writing in the language.
System Style
A turn based style is the most famous (or infamous) style that span out to generations of rpg player goodness. It was also at it's time an effecient and easy way to do battle without making the player press a lot of buttons (sure players press more buttons in later systems) All we need is an attack button. There can always be the option for skills and items but the simpler the system the sooner it's done.
Players
In scripting you may have heard of or known about a perticulat method of listing important (or non-important) items or objects. It's called arrays. For the players there needs to be a preset array before any sort of battle does happen. This array starts at zero. There can be preset heros or they are made by functions, but ultimatly, they need to be made. We can attach functions that act as objects that are tied to variables in an array. We need to make that function. First we need the array.
var players = new Array();
Then the function.
function player(name,mp,hp,atk,def)
{
this.name = name;
this.mp = mp;
this.hp = hp;
this.atk = atk;
this.def = def;
this.level = 0; // or 1...
this.exp = 0;
this.portrait = ""; // a small picture 32*32
// you can also have a spriteset or picture here, for my system... nah.
}
That is the easiest method to have a player come alive. I'm not using a spriteset or picture for your character because your trying to make a working ststem ;)
Now attach that function to your first player, lets name him... Mr.Sphere.
var players = new Array();
players[0] = new player("Mr.Sphere",2,6,2,1);
players[0].portrait = LoadImage("Mr.Sphere.png") // or your own.
You can go down the list for adding players, I'm adding one more. Ms.Esphera.
players[1] = new player("Ms.Esphera",5,5,1,2);
players[1].portrait = LoadImage("Ms.Esphera.png") // or your own.
We now can apply the same attributes to enemies.
Enemies
Enemies need their own array. What will be different this time is to make them have picture files. I find it better to fight enemies that you can see, not text that you read :).
enemies = new Array();
function enemy(name,mp,hp,atk,def)
{
this.name = name;
this.mp = mp;
this.hp = hp;
this.atk = atk;
this.def = def;
this.pic = "" // you'll modify this later.
}
Now we got an enemy array and a function. Lets add some enemys. Actually first they need pictures. I'm making a slime, a bug, and an ant. You can draw them however you want. Make them or resize them to 64*64. (your attacks will be this big too).
enemies[0] = new enemy("Slime",1,3,1,0);
enemies[0].pic = LoadImage("Slime.png") // or your image.
enemies[1] = new enemy("Bug",2,4,2,1);
enemies[1].pic = LoadImage("Bug.png") // or your image.
enemies[2] = new enemy("Ant",2,5,2,2);
enemies[2].pic = LoadImage("Ant.png") // or your image.
They now have stats and battle images. This is the end of first steps. With the arrays and battle system all planned out, we can now start building it.
Designing
We now have the players and enemies laid out. Where will they be? Where will the menus go? How are the attacks used and are implemented. Before it's all scripted out you need to know what is scripted out.
Window Style Layout
Should enemies have a window area? In this battle system, no. If you were able to see the enemies stats then there can be an advantage for the player. But the players and their information shoul go to the bottom. At the bottom because it will be easier to see the player stats (so you don't need to always look up). Also for the menus later on (so you don't read them downwards).
Make an easy windowstyle that sphere supports then save it.
Enemy Layout
Since enemies are pictures they need an area to be. You can't use the bottom because of the player window.
Lets have them blitted at the battle field, the middle of the screen.
Player Layout
Players have their own area, the bottom window. Players can have portraits they will be displayed by their names, along with their hp and mp.
Attacks
Attacks will happen at the middle of the screen. On top of the enemies. Making attacks as pictures will take time to animate. We can use spritesets.
Wrapping it All Up and Thinking Ahead
We now have it all planned out. Lets start drawing things.We need a background which will be with the rest of our global variables.
var bg = LoadImage("background.png") // or your image here.
var window = LoadWindowStyle("window.rws") // or your window here.
var font = LoadFont("font.rfn") // or your font here.
var BattleEnemies = new Array(); // the enemies in battle.
var CurrentEnemy = 0; // which array enemy of BattleEnemies.
var CurrentPlayer = 0; // the currently selected player.
var Items = new Array(); // party inventory
var Spells = new Array(); // character spells
var sh = GetScreenHeight();
var sw = GetScreenWidth();
I added an area for items and spells but first thing is to have the system work by being able to attack the selected target.
We need to be able to add battle enemies.
function addEnemy(number)
{
BattleEnemies.push(enemies[number]);
}
this function will add the enemies number of the enemies array to the next position of the BattleEnemies array. This is so you can have multiple enemies that are the same instead of globally drawing it by the enemies array. Because you can only draw it once globally if it's in that array once.
Lets add some enemies into that array!
function game()
{
addEnemy(0);
addEnemy(1);
addEnemy(2);
}
There are now three battle ready enemies, with nowhere to go and with no players in sight to fight.
Lets add Players!
Lets draw the player battle window!
function drawPlayerWindow(x,y,w,h)
{
window.drawWindow(x,y,w,h);
for (var i = 0; i < players.length; ++i)
{
players[i].portrait.blit((x+1)+i*128,y+4); // *64 for text and portrait room.
font.drawText((x+33)+i*128,y+4,players[i].name);
font.drawText((x+33)+i*128,y+24,"hp: " + players[i].hp);
font.drawText((x+33)+i*128,y+44,"mp: " +players[i].mp);
}
}
This will now draw the window with the players arraying to the left wherever the menu is placed. The menu will be placed at the bottom of the screen as discussed during the planning. Notice The addition in the coding where the player stuff is blitted. It allows for offset so the items won't be jumbled all together.
function game()
{
// create your enemies //
while (BattleEnemies.length > 0)
{
bg.blit(0,0); // draw's the playfield, it could come from an array.
drawPlayerWindow(3,sh-67,sw-6,64); // bottom of screen.
FlipScreen(); // to draw window and objects at it's coordinates.
}
}
Now the players and their attributes are drawn along with the window. I put in the while loop to make sure the objects keep appearing until there is no more enemies left (that is, when the BattleEnemies Array runs out to 0). You can have the backgrounds listed in an array much like the enemies and players, but were sticking with one, but it is advised that there should be varying backgrounds, just so you don't see the same one over again in a finished product. :)
Lets add Enemies!
Now when you run the game, a bottom windowstyle with player statistics is arrayed across, not bad to view the players for a battle system. But without working enemies that you can see, There will be no system...
The enemies can be added in a function :
function drawEnemies()
{
for (var i = 0; i < BattleEnemies.length; ++i)
{
BattleEnemies[i].pic.blit((sw/2-((BattleEnemies.length*64)/2))+i*65,sh/2-64)
}
}
Well I used some math there! Luckily most of the drawing in sphere is done in simple algebra. Though from time to time you can see things such as triginometry or simple calculus. But thise are less likely to occur in a simple game. So lets apply this function!
function game()
{
//enemies are set here
while(BattleEnemies.length > 0)
{
//draw player window here
drawEnemies();
}
}
That will now draw the enemies. We have our visiuals all set up, now lets continue with selections. I thought ahead by having enemy statistics in the same array as the pictures, so they are set up for us. I'm also going to do one last function. When your fighting battle information needs to appear. So lets make that function. To not let you out of the blue on great scripting, i'll make this window and it's contents move up and down.
// you need a few extra globals,
var pixilmovement = 0;
var pixilgap = 3;
var direction = "up"
// this is needed to delay the code so the digits aren't sped up.
function Delay(milliseconds)
{
var start = GetTime();
while (start + milliseconds > GetTime()) {
}
}
function InfoWindow(text,x,y,w,h)
{
if (!IsKeyPressed(KEY_ENTER)) {
if (pixilmovement < pixilgap && direction == "up")
{
Delay(80);
++pixilmovement;
if (pixilmovement == pixilgap)
{
direction = "down";
}
return windowstuff(text,x,y-pixilmovement,w,h);
}
if (pixilmovement <= pixilgap && direction == "down")
{
Delay(80);
--pixilmovement;
if (pixilmovement == 0)
{
direction = "up";
}
return windowstuff(text,x,y-pixilgap-pixilmovement,w,h);
}
}
}
function windowstuff(text,x,y,w,h) // to bundle up the return.
{
window.drawWindow(x,y,w,h);
font.drawText(x+6,y+6,text);
}
function TextMe(text,x,y,w,h) // used to call function with key enter attached
{
var screen = GrabImage(0,0,sw,sh);
while (!IsKeyPressed(KEY_ENTER))
{
screen.blit(0,0);
InfoWindow(text,x,y,w,h);
FlipScreen();
}
}
This will be displayed later on to draw battle messages! The delay is used to give time before the variable is plussed or minused again. Because your processor is too dang fast ;). The if's can't be elses though it can be expected. It is because the return will happen before anything is elsed and the process starts over again! That will suck if you only had a message box that went down or up the screen only! You can change pixilgap to be whatever you want, please keep in mind that the number I chose seemed reasonable enough to let the viewer easily follow the text without causing his/her eyes to follow the box.
The way this is used is to have a global variable change the position of a box either up or down by so many pixils (in this case the pixils are 4 saved as pixilgap to let you change it at will). If it goes up a certain amount of pixils (pixilgap), then it will start drawing it downward with a variable change (direction) until it has rested at 0 or the bottom. Then it goes upward. As you see, this is a circle story, this loop goes on forever unless broken by the player (by pressing enter).
I also made another function called windowstuff so anything that needs returning is bundled into 1. Because you can only return 1 thing.
Menus/Input
Every battle system needs some way into selection a player or an enemy to do battle with. There are many ways for selectors to be made, from the simpilist of rectangles to the more advanced moving boxes that fold into the selected target (yikes!). We are going to use a windowstyle with an opaque background area so you can see the enemy on the other side (thank god that sphere supports translucency :)).Selectors
The selectors that are used will need to fit aroundt he player or enemy area. There will need to be math that offsets these edges and variables that tell it where to go.
Enemy/player selecting
Lets start with the easier, players selector. All you need is to put a box or window around the current player. Current player sounds familiar doesn't it? I remember making a global variable called CurrentPlayer, strange huh. Well it's no coinicidence. Lets draw a window around the 'CurrentPlayer'!
// make a new global here:
var selector = LoadWindowStyle("selector.rws"); // or your window...
var attack = false;// start with players.
function drawPlayerSelector()
{
if (attack == false)
{
selector.drawWindow(6+CurrentPlayer*128,sh-64,122,58);
} // notice how CurrentPlayer was used...
}
This will make a selector around the current player. There is no way to move the box yet as 'CurrentPlayer' always equals 0 (for now). Thinking ahead, lets make a box for enemies. Because as you saw in the script there was a variable called attack that distinguishes which selector to use. In battle enemies or otherwise out of battle, players (attack == false).
function drawEnemySelector()
{
if (attack == true)
{
selector.drawWindow((sw/2-((BattleEnemies.length*64)/2))+CurrentEnemy*64,sh/2-64,64,64)
}
}
There that drawing method is similar to the enemy picture blitting. I just replaced the " *i+64 " with " +CurrentEnemy*64 ". Now we got selectors, lets add them to the game function!
function game()
{
addEnemy(0);
addEnemy(1);
addEnemy(2);
while (BattleEnemies.length > 0 && IsKeyPressed(KEY_ESCAPE) != true)
{
bg.blit(0,0);
drawPlayerWindow(3,sh-67,sw-6,64) // bottom of screen.
drawPlayerSelector(); // added in this
drawEnemies();
drawEnemySelector(); // added in this
FlipScreen() // to draw window and objects at it's coordinates.
}
}
By now that is what the whole game function should look. The enemy variable stops both selectors from blitting at the same time. Also I added in the IsKeyPressed(KEY_ESCAPE) != true, so you can press escape and exit the engine. Now lets move and use the selectors!
Handling Input
Well, there is a lot of stuff regarding the left and right arrow key's. Here is the code I made.
// another global...
var battlemenu = false;
function HandleInput()
{
if (attack == false)
{
if (IsKeyPressed(KEY_LEFT) == true && CurrentPlayer > 0)
{
Delay(250) // for finger reflexes
CurrentPlayer--;
}
if (IsKeyPressed(KEY_RIGHT) == true && CurrentPlayer < players.length-1)
{
Delay(250) // for finger reflexes
CurrentPlayer++;
}
if (IsKeyPressed(KEY_ENTER) == true)
{
battlemenu = true;
}
}
else
{
if (IsKeyPressed(KEY_LEFT) == true && CurrentEnemy > 0)
{
Delay(250) // for finger reflexes
CurrentEnemy--;
}
if (IsKeyPressed(KEY_RIGHT) == true && CurrentEnemy < BattleEnemies.length-1)
{
Delay(250) // for finger reflexes
CurrentEnemy++;
}
if (IsKeyPressed(KEY_ENTER) == true)
{
DoBattle(CurrentEnemy);
}
}
}
That is a long function! I added Enter for showing the menu on a player. I put enter as DoBattle() for the enemies. All we gotta do is make those two functions!
This is what the game function should look like.
function game()
{
addEnemy(0);
addEnemy(1);
addEnemy(2);
while (BattleEnemies.length > 0 && IsKeyPressed(KEY_ESCAPE) != true)
{
bg.blit(0,0);
drawPlayerWindow(3,sh-67,sw-6,64) // bottom of screen.
drawPlayerSelector();
drawEnemies();
drawEnemySelector();
if (battlemenu == true)
{
BattleMenu(); // new function to add
}
else
{
HandleInput();
}
FlipScreen(); // to draw window and objects at it's coordinates.
}
}
That is where the global takes place... If the battle menu is true, you can't go back switching players until a command is made. We need to make those commands and that new function to add.
Battle Menu
This is the Menu that you get if you pressed enter on the Player.
var options = new Array("Attack","Items","Spells","Flee","Back");
var CurrentOption = 0;
var Gray = CreateColor(175,175,175);
function BattleMenu()
{
window.drawWindow(sw-67,sh-67,62,64);
Rectangle(sw-67,sh-64+CurrentOption*12,62,12,Gray);
for (var i = 0; i < options.length; ++i)
{
font.drawText(sw-67,sh-67+i*12,options[i]);
}
if (IsKeyPressed(KEY_DOWN) == true && CurrentOption < options.length-1)
{
Delay(150); // Finger reflex time
CurrentOption++;
}
if (IsKeyPressed(KEY_UP) == true && CurrentOption > 0)
{
Delay(150); // Finger reflex time
CurrentOption--;
}
if (IsKeyPressed(KEY_ENTER) == true)
{
if (CurrentOption == 0)
{
Delay(150);
battlemenu = false;
attack = true;
}
if (CurrentOption == 1)
{
Delay(150);
battlemenu = false;
}
if (CurrentOption == 2)
{
Delay(150);
battlemenu = false;
}
if (CurrentOption == 3)
{
battlemenu = false;
var i = Math.floor(Math.random()*10);
if (i > 2)
{
Delay(250);
TextMe("You ran away safely!",3,7,sw-6,32);
Exit();
}
if (i < 2)
{
Delay(250);
TextMe("The Enemies have blocked your path!",3,7,sw-6,32);
enemyattack = true;
}
}
if (CurrentOption == 4)
{
Delay(150);
battlemenu = false;
}
}
}
This lengthy code does all the things that are present in the battle menu, except to array items and spells. Once your good enough you can tie in a spells array that does attacks much like you would by pressing attack. Only mana is involved. Well, run the script! You can do some stuff but you can't attack yet! Lets make it able for you to attack in the next section!
Output
Rounds and timing
There isn't going to be timing in this battle script because it, you go, they go. This repeats over and over again. So to get on the basic side lets start by attacking...
Player Attacks!
This is the attack that you do on enter. This converts the spriteset to an image array and displays the images across screen.
var CurrentImage = 0;
function DoBattle(enemy)
{
Delay(150);
AnimateAttack(LoadSpriteset("Attacks.rss"),enemy) // or your own spriteset.
BattleEnemies[enemy].hp -= players[CurrentPlayer].atk
}
function AnimateAttack(spriteset,enemy)
{
var screen = GrabImage(0,0,sw,sh);
var images = spriteset.images;
do
{
screen.blit(0,0);
images[CurrentImage].blit((sw/2-((BattleEnemies.length*64)/2))+CurrentEnemy*64,sh/2-64);
Delay(150);
CurrentImage++;
if (CurrentImage == 6)
{
attack = false;
CurrentImage = 0;
break;
}
FlipScreen();
}
while (CurrentImage >= 0)
}
It is quite lengthy, but at least it does it's job. Now all the enemies need to do is die! Let's modify the game function for that:
function game()
{
addEnemy(0);
addEnemy(1);
addEnemy(2);
while (BattleEnemies.length > 0 && IsKeyPressed(KEY_ESCAPE) != true)
{
bg.blit(0,0);
drawPlayerWindow(3,sh-67,sw-6,64) // bottom of screen.
drawPlayerSelector();
drawEnemies();
drawEnemySelector();
if (battlemenu == true)
{
BattleMenu();
}
else
{
HandleInput();
}
FlipScreen() // to draw window and objects at it's coordinates.
if (BattleEnemies[CurrentEnemy].hp <= 0)
{
BattleEnemies.splice(CurrentEnemy,1);
}
}
}
If you were copying me, that is howthe game function will look like. There is an if loop for those enemies who's health is or falls below 0. Well, now you can attack. Let's make them attack back!
Enemy Attacks!
So we made people attack, lets make them attack. I'm going to handle this in a random way ;)
// more globals.
var enemyattack = false;
function EnemyAttack()
{
var attackwho = Math.floor(Math.random()*10);
var whoattack = Math.floor(Math.random()*10);
if (whoattack < BattleEnemies.length && attackwho < players.length)
{
enemyattack = false;
Delay(250);
TextMe(BattleEnemies[whoattack].name + " has attacked " + players[attackwho].name + " for " + BattleEnemies[whoattack].atk + " points of damage.",3,6,sw-6,32);
players[attackwho].hp -= BattleEnemies[whoattack].atk;
Delay(250);
}
else
{
EnemyAttack();
}
}
There, that function will keep repeating until two special random numbers come up. The number for what enemy will attack and what person to attack. It will then show you the who and damage.
Here, I've modified the game function:
function game()
{
addEnemy(0);
addEnemy(1);
addEnemy(2);
while (IsKeyPressed(KEY_ESCAPE) != true)
{
bg.blit(0,0);
drawPlayerWindow(3,sh-67,sw-6,64) // bottom of screen.
drawPlayerSelector();
drawEnemies();
drawEnemySelector();
if (battlemenu == true)
{
BattleMenu();
}
if (enemyattack == true)
{
EnemyAttack();
}
if (battlemenu == false && enemyattack == false)
{
HandleInput();
}
FlipScreen() // to draw window and objects at it's coordinates.
for (var i = 0; i < BattleEnemies.length; i++)
{
if (BattleEnemies[CurrentEnemy].hp <= 0)
{
TextMe("The enemy has died!",3,6,sw-6,32);
BattleEnemies.splice(CurrentEnemy,1);
CurreneEnemy = 0;
Delay(200);
}
}
for (var i = 0; i < players.length;i++)
{
if (players[i].hp <= 0)
{
TextMe("A player has died.",3,6,sw-6,32);
players.splice(CurrentPlayer,1);
}
}
if (players.length == 0)
{
TextMe("Your party has perished!");
Exit();
}
if (BattleEnemies.length == 0)
{
TextMe("You have beat the battle!",3,6,sw-6,32);
Exit();
}
}
}
Well, thats it, the full battle system.
Battle Finishing
Well that's it, When I modified that last game function I added player losing and winning. It only quits the engine, but at least it works. And it was easy to do.
Download the Official Feek_EBS Script
It is here at this external link. Download Feek_EBS (Spherical mirror)
What's Next
Followed all that? Want to take it to the next level? Move forward to getting items to work, with Feeks EBS item implementing guide.





