반응형
[33회차 수업내용] 250702
1. 공격판정
2. 체력바
3. 아이템 획득
* (수정된 부분 정리) MonsterCore cs
using UnityEngine.UI; // 체력바 UI 조작을 위해 Image 타입을 사용
public abstract class MonsterCore : MonoBehaviour, IDamageable
// IDamageable 인터페이스를 구현하여 데미지 처리 가능하도록 함
public ItemManager itemManager; // 몬스터가 죽을 때 아이템을 드롭하기 위한 매니저
public Image hpBar; // 체력바 UI와 연동되는 이미지 컴포넌트
public float currHp; // 현재 체력 (hp는 최대 체력)
public float atkDamage; // 몬스터 공격력
private bool isDead; // 사망 여부 확인용 플래그. 사망 시 중복 처리 방지
// Init 메서드 확장 (공격력, 체력, 체력바, 아이템 매니저 설정 포함)
protected virtual void Init(float hp, float speed, float attackTime, float atkDamage)
{
this.hp = hp;
this.speed = speed;
this.attackTime = attackTime;
this.atkDamage = atkDamage;
itemManager = FindFirstObjectByType<ItemManager>();
target = GameObject.FindGameObjectWithTag("Player").transform;
animator = GetComponent<Animator>();
monsterRb = GetComponent<Rigidbody2D>();
monsterColl = GetComponent<Collider2D>();
currHp = hp;
hpBar.fillAmount = currHp / hp; // 체력바 초기값 설정
}
// 사망한 경우 행동하지 않도록 처리
void Update()
{
if (isDead)
return;
switch (monsterState)
{
case MonsterState.IDLE:
Idle();
break;
case MonsterState.PATROL:
Patrol();
break;
case MonsterState.TRACE:
Trace();
break;
case MonsterState.ATTACK:
Attack();
break;
}
}
// 트리거 충돌 처리
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Return"))
{
moveDir *= -1;
transform.localScale = new Vector3(moveDir, 1, 1);
}
if (other.GetComponent<IDamageable>() != null)
{
other.GetComponent<IDamageable>().TakeDamage(atkDamage);
// 데미지를 줄 수 있는 대상이면 공격력만큼 피해를 입힘
}
}
// IDamageable 구현체: 외부에서 데미지를 받을 때 호출됨
public void TakeDamage(float damage)
{
currHp -= damage;
hpBar.fillAmount = currHp / hp; // 체력바 UI 갱신
if (currHp <= 0f)
Death();
}
// 사망 처리 함수
public void Death()
{
isDead = true;
animator.SetTrigger("Death"); // 사망 애니메이션 실행
monsterColl.enabled = false; // 충돌 비활성화
monsterRb.gravityScale = 0f; // 중력 제거 (낙하 방지)
itemManager.DropItem(transform.position); // 아이템 드롭 실행
}
*Goblin (수정된것만)
// 공격력까지 포함하는 Init 호출로 변경
void Start()
{
Init(30f, 3f, 2f, 10f); // 체력, 속도, 공격 쿨타임, 공격력
// 플레이어 위치를 주기적으로 확인해 추적/공격 상태 전환
// <기존 monsterCore Update가 아닌 코루틴으로 작성해 idle, patrol일때만 작동하게 바꿈. 제어하기 더 편함
StartCoroutine(FindPlayerRoutine());
}
// Init 오버라이드 // 부모의 init 실행해서 컴포넌트연결, 기본값 설정 등 하고 체력바 초기화 등 진행)
protected override void Init(float hp, float speed, float attackTime, float atkDamage)
{
// base = 부모클래스 가리킴 Goblin이 부모인 MonsterCore의 초기화 코드를 그대로 활용
base.Init(hp, speed, attackTime, atkDamage);
// Goblin에서 따로 설정 - 처음 대기 상태 시간 랜덤 설정 (1~5초)
idleTime = Random.Range(1f, 5f);
}
// 플레이어와의 거리 및 방향을 판단하여 상태를 추적(TRACING) 또는 공격(ATTACK)으로 바꾸는 루프 (기존 Update 대신 동작)
IEnumerator FindPlayerRoutine()
{
while (true) // 무한 루프. 몬스터가 살아 있는 동안(destroy되거나 비활성화되지않는한) 계속 실행됨
{
yield return null; // 매 프레임마다 반복
targetDist = Vector3.Distance(transform.position, target.position);
// 현재 상태가 IDLE 또는 PATROL일 때만 추적 조건 검사
if (monsterState == MonsterState.IDLE || monsterState == MonsterState.PATROL)
{
Vector3 monsterDir = Vector3.right * moveDir;
Vector3 playerDir = (transform.position - target.position).normalized;
float dotValue = Vector3.Dot(monsterDir, playerDir);
isTrace = dotValue < -0.5f && dotValue >= -1f;
if (targetDist <= traceDist && isTrace)
{
animator.SetBool("isRun", true);
ChangeState(MonsterState.TRACE);
}
}
//추적중일때
else if (monsterState == MonsterState.TRACE)
{
if (targetDist > traceDist)
{
timer = 0f;
idleTime = Random.Range(1f, 5f);
animator.SetBool("isRun", false); // 애니메이션 트리거도 꼭 처리
ChangeState(MonsterState.IDLE);
}
if (targetDist < attackDist)
{
ChangeState(MonsterState.ATTACK);
}
}
}
}
public override void Idle()
{
timer += Time.deltaTime;
if (timer >= idleTime)
{
timer = 0f;
moveDir = Random.Range(0, 2) == 1 ? 1 : -1;
transform.localScale = new Vector3(moveDir, 1, 1);
//체력바는 반전되면 안되니까 다시한번 반전
hpBar.transform.localScale = new Vector3(moveDir, 1, 1);
patrolTime = Random.Range(1f, 5f);
animator.SetBool("isRun", true);
ChangeState(MonsterState.PATROL);
}
public override void Trace()
{
var targetDir = (target.position - transform.position).normalized;
transform.position += Vector3.right * targetDir.x * speed * Time.deltaTime;
var scaleX = targetDir.x > 0 ? 1 : -1;
transform.localScale = new Vector3(scaleX, 1, 1);
//체력바는 반전되면 안되니까 다시한번 반전
hpBar.transform.localScale = new Vector3(scaleX, 1, 1);
}
public override void Attack()
{
if (!isAttack)
StartCoroutine(AttackRoutine());
}
공격 애니메이션 시간 자동 대기 및 방향 유지, 추적 복귀
IEnumerator AttackRoutine()
{
isAttack = true;
animator.SetTrigger("Attack");
// 현재 애니메이터가 재생 중인 애니메이션의 길이(초)를 가져옴
float currAnimLength = animator.GetCurrentAnimatorStateInfo(0).length;
// 애니메이션이 끝날 때까지 대기 (시간 딜레이 삽입)
yield return new WaitForSeconds(currAnimLength);
// 공격 애니메이션 종료 후 달리기 애니메이션 비활성화
animator.SetBool("isRun", false);
// 남은 공격 쿨타임 동안 대기 (총 공격 시간 - 애니메이션 시간)
//공격 시간(attackTime)을 “전체 쿨타임”으로 사용하고, 애니메이션 시간은 그보다 짧다는 전제하에 작성된 구조
yield return new WaitForSeconds(attackTime - 1f);
var targetDir = (target.position - transform.position).normalized;
var scaleX = targetDir.x > 0 ? 1 : -1;
transform.localScale = new Vector3(scaleX, 1, 1);
hpBar.transform.localScale = new Vector3(scaleX, 1, 1);
// 상태를 추적 상태(TRACE)로 전환하여 플레이어 쫓도록 함
isAttack = false;
animator.SetBool("isRun", true);
ChangeState(MonsterState.TRACE);
}
- yield return null → “다음 프레임까지 대기”
- Unity는 매 프레임마다 코루틴을 업데이트하면서 yield return으로 멈췄던 지점에서 다시 이어서 실행함.
- 따라서 while (true) 안에서 yield return null을 쓰면 → 이 루프는 매 프레임 한 번씩 실행
고블린 어택 콜라이더 추가

고블린 HP 바 - Canvas 고블린에 넣고, World Space 로 설정 후 추가

*ItemManager
using System.Collections.Generic;
using UnityEngine;
public class ItemManager : MonoBehaviour
{
// 드랍 가능한 아이템 프리팹들을 배열로 등록
[SerializeField] private GameObject[] items;
// 지정된 위치에 아이템을 랜덤하게 생성하고 튕겨내는 함수
public void DropItem(Vector3 dropPos)
{
// items 배열 중에서 무작위로 하나를 선택 (0 이상 items.Length 미만)
var randomIndex = Random.Range(0, items.Length);
// 해당 위치에 랜덤 아이템 생성 (회전 없음)
GameObject item = Instantiate(items[randomIndex], dropPos, Quaternion.identity);
Rigidbody2D itemRb = item.GetComponent<Rigidbody2D>();
// 좌우 방향으로 약간의 랜덤한 힘을 가함 (좌우로 튕겨지도록)
itemRb.AddForceX(Random.Range(-2f, 2f), ForceMode2D.Impulse);
// 위쪽으로 일정한 힘을 가함 (위로 튀어오르게 함)
itemRb.AddForceY(3f, ForceMode2D.Impulse);
// 회전력도 랜덤하게 추가하여 아이템이 공중에서 회전하게 만듦
float ranPower = Random.Range(-1.5f, 1.5f);
itemRb.AddTorque(ranPower, ForceMode2D.Impulse);
}
}

*knightController_keyboard (수정된것만)
public class KnightController_Keyboard : MonoBehaviour, IDamageable
private Collider2D knightColl; // 죽었을때 콜라이더 비활성화하기위함
[SerializeField] private Image hpBar;
public float hp = 100f;
public float currHp;
void Start()
{
animator = GetComponent<Animator>();
knightRb = GetComponent<Rigidbody2D>();
knightColl = GetComponent<Collider2D>(); // 콜라이더 컴포넌트 캐싱
currHp = hp; // 현재 체력을 최대 체력으로 초기화
hpBar.fillAmount = currHp / hp; // 체력바 초기 표시
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Monster"))
{
if (other.GetComponent<IDamageable>() != null) // IDamageable 인터페이스가 구현된 객체인지 확인
{
other.GetComponent<IDamageable>().TakeDamage(atkDamage); // 데미지 적용
other.GetComponent<Animator>().SetTrigger("Hit"); // 히트 애니메이션 실행
}
}
}
void InputKeyboard()
{
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
inputDir = new Vector3(h, v, 0);
animator.SetFloat("JoystickX", inputDir.x);
animator.SetFloat("JoystickY", inputDir.y);
var capsule = GetComponent<CapsuleCollider2D>(); // 콜라이더 캐싱
if (inputDir.y < 0) // 아래 방향키 누르면
{
capsule.size = new Vector2(0.7f, 0.3f); // 콜라이더 크기 축소 (숙이기)
capsule.offset = new Vector2(0, 0.35f); // 콜라이더 위치 조정
}
else // 그 외에는 원래 크기
{
capsule.size = new Vector2(0.7f, 1.7f);
capsule.offset = new Vector2(0, 0.85f);
}
}
public void TakeDamage(float damage)
{
currHp -= damage; // 현재 체력 감소
hpBar.fillAmount = currHp / hp; // 체력바 비율 갱신
if (currHp <= 0f) // 체력이 0 이하이면
Death(); // 사망 처리 호출
}
public void Death()
{
animator.SetTrigger("Death"); // 사망 애니메이션 실행
knightColl.enabled = false; // 콜라이더 비활성화 (충돌 무시)
knightRb.gravityScale = 0f; // 중력 제거 (떨어지지 않도록 고정)
}
플레이어 hp바 - Canvas UI
- 내부 Bar는 Source Image 추가해서 Image Type Filled > Horizontal > Left 해주어야함

- 결과물
: 잘 싸워진다. 땅 콜라이더 설정이 이상해서 끼거나 그러는데 오늘 목표로 한 기능은 모두 구현이 됐다.



반응형
'프로그래밍 > 유니티 부트캠프' 카테고리의 다른 글
| 유니티(자료구조1) _ 멋쟁이사자처럼 유니티 부트캠프 후기 35회차 (2) | 2025.07.07 |
|---|---|
| 유니티(몬스터 행동패턴 (인벤토리 기능/ 플랫포머게임 마무리 / 2D Rigging) _ 멋쟁이사자처럼 유니티 부트캠프 후기 34회차 (3) | 2025.07.05 |
| 유니티(몬스터 행동패턴 (Finite State Machine, 유한 상태 머신) 만들기 ) _ 멋쟁이사자처럼 유니티 부트캠프 후기 32회차 (1) | 2025.07.05 |
| 유니티(플랫포머 맵 만들기 ) _ 멋쟁이사자처럼 유니티 부트캠프 후기 31회차 (5) | 2025.07.03 |
| 유니티(사운드매니저 / 설정창 / 씬 전환) _ 멋쟁이사자처럼 유니티 부트캠프 후기 30회차 (0) | 2025.07.03 |