하스스톤 유니티 튜토리얼



ItemSO.cs 소스입니다


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

[System.Serializable]
public class Item
{
    public string name;
    public int attack;
    public int health;
    public Sprite sprite;
    public float percent;
}

[CreateAssetMenu(fileName = "ItemSO", menuName = "Scriptable Object/ItemSO")]
public class ItemSO : ScriptableObject
{
    public Item[] items;
}









EndTurnBtn.cs 소스입니다


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

public class EndTurnBtn : MonoBehaviour
{
    [SerializeField] Sprite active;
    [SerializeField] Sprite inactive;
    [SerializeField] Text btnText;


    void Start()
    {
        Setup(false);
        TurnManager.OnTurnStarted += Setup;
    }

    void OnDestroy()
    {
        TurnManager.OnTurnStarted -= Setup;
    }

    public void Setup(bool isActive)
    {
        GetComponent<Image>().sprite = isActive ? active : inactive;
        GetComponent<Button>().interactable = isActive;
        btnText.color = isActive ? new Color32(255, 195, 90, 255) : new Color32(55, 55, 55, 255);
    }
}









NotificationPanel.cs 소스입니다


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

public class NotificationPanel : MonoBehaviour
{
[SerializeField] TMP_Text notificationTMP;


public void Show(string message)
{
notificationTMP.text = message;
Sequence sequence = DOTween.Sequence()
.Append(transform.DOScale(Vector3.one, 0.3f).SetEase(Ease.InOutQuad))
.AppendInterval(0.9f)
.Append(transform.DOScale(Vector3.zero, 0.3f).SetEase(Ease.InOutQuad));
}

void Start() => ScaleZero();

[ContextMenu("ScaleOne")]
void ScaleOne() => transform.localScale = Vector3.one;

[ContextMenu("ScaleZero")]
public void ScaleZero() => transform.localScale = Vector3.zero;
}










ResultPanel.cs 소스입니다


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using DG.Tweening;
using UnityEngine.SceneManagement;

public class ResultPanel : MonoBehaviour
{
[SerializeField] TMP_Text resultTMP;


public void Show(string message)
{
resultTMP.text = message;
transform.DOScale(Vector3.one, 0.5f).SetEase(Ease.InOutQuad);
}

public void Restart()
{
SceneManager.LoadScene(0);
}

void Start() => ScaleZero();

[ContextMenu("ScaleOne")]
void ScaleOne() => transform.localScale = Vector3.one;

[ContextMenu("ScaleZero")]
public void ScaleZero() => transform.localScale = Vector3.zero;
}









TitlePanel.cs 소스입니다


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

public class TitlePanel : MonoBehaviour
{
    public void StartGameClick()
    {
        GameManager.Inst.StartGame();
        Active(false);
    }

    public void Active(bool isActive)
    {
        gameObject.SetActive(isActive);
    }
}











CameraEffect.cs 소스입니다


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

public class CameraEffect : MonoBehaviour
{
[SerializeField] Material effectMat;


void OnRenderImage(RenderTexture _src, RenderTexture _dest)
{
if (effectMat == null)
return;

Graphics.Blit(_src, _dest, effectMat);
}

void OnDestroy()
{
SetGrayScale(false);
}

public void SetGrayScale(bool isGrayscale)
{
effectMat.SetFloat("_GrayscaleAmount", isGrayscale ? 1 : 0);
effectMat.SetFloat("_DarkAmount", isGrayscale ? 0.12f : 0);
}
}









Card.cs 소스입니다


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

public class Card : MonoBehaviour
{
    [SerializeField] SpriteRenderer card;
    [SerializeField] SpriteRenderer character;
    [SerializeField] TMP_Text nameTMP;
    [SerializeField] TMP_Text attackTMP;
    [SerializeField] TMP_Text healthTMP;
    [SerializeField] Sprite cardFront;
    [SerializeField] Sprite cardBack;

    public Item item;
    bool isFront;
    public PRS originPRS;


    public void Setup(Item item, bool isFront)
    {
        this.item = item;
        this.isFront = isFront;

        if (this.isFront)
        {
            character.sprite = this.item.sprite;
            nameTMP.text = this.item.name;
            attackTMP.text = this.item.attack.ToString();
            healthTMP.text = this.item.health.ToString();
        }
        else
        {
            card.sprite = cardBack;
            nameTMP.text = "";
            attackTMP.text = "";
            healthTMP.text = "";
        }
    }

    void OnMouseOver()
    {
        if (isFront)
            CardManager.Inst.CardMouseOver(this);
    }

    void OnMouseExit()
    {
        if (isFront)
            CardManager.Inst.CardMouseExit(this);
    }

    void OnMouseDown()
    {
        if (isFront)
            CardManager.Inst.CardMouseDown();
    }

    void OnMouseUp()
    {
        if (isFront)
            CardManager.Inst.CardMouseUp();
    }

    public void MoveTransform(PRS prs, bool useDotween, float dotweenTime = 0)
    {
        if (useDotween)
        {
            transform.DOMove(prs.pos, dotweenTime);
            transform.DORotateQuaternion(prs.rot, dotweenTime);
            transform.DOScale(prs.scale, dotweenTime);
        }
        else
        {
            transform.position = prs.pos;
            transform.rotation = prs.rot;
            transform.localScale = prs.scale;
        }
    }
}










CardManager.cs 소스입니다


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

public class CardManager : MonoBehaviour
{
    public static CardManager Inst { get; private set; }
    void Awake() => Inst = this;

    [SerializeField] ItemSO itemSO;
    [SerializeField] GameObject cardPrefab;
    [SerializeField] List<Card> myCards;
    [SerializeField] List<Card> otherCards;
    [SerializeField] Transform cardSpawnPoint;
    [SerializeField] Transform otherCardSpawnPoint;
    [SerializeField] Transform myCardLeft;
    [SerializeField] Transform myCardRight;
    [SerializeField] Transform otherCardLeft;
    [SerializeField] Transform otherCardRight;
    [SerializeField] ECardState eCardState;
    
    List<Item> itemBuffer;
    Card selectCard;
    bool isMyCardDrag;
    bool onMyCardArea;
    enum ECardState { Nothing, CanMouseOver, CanMouseDrag }
    int myPutCount;


    public Item PopItem()
    {
        if (itemBuffer.Count == 0)
            SetupItemBuffer();

        Item item = itemBuffer[0];
        itemBuffer.RemoveAt(0);
        return item;
    }

    void SetupItemBuffer()
    {
        itemBuffer = new List<Item>(100);
        for (int i = 0; i < itemSO.items.Length; i++)
        {
            Item item = itemSO.items[i];
            for (int j = 0; j < item.percent; j++)
                itemBuffer.Add(item);
        }

        for (int i = 0; i < itemBuffer.Count; i++)
        {
            int rand = Random.Range(i, itemBuffer.Count);
            Item temp = itemBuffer[i];
            itemBuffer[i] = itemBuffer[rand];
            itemBuffer[rand] = temp;
        }
    }

void Start()
{
        SetupItemBuffer();
        TurnManager.OnAddCard += AddCard;
        TurnManager.OnTurnStarted += OnTurnStarted;
    }

void OnDestroy()
{
        TurnManager.OnAddCard -= AddCard;
        TurnManager.OnTurnStarted -= OnTurnStarted;
    }

    void OnTurnStarted(bool myTurn) 
    {
        if (myTurn)
            myPutCount = 0;
    }

void Update()
{
        if (isMyCardDrag)
            CardDrag();

        DetectCardArea();
        SetECardState();
    }

void AddCard(bool isMine)
    {
        var cardObject = Instantiate(cardPrefab, cardSpawnPoint.position, Utils.QI);
        var card = cardObject.GetComponent<Card>();
        card.Setup(PopItem(), isMine);
        (isMine ? myCards : otherCards).Add(card);

        SetOriginOrder(isMine); 
        CardAlignment(isMine);
    }

    void SetOriginOrder(bool isMine)
    {
        int count = isMine ? myCards.Count : otherCards.Count;
        for (int i = 0; i < count; i++)
        {
            var targetCard = isMine ? myCards[i] : otherCards[i];
            targetCard?.GetComponent<Order>().SetOriginOrder(i);
        }
    }

    void CardAlignment(bool isMine) 
    {
        List<PRS> originCardPRSs = new List<PRS>();
        if (isMine)
            originCardPRSs = RoundAlignment(myCardLeft, myCardRight, myCards.Count, 0.5f, Vector3.one * 1.9f);
        else
            originCardPRSs = RoundAlignment(otherCardLeft, otherCardRight, otherCards.Count, -0.5f, Vector3.one * 1.9f);

        var targetCards = isMine ? myCards : otherCards;
        for (int i = 0; i < targetCards.Count; i++)
        {
            var targetCard = targetCards[i];

            targetCard.originPRS = originCardPRSs[i];
            targetCard.MoveTransform(targetCard.originPRS, true, 0.7f);
        }
    }

    List<PRS> RoundAlignment(Transform leftTr, Transform rightTr, int objCount, float height, Vector3 scale)
    {
        float[] objLerps = new float[objCount];
        List<PRS> results = new List<PRS>(objCount);

        switch (objCount)
        {
            case 1: objLerps = new float[] { 0.5f }; break;
            case 2: objLerps = new float[] { 0.27f, 0.73f }; break;
            case 3: objLerps = new float[] { 0.1f, 0.5f, 0.9f }; break;
            default:
                float interval = 1f / (objCount - 1);
                for (int i = 0; i < objCount; i++)
                    objLerps[i] = interval * i;
                break;
        }

        for (int i = 0; i < objCount; i++)
        {
            var targetPos = Vector3.Lerp(leftTr.position, rightTr.position, objLerps[i]);
            var targetRot = Utils.QI;
            if (objCount >= 4)
            {
                float curve = Mathf.Sqrt(Mathf.Pow(height, 2) - Mathf.Pow(objLerps[i] - 0.5f, 2));
                curve = height >= 0 ? curve : -curve;
                targetPos.y += curve;
                targetRot = Quaternion.Slerp(leftTr.rotation, rightTr.rotation, objLerps[i]);
            }
            results.Add(new PRS(targetPos, targetRot, scale));
        }
        return results;
    }

    public bool TryPutCard(bool isMine) 
    {
        if (isMine && myPutCount >= 1)
            return false;

        if (!isMine && otherCards.Count <= 0)
            return false;

        Card card = isMine ? selectCard : otherCards[Random.Range(0, otherCards.Count)];
        var spawnPos = isMine ? Utils.MousePos : otherCardSpawnPoint.position;
        var targetCards = isMine ? myCards : otherCards;

        if (EntityManager.Inst.SpawnEntity(isMine, card.item, spawnPos))
        {
            targetCards.Remove(card);
            card.transform.DOKill();
            DestroyImmediate(card.gameObject);
            if (isMine)
            {
                selectCard = null;
                myPutCount++;
            }
            CardAlignment(isMine);
            return true;
        }
        else 
        {
            targetCards.ForEach(x => x.GetComponent<Order>().SetMostFrontOrder(false));
            CardAlignment(isMine);
            return false;
        }
    }



    #region MyCard

    public void CardMouseOver(Card card)
    {
        if (eCardState == ECardState.Nothing)
            return;

        selectCard = card;
        EnlargeCard(true, card);
    }

    public void CardMouseExit(Card card)
    {
        EnlargeCard(false, card);
    }

    public void CardMouseDown() 
    {
        if (eCardState != ECardState.CanMouseDrag)
            return;

        isMyCardDrag = true;
    }

    public void CardMouseUp()
    {
        isMyCardDrag = false;

        if (eCardState != ECardState.CanMouseDrag)
            return;

        if (onMyCardArea)
            EntityManager.Inst.RemoveMyEmptyEntity();
        else
            TryPutCard(true);
    }

    void CardDrag()
    {
        if (eCardState != ECardState.CanMouseDrag)
            return;

        if (!onMyCardArea)
        {
            selectCard.MoveTransform(new PRS(Utils.MousePos, Utils.QI, selectCard.originPRS.scale), false);
            EntityManager.Inst.InsertMyEmptyEntity(Utils.MousePos.x);
        }
    }

    void DetectCardArea()
    {
        RaycastHit2D[] hits = Physics2D.RaycastAll(Utils.MousePos, Vector3.forward);
        int layer = LayerMask.NameToLayer("MyCardArea");
        onMyCardArea = Array.Exists(hits, x => x.collider.gameObject.layer == layer);
    }

    void EnlargeCard(bool isEnlarge, Card card)
    {
        if (isEnlarge)
        {
            Vector3 enlargePos = new Vector3(card.originPRS.pos.x, -4.8f, -10f);
            card.MoveTransform(new PRS(enlargePos, Utils.QI, Vector3.one * 3.5f), false);
        }
        else
            card.MoveTransform(card.originPRS, false);

        card.GetComponent<Order>().SetMostFrontOrder(isEnlarge);
    }

    void SetECardState()
    {
        if (TurnManager.Inst.isLoading)
            eCardState = ECardState.Nothing;

        else if (!TurnManager.Inst.myTurn || myPutCount == 1 || EntityManager.Inst.IsFullMyEntities)
            eCardState = ECardState.CanMouseOver;

        else if (TurnManager.Inst.myTurn && myPutCount == 0)
            eCardState = ECardState.CanMouseDrag;
    }

    #endregion
}










Damage.cs 소스입니다


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

public class Damage : MonoBehaviour
{
    [SerializeField] TMP_Text damageTMP;
    Transform tr;

    public void SetupTransform(Transform tr)
    {
        this.tr = tr;
    }

    void Update()
    {
        if (tr != null)
            transform.position = tr.position;
    }

    public void Damaged(int damage)
    {
        if (damage <= 0)
            return;

        GetComponent<Order>().SetOrder(1000);
        damageTMP.text = $"-{damage}";

        Sequence sequence = DOTween.Sequence()
            .Append(transform.DOScale(Vector3.one * 1.8f, 0.5f).SetEase(Ease.InOutBack))
            .AppendInterval(1.2f)
            .Append(transform.DOScale(Vector3.zero, 0.5f).SetEase(Ease.InOutBack))
            .OnComplete(() => Destroy(gameObject));
    }
}










Entity.cs 소스입니다


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

public class Entity : MonoBehaviour
{
    [SerializeField] Item item;
    [SerializeField] SpriteRenderer entity;
    [SerializeField] SpriteRenderer character;
    [SerializeField] TMP_Text nameTMP;
    [SerializeField] TMP_Text attackTMP;
    [SerializeField] TMP_Text healthTMP;
    [SerializeField] GameObject sleepParticle;

    public int attack;
    public int health;
    public bool isMine;
    public bool isDie;
    public bool isBossOrEmpty;
    public bool attackable;
    public Vector3 originPos;
    int liveCount;


    void Start()
    {
        TurnManager.OnTurnStarted += OnTurnStarted;
    }

    void OnDestroy()
    {
        TurnManager.OnTurnStarted -= OnTurnStarted;
    }

    void OnTurnStarted(bool myTurn)
    {
        if (isBossOrEmpty)
            return;

        if (isMine == myTurn)
            liveCount++;

        sleepParticle.SetActive(liveCount < 1);
    }

    public void Setup(Item item)
    {
        attack = item.attack;
        health = item.health;

        this.item = item;
        character.sprite = this.item.sprite;
        nameTMP.text = this.item.name;
        attackTMP.text = attack.ToString();
        healthTMP.text = health.ToString();
    }

    void OnMouseDown()
    {
        if (isMine)
            EntityManager.Inst.EntityMouseDown(this);
    }

    void OnMouseUp()
    {
        if (isMine)
            EntityManager.Inst.EntityMouseUp();
    }

    void OnMouseDrag()
    {
        if (isMine)
            EntityManager.Inst.EntityMouseDrag();
    }

    public bool Damaged(int damage)
    {
        health -= damage;
        healthTMP.text = health.ToString();

        if (health <= 0)
        {
            isDie = true;
            return true;
        }
        return false;
    }

    public void MoveTransform(Vector3 pos, bool useDotween, float dotweenTime = 0)
    {
        if (useDotween)
            transform.DOMove(pos, dotweenTime);
        else
            transform.position = pos;
    }
}











EntityManager.cs 소스입니다


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class EntityManager : MonoBehaviour
{
public static EntityManager Inst { get; private set; }
void Awake() => Inst = this;

[SerializeField] GameObject entityPrefab;
[SerializeField] GameObject damagePrefab;
[SerializeField] List<Entity> myEntities;
[SerializeField] List<Entity> otherEntities;
[SerializeField] GameObject TargetPicker;
[SerializeField] Entity myEmptyEntity;
[SerializeField] Entity myBossEntity;
[SerializeField] Entity otherBossEntity;

const int MAX_ENTITY_COUNT = 6;
public bool IsFullMyEntities => myEntities.Count >= MAX_ENTITY_COUNT && !ExistMyEmptyEntity;
bool IsFullOtherEntities => otherEntities.Count >= MAX_ENTITY_COUNT;
bool ExistTargetPickEntity => targetPickEntity != null;
bool ExistMyEmptyEntity => myEntities.Exists(x => x == myEmptyEntity);
int MyEmptyEntityIndex => myEntities.FindIndex(x => x == myEmptyEntity);
bool CanMouseInput => TurnManager.Inst.myTurn && !TurnManager.Inst.isLoading;

Entity selectEntity;
Entity targetPickEntity;
WaitForSeconds delay1 = new WaitForSeconds(1);
WaitForSeconds delay2 = new WaitForSeconds(2);



void Start()
{
TurnManager.OnTurnStarted += OnTurnStarted;
}

void OnDestroy()
{
TurnManager.OnTurnStarted -= OnTurnStarted;
}

void OnTurnStarted(bool myTurn)
{
AttackableReset(myTurn);

if (!myTurn)
StartCoroutine(AICo());
}

void Update()
{
ShowTargetPicker(ExistTargetPickEntity);
}

IEnumerator AICo()
{
CardManager.Inst.TryPutCard(false);
yield return delay1;

// attackable이 true인 모든 otherEntites를 가져와 순서를 섞는다
var attackers = new List<Entity>(otherEntities.FindAll(x => x.attackable == true));
for (int i = 0; i < attackers.Count; i++)
{
int rand = Random.Range(i, attackers.Count);
Entity temp = attackers[i];
attackers[i] = attackers[rand];
attackers[rand] = temp;
}

// 보스를 포함한 myEntities를 랜덤하게 시간차 공격한다
foreach (var attacker in attackers)
{
var defenders = new List<Entity>(myEntities);
defenders.Add(myBossEntity);
int rand = Random.Range(0, defenders.Count);
Attack(attacker, defenders[rand]);

if (TurnManager.Inst.isLoading)
yield break;

yield return delay2;
}
TurnManager.Inst.EndTurn();
}


void EntityAlignment(bool isMine)
{
float targetY = isMine ? -4.35f : 4.15f;
var targetEntities = isMine ? myEntities : otherEntities;

for (int i = 0; i < targetEntities.Count; i++)
{
float targetX = (targetEntities.Count - 1) * -3.4f + i * 6.8f;

var targetEntity = targetEntities[i];
targetEntity.originPos = new Vector3(targetX, targetY, 0);
targetEntity.MoveTransform(targetEntity.originPos, true, 0.5f);
targetEntity.GetComponent<Order>()?.SetOriginOrder(i);
}
}

public void InsertMyEmptyEntity(float xPos)
{
if (IsFullMyEntities)
return;

if (!ExistMyEmptyEntity)
myEntities.Add(myEmptyEntity);

Vector3 emptyEntityPos = myEmptyEntity.transform.position;
emptyEntityPos.x = xPos;
myEmptyEntity.transform.position = emptyEntityPos;

int _emptyEntityIndex = MyEmptyEntityIndex;
myEntities.Sort((entity1, entity2) => entity1.transform.position.x.CompareTo(entity2.transform.position.x));
if (MyEmptyEntityIndex != _emptyEntityIndex)
EntityAlignment(true);
}

public void RemoveMyEmptyEntity()
{
if (!ExistMyEmptyEntity)
return;

myEntities.RemoveAt(MyEmptyEntityIndex);
EntityAlignment(true);
}

public bool SpawnEntity(bool isMine, Item item, Vector3 spawnPos)
{
if (isMine)
{
if (IsFullMyEntities || !ExistMyEmptyEntity)
return false;
}
else
{
if (IsFullOtherEntities)
return false;
}

var entityObject = Instantiate(entityPrefab, spawnPos, Utils.QI);
var entity = entityObject.GetComponent<Entity>();

if (isMine)
myEntities[MyEmptyEntityIndex] = entity;
else
otherEntities.Insert(Random.Range(0, otherEntities.Count), entity);

entity.isMine = isMine;
entity.Setup(item);
EntityAlignment(isMine);

return true;
}

public void EntityMouseDown(Entity entity) 
{
if (!CanMouseInput)
return;

selectEntity = entity;
}

public void EntityMouseUp() 
{
if (!CanMouseInput)
return;

// selectEntity, targetPickEntity 둘다 존재하면 공격한다. 바로 null, null로 만든다.
if (selectEntity && targetPickEntity && selectEntity.attackable)
Attack(selectEntity, targetPickEntity);

selectEntity = null;
targetPickEntity = null;
}

public void EntityMouseDrag() 
{
if (!CanMouseInput || selectEntity == null)
return;

// other 타겟엔티티 찾기
bool existTarget = false;
foreach (var hit in Physics2D.RaycastAll(Utils.MousePos, Vector3.forward))
{
Entity entity = hit.collider?.GetComponent<Entity>();
if (entity != null && !entity.isMine && selectEntity.attackable)
{
targetPickEntity = entity;
existTarget = true;
break;
}
}
if (!existTarget)
targetPickEntity = null;
}

void Attack(Entity attacker, Entity defender)
{
// _attacker가 _defender의 위치로 이동하다 원래 위치로 온다, 이때 order가 높다
attacker.attackable = false;
attacker.GetComponent<Order>().SetMostFrontOrder(true);

Sequence sequence = DOTween.Sequence()
.Append(attacker.transform.DOMove(defender.originPos, 0.4f)).SetEase(Ease.InSine)
.AppendCallback(() =>
{
attacker.Damaged(defender.attack);
defender.Damaged(attacker.attack);
SpawnDamage(defender.attack, attacker.transform);
SpawnDamage(attacker.attack, defender.transform);
})
.Append(attacker.transform.DOMove(attacker.originPos, 0.4f)).SetEase(Ease.OutSine)
.OnComplete(() => AttackCallback(attacker, defender));
}

void AttackCallback(params Entity[] entities)
{
// 죽을 사람 골라서 죽음 처리
entities[0].GetComponent<Order>().SetMostFrontOrder(false);

foreach (var entity in entities)
{
if (!entity.isDie || entity.isBossOrEmpty)
continue;

if (entity.isMine)
myEntities.Remove(entity);
else
otherEntities.Remove(entity);

Sequence sequence = DOTween.Sequence()
.Append(entity.transform.DOShakePosition(1.3f))
.Append(entity.transform.DOScale(Vector3.zero, 0.3f)).SetEase(Ease.OutCirc)
.OnComplete(() =>
{
EntityAlignment(entity.isMine);
Destroy(entity.gameObject);
});
}
StartCoroutine(CheckBossDie());
}

IEnumerator CheckBossDie()
{
yield return delay2;

if (myBossEntity.isDie)
StartCoroutine(GameManager.Inst.GameOver(false));

if (otherBossEntity.isDie)
StartCoroutine(GameManager.Inst.GameOver(true));
}

public void DamageBoss(bool isMine, int damage)
{
var targetBossEntity = isMine ? myBossEntity : otherBossEntity;
targetBossEntity.Damaged(damage);
StartCoroutine(CheckBossDie());
}

void ShowTargetPicker(bool isShow)
{
TargetPicker.SetActive(isShow);
if (ExistTargetPickEntity)
TargetPicker.transform.position = targetPickEntity.transform.position;
}

void SpawnDamage(int damage, Transform tr)
{
if (damage <= 0)
return;

var damageComponent = Instantiate(damagePrefab).GetComponent<Damage>();
damageComponent.SetupTransform(tr);
damageComponent.Damaged(damage);
}

public void AttackableReset(bool isMine)
{
var targetEntites = isMine ? myEntities : otherEntities;
targetEntites.ForEach(x => x.attackable = true);
}

}














GameManager.cs 소스입니다


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

// 치트, UI, 랭킹, 게임오버
public class GameManager : MonoBehaviour
{
    public static GameManager Inst { get; private set; }
    void Awake() => Inst = this;

    [Multiline(10)]
    [SerializeField] string cheatInfo;
    [SerializeField] NotificationPanel notificationPanel;
    [SerializeField] ResultPanel resultPanel;
    [SerializeField] TitlePanel titlePanel;
    [SerializeField] CameraEffect cameraEffect;
    [SerializeField] GameObject endTurnBtn;

    WaitForSeconds delay2 = new WaitForSeconds(2);


    void Start()
    {
        UISetup();
    }

    void UISetup()
    {
        notificationPanel.ScaleZero();
        resultPanel.ScaleZero();
        titlePanel.Active(true);
        cameraEffect.SetGrayScale(false);
    }

    void Update()
    {
#if UNITY_EDITOR
        InputCheatKey();
#endif
    }

    void InputCheatKey()
    {
        if (Input.GetKeyDown(KeyCode.Keypad1))
            TurnManager.OnAddCard?.Invoke(true);

        if (Input.GetKeyDown(KeyCode.Keypad2))
            TurnManager.OnAddCard?.Invoke(false);

        if (Input.GetKeyDown(KeyCode.Keypad3))
            TurnManager.Inst.EndTurn();

        if (Input.GetKeyDown(KeyCode.Keypad4))
            CardManager.Inst.TryPutCard(false);

        if (Input.GetKeyDown(KeyCode.Keypad5))
            EntityManager.Inst.DamageBoss(true, 19);

        if (Input.GetKeyDown(KeyCode.Keypad6))
            EntityManager.Inst.DamageBoss(false, 19);
    }

    public void StartGame()
    {
        StartCoroutine(TurnManager.Inst.StartGameCo());
    }

    public void Notification(string message)
    {
        notificationPanel.Show(message);
    }

    public IEnumerator GameOver(bool isMyWin)
    {
        TurnManager.Inst.isLoading = true;
        endTurnBtn.SetActive(false);
        yield return delay2;

        TurnManager.Inst.isLoading = true;
        resultPanel.Show(isMyWin ? "승리" : "패배");
        cameraEffect.SetGrayScale(true);
    }
}













Order.cs 소스입니다


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

public class Order : MonoBehaviour
{
    [SerializeField] Renderer[] backRenderers;
    [SerializeField] Renderer[] middleRenderers;
    [SerializeField] string sortingLayerName;
    int originOrder;


    public void SetOriginOrder(int originOrder)
    {
        this.originOrder = originOrder;
        SetOrder(originOrder);
    }

    public void SetMostFrontOrder(bool isMostFront)
    {
        SetOrder(isMostFront ? 100 : originOrder);
    }

    public void SetOrder(int order)
    {
        int mulOrder = order * 10;

        foreach (var renderer in backRenderers)
        {
            renderer.sortingLayerName = sortingLayerName;
            renderer.sortingOrder = mulOrder;
        }

        foreach (var renderer in middleRenderers)
        {
            renderer.sortingLayerName = sortingLayerName;
            renderer.sortingOrder = mulOrder + 1;
        }
    }
}












TurnManager.cs 소스입니다


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

public class TurnManager : MonoBehaviour
{
public static TurnManager Inst { get; private set; }
void Awake() => Inst = this;

[Header("Develop")]
[SerializeField] [Tooltip("시작 턴 모드를 정합니다")] ETurnMode eTurnMode;
[SerializeField] [Tooltip("카드 배분이 매우 빨라집니다")] bool fastMode;
[SerializeField] [Tooltip("시작 카드 개수를 정합니다")] int startCardCount;

[Header("Properties")]
public bool isLoading; // 게임 끝나면 isLoading을 true로 하면 카드와 엔티티 클릭방지
public bool myTurn;

enum ETurnMode { Random, My, Other }
WaitForSeconds delay05 = new WaitForSeconds(0.5f);
WaitForSeconds delay07 = new WaitForSeconds(0.7f);

public static Action<bool> OnAddCard;
public static event Action<bool> OnTurnStarted;


void GameSetup()
{
if (fastMode)
delay05 = new WaitForSeconds(0.05f);

switch (eTurnMode)
{
case ETurnMode.Random:
myTurn = Random.Range(0, 2) == 0;
break;
case ETurnMode.My:
myTurn = true;
break;
case ETurnMode.Other:
myTurn = false;
break;
}
}

public IEnumerator StartGameCo()
{
GameSetup();
isLoading = true;

for (int i = 0; i < startCardCount; i++)
{
yield return delay05;
OnAddCard?.Invoke(false);
yield return delay05;
OnAddCard?.Invoke(true);
}
StartCoroutine(StartTurnCo());
}

IEnumerator StartTurnCo()
{
isLoading = true;
if (myTurn)
GameManager.Inst.Notification("나의 턴");

yield return delay07;
OnAddCard?.Invoke(myTurn);
yield return delay07;
isLoading = false;
OnTurnStarted?.Invoke(myTurn);
}

public void EndTurn()
{
myTurn = !myTurn;
StartCoroutine(StartTurnCo());
}
}













Utils.cs 소스입니다


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


[System.Serializable]
public class PRS
{
public Vector3 pos;
public Quaternion rot;
public Vector3 scale;

public PRS(Vector3 pos, Quaternion rot, Vector3 scale)
{
this.pos = pos;
this.rot = rot;
this.scale = scale;
}
}

public class Utils
{
public static Quaternion QI => Quaternion.identity;

public static Vector3 MousePos
{
get
{
Vector3 result = Camera.main.ScreenToWorldPoint(Input.mousePosition);
result.z = -10;
return result;
}
}
}













GrayscaleShader.shader 소스입니다


Shader "Custom/GrayscaleShader"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_GrayscaleAmount("Gray Scale Amount", Range(0, 1)) = 1.0
_DarkAmount("Dark Amount", Range(0, 1)) = 1.0
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};

v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}

sampler2D _MainTex;
float _GrayscaleAmount;
float _DarkAmount;

fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb = lerp(col.rgb, dot(col.rgb, float3(0.3, 0.59, 0.11)), _GrayscaleAmount);
col.rgb -= _DarkAmount;

return col;
}
ENDCG
}
}
}