Learn To Create a Card Combat Game With Unity & C#

1. Getting Started

3. Setting Up A New URP Project



拖动资源到项目中

2. Layouts

1. Laying Out Our Scene

右键, 创建3d地板
camera的位置
card frame 拖动到项目目录

4个小卡片x分别是-1.5, -3, 1.5, 3
新建empty object

2. Making A Card

新建empty objectcard model文件拖入项目


文字居中
关掉3D icon
创建第2个, 后面加outline

3. Finishing The Card Layout

从上到下auto size的最大值22, 20, 20
拖动图片到source image属性, 点击preserve aspect可以按比例缩放
新建image, 点击关闭maskable, add component添加mask
调整mask image的大小
这个随着canvas的创建而出现, 但是我们不需要, 所以删掉

3. Cards

1. Creating Our First Script

创建script文件夹右键创建c#脚本
创建的时候, 名称和Hierarchy的项目名称对上, 否则创建完再改名, 脚本的class名称还是默认的
脚本拖动到card项目目录在这配置双击脚本打开的ide

2. Making The Card Script Work

定义的属性在unity中显示了在unity中绑定变量
启动时设置text的值运行看到text确实被改变了
选择max的那个, 运行时就会全屏

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class Card : MonoBehaviour
{
    public int currentHealth;
    public int attackPower;
    public int manaCost;

    public TMP_Text healthText, attackText, costText;

    // Start is called before the first frame update
    void Start()
    {
        healthText.text = currentHealth.ToString();
        attackText.text = attackPower.ToString();
        costText.text = manaCost.ToString();
    }

    // Update is called once per frame
    void Update()
    {
    }
}

3. Creating A Scriptable Object


textArea注释unity中该选项发生变化
创建专门存放card的文件夹

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 添加一个菜单创建选项, 文件名New Card, 菜单项名Card
[CreateAssetMenu(fileName = "New Card", menuName = "Card", order = 1)]
public class CardScriptableObject : ScriptableObject
{
    public string cardName;
     
    [TextArea]
    public string actionDescription, cardLore;

    public int currentHealth;
    public int attackPower;
    public int manaCost;

    public Sprite characterSprite, bgSprite;
}

现在我们存储了数据到自定义的对象, 接下来就是读取并写入card

4. Loading Data From The Scriptable Object

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.UI;

public class Card : MonoBehaviour
{
    public CardScriptableObject cardSO;

    public int currentHealth;
    public int attackPower;
    public int manaCost;

    public TMP_Text healthText, attackText, costText;
    public TMP_Text nameText, actionDescriptionText, loreText;

    public Image characterArt, bgArt;

    // Start is called before the first frame update
    void Start()
    {
        setupCard();
    }

    public void setupCard()
    {
        currentHealth = cardSO.currentHealth;
        attackPower = cardSO.attackPower;
        manaCost = cardSO.manaCost;

        healthText.text = currentHealth.ToString();
        attackText.text = attackPower.ToString();
        costText.text = manaCost.ToString();

        nameText.text = cardSO.cardName;
        actionDescriptionText.text = cardSO.actionDescription;
        loreText.text = cardSO.cardLore;

        characterArt.sprite = cardSO.characterSprite;
        bgArt.sprite = cardSO.bgSprite;
    }

    // Update is called once per frame
    void Update()
    {
    }
}

添加属性绑定card object
创建多个card和cardObject, 然后一一绑定, 使用cardScriptObject比使用多个card内存占用来得少

4. Holding Hands

1. Setting Up Our Hand

empty object拖动handControler脚本到handController项目对象
empty object两个小球用来定位
把小球删除

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandController : MonoBehaviour
{
    public Card[] heldCards;

    public Transform minPos, maxPos;

    // Start is called before the first frame update
    void Start()
    {
        SetCardPositionsInHand();
    }

    // Update is called once per frame
    void Update()
    {
    }

    public void SetCardPositionsInHand()
    {
    }
}

2. Setting Card Positions In Hand

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandController : MonoBehaviour
{
    public List<Card> heldCards = new List<Card>();

    public Transform minPos, maxPos;
    public List<Vector3> cardPositions = new List<Vector3>();

    // Start is called before the first frame update
    void Start()
    {
        SetCardPositionsInHand();
    }

    // Update is called once per frame
    void Update()
    {
    }

    public void SetCardPositionsInHand()
    {
        cardPositions.Clear();

        Vector3 distanceBetweenPoints = Vector3.zero;
        if (heldCards.Count > 1)
        {
            distanceBetweenPoints = (maxPos.position - minPos.position) / (heldCards.Count - 1);
        }

        for (int i = 0; i < heldCards.Count; i++)
        {
            cardPositions.Add(minPos.position + (distanceBetweenPoints * i));

            heldCards[i].transform.position = cardPositions[i];
        }
    }
}

3. Preventing Overlapping Cards

选择hand controller, 右上角锁住面板, 左侧选中卡片, 拖动到held cards属性
修改一下minPos的rotation(倾斜)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandController : MonoBehaviour
{
    // ...

    public void SetCardPositionsInHand()
    {
        // ...

        for (int i = 0; i < heldCards.Count; i++)
        {
            cardPositions.Add(minPos.position + (distanceBetweenPoints * i));

            heldCards[i].transform.position = cardPositions[i];
            // 卡片倾斜, 防止叠在一起穿模
            heldCards[i].transform.rotation = minPos.rotation;
        }
    }
}

4. Moving Cards Smoothly

debug模式可以看到private属性

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using Unity.Mathematics;
using UnityEngine.UI;

public class Card : MonoBehaviour
{
    ///

    private Vector3 _targetPoint;
    private Quaternion _targetRot;
    public float moveSpeed;
    public float rotateSpeed;

    // Start is called before the first frame update
    void Start()
    {
        setupCard();
    }

    public void setupCard()
    {
        // ...

        moveSpeed = 5f;
        rotateSpeed = 540f;
    }

    // Update is called once per frame
    private void Update()
    {
        transform.position = Vector3.Lerp(transform.position, _targetPoint, moveSpeed * Time.deltaTime);
        transform.rotation = 
            Quaternion.RotateTowards(transform.rotation, _targetRot, rotateSpeed * Time.deltaTime);
    }

    public void MoveToPoint(Vector3 pointToMoveTo, Quaternion rotToMatch)
    {
        _targetPoint = pointToMoveTo;
        _targetRot = rotToMatch;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandController : MonoBehaviour
{
    ///

    public void SetCardPositionsInHand()
    {
        ///

        for (int i = 0; i < heldCards.Count; i++)
        {
            cardPositions.Add(minPos.position + (distanceBetweenPoints * i));

            // heldCards[i].transform.position = cardPositions[i];
            // 卡片倾斜, 防止叠在一起穿模
            // heldCards[i].transform.rotation = minPos.rotation;
            // 移动卡片
            heldCards[i].MoveToPoint(cardPositions[i], minPos.rotation);
        }
    }
}

5. Raising Cards While Hovering

3d->spherep->向上移动一点->add component->rigidbody
小球和card碰撞测试

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using Unity.Mathematics;
using UnityEngine.UI;

public class Card : MonoBehaviour
{
    ///
    
    public bool inHand; 
    public int handPosition;

    private HandController theHC;

    // Start is called before the first frame update
    void Start()
    {
        setupCard();

        theHC = FindObjectOfType<HandController>();
    }


    private void OnMouseOver()
    {
        if (inHand)
        {
            // 该卡的位置向y(上)移动
            MoveToPoint(
                theHC.cardPositions[handPosition] + new Vector3(0f, 1f, .5f),
                quaternion.identity
            );
        }
    }

    private void OnMouseExit()
    {
        if (inHand)
        {
            // 该卡的位置向y(上)移动
            MoveToPoint(
                theHC.cardPositions[handPosition],
                theHC.minPos.rotation
            );
        }
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandController : MonoBehaviour
{
    ///

    public void SetCardPositionsInHand()
    {
        ///

        for (int i = 0; i < heldCards.Count; i++)
        {
            cardPositions.Add(minPos.position + (distanceBetweenPoints * i));

            // heldCards[i].transform.position = cardPositions[i];
            // 卡片倾斜, 防止叠在一起穿模
            // heldCards[i].transform.rotation = minPos.rotation;
            // 移动卡片
            heldCards[i].MoveToPoint(cardPositions[i], minPos.rotation);

            heldCards[i].inHand = true;
            heldCards[i].handPosition = i;
        }
    }
}

5. Pick & Place

1. Selecting Cards


using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using Unity.Mathematics;
using UnityEngine.UI;

public class Card : MonoBehaviour
{
    ///

    public bool inHand; // 是否在手上
    public int handPosition;

    private HandController theHC;

    private bool _isSelected;

    // 碰撞
    private Collider _theCol;

    public LayerMask whatIsDesktop;

    // Start is called before the first frame update
    void Start()
    {
        setupCard();

        theHC = FindObjectOfType<HandController>();
        _theCol = GetComponent<Collider>();
    }

    ///

    // Update is called once per frame
    private void Update()
    {
        transform.position = Vector3.Lerp(transform.position, _targetPoint, moveSpeed * Time.deltaTime);
        transform.rotation =
            Quaternion.RotateTowards(transform.rotation, _targetRot, rotateSpeed * Time.deltaTime);

        if (_isSelected)
        {
            // 射线 -> 鼠标
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 100f, whatIsDesktop))
            {
                // hit: 射线命中的对象
                // 移动到鼠标所在位置
                MoveToPoint(hit.point + new Vector3(0f, 2f, 0f), quaternion.identity);
            }
        }
    } 
    
    ///

    private void OnMouseDown()
    {
        if (inHand)
        {
            _isSelected = true;
            // 无法再hold浮起
            _theCol.enabled = false;
        }
    }
}

2. Returning A Card To Hand

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using Unity.Mathematics;
using UnityEngine.UI;

public class Card : MonoBehaviour
{
    ///
    
    // Update is called once per frame
    private void Update()
    {
        transform.position = Vector3.Lerp(transform.position, _targetPoint, moveSpeed * Time.deltaTime);
        transform.rotation =
            Quaternion.RotateTowards(transform.rotation, _targetRot, rotateSpeed * Time.deltaTime);

        if (_isSelected)
        {
            // 射线 -> 鼠标
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 100f, whatIsDesktop))
            {
                ///
            }

            // 右键单击
            if (Input.GetMouseButtonDown(1))
            {
                ReturnToHand();
            }
        }
    }

    ///

    private void ReturnToHand()
    {
        _isSelected = false;
        _theCol.enabled = true;

        MoveToPoint(theHC.cardPositions[handPosition], theHC.minPos.rotation);
    }
}

3. Creating Card Placement Areas

新建图层
调整位置(从左到右的顺序), 然后重命名
新建脚本给这五个绑定脚本
复制->改名 enemy card points->拖动->改色(黑)
isPlayerPoint check

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CardPlacePoint : MonoBehaviour
{
    public Card activeCard;
    public bool isPlayerPoint;
}

4. Placing A Card On The Board

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using Unity.Mathematics;
using UnityEngine.UI;

public class Card : MonoBehaviour
{
    ///

    public LayerMask whatIsDesktop;
    public LayerMask whatIsPlacement;

    private bool _justPressed;

    public CardPlacePoint assignedPlace;

    ///

    // Update is called once per frame
    private void Update()
    {
        transform.position = Vector3.Lerp(transform.position, _targetPoint, moveSpeed * Time.deltaTime);
        transform.rotation =
            Quaternion.RotateTowards(transform.rotation, _targetRot, rotateSpeed * Time.deltaTime);

        if (_isSelected)
        {
            ///

            // 右键单击
            if (Input.GetMouseButtonDown(1))
            {
                ReturnToHand();
            }

            // 左键单击
            if (Input.GetMouseButtonDown(0) && _justPressed == false)
            {
                if (Physics.Raycast(ray, out hit, 100f, whatIsPlacement))
                {
                    // 当前选中的point
                    CardPlacePoint selectedPoint = hit.collider.GetComponent<CardPlacePoint>();

                    // 当前选中的point没有卡, 并且是玩家这边的
                    if (selectedPoint.activeCard == null && selectedPoint.isPlayerPoint)
                    {
                        selectedPoint.activeCard = this;
                        // 记录放在哪个位置
                        assignedPlace = selectedPoint;

                        // 放入场地
                        MoveToPoint(selectedPoint.transform.position, quaternion.identity);

                        inHand = false;

                        _isSelected = false;
                    }
                    else
                    {
                        ReturnToHand();
                    }
                }
                else
                {
                    ReturnToHand();
                }
            }
        }

        // 隔一段时间没左键 就可以 再次左键 然后触发返回
        _justPressed = false;
    } 
    
    ///
    
    private void OnMouseDown()
    {
        if (inHand)
        {
            _isSelected = true;
            // 无法再hold浮起
            _theCol.enabled = false;
            // 让card可以浮动
            _justPressed = true;
        }
    } 
    ///
}

5. Removing Card From Hand

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandController : MonoBehaviour
{
    ///

    public void RemoveCardFromHand(Card cardToRemove)
    {
        if (heldCards[cardToRemove.handPosition] == cardToRemove)
        {
            heldCards.RemoveAt(cardToRemove.handPosition);
        }
        else
        {
            Debug.LogError("Card at position " + cardToRemove.handPosition +
                           " is not the card being removed from hand");
        }
        
        // 重新分配卡片位置
        SetCardPositionsInHand();
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using Unity.Mathematics;
using UnityEngine.UI;

public class Card : MonoBehaviour
{
    ///

    // Update is called once per frame
    private void Update()
    {
        transform.position = Vector3.Lerp(transform.position, _targetPoint, moveSpeed * Time.deltaTime);
        transform.rotation =
            Quaternion.RotateTowards(transform.rotation, _targetRot, rotateSpeed * Time.deltaTime);

        if (_isSelected)
        {
            ///

            // 左键单击
            if (Input.GetMouseButtonDown(0) && _justPressed == false)
            {
                if (Physics.Raycast(ray, out hit, 100f, whatIsPlacement))
                {
                    // 当前选中的point
                    CardPlacePoint selectedPoint = hit.collider.GetComponent<CardPlacePoint>();

                    // 当前选中的point没有卡, 并且是玩家这边的
                    if (selectedPoint.activeCard == null && selectedPoint.isPlayerPoint)
                    {
                        ///

                        _isSelected = false;
                        
                        // 将卡牌移出数组(list)
                        theHC.RemoveCardFromHand(this);
                    }
                    else
                    {
                        ReturnToHand();
                    }
                }
                else
                {
                    ReturnToHand();
                }
            }
        }

        // 隔一段时间没左键 就可以 再次左键 然后触发返回
        _justPressed = false;
    }

    ///
}

打出一张牌, 看到hand controller的held cards的数量-1

6. Mana System

1. Spending Mana

新脚本empty object

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BattleController : MonoBehaviour
{
    // 单例
    public static BattleController instance;

    private void Awake()
    {
        instance = this;
    }

    public int startingMana = 4, maxMana = 12;
    public int playerMana;

    // Start is called before the first frame update
    void Start()
    {
        playerMana = startingMana;
    }

    // Update is called once per frame
    void Update()
    {
    }

    public void SpendPlayerMana(int amountToSpend)
    {
        playerMana = playerMana - amountToSpend;

        // 防止意外情况
        if (playerMana < 0)
        {
            playerMana = 0;
        }
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using Unity.Mathematics;
using UnityEngine.UI;

public class Card : MonoBehaviour
{
    ///

    // Update is called once per frame
    private void Update()
    {
        transform.position = Vector3.Lerp(transform.position, _targetPoint, moveSpeed * Time.deltaTime);
        transform.rotation =
            Quaternion.RotateTowards(transform.rotation, _targetRot, rotateSpeed * Time.deltaTime);

        if (_isSelected)
        {
            ///

            // 左键单击
            if (Input.GetMouseButtonDown(0) && _justPressed == false)
            {
                if (Physics.Raycast(ray, out hit, 100f, whatIsPlacement))
                {
                    // 当前选中的point
                    CardPlacePoint selectedPoint = hit.collider.GetComponent<CardPlacePoint>();

                    // 当前选中的point没有卡, 并且是玩家这边的
                    if (selectedPoint.activeCard == null && selectedPoint.isPlayerPoint)
                    {
                        if (BattleController.instance.playerMana < manaCost)
                        {
                            ReturnToHand();
                            return;
                        }

                        ///

                        // 将卡牌移出数组(list)
                        theHC.RemoveCardFromHand(this);

                        // 花费mana点
                        BattleController.instance.SpendPlayerMana(manaCost);
                    }
                    else
                    {
                        ReturnToHand();
                    }
                }
                else
                {
                    ReturnToHand();
                }
            }
        }

        // 隔一段时间没左键 就可以 再次左键 然后触发返回
        _justPressed = false;
    }

    ///
}

2. Showing Mana On UI



ctrl c v

绑定text

using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class UIController : MonoBehaviour
{
    // 单例
    public static UIController instance;

    private void Awake()
    {
        instance = this;
    }

    public TMP_Text playerManaText;

    // Start is called before the first frame update
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
    }

    public void SetPlayerManaText(int manaAmount)
    {
        playerManaText.text = "Mana: " + manaAmount;
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BattleController : MonoBehaviour
{
    ///

    // Start is called before the first frame update
    void Start()
    {
        playerMana = startingMana;
        UIController.instance.SetPlayerManaText(playerMana);
    }

    ///

    public void SpendPlayerMana(int amountToSpend)
    {
        playerMana = playerMana - amountToSpend;

        // 防止意外情况
        if (playerMana < 0)
        {
            playerMana = 0;
        }

        UIController.instance.SetPlayerManaText(playerMana);
    }
}

3. Displaying A Low Mana Warning

添加警告文字(mana不足)
auto size->Alignment: Center->Font Assert: SDF outline->向上移动一点

绑定一下

using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class UIController : MonoBehaviour
{
    ///

    public TMP_Text playerManaText; // 左上角的MANA数量 txt
    public GameObject manaWarning; // mana不足提示信息
    public float manaWarningTime; // mana不足的提示信息的显示持续时间
    private float _manaWarningCounter;

    // Start is called before the first frame update
    void Start()
    {
        manaWarning.SetActive(false);
    }

    // Update is called once per frame
    void Update()
    {
        if (_manaWarningCounter > 0)
        {
            _manaWarningCounter -= Time.deltaTime;

            if (_manaWarningCounter <= 0)
            {
                manaWarning.SetActive(false);
            }
        }
    }

    ///

    public void ShowManaWarning()
    {
        // ActivatesDeactivates the GameObject,
        // depending on the given true or false/ value.
        manaWarning.SetActive(true); // 启用manaWarning
        _manaWarningCounter = manaWarningTime;
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using Unity.Mathematics;
using UnityEngine.UI;

public class Card : MonoBehaviour
{
    ///

    // Update is called once per frame
    private void Update()
    {
        transform.position = Vector3.Lerp(transform.position, _targetPoint, moveSpeed * Time.deltaTime);
        transform.rotation =
            Quaternion.RotateTowards(transform.rotation, _targetRot, rotateSpeed * Time.deltaTime);

        if (_isSelected)
        {
            ///

            // 左键单击
            if (Input.GetMouseButtonDown(0) && _justPressed == false)
            {
                if (Physics.Raycast(ray, out hit, 100f, whatIsPlacement))
                {
                    // 当前选中的point
                    CardPlacePoint selectedPoint = hit.collider.GetComponent<CardPlacePoint>();

                    // 当前选中的point没有卡, 并且是玩家这边的
                    if (selectedPoint.activeCard == null && selectedPoint.isPlayerPoint)
                    {
                        if (BattleController.instance.playerMana < manaCost)
                        {
                            ReturnToHand();
                            // 显示mana不足的提示信息
                            UIController.instance.ShowManaWarning();
                            return;
                        }

                        ///
                    }
                    else
                    {
                        ReturnToHand();
                    }
                }
                else
                {
                    ReturnToHand();
                }
            }
        }

        // 隔一段时间没左键 就可以 再次左键 然后触发返回
        _justPressed = false;
    }

    ///
}

7. Decks

1. Creating A Deck

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class DeskController : MonoBehaviour
{
    public static DeskController instance;

    private void Awake()
    {
        instance = this;
    }

    // 我们在unity面板填充的card
    public List deskToUse = new List();

    private List activeCards = new List();

    // Start is called before the first frame update
    void Start()
    {
        SetupDesk();
    }

    // Update is called once per frame
    void Update()
    {
    }

    public void SetupDesk()
    {
        activeCards.Clear();

        // 临时卡堆
        List tempDesk = new List();
        tempDesk.AddRange(deskToUse);

        int iterations = 0; // 防止无限循环
        while (tempDesk.Count > 0 && iterations < 500)
        {
            // 0~3之间的随机数(不含3)
            int selected = Random.Range(0, tempDesk.Count);
            // 加入卡堆
            activeCards.Add(tempDesk[selected]);
            tempDesk.RemoveAt(selected);

            iterations++;
        }
    }
}

2. Drawing A Card

创建文件夹Prefabs, 将Card拖入, 此时Card成为预制件, 可以重复使用删除原来的卡片
拖入项目复制3份->填入Hand Controller的heldCards
预制件实例的override可以改变预制件的属性, 但是我们一般只是为了复用预制件, 没必要更改

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class DeskController : MonoBehaviour
{
    ///

    public Card cardToSpawn;
    
    ///

    // Update is called once per frame
    void Update()
    {
        // 按下T
        if (Input.GetKeyDown(KeyCode.T))
        {
            DrawCardToHand();
        }
    }

    ///

    public void DrawCardToHand()
    {
        // 当前没有手牌, 就填充
        if (activeCards.Count == 0)
        {
            SetupDesk();
        }

        // 创建cardToSpawn的副本
        Card newCard = Instantiate(cardToSpawn, transform.position, transform.rotation);
        newCard.cardSO = activeCards[0];
        newCard.setupCard();

        activeCards.RemoveAt(0);
    }
}

记得绑定card -- deskController按T, 出现很多副本

3. Moving Card To Hand

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandController : MonoBehaviour
{
    public static HandController instance;

    private void Awake()
    {
        instance = this;
    }

    ///

    public void AddCardToHand(Card cardToAdd)
    {
        heldCards.Add(cardToAdd);
        SetCardPositionsInHand();
    }
}
using System;   
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class DeskController : MonoBehaviour
{
    ///

    // Update is called once per frame
    void Update()
    {
        // 按下T
        if (Input.GetKeyDown(KeyCode.T))
        {
            DrawCardToHand();
        }
    }

    ///

    public void DrawCardToHand()
    {
        // 当前没有手牌, 就填充
        if (activeCards.Count == 0)
        {
            // 将deskToUse打乱设置到activeCards
            SetupDesk();
        }

        // 创建cardToSpawn的副本
        Card newCard = Instantiate(cardToSpawn, transform.position, transform.rotation) as Card;
        newCard.cardSO = activeCards[0];
        newCard.setupCard();

        activeCards.RemoveAt(0);
        
        HandController.instance.AddCardToHand(newCard);
    }
}

4. Showing A Physical Deck

deskController(transform)移动一下拖动CardModel到deskController
改变厚度scalerotation
cardmodel->transform->y->-0.18, 这样抽牌看起来才是从上面抽
deskController-transform-y这样看起来牌会先翻过来再放到手中

5. Click To Draw Cards

font(SDF)
auto size拖动blue到source image拖动
TextMeshPro Text (Ul)->extra settings->绑定点击事件

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class DeskController : MonoBehaviour
{
    ///

    // Update is called once per frame
    void Update()
    {
        // 按下T
        // if (Input.GetKeyDown(KeyCode.T))
        // {
        //     DrawCardToHand();
        // }
    }

    ///

    public void DrawCardForMana()
    {
        if (BattleController.instance.playerMana >= drawCardCost)
        {
            // 抽卡
            DrawCardToHand();
            // 消耗mana
            BattleController.instance.SpendPlayerMana(drawCardCost);
        }
        else
        {
            // mana不足提示信息
            UIController.instance.ShowManaWarning();
            // 按钮消失
            UIController.instance.drawCardButton.SetActive(false);
        }
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class UIController : MonoBehaviour
{
    ///

    public void DrawCard()
    {
        DeskController.instance.DrawCardForMana();
    }
}

8. Turns System

1. Setting Up The Turn Order

遇到bug, 查了下Window > General > Console打开输出控制台

public class BattleController : MonoBehaviour
{
    ///
    
    public enum TurnOrder
    {
        playerActive,
        playerCardAttacks,
        enemyActive,
        enemyCardAttacks
    }

    public TurnOrder currentPhase;

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.T))
        {
            AdvanceTurn();
        }
    }
    
    /// 
    
    public void AdvanceTurn()
    {
        currentPhase++;

        if ((int)currentPhase >= System.Enum.GetValues(typeof(TurnOrder)).Length)
        {
            Debug.Log("0");
            currentPhase = 0;   
        }

        switch (currentPhase)
        {
            case TurnOrder.playerActive: 
                break;
            case TurnOrder.playerCardAttacks:
                Debug.Log("2");
                AdvanceTurn();
                break;
            case TurnOrder.enemyActive:
                Debug.Log("3");
                AdvanceTurn();
                break;
            case TurnOrder.enemyCardAttacks:
                Debug.Log("4");
                AdvanceTurn();
                break;
        }
    }
}

2. Limiting Actions To The Player’s Turn

编写玩家回合才能出牌的逻辑

public class BattleController : MonoBehaviour
{
    ///
    public void AdvanceTurn()
    {
        ///

        switch (currentPhase)
        {
            case TurnOrder.playerActive: 
                break;
            case TurnOrder.playerCardAttacks:
                Debug.Log("2");
                // AdvanceTurn();
                break;
            ///
        }
    }
}
public class Card : MonoBehaviour
{
    /// 

    // Update is called once per frame
    private void Update()
    {
        transform.position = Vector3.Lerp(transform.position, _targetPoint, moveSpeed * Time.deltaTime);
        transform.rotation =
            Quaternion.RotateTowards(transform.rotation, _targetRot, rotateSpeed * Time.deltaTime);

        if (_isSelected)
        {
            ///

            // 左键单击
            if (Input.GetMouseButtonDown(0) && _justPressed == false)
            {
                if (Physics.Raycast(ray, out hit, 100f, whatIsPlacement) &&
                    // 玩家回合才能出牌
                    BattleController.instance.currentPhase == BattleController.TurnOrder.playerActive)
                {
                    ///
                }
                else
                {
                    ReturnToHand();
                }
            } 
        }

        // 隔一段时间没左键 就可以 再次左键 然后触发返回
        _justPressed = false;
    }
    
    ///
    
    private void OnMouseDown()
    {
        // 玩家的回合才能出牌
        if (inHand && BattleController.instance.currentPhase == BattleController.TurnOrder.playerActive)
        {
            ///
        }
    }
}

3. Ending The Player’s Turn

结束回合时禁用两个按钮

复制一个结束回合的button
记得绑定按钮

public class BattleController : MonoBehaviour
{
    ///
    
    // 回合前进(玩家出牌->玩家卡牌攻击(auto)->敌人出牌->敌人卡牌攻击(auto)
    public void AdvanceTurn()
    {
        ///

        switch (currentPhase)
        {
            case TurnOrder.playerActive:
                // 启用: 结束回合btn 和 取牌btn
                UIController.instance.endTurnButton.SetActive(true);
                UIController.instance.drawCardButton.SetActive(true);

                break;
           ///
        }
    }

    public void EndPlayerTurn()
    {
        // 禁用 结束回合 按钮
        UIController.instance.endTurnButton.SetActive(false);
        // 禁用 取牌 按钮
        UIController.instance.drawCardButton.SetActive(false);

        AdvanceTurn();
    }
}
public class UIController : MonoBehaviour
{
    /// 
    
    public void EndPlayerTurn()
    {
        BattleController.instance.EndPlayerTurn();
    }
}

4. Refilling The Mana Pool

public class DeskController : MonoBehaviour
{
    ///
    
    public float waitBetweenDrawingCards = .25f;

    public void DrawMultipleCards(int amountToDraw)
    {
        StartCoroutine(DrawMultipleCo(amountToDraw));
    }

    IEnumerator DrawMultipleCo(int amountToDraw)
    {
        for (int i = 0; i < amountToDraw; i++)
        {
            DrawCardToHand();

            yield return new WaitForSeconds(waitBetweenDrawingCards);
        }
    }
}
public class BattleController : MonoBehaviour
{
    ///
    public int startingCardsAmount = 5;
    
    // Start is called before the first frame update
    void Start()
    {
        // playerMana = startingMana;
        // UIController.instance.SetPlayerManaText(playerMana);
        FillPlayerMana();

        // 初始化: 拿几张卡到手牌
        DeskController.instance.DrawMultipleCards(startingCardsAmount);
    }
    
    // 回复玩家的mana
    public void FillPlayerMana()
    {
        playerMana = startingMana;
        UIController.instance.SetPlayerManaText(playerMana);
    }

    // 回合前进(玩家出牌->玩家卡牌攻击(auto)->敌人出牌->敌人卡牌攻击(auto)
    public void AdvanceTurn()
    {
        ///

        switch (currentPhase)
        {
            case TurnOrder.playerActive:
                // 启用: 结束回合btn 和 取牌btn
                UIController.instance.endTurnButton.SetActive(true);
                UIController.instance.drawCardButton.SetActive(true);

                // 重新填充mana
                FillPlayerMana();

                break;
            case TurnOrder.playerCardAttacks:
                Debug.Log("2");
                AdvanceTurn();
                break;
            ///
        }
    }
}

5. Mana Pool Growth

public class BattleController : MonoBehaviour
{
    ///
    // 玩家当前最大mana数, 每回合+1
    private int _currentPlayerMaxMana;
    
    // Start is called before the first frame update
    void Start()
    {
        // playerMana = startingMana;
        // UIController.instance.SetPlayerManaText(playerMana);
        _currentPlayerMaxMana = startingMana;
        FillPlayerMana();

        // 初始化: 拿几张卡到手牌
        DeskController.instance.DrawMultipleCards(startingCardsAmount);
    }
    
    ///
    
    // 回复玩家的mana
    public void FillPlayerMana()
    {
        // playerMana = startingMana;
        playerMana = _currentPlayerMaxMana;
        UIController.instance.SetPlayerManaText(playerMana);
    }
    
    // 回合前进(玩家出牌->玩家卡牌攻击(auto)->敌人出牌->敌人卡牌攻击(auto)
    public void AdvanceTurn()
    {
        ///

        switch (currentPhase)
        {
            case TurnOrder.playerActive:
                // 启用: 结束回合btn 和 取牌btn
                UIController.instance.endTurnButton.SetActive(true);
                UIController.instance.drawCardButton.SetActive(true);

                // 回合开始, 玩家mana上限+1
                if (_currentPlayerMaxMana < maxMana)
                {
                    _currentPlayerMaxMana++;
                }

                // 重新填充mana
                FillPlayerMana();

                break;
            ///
        }
    }
}

6. Drawing Cards Each Turn

public class BattleController : MonoBehaviour
{
    /// 
    
    // 每回合开始抽多少张
    public int cardsToDrawPerTurn = 2;
    
    // Start is called before the first frame update
    void Start()
    {
        // 可能unity里的设值会覆盖(默认0, 可以自己改为2, 就不用下面这行), 这里手动设置为2
        cardsToDrawPerTurn = 2;
        
        ///
    }
    
    public void AdvanceTurn()
    {
        ///

        switch (currentPhase)
        {
            case TurnOrder.playerActive:
                ///

                // 重新填充mana
                FillPlayerMana();

                // 回合开始, 抽卡
                DeskController.instance.DrawMultipleCards(cardsToDrawPerTurn);

                break;
            ///
        }
    }
}

9. Doing Damage

复制一张牌到敌方区域他将卡牌转半圈, 卡牌信息又转回来, 但是我觉得没必要
作为新的预制件enemy card 拖动到prefabs, 点击第二个创建为旧预制件的变体而不是覆盖

public class Card : MonoBehaviour
{
    void Start()
    {
        // 如果没有这个, 我们刚才创建的enemy卡片会移动到player的point
        if(_targetPoint == Vector3.zero)
        {
            _targetPoint = transform.position;
            _targetRot = transform.rotation;
        }
        
        setupCard();

        theHC = FindObjectOfType<HandController>();
        _theCol = GetComponent<Collider>();
    }
}


  目录