유니티 프로젝트/뱀서라이크
[Unity/유니티] 기초-뱀서라이크: 타격감 있는 몬스터 처치 만들기[09]
JongHoon
2023. 7. 10. 20:22
개요
유니티 독학을 위해 아래 링크의 골드메탈님의 영상들을 보고 직접 따라 해보면서 진행 상황을 쓰고 배웠던 점을 요약한다.
https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2
📚유니티 기초 강좌
유니티 게임 개발을 배우고 싶은 분들을 위한 기초 강좌
www.youtube.com
뱀서라이크: 타격감 있는 몬스터 처치 만들기[09]
1. 피격 리액션
//Enemy Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour {
public RuntimeAnimatorController[] animCon;
public float health;
public float maxHealth;
public float speed;
public Rigidbody2D target; //쫓아갈 타겟(플레이어)
bool isLive;
Animator anim;
Rigidbody2D rigid;
SpriteRenderer spriter;
WaitForFixedUpdate wait;
void Awake() {
anim = GetComponent<Animator>();
rigid = GetComponent<Rigidbody2D>();
spriter = GetComponent<SpriteRenderer>();
wait = new WaitForFixedUpdate();
}
void FixedUpdate() {
if (!isLive || anim.GetCurrentAnimatorStateInfo(0).IsName("Hit")) //GetCurrentAnimationStateInfo : 현재 상태 정보를 가져오는 함수
return;
Vector2 dirVec = target.position - rigid.position; // 방향 = 위치 차이의 정규화(Normalized). 위치 차이 = 타겟 위치 - 나의 위치.
Vector2 nextVec = dirVec.normalized * speed * Time.fixedDeltaTime;
rigid.MovePosition(rigid.position + nextVec); //플레이어의 키 입력값을 더한 이동 = 몬스터의 방향 값을 더한 이동
rigid.velocity = Vector2.zero; //물리 속도가 이동에 영향을 주지 않도록 속도 제거
}
void LateUpdate() {
if (!isLive)
return;
spriter.flipX = target.position.x < rigid.position.x;
}
void OnEnable() {
target = GameManager.instance.player.GetComponent<Rigidbody2D>(); //플레이어 할당
isLive = true;
health = maxHealth;
}
//데이터를 가져오기 위한 초기화 함수
public void Init(SpawnData data) {
anim.runtimeAnimatorController = animCon[data.spriteType];
speed = data.speed;
maxHealth = data.health;
health = data.health;
}
void OnTriggerEnter2D(Collider2D collision) {
if (!collision.CompareTag("Bullet"))
return;
health -= collision.GetComponent<Bullet>().damage; //총알 데미지만큼 Enemy 체력 감소
StartCoroutine(KnockBack());
if(health > 0) {
anim.SetTrigger("Hit");
}
else {
// .. Die
Dead();
}
}
IEnumerator KnockBack() {
yield return wait; //다음 하나의 물리 프레임 딜레이
Vector3 playerPos = GameManager.instance.player.transform.position;
Vector3 dirVec = transform.position - playerPos;
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
}
void Dead() {
gameObject.SetActive(false);
}
}
- 몬스터는 rigidbody를 통해 물리적인 방법으로 플레이어를 향해 이동하기 때문에, Fixed Update에서 animation 상태에 따른 조건을 주지 않으면 KnockBack이 발동하지 않게 된다.
- 따라서 KnockBack을 정상적으로 실행하려면 위와 같이 Fixed Update의 if문에 조건을 추가해서 Hit 상태일 때는 이동하지 않게끔 만들어줘야한다.
2. 사망 리액션
//Enemy Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour {
public RuntimeAnimatorController[] animCon;
public float health;
public float maxHealth;
public float speed;
public Rigidbody2D target; //쫓아갈 타겟(플레이어)
bool isLive;
Rigidbody2D rigid;
Collider2D coll;
Animator anim;
SpriteRenderer spriter;
WaitForFixedUpdate wait;
void Awake() {
rigid = GetComponent<Rigidbody2D>();
coll = GetComponent<Collider2D>();
anim = GetComponent<Animator>();
spriter = GetComponent<SpriteRenderer>();
wait = new WaitForFixedUpdate();
}
void FixedUpdate() {
if (!isLive || anim.GetCurrentAnimatorStateInfo(0).IsName("Hit")) //GetCurrentAnimationStateInfo : 현재 상태 정보를 가져오는 함수
return;
Vector2 dirVec = target.position - rigid.position; // 방향 = 위치 차이의 정규화(Normalized). 위치 차이 = 타겟 위치 - 나의 위치.
Vector2 nextVec = dirVec.normalized * speed * Time.fixedDeltaTime;
rigid.MovePosition(rigid.position + nextVec); //플레이어의 키 입력값을 더한 이동 = 몬스터의 방향 값을 더한 이동
rigid.velocity = Vector2.zero; //물리 속도가 이동에 영향을 주지 않도록 속도 제거
}
void LateUpdate() {
if (!isLive)
return;
spriter.flipX = target.position.x < rigid.position.x;
}
void OnEnable() {
target = GameManager.instance.player.GetComponent<Rigidbody2D>(); //플레이어 할당
isLive = true;
coll.enabled = true;
rigid.simulated = true;
spriter.sortingOrder = 2;
anim.SetBool("Dead", false);
health = maxHealth;
}
//데이터를 가져오기 위한 초기화 함수
public void Init(SpawnData data) {
anim.runtimeAnimatorController = animCon[data.spriteType];
speed = data.speed;
maxHealth = data.health;
health = data.health;
}
void OnTriggerEnter2D(Collider2D collision) {
if (!collision.CompareTag("Bullet"))
return;
health -= collision.GetComponent<Bullet>().damage; //총알 데미지만큼 Enemy 체력 감소
StartCoroutine(KnockBack());
if(health > 0) {
anim.SetTrigger("Hit");
}
else {
isLive = false;
coll.enabled = false;
rigid.simulated = false; //rigidbody의 물리적 비활성화는 simulated를 false로 설정.
spriter.sortingOrder = 1; //SpriteRenderer의 Sorting Order를 감소시켜 다른 몬스터를 가리지 않게 함.
anim.SetBool("Dead", true);
}
}
IEnumerator KnockBack() {
yield return wait; //다음 하나의 물리 프레임 딜레이
Vector3 playerPos = GameManager.instance.player.transform.position;
Vector3 dirVec = transform.position - playerPos;
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
}
void Dead() {
gameObject.SetActive(false);
}
}
- 이와 같이 Animation의 Event를 통해서 Enemy 스크립트의 Dead() 함수를 애니메이션을 통해 실행시킬 수 있다.
3. 처치 데이터 읽기
//GameManager Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour {
public static GameManager instance;
[Header("# Game Control")]
public float gameTime; //게임 시간 변수
public float maxGameTime = 2 * 10f; //최대 게임 시간 변수(20초).
[Header("# Player Info")]
public int level;
public int kill;
public int exp;
public int[] nextExp = { 3, 5, 10, 100, 150, 210, 280, 360, 450, 600 };
[Header("# Game Object")]
public PoolManager pool;
public Player player;
void Awake() {
instance = this;
}
void Update() {
gameTime += Time.deltaTime;
if (gameTime > maxGameTime) {
gameTime = maxGameTime;
}
}
public void GetExp() {
exp++;
if (exp == nextExp[level]) {
level++;
exp = 0;
}
}
}
//Enemy Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour {
public RuntimeAnimatorController[] animCon;
public float health;
public float maxHealth;
public float speed;
public Rigidbody2D target; //쫓아갈 타겟(플레이어)
bool isLive;
Rigidbody2D rigid;
Collider2D coll;
Animator anim;
SpriteRenderer spriter;
WaitForFixedUpdate wait;
void Awake() {
rigid = GetComponent<Rigidbody2D>();
coll = GetComponent<Collider2D>();
anim = GetComponent<Animator>();
spriter = GetComponent<SpriteRenderer>();
wait = new WaitForFixedUpdate();
}
void FixedUpdate() {
if (!isLive || anim.GetCurrentAnimatorStateInfo(0).IsName("Hit")) //GetCurrentAnimationStateInfo : 현재 상태 정보를 가져오는 함수
return;
Vector2 dirVec = target.position - rigid.position; // 방향 = 위치 차이의 정규화(Normalized). 위치 차이 = 타겟 위치 - 나의 위치.
Vector2 nextVec = dirVec.normalized * speed * Time.fixedDeltaTime;
rigid.MovePosition(rigid.position + nextVec); //플레이어의 키 입력값을 더한 이동 = 몬스터의 방향 값을 더한 이동
rigid.velocity = Vector2.zero; //물리 속도가 이동에 영향을 주지 않도록 속도 제거
}
void LateUpdate() {
if (!isLive)
return;
spriter.flipX = target.position.x < rigid.position.x;
}
void OnEnable() {
target = GameManager.instance.player.GetComponent<Rigidbody2D>(); //플레이어 할당
isLive = true;
coll.enabled = true;
rigid.simulated = true;
spriter.sortingOrder = 2;
anim.SetBool("Dead", false);
health = maxHealth;
}
//데이터를 가져오기 위한 초기화 함수
public void Init(SpawnData data) {
anim.runtimeAnimatorController = animCon[data.spriteType];
speed = data.speed;
maxHealth = data.health;
health = data.health;
}
void OnTriggerEnter2D(Collider2D collision) {
if (!collision.CompareTag("Bullet") || !isLive)
return;
health -= collision.GetComponent<Bullet>().damage; //총알 데미지만큼 Enemy 체력 감소
StartCoroutine(KnockBack());
if(health > 0) {
anim.SetTrigger("Hit");
}
else {
isLive = false;
coll.enabled = false;
rigid.simulated = false; //rigidbody의 물리적 비활성화는 simulated를 false로 설정.
spriter.sortingOrder = 1; //SpriteRenderer의 Sorting Order를 감소시켜 다른 몬스터를 가리지 않게 함.
anim.SetBool("Dead", true);
GameManager.instance.kill++;
GameManager.instance.GetExp();
}
}
IEnumerator KnockBack() {
yield return wait; //다음 하나의 물리 프레임 딜레이
Vector3 playerPos = GameManager.instance.player.transform.position;
Vector3 dirVec = transform.position - playerPos;
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
}
void Dead() {
gameObject.SetActive(false);
}
}