Search Unity

Coding for Player Turns (abstract???)

Discussion in 'Scripting' started by VFran, Oct 27, 2020.

  1. VFran

    VFran

    Joined:
    Sep 6, 2019
    Posts:
    12
    Hi,

    I've been wanting to make a logic game (like Zoombinis!) or maybe a survival game -- but I don't quite understand how to code a turn.
    Ex: (let's keep it simple and say moving one space forward is a turn)
    Player turn begins, moves forward, turn ends. # Enemy turn begins, moves forward, turn ends and so on

    The idea of a "turn" is so abstract to me -- it is entirely based off of whatever the design of the game is, and so I am unsure as to how to go about programming a turn.

    Any ideas? Thank you!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,686
    One generic approach is to track the state of each part of the turn yourself, advance timers, set flags, set states, show messages, etc.

    Another simpler but often less-flexible way is to use coroutines that advance through with yields for specific times (or events, such as "Press OK").

    What you use is fairly tightly coupled to the precise nature of your game so you should try a few approaches, see what resonates well with your problem space.
     
    Bunny83 and VFran like this.
  3. spryx

    spryx

    Joined:
    Jul 23, 2013
    Posts:
    557
    Quite a few ways to do this. A singleton or "one class to rule them all" is probably the easiest way. Simple object booleans can work.

    If you have lots of things that need turns, you might consider some sort of scheduling system. I've adapted one from RogueSharp. In practice, because in my current project, I need things to repeat actions, I've opted to use a circular queue.
    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. namespace Assets.Scripts.Elements
    7. {
    8.     public class CircularQueue<T> : IEnumerable
    9.     {
    10.         private readonly Queue<T> _internalQueue;
    11.  
    12.         public CircularQueue()
    13.         {
    14.             _internalQueue = new Queue<T>();
    15.         }
    16.  
    17.         public void Enqueue(T item)
    18.         {
    19.             try
    20.             {
    21.                 _internalQueue.Enqueue(item);
    22.             }
    23.             catch (Exception e)
    24.             {
    25.                 throw new Exception(e.Message);
    26.             }
    27.         }
    28.  
    29.         public T Dequeue()
    30.         {
    31.             while (true)
    32.             {
    33.                 if (_internalQueue.Count == 0)
    34.                 {
    35.                     // ReSharper disable once RedundantTypeSpecificationInDefaultExpression
    36.                     return default;
    37.                 }
    38.  
    39.                 var item = _internalQueue.Dequeue();
    40.  
    41.                 if (item == null || IsUnityObjectNull(item))
    42.                 {
    43.                     continue;
    44.                 }
    45.  
    46.                 //Put back into the queue
    47.                 _internalQueue.Enqueue(item);
    48.  
    49.                 return item;
    50.             }
    51.         }
    52.  
    53.         private static bool IsUnityObjectNull(object o)
    54.         {
    55.             var ueo = o as UnityEngine.Object;
    56.             return ueo == null;
    57.         }
    58.  
    59.         public IEnumerator<T> GetEnumerator()
    60.         {
    61.             return _internalQueue.GetEnumerator();
    62.         }
    63.  
    64.         IEnumerator IEnumerable.GetEnumerator()
    65.         {
    66.             return _internalQueue.GetEnumerator();
    67.         }
    68.  
    69.         public int GetCount()
    70.         {
    71.             return _internalQueue.Count;
    72.         }
    73.     }
    74. }
    75.  

    The action scheduler uses that queue internally to determine whose "turn" it is. At the end of the turn we dequeue the next item.
    Code (CSharp):
    1.  
    2. using System;
    3. using Assets.Scripts.Elements;
    4. using Assets.Scripts.Interfaces.System;
    5. using UnityEngine;
    6.  
    7. namespace Assets.Scripts.Systems
    8. {
    9.     public class ActionScheduler : MonoBehaviour
    10.     {
    11.         public static ActionScheduler System;
    12.         private readonly CircularQueue<ISchedulable> _scheduleQueue;
    13.  
    14.         public ActionScheduler()
    15.         {
    16.             System = this;
    17.             _scheduleQueue = new CircularQueue<ISchedulable>();
    18.         }
    19.  
    20.         public void DoNext()
    21.         {
    22.             ISchedulable task = _scheduleQueue?.Dequeue();
    23.  
    24.             if (task == null)
    25.             {
    26.                 throw new Exception("Tried to perform null task.");
    27.             }
    28.  
    29.             //Set task complete action
    30.             task.CompletedTaskAction = DoNext;
    31.  
    32.             task.StartScheduledTask();
    33.         }
    34.  
    35.         public void Schedule(ISchedulable item)
    36.         {
    37.             _scheduleQueue.Enqueue(item);
    38.         }
    39.     }
    40. }
    41.  

    Finally, something needs to implement ISchedulable. For me, this is actors.
    Code (CSharp):
    1. using System;
    2.  
    3. namespace Assets.Scripts.Interfaces.System
    4. {
    5.     public interface ISchedulable
    6.     {
    7.         void StartScheduledTask();
    8.  
    9.         Action CompletedTaskAction {get; set;}
    10.     }
    11. }
    12.  

    From there, it is as simple as calling whatever behavior needs to occur. If that is input, you would need to wait to call CompletedTaskAction until you are ready for the behavior or "turn" to end.
    Code (CSharp):
    1. public override void StartScheduledTask()
    2. {
    3.         BeginBehavior();
    4.         CompletedTaskAction?.Invoke();
    5. }
    Admittedly, this might be more complex than what you are looking for, but I hope it helps.
     
    VFran likes this.
  4. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,104
    Turns are coded with respect to the UI events, not standard frametime. Simple as that.
    You could say that the progression of a turn-based game, depends not on a clock, but on a user confirmation.
     
    VFran likes this.
  5. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,913
    A turn can't really happen without a higher system that is waiting for the turn to finish. The turn would be a function that notifies the higher system when it's completed. The higher system would probably be some kind of state machine that runs the turns over and over.

    The higher system could be anything such as a BattleSystem (for an RPG), or a GameManager (for a Civilization style game).

    A simple example is this:
    Code (csharp):
    1. public class TestSystem : MonoBehaviour
    2. {
    3.     public bool IsDoingTurn;
    4.  
    5.     void Start() => StartCoroutine(DoTurn());
    6.  
    7.     IEnumerator DoTurn()
    8.     {
    9.         Debug.Log("Turn Started");
    10.         IsDoingTurn = true;
    11.  
    12.         // Wait until spacebar is pressed
    13.         while (IsDoingTurn)
    14.         {
    15.             if (Input.GetKeyDown(Keycode.Space))
    16.             {
    17.                 IsDoingTurn = false;
    18.             }
    19.  
    20.             yield return null;
    21.         }
    22.  
    23.         Debug.Log("Turn Completed");
    24.         StartCoroutine(DoTurn()) // Run again.
    25.     }
    26. }
    Obviously, this is useless for an actual game, but a turn just waits for something to trigger its completion. Turn-based RPG's will end the turn when choosing a move. Civilization ends the turn when you click a UI button.

    In a turn-based strategy game, the system might store a list of all participants for each team, and wait for them to exhaust all of their options, then end the turn after recieving an event.
     
    VFran likes this.