I'm stuck on trying to create a Turn based RPG battle system

Hey guys!

I am currently working on a turn based RPG battle system, but I’m stuck and not sure where to work on next.

Here is what I have done so far:

  • NPC initiates battle when touching another player
  • Setting the battle stage for the player and the NPC
  • Did some progress on the camera.

image
image
image
image

Now this is the bit where I’m stuck on, how do I actually turn it into a turn based battle?

When the NPC touches the player, it fires some remote events:
image

It leads to a script which positions the player and the enemy into battle


image

I was originally thinking to add a while true do loop into the script but then I thought about how was I going to fire the enemy/player attacks? I’m just really confused and if someone can help me that would be much appreciated.

3 Likes

I think a finite state machine would work really well for a turn-based battle. You could create whichever states you need to define the battle and then run logic whenever you enter/exit a state. I drew up a quick example of how I’d probably design it.

When the battle starts, it goes into Start Battle. That’s where I’d probably set up and initialize the battle. From there, it’ll go into the Select Next Attackers state to see who should be attacking. Afterwards, it’ll go around to choosing attacks, performing attacks, and determining a winner. If no winner has been decided yet, it loops back around and starts at Select Next Attackers again. If a winner has been decided, it’ll end the battle.

I also wrote up a simple implementation. I haven’t tested it, but I wanted to show an example of how I’d code it.

local StartBattleState = {}
function StartBattleState.OnEnter(Battle)
    -- Setup the camera and players in their correct positions, like how you already do in the handler
    -- for BindableEvents.StartTheBattle

    -- Maybe select who will go first
    -- Battle.CurrentAttacker = Participants[1]

    Battle:GotoState("SelectNextAttackers")
end

local SelectNextAttackersState = {}
function SelectNextAttackersState.OnEnter(Battle)
    -- Basically just cycling the list here but you can decide how you want to choose the next player
    local CurrentIndex = table.find(Battle.Participants, Battle.CurrentAttacker)
    if CurrentIndex == -1 then
        Battle.CurrentAttacker = Participants[1]
    else
        local NextIndex = (CurrentIndex % #Battle.Participants) + 1
        Battle.CurrentAttacker = Battle.Participants[NextIndex]
    end

    Battle:GotoState("ChooseAttacks")
end

local ChooseAttacksState = {}
function ChooseAttacksState.OnEnter(Battle)
    local IsNpc = false -- Somehow determine if the current attacker is an NPC or a player
    if isNPC then
        -- If the current attacker is an NPC, immediately choose which attacks the NPC should use
        local NpcAttack = NPCSystem:DetermineAttacks(Battle.CurrentAttacker)
        Battle:GotoState("PerformAttacks", NpcAttack)
    else
        -- If the current attacker is a player, maybe bring up UI here or something that lets the player choose
        -- the attack they want, and listen for events that fire when they choose
        UISystem:ShowAttackOptions(Battle.CurrentAttacker)
        UISystem.AttackSelected:Once(function(Attack)
            Battle:GotoState("PerformAttacks", Attack)
        end)
    end
end

local PerformAttacksState = {}
function PerformAttacksState.OnEnter(Battle, Attack)
    -- Maybe show animations? Show UI health changing? Anything that actually does the attack
end

local DetermineWinnerState = {}
function DetermineWinnerState.OnEnter(Battle)
    local Winner = nil -- Determine if someone has lost/won after the attack
    if Winner ~= nil then
        Battle:GotoState("EndBattle", Winner)
    else
        Battle:GotoState("SelectNextAttackers")
    end
end

local EndBattleState = {}
function EndBattleState.OnEnter(Battle, Winner)
    -- Move the camera and players/npcs back to original position
    -- Give rewards to any of the winners, etc.
end

local TurnBasedBattle = {}
TurnBasedBattle.__index = TurnBasedBattle

function TurnBasedBattle:GotoState(StateName, ...)
    if self.States[StateName] == nil then
        warn(`Unable to find state '{StateName}'`)
        return
    end

    if self.CurrentState ~= nil then
        -- Make sure the function exists
        if typeof(self.CurrentState.OnExit) == "function" then
            self.CurrentState.OnExit(self)
        end
    end

    self.CurrentState = self.States[StateName]

    -- Make sure the function exists
    if typeof(self.CurrentState.OnEnter) == "function" then
        -- Deferring this call so we don't get caught in a potential endless cycle 
        -- of GotoState -> State.OnEnter -> GotoState -> State.OnEnter
        task.defer(function()
            self.CurrentState.OnEnter(self, ...)
        end)
    end
end

local function NewBattle(Players, Enemies)
    -- Moving the players and enemies into a single participants array
    local Participants = table.clone(Players)
    table.move(Enemies, 1, #Enemies, #Participants + 1, Participants)

    return setmetatable({
        Participants = Participants,
        CurrentAttacker = Participants[1] -- Could even decide this in the StartBattle state

        CurrentState = nil
        States = {
            StartBattle = StartBattleState,
            SelectNextAttackers = SelectNextAttackersState,
            ChooseAttacks = ChooseAttacksState,
            PerformAttacks = PerformAttacksState,
            DetermineWinner = DetermineWinnerState,
            EndBattle = EndBattleState
        }
    }, TurnBasedBattle)
end

local Battle = NewBattle(Players, Enemies)
Battle:GotoState("StartBattle")

Obviously, you’d need to consider whether this works well within your current game’s architecture/framework, but hopefully it helps and gives you some ideas on how to implement it. I think finite state machines work really well in these types of situations.

1 Like

Wow! This is a lot to take in. Thank you for making this script! I’ll see if I can try add this into my game and then Ill report back to you if I got any questions or if I am satisfied with the final product.

1 Like

Don’t just add the script to your game, read about the concept and write your own implementation. In the long run it’ll be a lot better, as it allows you to learn the concept and add modifications easier.

2 Likes

Don’t worry, I’m not just adding the whole entire script into my game, I’m looking over it first.

1 Like