- Home /
Turn Based Battle System
I've been trying to set up a battle system using Unity but I've hit a bit of a snag: Player Turn script
static var Playerturn: boolean = true;
function Awake(){
Select();
}
function Select(){ print("Make your move!!"); yield WaitForSeconds(2); Battle_Menu.choice = true; print("Battle Phase Begins!!"); yield WaitForSeconds(2); //BattlePhase(); }
static function BattlePhase(){ //RPGPlayer.OnAttack(); RPGEnemy.OnDamage(); yield WaitForSeconds(2); print("Battle Phase Ends!!"); TurnEnd(); }
static function TurnEnd(){ print("Turn end!"); if(Playerturn == true){ Playerturn = false; Enemy_Turn.Select(); } if(Playerturn == false){ Playerturn = true; } }
function GameOver(){ //Play audio oneshot //Destroy player //WaitforSeconds(); //Load level GameOver }
function Victory(){ //Play audio oneshot //Destroy enemy //WaitforSeconds(); //Load level Victory }
Menu Script:
static var choice: boolean = false;
function OnGUI () { if (choice == true){ // Make a background box GUI.Box (Rect (10,10,100,90), "Battle Menu");
// Make the first button. If it is pressed, Application.Loadlevel (1) will be executed
if (GUI.Button (Rect (20,40,80,20), "Attack")) {
//Application.LoadLevel (1);
RPGPlayer.OnAttack();
print("The Player Attacks!!");
Run();
choice = false;
}
// Make the second button.
if (GUI.Button (Rect (20,70,80,20), "Defend")) {
//Application.LoadLevel (2);
RPGPlayer.OnDefense();
print("The Player blocked!!");
Run();
choice = false;
}
}
if (choice == false){
return;
}
}
function Run(){ Player_Turn.BattlePhase();
}
Basically, I tried to run it to where the player turns begins and displays an attack or defend button using OnGUI and it sends makes the Player Turn function Battle phase run. However, the code stops progressing once the player chooses attack or defend and it stays put.
Does anyone have a solution?
Edit: Thanks to all the gave an answer. This will greatly benefit the Unity community!
Answer by Statement · Dec 06, 2010 at 11:51 PM
This is a sort of rewrite entirely.
Mainly because I got confused reading the original code, I am not accustomed to JS but the bounty made me want to have a stab at it.
The heart of it is the Start function, which allows each player ("human" and "enemy") to take turns. There is no actual damage implementation, and no real "players" at all, just a string representation. You'd want to plug in your actual objects in the code. This is probably more of a scaffolding. If you set battle = false, the current round ends (a round is a turn each).
var action : String; var player : String; var battle = true; var displayGui = false;
function Start () { while (battle) { yield PlayerChoice(); yield BattlePhase(); yield TurnEnd();
yield EnemyChoice();
yield BattlePhase();
yield TurnEnd();
}
}
function PlayerChoice() { print("human makes a decision"); player = "human"; displayGui = true;
while (displayGui)
yield;
}
function BattlePhase() { print("battle rages on"); yield new WaitForSeconds(1); print(player + " " + action); yield new WaitForSeconds(1); }
function EnemyChoice() { print("enemy makes a decision"); yield new WaitForSeconds(1); player = "enemy"; action = "attacks"; }
function TurnEnd() { print("turn ends"); yield new WaitForSeconds(1); }
function OnGUI() { if (!displayGui) return;
GUI.Box (Rect (10,10,100,90), "Battle Menu");
if (GUI.Button (Rect (20,40,80,20), "Attack"))
{
action = "attacks";
displayGui = false;
}
if (GUI.Button (Rect (20,70,80,20), "Defend"))
{
action = "defends";
displayGui = false;
}
}
Nice answer. This one is done in Javascript so I can understand it better. It's easily understandable too, so I can grasp it mostly.
I have been looking for the answer to how to make a turn based system in Unity for a couple weeks... This topic was VERY useful to me, and helped me with some school work drastically.
Answer by Peter G · Dec 07, 2010 at 12:28 AM
I would use a more state based driven code with events to set up your turn based system, now you seem to be trying to do that, but you are waiting an arbitrary length (2 seconds) before you move onto the next screen. Here's an example of how to have one turn trigger the next. It has 2 built-in events. Each one fires when its respective turn ends. It's written in C# because js has a strange if existent ways of doing events and I don't like how it writes Coroutines (C# is more explicit in the latter).
What you still have to add is the instruction for finding an object, and responding to the events. I haven't fully tested this code either. It might be missing a yield or 2.
So here it is full of comments.
using UnityEngine; using System.Collections;
public delegate void TurnEnded(TurnInfo tI);
public class StateManager : MonoBehaviour {
public event TurnEnded enemyTurnEnded;
public event TurnEnded playerTurnEnded;
// Use this for initialization
void Start () {
StartCoroutine(UpdateState());
//Immediately start our loop
}
// Update is called once per frame
IEnumerator UpdateState () {
for(;;) {
//This is short hand for infinity loop. Same as while(true).
yield return StartCoroutine(PlayerTurn());
//Start our player loop, and wait for it to finish
//Before we continue.
yield return StartCoroutine(EnemyTurn());
//Do enemy loop, finish, restart loop
}
}
IEnumerator PlayerTurn() {
Transform target = null;
//This could be placed in a higher scope for memory purposes.
bool objectSelected = false;
//have we selected an object.
TurnInfo tI = new TurnInfo();
//create a new turn info tracker.
yield return StartCoroutine(SelectObject(target));
//Wait until we find a target before continuing.
if(target != null)
yield return StartCoroutine(Attack(target));
//Wait until we find a target before continuing.
Debug.Log("Attacked");
if(playerTurnEnded != null)
playerTurnEnded(tI);
}
IEnumerator EnemyTurn() {
Transform target = null;
//This could be placed in a higher scope for memory purposes.
bool objectSelected = false;
//have we selected an object.
TurnInfo tI = new TurnInfo();
//create a new turn info tracker.
yield return StartCoroutine(SelectObject(target));
//Wait until we find a target before continuing.
if(target != null)
yield return StartCoroutine(Attack(target));
//Wait until we find a target before continuing.
Debug.Log("Attacked1");
if(enemyTurnEnded != null)
playerTurnEnded(tI);
}
IEnumerator SelectObject (Transform target) {
bool objectSelected = false;
while (!objectSelected) {
target = SomeMethodForFindingATarget();
if(target != null) {
objectSelected = true;
Debug.Log("object selected");
}
yield return null;
}
}
Transform SomeMethodForFindingATarget() {
return null;
//return a transform
}
IEnumerator Attack (Transform target) {
bool attacked = false;
while(!attacked) {
//Attack the targeted object.
target.SendMessage("Attacked", SendMessageOptions.DontRequireReceiver);
//the last value should always be true.
//If the attack was successful and the turn will end.
attacked = true;
yield return null;
}
}
}
public struct TurnInfo { //custom Turn info struct.
//Feel free add or remove any fields.
private float m_DamageDone;
private string m_MoveUsed;
private float m_HealthLeft;
private bool m_TurnOver;
public float DamageDone
{
get {
return m_DamageDone;
}
set {
m_DamageDone = value;
}
}
//How much damage did we do.
public string MoveUsed
{
get {
return m_MoveUsed;
}
set {
m_MoveUsed = value;
}
}
//What move did we use.
public float HealthLeft
{
get {
return m_HealthLeft;
}
set {
m_HealthLeft = value;
}
}
//How much health do we have left.
public bool TurnOver
{
get {
return m_TurnOver;
//read only
}
}
//Should the turn end?
//Constructor.
public TurnInfo(float damage, string moveUsed, float healthLeft, bool turnOver) {
m_DamageDone = damage;
m_MoveUsed = moveUsed;
m_HealthLeft = healthLeft;
m_TurnOver = turnOver;
}
}
I revised my code to be more state driven. Should work generally better now. You can find the old version in the edited page if you need it though.
well done on answer, and quite rare seeing for(;;) being used aswell, first time I have seen it outside of my own code or a tutorial
Could you please tell why you created the UpdateState ins$$anonymous$$d of using the regular Update?
It has to do with the fact that TBS games are essentially event driven. Update() is called every frame, and while Update in itself is an event, it is difficult to create an event based system from that without using excessive conditional statements. So, UpdateState works by creating what is basically event driven. UpdateState first calls the PlayerTurn() method, then waits until that finishes (@ the end of the player's turn) then calls the EnemyTurn() method. After that it restarts. In order to do that in Update() you would have to have have several bools that switch as the turn (cont.)
(cont.) progresses. The other nice thing this set up does is it fires events when each turn ends. You could do that in Update(), but I do think it is worth mentioning that you can have other objects listen for these events to do other implementation.
I know I'm gravedigging the hell out of this, I hope you can still answer a quick question:
The only part of this sample code that confuses me is the statement at the end of the turns:
if(playerTurnEnded != null)
playerTurnEnded(tI);
and
if(enemyTurnEnded != null)
playerTurnEnded(tI);
$$anonymous$$aybe I just don't understand events in C#, but what does calling these accomplish? The events dont seem to be used anywhere but those 4 lines.
Also, why does the event take the TurnInfo as an argument? Where do you specify what it does with that?
Answer by Proclyon · Dec 07, 2010 at 02:07 PM
Well here's some advice , not really much of an answer.
I really can not figure out why there is no state controller being used for a turn based system. Turn based is all about states. When is who doing what.
Make a control flow for your game.
Somewhat like magic the gathering: (Somewhat similar , this is not exactly the same)
[Draw]
[Upkeep]
[Pre-Attack Main Phase] (spells and creatures lands > etc)
[Attack Phase] --> Declare attackkers, defenders instant spells etc Interaction with enemy
[Post-Attack Main Phase] (more spells creatures lands etc)
[Discard/CleanUp]
Use an enum a bunch of bools any container to keep track of what object is in what state.
If you have a master controller where there is per player per turn instead of all players in one turn (the 2 most common turn based systems) you need to focus on keeping track of pausing when interacting between two objects, without the master controller you need to force control the order in which the events can take place on it self instead rather than also work on global control flow.
Static functions are great sure, untill you acces it when you shouldn't be accessing it. Anybody can call end turn at any time? That takes a lot of accuracy in the control flow, not to mention
Playing sounds and performing attacks should only be called and automatically be called when the player is performing an action called "Attacking" or otherwise named. My point being that when you have a state called "Attack Phase" you can slide permission for the static attack function call to be set to true, for THIS object even would be an even better specification, as globally accessible functions do not differentiate between anything unless caught. Hence my conclusion to use Player as an object and modify it when a master controller decides it gets to do anything at all, all actions the player can perform should be methods inside the player, all public, all encapsulated except the user input requests marked as public , the rest is not accesible and just does the real work. (Public private , if you just went WTF , you can mimic it here if you want )
If you want to skip keeping track of states, and ignore the permissions set to who can call what when in a turn based game, I will simply tell you , test , test , test again, pray it works.
Answer by · Oct 22, 2010 at 02:29 AM
TurnEnd() will always leave Playerturn as 'true', which will no doubt cause problems along the way.
static function TurnEnd(){
print("Turn end!");
if(Playerturn == true){ // Playerturn is initially true, so this is true
Playerturn = false; // Playerturn is now false
Enemy_Turn.Select();
}
if(Playerturn == false){ // Playerturn was just made false
Playerturn = true; // Playerturn is set back to true
}
}
You could rewrite it like this:
static function TurnEnd(){
print("Turn end!");
if(Playerturn == true){ // if it was the playerturn before TurnEnd()
Enemy_Turn.Select(); // run Enemy_Turn.Select()
}
Playerturn = !Playerturn; // Playerturn boolean is flipped
}
It might not solve all your problems, but it'll be a start. Let me know how it goes!
No visible changes. The problem is most likely before that part of the code initializes. Still, it'll save me many problems in the future since I set myself in an endless loop like that.
I totally forgot to investigate over the weekend, apologies! I'll have another think today, but hopefully someone else will be able to spot the problem.
Answer by bjarnefisker · Dec 06, 2010 at 10:39 PM
I'm pretty sure that you can't use yield in a static function. Try to comment out the yield command in the BattlePhase function and i think your good to go.
static function BattlePhase(){
//RPGPlayer.OnAttack();
RPGEnemy.OnDamage();
//yield WaitForSeconds(2);
print("Battle Phase Ends!!");
TurnEnd();
}
Also i agree with Marowi that you might want to look through your if statements and when you are setting the Playerturn. But fixing this bug would make it a lot easier to debug :)
Let me know how it works.