스킬
UI 버튼이 눌렸을 때, 스킬이 사용되도록 만들어 보자.
우선, 스킬 버튼을 만들어 보자.
해당 버튼은 다음과 같은 스크립트를 갖고 있다.
버튼 입력 처리를 위한 Listener를 관리하는 스크립트이다.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
public class CButton : MonoBehaviour
{
[SerializeField]private Button button;
private void Awake()
{
button = GetComponent<Button>();
}
public void AddListener(UnityAction callback)
{
button.onClick.AddListener(callback);
}
public void RemoveListener(UnityAction callback)
{
button.onClick.RemoveListener(callback);
}
}
이제 버튼이 클릭되면 스킬이 실행되도록 Controller에서 callback을 추가해 주자.
우선, 버튼이 클릭되면 Animation이 출력하도록 만들었다.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class CharacterController : MonoBehaviour
{
[Header("Move")]
public bool bCanMove = true;
[Header("UI")]
public List<CButton> _buttons;
void Start()
{
foreach (var button in _buttons)
{
button.AddListener(FireSkill);
}
}
void FireSkill()
{
_animator.Rebind();
_animator.CrossFade("Alchemist_Attack", 0.1f);
}
public void SetCanMove(int flag)
{
bCanMove = flag == 1;
}
void Update()
{
if (bCanMove)
{
Vector2 moveValue = Move_Input.ReadValue<Vector2>();
if (moveValue.x != 0)
{
_spriteRenderer.flipX = moveValue.x < 0;
_rigidbody.velocity = Vector3.up * _rigidbody.velocity.y;
}
_animator.SetFloat(SpeedId, Mathf.Abs(moveValue.x));
transform.position += new Vector3(moveValue.x * Speed * Time.deltaTime, 0);
if (Jump_Input.triggered && IsGround)
{
_rigidbody.AddForce(Vector2.up * JumpSpeed, ForceMode2D.Impulse);
_animator.Play("Alchemist_Jump");
}
}
}
}
애니메이션이 실행되다 특정 프레임에서 Event를 발생시켜 설정해 놓은 함수가 실행되도록 만들었다.
public void SpawnDamageField(int index)
{
var damageField = Instantiate(_damageFields[index], transform.position, Quaternion.identity);
damageField._data = new DamageFieldData(){dir = this.direction, lifeTime = 3f, SpawnOffset = new Vector3(1.5f, 0f, 0f)};
}
Event Callback함수에서는 데미지 필드를 생성해서 몬스터가 충돌했을 때, 데미지를 주면 된다.
이때, 데미지 필드의 위치와 지속 시간 등의 데이터를 관리하기 위해 DamageFiledData라는 구조체를 만들어 설정해 주자.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public struct DamageFieldData
{
public Vector3 SpawnOffset;
public float lifeTime;
public float dir;
}
public class DamageField : MonoBehaviour
{
public DamageFieldData _data { get; set; }
private float time = 0f;
void Start()
{
transform.position += _data.SpawnOffset * _data.dir;
}
private void Update()
{
time += Time.deltaTime;
if(time > _data.lifeTime) Destroy(gameObject);
}
private void OnTriggerEnter2D(Collider2D other)
{
Debug.Log(other.gameObject.name);
}
}
데미지 필드에서는 Data를 프로퍼티로 갖고 있고 이를 생성하고 설정해 두면 Start함수가 실행되면서 위치를 변경한다.
그리고 몬스터와 충돌하면 몬스터에게 피해를 입힌다.(현재는 로그만)
몬스터가 피해를 입은 것을 표현하기 위해 체력을 추가해 보자.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Monster : MonoBehaviour
{
public int MaxHealth = 3;
private int currentHealth;
public void TakeDamage()
{
currentHealth--;
var last = LifeImages.Last();
LifeImages.Remove(last);
Destroy(last);
if (currentHealth == 0)
{
StartCoroutine(SetDeath());
}
}
private IEnumerator SetDeath()
{
_animator.CrossFade("Death", 0f);
yield return null;
while (_animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1f)
{
yield return null;
}
Destroy(gameObject);
}
}
피해를 입으면 체력이 1 줄고, 체력이 0이 된다면 죽는 애니메이션을 실행하고 해당 애니메이션이 종료되면 소멸한다.
그리고 Health를 시각화할 수 있게 몬스터에 Image를 추가할 수 있는 Canvas와 Container를 추가했다.
그리고 Monster의 Start에서 현재 체력만큼 이미지를 붙여준다.
void Start()
{
currentHealth = MaxHealth;
for (int i = 0; i < currentHealth; i++)
{
LifeImages.Add(Instantiate(LifePrefab, LifeContainer.transform));
}
}
이제 데미지 필드의 OnTriggerEnter2D에서 충돌한 몬스터의 TakeDamage를 실행하면 된다.
private void OnTriggerEnter2D(Collider2D other)
{
var monster = other.transform.root.GetComponent<Monster>();
if (!monster) return;
monster.TakeDamage();
Destroy(gameObject);
}
(간단 Tip: 최상위 오브젝트에 간단하게 접근하려면 컴포넌트의 transform.root에 접근하면 된다.)