오늘은 어제 마저 구현하지 못했던 플레이어의 나머지 부분을 구현을 했다.
먼저 캐릭터의 피격판정과 죽음 판정, 체력시스템과 스태미나 시스템을 연결을 해보았다.
캐릭터의 피격판정을 만들기 위해서는 캐릭터가 공격을 했을때 충돌처리가 일어날 수 있도록 무기에 콜라이더를 주어야 하는데, 3D모델링이 된 캐릭터들은 Rig라는 것으로 각각의 관절이 움직이도록 구현되어 있기 때문에 무기를 쥐고 있는 오른손에 Weapon이라는 빈 오브젝트를 생성하여 일종의 충돌체를 부여해 주어야 한다.
그리고 Weapon 오브젝트에 Capsule Collider와 Rigidbody컴포넌트를 달아주고 콜라이더는 무기와 잘 맞춰주고 Is Trigger를 켜주고, Rigidbody는 use Gravity를 꺼주고 Is Kinematic은 켜주면 된다. 여기서 Is Kinematic이란 트리거충돌과 비슷한데, 물리연산을 하지는 않지만 물리적 충돌을 인지한다는 것을 말한다.
그리고 Weapon 오브젝트에 Weapon 스크립트를 추가한 후, My Collider 부분에는 해당 오브젝트가 가지고 있는 콜라이더를 추가하면 되기 때문에 플레이어 자신을 넣어준다.
<Weapon>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Weapon : MonoBehaviour
{
[SerializeField] private Collider myCollider;
private int damage;
private float knockback;
private List<Collider> alreadyColliderwith = new List<Collider>();
private void OnEnable()
{
alreadyColliderwith.Clear();
}
private void OnTriggerEnter(Collider other)
{
if (other == myCollider) return;
if(alreadyColliderwith.Contains(other)) return;
alreadyColliderwith.Add(other);
if(other.TryGetComponent(out Health health))
{
health.TakeDamage(damage);
}
if(other.TryGetComponent(out ForceReceiver forceReceiver))
{
Vector3 direction = (other.transform.position - myCollider.transform.position).normalized;
forceReceiver.AddForce(direction * knockback);
}
}
public void SetAttack(int damage, float knockback)
{
this.damage = damage;
this.knockback = knockback;
}
}
그리고 플레이어에게도 무기를 인식할 수 있도록 Player 스크립트도 한줄 추가해 주고 Player에 Weapon오브젝트를 입혀주면 플레이어에게 공격기능도 추가되는 것이다.
Player 수정
[field: SerializeField] public Weapon Weapon { get; private set; }
이어서 체력이 다 깎였을 때 죽게되는 로직을 구현하기 위해서는 체력시스템을 만들어야 한다.
<Health>
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Health : MonoBehaviour
{
[SerializeField] private int maxHealth = 100;
private int health;
public event Action OnDie;
public bool IsDead => health == 0;
private void Start()
{
health = maxHealth;
}
public void TakeDamage(int damage)
{
if (health == 0) return;
health = Mathf.Max(health - damage, 0);
if (health == 0)
OnDie?.Invoke();
Debug.Log(health);
}
}
Player 수정
public Health Health { get; private set; }
private void Awake()
{
// 생략
Health = GetComponent<Health>();
}
private void Start()
{
// 생략
Health.OnDie += OnDie;
}
void OnDie()
{
Animator.SetTrigger("Die");
enabled = false;
}
애니메이션 수정
죽었을 때를 나타내는 Die 파라미터는 bool값이 아닌 Trigger값으로 생성을 해야 애니메이션이 반복이 되지 않고 한 번만 처리된다. 그리고 Die 애니메이션은 다른 상태의 애니메이션과 조금 다른데, Any State로 연결을 해야 한다.
Any State에서 연결을 하게 되면, 어떤상태이던 간에 Transition으로 연결된 상태로 갈 수 있도록 만들 수 있다.
여기까지 하면 대충 플레이어의 이동상태, 점프, 공격, 구르기까지 완료가 되었다.
그리고 플레이어의 체력과 스태미나 등 상태에 대해서 고민을 하다가, Health 스크립트를 따로 작성한 것 처럼
Stamina와 Hunger의 스크립트도 따로 작성을 해서 관리를 하게 되면 힘들 것 같아서 Survival 프로젝트에서 했던 것처럼
PlayerCondition에서 Health, Stamina, Hunger를 관리하도록 만드는 게 더 편할 것 같다.
<PlayerCondition>
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
public interface IDamagable
{
void TakePhysicalDamage(int damage);
}
[System.Serializable]
public class Condition
{
[HideInInspector]
public float curValue;
public float maxValue;
public float startValue;
public float regenRate;
public float decayRate;
public Image uiBar;
public void Add(float amount)
{
curValue = Mathf.Min(curValue+ amount, maxValue);
}
public void Subtract(float amount)
{
curValue = Mathf.Max(curValue- amount, maxValue);
}
public float GetPercentage()
{
return curValue / maxValue;
}
}
public class Playerconditions : MonoBehaviour, IDamagable
{
public Condition health;
public Condition hunger;
public Condition stamina;
public event Action OnDie;
public float noHungerHealthDecay;
public UnityEvent onTakeDamage;
void Start()
{
health.curValue = health.startValue;
hunger.curValue = hunger.startValue;
stamina.curValue = stamina.startValue;
}
void Update()
{
hunger.Subtract(hunger.decayRate * Time.deltaTime);
stamina.Add(stamina.regenRate * Time.deltaTime);
if (hunger.curValue == 0.0f)
health.Subtract(noHungerHealthDecay * Time.deltaTime);
if (health.curValue == 0.0f)
Die();
health.uiBar.fillAmount = health.GetPercentage();
hunger.uiBar.fillAmount = hunger.GetPercentage();
stamina.uiBar.fillAmount = stamina.GetPercentage();
}
public void Heal(float amount)
{
health.Add(amount);
}
public void Eat(float amount)
{
hunger.Add(amount);
}
public bool UseStamina(float amount)
{
if(stamina.curValue - amount < 0)
return false;
stamina.Subtract(amount);
return true;
}
public void Die()
{
OnDie?.Invoke();
}
public void TakePhysicalDamage(int damage)
{
health.Subtract(damage);
onTakeDamage?.Invoke();
}
}
그리고 플레이어에게 PlayerCondition을 넣어준 뒤 각각의 ui에 맞는 상태바를 넣어주고 값을 지정해 주면 완성이다.
오늘은 계획했던 구현들을 거의 다 완성하였기 때문에 내일은 조금 더 다듬고 다른 추가 구현사항들을 도전해 봐야겠다.
'TIL (since 2023.08.07 ~ )' 카테고리의 다른 글
2023-11-02 TIL(Unity 최종 프로젝트 9일차) (0) | 2023.11.02 |
---|---|
2023-11-01 TIL(Unity 최종 프로젝트 8일차) (0) | 2023.11.01 |
2023-10-30 TIL(Unity 최종 프로젝트 6일차) (0) | 2023.10.30 |
2023-10-27 TIL(Unity 최종 프로젝트 5일차) (0) | 2023.10.27 |
2023-10-26 TIL(Unity 최종 프로젝트 4일차) (0) | 2023.10.26 |