에셋 스토어를 둘러보다 깔끔한 픽셀 포션 아트를 찾았습니다.
https://assetstore.unity.com/packages/2d/pixel-art-potion-pack-animated-270453
저번에 쓰던 카드UI는 그대로 쓰고, 포션 스프라이트만 교체하고 진행하겠습니다.
저번에 카드와 이펙트에서 상속을 사용하다 보니 조금 복잡해진듯 해서, 정리를 하고 가겠습니다.

핵심적인 부분만 나타낸 UML입니다. 저번에 만든 부분과 만들어야하는 부분을 약간 추가했습니다.
이번에 만들 부분은 RedEffect 부분입니다.
RedEffect 에서는 범위 내의 적에게 도트 데미지를 주고 EnemyBT 에서 피격 및 죽는 모션까지 만들어보겠습니다.
카드와 이펙트를 꾸며보겠습니다.


조금 빨갛게 꾸며둔 뒤에 RedEffect를 살펴보겠습니다.
public class RedEffect : PotionEffect
{
private float tickTime = 1f;
public override void OnEnable()
{
base.OnEnable();
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.GetComponent<EnemyBT>() != null)
{
enemies.Add(collision.GetComponent<EnemyBT>());
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.GetComponent<EnemyBT>() != null)
enemies.Remove(collision.GetComponent<EnemyBT>());
}
public IEnumerator TimerCoroutine(float duringTime)
{
float elapsedTime = 0f;
float elapsedTickTime = -tickTime/2;
yield return new WaitForSeconds(PotionCard.THROWTIME);
ShowEffect();
while (elapsedTime < duringTime)
{
elapsedTime += Time.deltaTime;
if (elapsedTime - elapsedTickTime > tickTime)
{
// 데미지를 줌
elapsedTickTime += tickTime;
foreach (EnemyBT enemy in enemies)
{
// 공격력 구현
enemy.OnDamaged(2);
}
}
textMesh.text = (duringTime - elapsedTime).ToString("0.0");
yield return null;
}
Destroy(this.gameObject);
}
public override void StartEffect(float duringTime)
{
StartCoroutine(TimerCoroutine(duringTime));
}
}
일정 시간마다 피해를 주어야하기 때문에 OnTriggerEnter/Exit2D 에서 List에 범위안의 적들을 전부 모아줍니다.
그 후에 코루틴이 돌아가면서 tickTime 마다 데미지를 줍니다.
이번거를 만들면서 적 애니메이션도 수정을 해서, EnemyBT도 보겠습니다.

적의 Animator 입니다. 블랜더를 위한 float X, Y를 제외하고 나머지 애니메이션 제어를 위한 파라미터는 전부 bool로 뒀습니다.
public static void SetAnimatior(Animator anim, string name)
{
anim.SetBool("Attack", false);
anim.SetBool("Die", false);
anim.SetBool("Damage", false);
anim.SetBool("Walk", false);
anim.SetBool("Idle", false);
anim.SetBool(name, true);
}
코드에서는 이 함수로만 파라미터를 조작합니다.
public void OnDamaged(float damage)
{
enemyStat.Hp -= damage;
if (enemyStat.Hp > 0)
{
var animator = transform.GetComponent<Animator>();
SetAnimatior(animator, "Damage");
return;
}
}
RedEffect 에서 사용한 OnDamaged 함수입니다.
각 노드에서 참조중인 enemyStat의 체력을 깎아주고 Damage 애니메이션을 실행시킵니다.
public override Node SetupRoot()
{
Node root = new Selector(new List<Node> {
new Sequence(new List<Node>
{
new IsDead(transform, enemyStat),
new Disappear(transform, enemyStat)
}) ,
new Sequence(new List<Node>
{
new Search(transform, searchRange),
new Move(transform, destination, enemyStat)
}),
new Sequence(new List<Node>
{
new IsAttacking(transform),
new Track(transform, attackRange, enemyStat),
new Attack(transform)
})
});
return root;
}
체력을 감지하고 죽는걸 관리해주는 노드를 추가했습니다.
저번에 EnemyBT에 대해서 다뤘으니 이 부분은 넘어가겠습니다.
public override NodeState Evaluate()
{
// damage 받았을떄 success return해서 move는 안가게
if (transform.GetComponent<Animator>().GetBool("Damage"))
{
damageTrigger += Time.deltaTime;
if(damageTrigger >= 0.4f)
{
damageTrigger = 0f;
EnemyBT.SetAnimatior(transform.GetComponent<Animator>(), "Idle");
return NodeState.Failure;
}
return NodeState.Running;
}
if (stat.Hp <= 0)
{
if (!transform.GetComponent<Animator>().GetBool("Die"))
{
EnemyBT.SetAnimatior(transform.GetComponent<Animator>(), "Die");
}
return NodeState.Success;
}
else
{
return NodeState.Failure;
}
}
데미지를 받고 약 0.4초간 애니메이션이 실행되므로, 그 동안에는 Move 노드에 도달하지 못하도록 Running을 반환해줍니다.
만약 Hp가 0보다 작아지면 Success로 두고 Die애니메이션을 실행시킵니다. 이후는 Disappear 노드가 애니메이션이 끝날때까지 기다렸다가 Destroy 시킵니다.
나머지 부분들도 수정했지만, 애니메이션 조작 부분만 저 함수로 변경한거라 생략하겠습니다.

잘 작동하는 모습을 볼 수 있습니다.
점점 카드가 많아질 예정이니, ResourceManager 에서 각 요소를 받아올 수 있도록 하겠습니다.
json파일부터 시작하겠습니다.
{
"cardInfo" :[
{
"cardId": 11,
"cardType": 1,
"cardCost": 3,
"duration": 5,
"value": 2,
"cardName": "curse potion",
"cardDesc": "It's possible to slow down moving enemies."
},
{
"cardId": 12,
"cardType": 1,
"cardCost": 5,
"duration": 4,
"value": 1,
"cardName": "blood potion",
"cardDesc": "spread blood. it can hurt enemies."
}
]
}
이제 이 파일을 ResourceManager에서 게임 시작할 때 클래스로 받아오겠습니다.
[Serializable]
public class CardInfo
{
public int cardId;
public int cardCost;
public Define.CardType cardType;
public float duration;
public float value;
public string cardName;
public string cardDesc;
}
[Serializable]
public class CardInfos
{
public CardInfo[] cardInfo;
}
public class ResourceManager
{
private string cardInfoPath = "JsonData/CardData";
// json->리소스 받아오기, 배열에 저장
private CardInfos cardInfos = new CardInfos();
private GameObject[] cardPrefabs;
const int CARDOFFSET = 11;
public void Init()
{
cardInfos = JsonUtility.FromJson<CardInfos>(Resources.Load<TextAsset>(cardInfoPath).text);
cardPrefabs = Resources.LoadAll<GameObject>("Prefabs/Card");
}
public CardInfo GetCardInfo(int id)
{
return cardInfos.cardInfo[id - CARDOFFSET];
}
public GameObject GetCardPrefab(int id)
{
return cardPrefabs[id - CARDOFFSET];
}
}
json파일에서 배열을 바로 받아올 수 없어서, 클래스의 멤버 변수로 배열을 선언하고 그 클래스의 인스턴스로 받아옵니다.
프리팹도 잘 정리해둔 다음에 LoadAll 로 받아옵니다.
이제 ID만 있으면 GetCardInfo 로 정보를 가져올 수 있고, GetCardPrefab 으로 카드 프리팹도 가져올 수 있습니다.
원래 하드코딩으로 구현하던 부분들을 전부 ResourceManager 에서 값을 받아오거나 그 값을 함수의 인자로 넘기도록 처리하면 끝입니다. 이제 이펙트와 스프라이트만 있으면 카드를 간편하게 만들어낼 수 있습니다.
일단 여기까지 만든 후에,
여기까지 만들고 디자인만 깔끔하게 다듬으면 데모정도는 나올 것 같습니다.
학기중이라 속도가 느리지만 방학쯤 까지는 위에 말한것들을 목표로 달려보겠습니다.
https://assetstore.unity.com/packages/2d/pixel-art-potion-pack-animated-270453