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地板
右键, 创建3d地板

camera的位置
camera的位置

card frame 拖动到项目目录
card frame 拖动到项目目录


4个小卡片x分别是-1.5, -3, 1.5, 3
4个小卡片x分别是-1.5, -3, 1.5, 3

新建empty object
新建empty object

2. Making A Card

新建empty object
新建empty object
card model文件拖入项目
card model文件拖入项目



文字居中
文字居中

关掉3D icon
关掉3D icon

创建第2个, 后面加outline
创建第2个, 后面加outline

3. Finishing The Card Layout

从上到下auto size的最大值22, 20, 20
从上到下auto size的最大值22, 20, 20

拖动图片到source image属性, 点击preserve aspect可以按比例缩放
拖动图片到source image属性, 点击preserve aspect可以按比例缩放

新建image, 点击关闭maskable, add component添加mask
新建image, 点击关闭maskable, add component添加mask

调整mask image的大小
调整mask image的大小

这个随着canvas的创建而出现, 但是我们不需要, 所以删掉
这个随着canvas的创建而出现, 但是我们不需要, 所以删掉

3. Cards

1. Creating Our First Script

创建script文件夹
创建script文件夹
右键创建c#脚本
右键创建c#脚本

创建的时候, 名称和Hierarchy的项目名称对上, 否则创建完再改名, 脚本的class名称还是默认的
创建的时候, 名称和Hierarchy的项目名称对上, 否则创建完再改名, 脚本的class名称还是默认的

脚本拖动到card项目目录
脚本拖动到card项目目录
在这配置双击脚本打开的ide
在这配置双击脚本打开的ide

2. Making The Card Script Work

定义的属性在unity中显示了
定义的属性在unity中显示了
在unity中绑定变量
在unity中绑定变量

启动时设置text的值
启动时设置text的值
运行
运行
看到text确实被改变了
看到text确实被改变了

选择max的那个, 运行时就会全屏
选择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注释
textArea注释
unity中该选项发生变化
unity中该选项发生变化

创建专门存放card的文件夹
创建专门存放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 object

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

4. Holding Hands

1. Setting Up Our Hand

empty object
empty object
拖动handControler脚本到handController项目对象
拖动handControler脚本到handController项目对象

empty object
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属性
选择hand controller, 右上角锁住面板, 左侧选中卡片, 拖动到held cards属性

修改一下minPos的rotation(倾斜)
修改一下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属性
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
3d->spherep->向上移动一点->add component->rigidbody

小球和card碰撞测试
小球和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->拖动->改色(黑)
复制->改名 enemy card points->拖动->改色(黑)

isPlayerPoint check
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
打出一张牌, 看到hand controller的held cards的数量-1

6. Mana System

1. Spending Mana

新脚本
新脚本
empty object
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
ctrl c v


绑定text
绑定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不足)
添加警告文字(mana不足)

auto size->Alignment: Center->Font Assert: SDF outline->向上移动一点
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成为预制件, 可以重复使用
创建文件夹Prefabs, 将Card拖入, 此时Card成为预制件, 可以重复使用
删除原来的卡片
删除原来的卡片

拖入项目
拖入项目
复制3份->填入Hand Controller的heldCards
复制3份->填入Hand Controller的heldCards

预制件实例的override可以改变预制件的属性, 但是我们一般只是为了复用预制件, 没必要更改
预制件实例的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
记得绑定card -- deskController
按T, 出现很多副本
按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)移动一下
deskController(transform)移动一下
拖动CardModel到deskController
拖动CardModel到deskController

改变厚度
改变厚度
scale
scale
rotation
rotation

cardmodel->transform->y->-0.18, 这样抽牌看起来才是从上面抽
cardmodel->transform->y->-0.18, 这样抽牌看起来才是从上面抽

deskController-transform-y
deskController-transform-y
这样看起来牌会先翻过来再放到手中
这样看起来牌会先翻过来再放到手中

5. Click To Draw Cards

font(SDF)
font(SDF)

auto size
auto size
拖动blue到source image
拖动blue到source image
拖动
拖动

TextMeshPro Text (Ul)->extra settings->
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, 查了下
遇到bug, 查了下
Window > General > Console打开输出控制台
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
复制一个结束回合的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, 点击第二个创建为旧预制件的变体而不是覆盖
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>();
    }
}


Related Issues not found

Please contact @malred to initialize the comment

Error: Comments Not Initialized
Emoji | Preview
Code -1: Request has been terminated
Possible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.
Powered By Valine
v1.3.4
访问我的GitHub
邮件联系我
QQ联系我: 2725953379
访问我的GitHub
邮件联系我
QQ联系我: 2725953379