Search Unity

Help With Card Combat State Machine

Discussion in 'Scripting' started by MJacobsen186, Jan 9, 2016.

  1. MJacobsen186

    MJacobsen186

    Joined:
    Aug 28, 2015
    Posts:
    21
    Hello everyone! I've been slowly working on a card game pet project in order to teach myself the ins and outs of Unity and C#. I've been trying to design the state machine which handles the combat in the game. As for right now, I'm just trying to have it so you click a card object and then the next card object you click gets assigned damage and the card you originally clicked gets assigned the clicked card's damage.

    In simpler terms its Hearthstone combat. I click a 1 attack and 1 health creature and tell it to attack a 2 attack and 2 health creature. The 1/1 dies and the 2/2 becomes a 2/1.

    For right now, I've been creating the card's attributes in separate components. So there are separate health and attack components. I will be posting those components as well as the state machine below. I got stuck when I had to design the damage assignment. I did not know how to assign the damage to the next clicked object - let alone how to make them trade damage when they attack (the above example of Hearthstone combat). Any help or guidance would be greatly appreciated.

    Health Class:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Health : MonoBehaviour {
    5.  
    6.     public int maxHealth;
    7.     public int currentHealth;
    8.     public UILabel healthLabel;
    9.  
    10.  
    11.  
    12.     public void Start(){
    13.  
    14.         SetMaxHealth ();
    15.         healthLabel.text = "" + maxHealth;
    16.  
    17.     }
    18.  
    19.     public void Update(){
    20.  
    21.         healthLabel.text = "" + currentHealth;
    22.  
    23.     }
    24.  
    25.     public void RestoreHealth (int amount){
    26.         currentHealth += amount;
    27.     }
    28.  
    29.     public void ReduceHealth (int amount){
    30.         currentHealth -= amount;
    31.     }
    32.  
    33.     public void SetZeroHealth(){
    34.  
    35.         currentHealth = 0;
    36.     }
    37.  
    38.     public bool AtZeroHealth(){
    39.         return (currentHealth <= 0);
    40.     }
    41.  
    42.     public void SetMaxHealth(){
    43.         currentHealth = maxHealth;
    44.     }
    45.  
    46.     public bool AtMaxHealth(){
    47.         return (currentHealth >= maxHealth);
    48.     }
    49. }
    50.  
    Attack Class:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Attack : MonoBehaviour {
    5.  
    6.     public int maxAttack;
    7.     public int currentAttack;
    8.     public UILabel attackLabel;
    9.     public bool expended = false;
    10.  
    11.  
    12.     void Start () {
    13.  
    14.         attackLabel.text = "" + maxAttack;
    15.     }
    16.  
    17.  
    18.     void Update () {
    19.  
    20.  
    21.  
    22.         attackLabel.text = "" + currentAttack;
    23.  
    24.     }
    25.  
    26.     void OnClick(){
    27.  
    28.         BattleStateMachine.damageToAssign = currentAttack;
    29.     }
    30.  
    31.     public void AddAttack (int amount){
    32.         currentAttack += amount;
    33.     }
    34.  
    35.     public void ReduceAttack (int amount){
    36.         currentAttack -= amount;
    37.     }
    38.  
    39.     public void SetZeroAttack(){
    40.      
    41.         currentAttack = 0;
    42.     }
    43.  
    44.     public void DealDamage(){
    45.  
    46.     }
    47.  
    48. }
    State Machine:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class BattleStateMachine : MonoBehaviour {
    5.  
    6.     private BattleStateStart battleStateStartScript = new BattleStateStart();
    7.  
    8.     public enum BattleStates
    9.     {START,
    10.      PLAYERONETURN,
    11.      PLAYERTWOTURN,
    12.      CALCDAMAGE,
    13.      LOSE,
    14.      WIN}
    15.  
    16.     public static BattleStates currentState;
    17.  
    18.     public static int damageToAssign;
    19.  
    20.  
    21.     void Start () {
    22.         currentState = BattleStates.START;
    23.     }
    24.  
    25.  
    26.     void Update () {
    27.  
    28.         switch (currentState) {
    29.         case (BattleStates.START):
    30.             battleStateStartScript.PrepareGame();
    31.             break;
    32.         case (BattleStates.PLAYERONETURN):
    33.             damageToAssign = 0;
    34.             //damage to assign = what you clicked
    35.             break;
    36.         case (BattleStates.PLAYERTWOTURN):
    37.             break;
    38.         case (BattleStates.CALCDAMAGE):
    39.             break;
    40.         case (BattleStates.WIN):
    41.             break;
    42.         case (BattleStates.LOSE):
    43.             break;
    44.         }
    45.  
    46.     }
    47. }
     
  2. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    In my opinion, you are mixing states with phases. Your states could be something like:
    Code (CSharp):
    1. public enum GameState
    2. {
    3.      IDLE,     // nothing happens on screen, accept player input
    4.      GAMELOGIC,    // you received input and logic executes
    5.      GAMEOVER, // game is over, stop all scripts here
    6.      PAUSE,  // game paused, time.timeScale = 0 could be put here
    7.      DISCONNECTED // got disconnect, wait until reconnect
    8. }
    On the other hand, phases could be your game logic specific actions, like start/end turn, playing cards, spells, before attack, attack, after attack, before death, death, after death, etc... you get the point.

    However, if you're planning to make something like Hearthstone mechanics, a quick google search popped this:
    http://hearthstone.gamepedia.com/Advanced_rulebook#Advanced_mechanics_101_.28READ_THIS_FIRST.29

    Seems quite informative! I'm sure it will help you point you in the right direction. Hope it helps!
     
    MJacobsen186 likes this.
  3. MJacobsen186

    MJacobsen186

    Joined:
    Aug 28, 2015
    Posts:
    21
    @Fajlworks Thank you for the advice regarding the state machine setup! I checked out the link and it does seem extremely helpful. I'll have more time to look it over later today - I'll post the revised state machine when I get a chance.

    Also, would you (or anyone else reading this) know how to go about assigning damage like Hearthstone does? Where the creatures deal damage to each other equal to their attack value? I've been watching videos and reading up on combat systems but I can't seem to find an answer.

    Thank you again!
     
  4. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    Since the whole combat sequence requires more than one class, you should carefully plan out your class structure, how you will approach different states and event handling. There are certainly many ways to do it, but I will give you an example of how I would attempt to tackle it. With diagrams of course:



    Your Attack component gives your GameObject the ability to attack. If it has no Attack component, no attacking. (Like spells, they do not have the luxury of attacking like minions)

    Do note, this is more like proof of concept. You could include additional checks, if your minion has 0 attack to prevent going into Targeting state and throw error "Minions cannot attack with 0 power", or they already attacked this turn, etc.

    We added the Coroutine: Wait for click not only to wait for input who to attack, but to provide a clean way to add your red arrow gameObject when attacking other minions (arrow that goes from selected minion to the tip of the mouse cursor). When the coroutine stops, you remove the arrow.

    If everything was valid with Targeting sequence, we enqueue a message, like JSON, that holds both minions ID so we can reference them later when the message arrives to all clients. Also, you should reset State back to Idle when finished. (Diagram fails to include that part ¬_¬)

    Now, your GameLoop will check for requests and will go through them. This GameLoop above is fairly simple, without the eventual complexity that comes with a TCG, but should explain the intent. When it processes your request, that is the best time to add your Attack logic you asked for in the opening question!

    Code (CSharp):
    1. void ProcessRequest( JSONObject json )
    2. {
    3.      Attack attacker = // ... get your attacker object reference with ID
    4.      Attack defender = // ... get defender object reference with ID
    5.  
    6.      // check if they are still alive
    7.      if (attacker. != null && defender != null )
    8.      {
    9.           attacker.PerformAttack( defender );
    10.           defender.PerformAttack( attacker );
    11.      }
    12. }
    And your PerformAttack logic could be something like:
    Code (CSharp):
    1. public void PerformAttack( Attack target )
    2. {
    3.     Health hp = target.GetComponent<Health>();
    4.     if (hp == null)
    5.          return;
    6.  
    7.     PerformAttack( hp );
    8. }
    9.  
    10. public void PerformAttack( Health target )
    11. {
    12.     target.ReduceHealth( currentAttack );
    13. }
    14.  
    Sorry for the wall of text. I know it is not a code that you can use immediately, but hopefully those diagrams will help you lay out the classes more easily. Hope this helps guide you in the right direction!
     
    MJacobsen186 likes this.
  5. MJacobsen186

    MJacobsen186

    Joined:
    Aug 28, 2015
    Posts:
    21
    @Fajlworks I just wanted to stop by the thread and give you a huge thank you! The information you provided was insanely helpful. I have been working on the state machine as well as updating my other card components over the weekend. I'm not finished yet, but when I have an updated system up and running I will try to post it here.

    Again, thank you so so much! I wish I could nominate you for some type of forum award haha.