Battle System Theory - I refuse to give up!

I guess I prefix with a bit of background. I’m a c# programmer (event driven business applications). I do a bit of art(3d modeling,animation etc) and music as well. I thought to myself “oh go learn how to do game programming.” I’m going crazy though.(I wake up in the middle of the night with what I think could be the answer :() I’ve created and destroyed so many prototypes and finally decided to post here for tips and advice on how to go about what I want to do.

I’m making(Well want to make) a simple RPG in the style of NES rpg games of old. The hitch is this turn based battle system I want to make. I want to make the code modular and reusable as possible. I seem to get stuck trying to figure out how to model an event driven scenario in a real-time engine.

My idea is

BattleManager Class - Component of the BattleField GameObject.
Orchestrates the Turns and Battle Session.
Array of Players,Array of Enemies.

BattleActor Class (ActorName, HP, Is Dead, etc).
Player and Enemy types are derived from this.

BattleAttack Class - Contains Details about the attack. Damage amount, Name, Players and Enemies have an array of these.

So then it comes down to orchestrating it I had thought to make an enumeration of BattleStates(PlayerTurn, EnemyTurn, BattleOver) then use a switch statement in the update method to call methods that correspond to these states. This doesn’t seem as good compared using a while in the start method maybe.

I need to somehow loop through player objects and let them take their turn. Wait till they are done and do the same for the enemies.

That to me sounds like co-routines! But couldn’t this also somehow be done event based? Having them all subscribe to a turn listener and checking to see if they are the receiver being called.

I’ll just stop here as I’ve already babbled long enough. I must say this is the most thought provoking programming I’ve ever done. I think it’s making me crazy…

Sure it could be event based, easily.

Off the top of my head, one was is that when you initiate attacks, stick references to them in a list in the BattleManager. Have a public method in the BattleManager which allow BattleAttacks to inform it when they’re finished, wherein you pop remove them from the list. If the list is empty, you know that you’ve finished all of the attacks and can do the next thing.

Personally, I often use delegates to do this kind of thing at a general level. Objects don’t need to be aware of where events come from or of what might respond to them.

The BattleManager sounds like the correct direction. You could use it like a Singleton which would make it easy to handle battles… Assuming one battle at a time:

public class BattleManager : MonoBehaviour 
{
	//Reference Holders of the current battle 
	private List<BattleActor> playerEntities = new List<BattleActor>();
	private List<BattleActor> enemyEntities = new List<BattleActor>();

	private BattleActor selectedEntity = null;
	public BattleActor SelectedEntity
	{
		get{return selectedEntity;}
		set{selectedEntity = value;}
	}

	private BattleState currentState = BattleState.BattleOver;

	private static BattleManager _instance;
	public static BattleManager Instance
	{
		get { return _instance; }
		private set { _instance = value; }
	}

	public void Awake()
	{
		if (Instance != null)
		{
			Destroy(gameObject);
		}
		else
		{
			Instance = this;
            DontDestroyOnLoad(this);
		}
	}

	public void BeginBattle(List<BattleActor> player, List<BattleActor> enemy)
	{
		// Do initialization
		playerEntities = player;
		enemyEntities = enemy;

		//Decide who goes first.
		currentState = CalculateFirstStrike();	//Maybe you have some info in BattleActor that allows for first strike.
		StartCoroutine(StartTurns());
	}

	private BattleState CalculateFirstStrike(List<BattleActor> playerEntities, List<BattleActor> enemyEntities)
	{
		BattleState startingOpponent = BattleState.EnemyTurn;

		int pOverallFirstStrike = 0;
		foreach(BattleActor ba in playerEntities)
		{
			pOverAllFirstStrike+= ba.firstStrike;
		}

		int eOverallFirstStrike = 0;
		foreach(BattleActor ba in enemyEntities)
		{
			eOverAllFirstStrike+= ba.firstStrike;
		}

		if(pOverAllFirstStrike > eOverAllFirstStrike)
		{
			startingOpponent = BattleState.PlayerTurn;
		}
		else if(pOverAllFirstStrike < eOverAllFirstStrike)
		{
			startingOpponent = BattleState.EnemyTurn;
		}
		else
		{
			//They have the same score.. Random it?
			int rnd = Random.Range(1, 100);
			if(rnd > 50)
			{
				startingOpponent = BattleState.PlayerTurn;
			}
			else
			{
				startingOpponent = BattleState.EnemyTurn;
			}
		}
		return startingOpponent;
	}

	private IEnumerator InitiateBattleTurns()
	{
		while(currentState != BattleState.BattleOver)
		{
			bool hasRemaingActions = false;
			Switch(currentState)
			{
				case BattleState.PlayerTurn:
						if(selectedEntity != null)
						{
							//Show Options for this entity
							selectedEntity.ShowActions();
						}

						//Check to see if the player still has remaining actions to use.
						hasRemaingActions = false;
						foreach(BattleActor ba in playerEntities)
						{
							if(!hasRemaingActions)
								ba.HasActionsRemaining;
						}

						if(hasRemaingActions)
						{
							//Switch the State
							currentState = BattleState.EnemyTurn;
						}
					break;
				case BattleState.EnemyTurn:
						//Do enemy AI Actions

						//Check to see if the enemy still has remaining actions to use.
						hasRemaingActions = false;
						foreach(BattleActor ba in enemyEntities)
						{
							if(!hasRemaingActions)
								ba.HasActionsRemaining;
						}

						if(hasRemaingActions)
						{
							//Switch the State
							currentState = BattleState.EnemyTurn;
						}
					break;
			}

			yield return new WaitForSeconds(0.1f);
		}
	}
}

Thanks for the information. I hadn’t seen your post before I made this structure. It’s not complete yet, but it does pass the baton back and forth. Be warned this is my first foray into game programming so please be gentle! :frowning:

BattleManager:

using UnityEngine;
using System.Collections;
public class BattleManager : MonoBehaviour {

//Array containing Actors in this battle.
public GameObject[] Actors;
	
//Current index of Actor taking a turn.
public int currentActor = 0;
public int ActorIndex;
//Event subscribed to by Battle Actors to see if it is their turn.
public delegate void TakeTurnEvent(GameObject g);
public event TakeTurnEvent TakeTurn;	

	// Use this for initialization
	void Start () 
	{
	
		//Initialize the listeners.
		SetupEventListeners();	
		ActorIndex = Actors.Length -1;
		TakeTurn(Actors[currentActor]);
	}
	
	
	//Setup event listeners.
	void SetupEventListeners()
	{
		//For each actor subscribe to its turn over event.
		foreach(GameObject g in Actors)
		{
			g.GetComponent<BattleActor>().TurnOver += TurnOver;
		}
	}

//Gets fired when an actor has finished their turn.
	void TurnOver()
	{	
		CheckBattle();			
	}

	
	void CheckBattle()
	{	
		if(currentActor < ActorIndex)
		{
		currentActor++;
		}
		else
		{
			currentActor = 0;
		}
		//Let the next Actor take their turn.
		TakeTurn(Actors[currentActor]);
	}
		
}

BattleActor:

using UnityEngine;
using System.Collections;

public class BattleActor : MonoBehaviour {

	
public string ActorName;

//Event to be subscribed to if you need to know when the turn is over.
public delegate void TurnOverEvent();
public event TurnOverEvent TurnOver;

//Reference to the current BattleOrchestrator.
public GameObject battleOrchestrator;	
//Reference to the BattleManager in the scene.
public BattleManager battleManger;
	
	void Awake()
	{
	//Get references.
	battleOrchestrator = GameObject.Find("BattleOrchestrator");	
	battleManger = battleOrchestrator.GetComponent<BattleManager>();
	//Subscribe to the take turn
	battleManger.TakeTurn += TakeTurn;
		
	}
		
	//Protected event to allow raising in the derived classes BattlePlayer, BattleEnemy.
	protected virtual void OnTurnOver()
	{
		TurnOverEvent handler = TurnOver;
		if(handler != null)
		{
			handler();
		}
	}
	
	public virtual void TakeTurn(GameObject g)
	{
	}
	
}

BattlePlayer:

using UnityEngine;
using System.Collections;

public class BattlePlayer : BattleActor {
	
	public bool WaitForInput = false;
	
	// Use this for initialization
	void Start () 
	{

	}
	
	// Update is called once per frame
	void Update () 
	{
		if(WaitForInput)
		{
			if(Input.GetKeyUp(KeyCode.A))
			{
				WaitForInput = false;
				OnTurnOver();
			}
		}
	}
	
	public override void TakeTurn(GameObject g)
	{
		if(g == this.gameObject)
		{			
			Debug.Log(ActorName + " Hey it's my turn!");
			WaitForInput = true;
		}
	}
	
}

BattleEnemy:

using UnityEngine;
using System.Collections;

public class BattleEnemy : BattleActor {

	// Use this for initialization
	void Start () {
	
	}
	
	// Update is called once per frame
	void Update () {
	
	}
	
	public override void TakeTurn(GameObject g)
	{
		if(g == this.gameObject)
		{			
			Debug.Log(ActorName + " Hey it's my turn!");
			OnTurnOver();
		}
	}
}