• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

GML Turn Based Combat

B

bobula13

Guest
When I first started programming with GameMaker, one of the first things I wanted to work on was an old school, NES Dragon Warrior/Quest RPG. I have fond memories of wasting my youth playing these games and thought it would be great to make my own. One of the more frustrating things when trying to learn how to create a game like this was trying to find tutorials. Most of the time they were for turn based strategy games and for whatever reason I wasn't able to apply it to what I was trying to achieve. When you look through the forums here, most of the time you get the response of "learn it on your own".

Lately I've been working on a basic combat engine for these old school type of games and I thought that it would be neat to share it with the GM community. As a forewarning, I know that this is far from perfect, and I am not the best programmer. But in putting this out there I thought it would be great to see what ways anyone could improve on this.

I don't have any screen shots for this because I literally have two squares in a room. So please use your imagination and pretend that I am an amazing artist and there are two fearless combatants fighting to the death.

First off, I created three objects: a player, an enemy and a battle controller. Both the player and enemy have no code in them for now and are just placed in the room. Since I have not added any kind of attributes/statistics yet for the ACTUAL combat, they're just bare bones figures.

The look of this is to have the opponents choose their attack, move forward toward their opponent, complete their attack and move back. Then the turn goes back to the other opponent.

Here are the variables in the create event of the battle controller:
Code:
//these determine which opponent is attacking.  The player is set to true for testing purposes before I implement the initiative system

global.player_turn = true;
global.enemy_turn = false;

//enum for player state machine
enum player_combat {
    ready,
    attack,
    magic, //not used yet
    run //not used yet
}

//enum for enemy state machine
enum enemy_combat {
    ready,
    attack,
    magic, //not used yet
    run //not used yet
}

//the distance the player and enemy will move forward and back once they complete their attack
player_x = 30;
enemy_x = 30;

//beginning enemy and player state
p_state = player_combat.ready;
e_state = player_combat.ready;

//counter used for enemy to make their turn
e_counter = 0;
e_count_begin = false;
Next in the Step event are the state machines for combat:
Code:
switch (p_state){
    case player_combat.ready:
//determines if it is the player's turn and can move forward with combat
        if(global.player_turn == true) && (global.enemy_turn == false){
            p_state = player_combat.attack;   
        }
    break;
    case player_combat.attack:
//once the player is in the attack state they move forward signifying they attacked
//then alarm 0
        if(keyboard_check_pressed(vk_enter)){
            obj_player.x += player_x;
            global.player_turn = false;
            alarm[0] = 60;
        }
    break;
}

//the same applies to the enemy
switch (e_state){
    case enemy_combat.ready:
        if(global.enemy_turn == true) && (global.player_turn == false){
            e_state = enemy_combat.attack;   
        }
    break;
    case enemy_combat.attack:
        if(e_count_begin == true){
            e_counter++
        }
        if(e_counter >= 60){
            e_count_begin = false;
            e_counter = 0;
            obj_enemy.x -= enemy_x;
            global.enemy_turn = false;
            alarm[1] = 60;
        }
    break;
}
And here is alarm 0 and 1:

Code:
//ALARM 0
//once the alarm completes the player returns to their original x origin and then the player's turn ends and the opponent's begins.

obj_player.x -= player_x;
p_state = player_combat.ready;
global.enemy_turn = true;
e_count_begin = true;


//ALARM 1
//same as above
obj_enemy.x += enemy_x;
e_state = enemy_combat.ready;

global.player_turn = true;
Like I mentioned before, I am not the best programmer and a lot of this may seem real sloppy, but it works to get the general feel or look down. Right now I am working on actually implementing the combat with attributes, deciding initiative, hit or miss attacks, damage and if you can run away or not.

Tell me what you think, if this is something that interests you at all, and if you have any ideas on how to improve the very, VERY, basics of this system so far.
 
E

Ephemeral

Guest
I am also involved in designing my own turn-based combat, currently, so this is interesting.

I think you've got a neat little proof-of-concept there, but what little experience I have myself is telling me that you're setting yourself up for monstrous amounts of pain later on, in terms of how you've structured everything.

Specifically, if you ever find yourself using two separate variables to answer the same question, you're probably coming at the problem from the wrong direction, and you're using two entire state machines that way...

I would suggest beginning with a simple, flexible, three-object system:
hndl_stats to keep track of all the stats and other numbers associated with each character (permanent object)
hndl_battle to handle the single state machine that controls a battle (you'd begin a battle by creating an instance of this and manually setting just one variable, either a numerical battle_id or by passing it a fully nested array battle_data.)
obj_actor to be spawned in and moved around (just a puppet to show sprites to the player, to save yourself some draw coding)

Conceptually, each phase, turn, and action of the battle, would be the hndl_battle state machine iterating through an array. For example, you'd have:

"SETUP" state, which only runs once and is basically its own mini create event so you don't have to make a separate object for each individual battle, you can just spawn the generic hndl_battle and have it enact any battle at all by feeding it data. This is where you sort and loop through the party and the enemies and spawn an obj_actor for each.

"INPUT" state, where the player inputs commands, iterating through each player character and enemy character in turn, and adding the commands to an array, or a data-structure if you want to get fancy and have the order of commands different than the order of actions. (This may be divided into a MENU state and a CHOOSE TARGET state, if you feel the need)

"TURN BEGIN" state where the next entry in the command array is unpacked, and the relevant actor object moved to its target.

"TURN CLASH" state triggered by actor reaching the correct position etc., does the attack or whatever animation, and calculates damage, status, etc. This is where you'd spawn your floating damage numbers if you were going to do that.

"TURN RESOLVE" state where the actor is moved back to its spawn position, and or plays death animations etc, then either increment the command array index and go back to Turn Begin, or if everyone's had their turn, go back to Input. Or check for victory / loss conditions, and go to End.

"END" state where you either show the xp gain and loot, or game over, or whatever (may be divided into multiple states if you want to do something fancy or dynamic)

"DEATH" state, referring to the handler object's death, fading out, room transition, whatever, and/or the instance destroying itself.

You could add more states to do more complicated things, like a transition state between the end of a round and going back to the Input state, or a generic Wait state to pause the battle while scripted events like dialogue, special animations, or whatever, plays out.

You can just leave the Setup state blank and hard-code the battle in the Create Event for testing purposes, or even spawn a blank hndl_battle and set its battle variables manually in the code where you call the instance create function, so you can get the mechanics working before you start worrying about content.

But since you have to import the party's status anyway, you might as well do it in the Setup state so you can import everything so your turn-based encounter system for your whole game can run on just these three objects and you only have to do the hard parts once.
 
Last edited by a moderator:

GMWolf

aka fel666
When dealing with turned based systems, I love using Actions!
I made a couple tutorials:

The second one, stacked actions, is especially good with complex turned based System, where the game loop is not always fixed. (Different actions can affect the loop, like stunning enemies, etc)
 
B

bobula13

Guest
I am also involved in designing my own turn-based combat, currently, so this is interesting.

I think you've got a neat little proof-of-concept there, but what little experience I have myself is telling me that you're setting yourself up for monstrous amounts of pain later on, in terms of how you've structured everything.

Specifically, if you ever find yourself using two separate variables to answer the same question, you're probably coming at the problem from the wrong direction, and you're using two entire state machines that way...

I would suggest beginning with a simple, flexible, three-object system:
hndl_stats to keep track of all the stats and other numbers associated with each character (permanent object)
hndl_battle to handle the single state machine that controls a battle (you'd begin a battle by creating an instance of this and manually setting just one variable, either a numerical battle_id or by passing it a fully nested array battle_data.)
obj_actor to be spawned in and moved around (just a puppet to show sprites to the player, to save yourself some draw coding)

Conceptually, each phase, turn, and action of the battle, would be the hndl_battle state machine iterating through an array. For example, you'd have:

"SETUP" state, which only runs once and is basically its own mini create event so you don't have to make a separate object for each individual battle, you can just spawn the generic hndl_battle and have it enact any battle at all by feeding it data. This is where you sort and loop through the party and the enemies and spawn an obj_actor for each.

"INPUT" state, where the player inputs commands, iterating through each player character and enemy character in turn, and adding the commands to an array, or a data-structure if you want to get fancy and have the order of commands different than the order of actions. (This may be divided into a MENU state and a CHOOSE TARGET state, if you feel the need)

"TURN BEGIN" state where the next entry in the command array is unpacked, and the relevant actor object moved to its target.

"TURN CLASH" state triggered by actor reaching the correct position etc., does the attack or whatever animation, and calculates damage, status, etc. This is where you'd spawn your floating damage numbers if you were going to do that.

"TURN RESOLVE" state where the actor is moved back to its spawn position, and or plays death animations etc, then either increment the command array index and go back to Turn Begin, or if everyone's had their turn, go back to Input. Or check for victory / loss conditions, and go to End.

"END" state where you either show the xp gain and loot, or game over, or whatever (may be divided into multiple states if you want to do something fancy or dynamic)

"DEATH" state, referring to the handler object's death, fading out, room transition, whatever, and/or the instance destroying itself.

You could add more states to do more complicated things, like a transition state between the end of a round and going back to the Input state, or a generic Wait state to pause the battle while scripted events like dialogue, special animations, or whatever, plays out.

You can just leave the Setup state blank and hard-code the battle in the Create Event for testing purposes, or even spawn a blank hndl_battle and set its battle variables manually in the code where you call the instance create function, so you can get the mechanics working before you start worrying about content.

But since you have to import the party's status anyway, you might as well do it in the Setup state so you can import everything so your turn-based encounter system for your whole game can run on just these three objects and you only have to do the hard parts once.
I absolutely agree with you on this. You put it best by saying this is more of a proof of concept. I've been taking my time starting from the ground up on this, and the presentation of it felt like it should be taken care of first for me.

I've been creating a 'concept sheet', for lack of a better word, on how and when I want to incorporate certain elements. Right now I've been working on the initiative phase, simulating very old school rpg style where you just see who rolls the highest. I set certain attributes that add or take away an advantage to that roll.

Almost all the ideas you suggested I plan on incorporating, I'm just taking my sweet time doing it. I totally agree with having an object that specifically handles player stats, and one that handles the battle. My concept right now for enemy stats is just having a parent enemy object with all the base stats, and then just change them for each enemy accordingly.

I am always on the lookout for trying to better simplify my programming, since I am a hobbyist at best. I definitely want to look into the tutorials that Fel666 set up.

I have to give it a rest for a few days though. I have epilepsy and I had a real bad seizure earlier this morning and I'm completely out of it, so I need to give things a rest for a few days.

I'll post some more info on progress once I start feeling better.

Mainly I wanted to thank you guys for your input, it truly helps me out a lot and sets me in a better direction.
 
W

Wunjo

Guest
Im also having a dabble in a turned based engine, and I can tell you your code is far from being sloppy.
 

GMWolf

aka fel666
I am going to point out that you have questionable state represenation
like have two boolean player_turn and enemy_turn variables. Will they ever both be false? No, yet your varaibles can be.
Its probably better to have a single "Turn" varaible that holds the value of whos turn it it.
like "turn = turn.player" or "turn = turn.enemy"

That way your data is always intrinsically in a valid state and you cut down on possible headaches down the road.
 
B

bobula13

Guest
I am going to point out that you have questionable state represenation
like have two boolean player_turn and enemy_turn variables. Will they ever both be false? No, yet your varaibles can be.
Its probably better to have a single "Turn" varaible that holds the value of whos turn it it.
like "turn = turn.player" or "turn = turn.enemy"

That way your data is always intrinsically in a valid state and you cut down on possible headaches down the road.
Very good point! I went in with the thinking of pen and paper rpg, you know you say out loud that "player" has won initiative and "goblin thingy" lost it, so that's the main reason I put in two variables. Looking back at what I wrote I definitely agree with you on having just one global variable for the initiative.

I was going to post some more stuff based on how I streamlined the initiative process that I have worked out, but after reading this and looking back at what I've written, I'm going to implement just the one variable for determining who goes first.
 
B

bobula13

Guest
I worked on it some more and I got a partial solution. Originally I was working with a combat object that was going to decide on certain elements, but for now I'm just keeping everything in the player and enemy objects:

Here's the create event:
Code:
enum player_combat {
    init, // initiative phase is now  started by the player
    ready,
    attack,
    magic,
    run,
    hit
}

global.turn = 0; // global turn variable
player_x = 30;
p_state = player_combat.ready;
p_turn = 0;//player turn
e_turn = 0.// enemy turn
d_20_roll = irandom_range(1, 20);//20 sided dice
Step event:
Code:
switch (p_state){
    case player_combat.init:
        if(d_20_roll >= obj_enemy_attack.e_init) { // if the 'dice' roll is higher than the enemies player then he gets to go first
            global.turn = p_turn;
            p_state = player_combat.ready;
        } else {
            global.turn = e_turn;
            global.e_count_begin = true;
            obj_enemy_attack.e_state = enemy_combat.ready;
        }
    break;
    case player_combat.ready:
        if(global.turn == p_turn){
            p_state = player_combat.attack;   
        } else {
            p_state = player_combat.ready;
        }
    break;
    case player_combat.attack:
        if(keyboard_check_pressed(vk_enter) && global.turn == p_turn){
            x += player_x;
            alarm[0] = 60; // the alarm just resets the player's position and sets the global.turn to e_turn
        }
    break;
}
enemy create:

Code:
enum enemy_combat {
    ready,
    attack,
    magic,
    run
}

enemy_x = 30;
e_state = player_combat.ready;
e_counter = 0;
global.e_count_begin = false;
e_init = irandom_range(1, 20);// the enemy 20 sided roll
enemy step event:
Code:
switch (e_state){
    case enemy_combat.ready:
        if(global.turn == obj_player_attack.e_turn){
            e_state = enemy_combat.attack;   
        }
    break;
    case enemy_combat.attack:
        if(global.e_count_begin == true){
            e_counter++
            if(e_counter >= 60){
                e_counter = 0;
                x -= enemy_x;
                alarm[0] = 60; // same as player alarm, resets position and switches turn
            }
        }
    break;
}
Down the road I know I want to have a battle controller object and simplify it with a bunch of scripts. Plus once I start implementing some menus it will make things a lot easier, IMO.
Anything you guys see that would make it more streamlined or at least pretty?

Thanks,
 
B

bobula13

Guest
I've been sick for a long while now, so I haven't been working on any of my gamemaker projects. Now that I'm doing better I decided to come back to this project.
I found on youtube a great script for simulating dice rolls (I can't remember where I got it from) and have been using it to simplify my initiative process in combat.

the script is as follows:
Code:
//scr_dice_roll
/// @param dice

randomize();
var number;
number = irandom_range(1, argument0);
return number;
So basically you call up the script, then simply put in the dice value you would have it roll. It works great!

The approach I took to designing the game, other than inspiration from old turn based rpg's on nes, was old school pen and paper rpg's. The main thing I implemented were attributes/statistics. On paper I have been writing out all the attributes I want to use, and some basic math on how they would be implemented in combat, magic use, retreating and initiative.
The way I have it set up for now is that both the enemy 'roll' a 20 sided dice using the dice roll script above. Whoever rolls highest goes first. But that is too simple and really takes away any advantage a player might have for leveling throughout the game. Two of the attributes I use are dexterity(dex) and speed (spd). I add these two variables together along with the dice_roll script and whoever has the largest amount goes first.

But there's more!

To try and add an even more bit of flavor to this initiative system, I have not just the attributes added together, but also randomized between there value and 1. To me, this simulates the stresses of combat and even chance, and adds a bit of uncertainty to the whole process. For example, you run into a lowly level one slime and you are a beefy level four with higher stats, but somehow with just a spot of luck, chance and what have ya', the slime manages to get the first strike in.

Here is the script I made for initiative:

Code:
///scr_initiative;
/// @param p_spd
/// @param p_dex
/// @param e_spd
/// @param e_dex

randomize();

var p_roll = irandom_range(1, argument0) + irandom_range(1, argument1);
var e_roll = irandom_range(1, argument2) + irandom_range(1, argument3);

if(p_roll + scr_dice_roll(20) > e_roll + scr_dice_roll(20)){
    obj_battle_controller.turn_state = turn.player //_state = turn_order.player;*/
} else {
    if(p_roll + scr_dice_roll(20) < e_roll + scr_dice_roll(20)){
        obj_battle_controller.turn_state = turn.enemy
    }
}
For this, and actually all combat, I am using only three objects, the battle controller that...controls the battle, the enemy that is attacking and a statistic object for the player.
Just to save some space, the battle controller just has a switch statement that starts out with an initiative state, then player and enemy turn states, all just for testing for now.
In the arguments I just add obj_stats.spd, obj_stats.dex, obj_enemy_par.spd, obj_enemy.dex.

The only thing I was worried about is what Fel666 pointed out, which is what happens when the 'roll' in the initiative script are equal to each other. When testing it I noticed that it just randomly picks which character will go first. Sometimes it was the player, other times the enemy. For me this works just fine.

I know these are long, LONG posts, but I figured I might as well verbally vomit all I have been doing so that someone else might be able to use it. So please, if anyone has any advice or endless amounts of praise I would be more than happy to hear it.

Next I'm going to try and tackle basic attacks. Hopefully it won't be all that painful.
 
Top