Skill 만들기
스킬 버튼 만들기 & 스킬 실행하기
우선 스킬 버튼을 UI상에서 추가하고, 앵커를 맞춰 배치해준다.
*앵커의 위치를 맞춰주면, 해상도가 바뀌어도 최대한 잡아둔 위치 주변에 머무른다. (앵커로부터의 거리이기 때문에)
버튼에 스크립트를 달아준다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
public class CButton : MonoBehaviour
{
[SerializeField]private Button button;
void Awake()
{
button = GetComponent<Button>();
}
public void AddListener(UnityAction listener)
{
button.onClick.AddListener(listener);
}
public void RemoveListener(UnityAction listener)
{
button.onClick.RemoveListener(listener);
}
}
이 Class는, 달린 부모 오브젝트의 Button컴포넌트를 가져와서 button이라는 변수에 할당해준다.
그리고 두 가지 함수를 사용할수 있다. AddListener와 RevoeListener이다.
- AddListener : 버튼을 클릭했을 때 발동되는 기능을 추가한다.
- RemoveListnener : 버튼을 클릭했을 때 발동되는 기능을 삭제한다.
이제 , 만들어두었던 ChaController(CharacterController)에 버튼을 이용한 액션들을 달아줄 것이다.
버튼을 하나만 달면 리스트를 사용하지 않아도 괜찮지만, 확장성을 위해 다음과 같은 변수를 선언해준다.
public List<CButton> _buttons;
이제 버튼들에 각각의 스킬들을 할당해줄 차례이다. 우리는 3개의 버튼을 만들었으니 세 번의 할당을 한다.
또 생성될 데미지필드를 위한 스크립트를 하나 만들어준다. 이 데미지필드는 데미지필드 안에 몬스터가 들어오면, HP의 가 변화하게 하기 위한 이유 등으로 필요하다. 또 각각 스킬 프리팹 안에 들어가 ,이를 생성하는데에도 접근할 수 있는 역할을 한다.
public List<DamageField> _damageFields;
문제 1 : 버튼들을 하나의 코드로 간단하게 할당할수 있는 방법은 없는가 ?
일단 내가 행한 방법은 하나씩 할당해 주는 것이다. 버튼의 수가 많지 않아 쉬웠지만, 스킬이 수십개씩 된다면 ?
아마 매우 고된 과정일 것이다.
_buttons[0].AddListener(FireSkill);
_buttons[1].AddListener(UltimateSkill);
_buttons[2].AddListener(RangeSkill);
3개의 스킬을 버튼 1, 2, 3 에 할당해준다.
우리는 강사님의 방식대로, transition으로 애니메이터를 플레이하지 않고, 직접적으로 play를 할 것이다.
void FireSkill()
{
_animator.Play("Attack");
}
void UltimateSkill()
{
_animator.Play("Ultimate");
}
void RangeSkill()
{
_animator.Play("Range");
}
그리고 animator에서 조작을 통해, 각각의 다른 함수를 실행시킬 것이다.
함수의 한 예시이다.
void UltimateDamageField(int index)
{
Debug.Log("UltimateDamgeField 실행");
GameObject go = Instantiate(_damageFields[index].gameObject, transform);
go.GetComponent<DamageField>().MyOwnerTag = "Player";
go.transform.position = transform.position + transform.right * _damageFieldDatas.distance;
Destroy(go , 3.0f);
}
우선 잘 실행되는지 확인하기 위해 로그를 찍어주고, 새로운 오브젝트를 생성하고 할당한다.
이때, 인스펙터 창에 등록된 다른 데미지필드들을 컴포넌트로 갖고 있는 오브젝트를 생성한다.
생성된 데미지 필드 오브젝트는, 몬스터와의 충돌 등을 위해 태그를 설정해주고,
지금의 위치는 transform, 즉 부모 오브젝트의(캐릭터)의 위치이니 오른쪽으로 이동하게 해 준다.
그리고 3초뒤에 파괴한다.
여기서 _damageFieldDatas.distance 는 지금 당장은 distace만 들어있는 구조체이다. 나중에 datafield가 크기도 달라지고, 거리도 달라지는 등이 있으면 이것 저것 추가하고 리스트로 만들어 저장하겠지만, 지금 당장은 하나로 만들었다.
[Serializable]
public struct DamageFieldData
{
public float distance;
}
*위는 구조체 , 밑은 변수 선언 // 인스펙터 창에서 생성되는 거리를 조절할 수 있다.
public DamageFieldData _damageFieldDatas;
문제 2 : 오른쪽에만 생성되지 않게 하려면 방향을 어떻게 설정해야하는가 ?
마침 3개의 애니메이션이 있다. 각각 다음과 같이 맞는 Function을 넣어주고, int값도 넣어준다.
위 사진은, 애니메이션이 플레이되고, 1 :00에 도달하면 UltmateDamageField라는 인덱스에 1을 넣으면서 실행한다는 의미이다. 마찬가지로 다른 2개의 애니메이션에도 각각에 맞는 Function(각각의 DamageField)와 index를 넣어준다.
index는 인스펙터 창에서 선택할 damagefield의 순서이므로, 0,1,2를 버튼 순서에 맞게 넣어준다.
문제 3 : 똑같은 애니메이션으로, 다른 스킬들을 구현하려면 어떻게 해야하는가 ?
이제 인스펙터 창에서 데이터필드프리팹을 할당해줄 차례이다. 각각의 데미지필드 프리팹은 하위 오브젝트로 모션을 갖고 있고, 컴포넌트로 데미지필드를 갖고 있다. (간단하게 하기 위해서, 각각 다른 데미지 필드일수도 있다. )
애니메이션 동작 시, 움직이지 않게 하려면 간단하게 애니메이션에
CanMove라는 것을 달아준다. 그리고 다음과 같은 함수에 연결한다.
public bool canMove = true;
void CanMove(int bMove)
{
canMove = bMove == 1;
}
void Update()
{
if (!canMove)
{
moveValue = Vector2.zero;
}
}
몬애니메이션이 동작하기 시작할 때 0값을 보내주고 끝날 때 1 값을 보내준다.
받은 값은 bMove로 들어가 1과 비교를 하게 된다.
만약 0이라면 false값이므로 canMove가 false 값이 되고,
moveValue가 Vector2.zero가 된다.
스터 flip과 똑같은 로직으로,
몬스터 HP & Skill 적중시 체력 닳기
위를 보면, 데미지필드 프리팹에 콜라이더가 있는 것을 확인할 수 있다. 이는 충돌감지를 위한 것이다.
스킬마다 모션에 따라 수동으로 다르게 설정해 주었다.
이제, 몬스터에게 HP를 주고, HP bar를 표시할 것이다.
변수를 선언해준다.
public int MonsterHp = 100;
public int MonsterMaxHp = 100;
public Transform Hpbar;
HP bar는 도저히 UI로 하는 방식이 떠오르지 않아서 네모난 sprite의 크기가 작아지고 커지고로 표현했다.
*인스펙터에서 HPbar 할당에 주의
public void HPUpdate(float MonsterHp)
{
if (MonsterHp >= 0)
{
float hpratio = MonsterHp / MonsterMaxHp;
Hpbar.localScale = new Vector3(hpratio, 0.2f, 1);
}
if (MonsterHp < 0)
{
Destroy(this.gameObject);
Debug.Log("이미 죽은 몬스터입니다");
}
}
계속해서 hp를 업데이트하고 표시하는 것은 비효율적이니, hp가 변하는 상황에 호출할 수 있도록 함수를 만들었다.
hp가 변한 상황에서, 0보다 크다면 현재hp/최대hp 비율을 구하고, hpbar의 스케일을 x축만 줄여준다.
그렇다면 hpbar가 줄어드는 것 같은 효과가 나게 된다.
그리고 몬스터의 hp가 0보다 작다면, 몬스터를 파괴하고 죽었다는 메세지를 출력한다.
hpbar는 다음과 같이 Monster에 자식 오브젝트로 달아주고, 이미지만 표현해주었다.
이제 데미지필드의 코드를 변경해 HP를 감소시킬 것이다.
private void OnTriggerEnter2D(Collider2D other)
{
GameObject monster = other.gameObject;
if (other.CompareTag("Enemy"))
{
Debug.Log("몬스터 접촉");
var monsterhp = monster.GetComponentInParent<Monster>().MonsterHp;
monsterhp -= 10;
monster.GetComponent<Monster>().MonsterHp = monsterhp;
monster.GetComponent<Monster>().HPUpdate(monsterhp);
Debug.Log(monsterhp);
}
else
{
Debug.Log(other.gameObject.tag, other.gameObject);
Debug.Log("아무튼 접촉");
}
다른 물체가 감지되면, 일단 monster라고 이름짓고, 태그를 비교해 몬스터이면 hp를 감소시키고 저장하며
그렇지 않다면 어떤 것이 접촉되었는지 로그를 보여준다.
몬스터의 태그도 잘 바꿔주고,
데미지필드는 몬스터를 제외한 다른 아무것도 충돌하지 않게 해 준다. (IsTrigger체크주의)
모든 데미지필드들의 레이어도 바꿔준다.
끝 ! 이제 남은 문제들은 주말에 풀거나, 개념 공부하다가 힘들면 도전해보겠다.
문제 4 : 태그를 바꿨으나, 여전히 인식하고 HP는 깎이지 않는 문제.
다음과 같은 문제가 발생했다, 분명 if문 안에는 디버그와 hp를 감소시키는 과정이 같이 있는데 하나만 작동되는.
알고보니 hitcollision도 있어서 hitcollision은 hp가 없으므로 hp를 감소시키는 로직은 작동되지 않았던 것이었다.
해결 4 : 태그를 바꿨으나, 여전히 인식하고 HP는 깎이지 않는 문제.
문제 2 : 오른쪽에만 생성되지 않게 하려면 방향을 어떻게 설정해야하는가 ?
우리는 지금, sprite를 입력받아 inputAction에서 들어온 x값을 움직이기만 하고 있다.
왼쪽, 오른쪽으로 움직이는 것처럼 보이는 이유는
if (moveValue.x != 0)
{
_spriteRenderer.flipX = moveValue.x < 0;
}
moveValue값이 -0이 아닐 때 , 즉 움직이고 있다면 실행되는 조건문으로
우측 값이 참이면 flipX가 1값이 들어가고, 거짓이면 0 값이 들어간다.
moveValue값이 +일 때 flipX가 0이어야 하므로 위 식이 맞다.
정리해보자면, 우리는 단지 sprite의 방향만 바꿔주었을 뿐이다.
'캐릭터가 바라보는 방향'은 정해주지 않았다. 그래서 몬스터의 방향을 바꾸었을 때처럼 direction을 정해준다.
public Vector2 direction;
변수를 할당해준다.
if (moveValue.x > 0)
direction = transform.right;
else if (moveValue.x < 0)
{
direction = -transform.right;
}
Update문에 넣어주어 moveValue의 변화에 따라 direction이 변하게 해준다.
집어 넣고, 속도가 줄어(움직임을 멈춰) 0이 되어도, direction 값은 남아 있어 바라보는 방향은 유지된다.
void FireDamageField (int index)
{
Debug.Log("FireDamageField 실행");
GameObject go = Instantiate(_damageFields[index].gameObject, transform);
go.GetComponent<DamageField>().MyOwnerTag = "Player";
var directionlr = new Vector3(direction.x, 0, 0);
go.transform.position = transform.position + directionlr * _damageFieldDatas.distance;
Destroy(go , 3.0f);
}
생성되는 데이터필드의 포지션은 Vector3값이므로, direction을 Vector3D로 변경해주고
바라보는 방향 x distance로 go의 위치를 설정해준다.
기본 direction값을 설정하지 않아서, 처음에는 transform.position에 생성된다
그 후 양쪽으로 스킬을 사용해보고,
우리는 스킬 사용 시점이 애니메이션이 끝나는 시점이므로(애니메이션으로 함수를 호출했다)
모션 중간에 스킬 사용 방향을 바꾸는 것도 가능하다.
해결2 : 오른쪽에만 생성되지 않게 하려면 방향을 어떻게 설정해야하는가 ?
문제 5 : 여러가지 오류
자꾸, NullReferenceException이 뜨길래 Layer랑 Tag를 구분짓고 한참을 바꿔서 해결했다.
아무래도 Collider, Rigidbody를 조금 더 자세히 살펴봐야겠다. (개념공부)
남은 문제는 2개이다.
문제 1 : 버튼들을 하나의 코드로 간단하게 할당할수 있는 방법은 없는가 ?
이 문제는 skillList를 만들고, 스킬을 순서대로 저장한다음 for문을 돌면서 순서대로 넣으면 될 것이다.
하지만 3개인 지금은 오히려 불편한 과정이 될 수 있으므로 생략한다.
문제 3 : 똑같은 애니메이션으로, 다른 스킬들을 구현하려면 어떻게 해야하는가 ?
가장 노가다같은 방법으로는 똑같은 애니메이션을 복사하고, 애니메이션의 끝에 다른 데미지필드를
넣어주면 기능할 것이다. 다만 굳이.. 라는 생각이 들긴 한다.
아무래도 애니메이션에 연결하는 방식으로는 힘들것 같다.
RemoveListner를 쓰는 방식도 아닐테고... 좀 더 고민해보다 질문해야겠다.
'TIL' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL회고] Unity 게임 개발 3기 31일차 (0) | 2024.12.20 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL회고] Unity 게임 개발 3기 30일차 (0) | 2024.12.20 |
[멋쟁이사자처럼 부트캠프 TIL회고] Unity 게임개발 3기 28일차 (1) | 2024.12.17 |
[멋쟁이사자처럼 부트캠프 TIL회고] Unity 게임 개발 3기 27일차 (1) | 2024.12.17 |
[멋쟁이사자처럼 부트캠프 TIL회고] Unity 게임개발 3기 25-26일차 (0) | 2024.12.15 |