0. 들어가기 전에
이번엔 플레이어와 장애물이 상호작용 하는 기능을 구현했다.
1. 프리팹
이번에 새로 만든 프리팹은 장애물(뾰족한 삼각형) 이다. 이렇게 만든 프리팹은 나중에 맵 프리팹을 만들 때 이용할 것이다.
1.1 Spike
1. 인스펙터 창
게임 오브젝트의 Tag 와 Layer 를 Attack 으로 설정해줬다. 나중에 스크립트로 해당 게임 오브젝트가 무엇인지 판단할 때 이용하기 위함이다.

1.2 Monster
1. 인스펙터 창
현재 몬스터 게임 오브젝트의 인스펙터 창은 다음과 같다. 새로 추가된 컴포넌트는 Audio Source 인데 몬스터가 플레이어한테 밟혀 죽는 상황에서 소리를 내게 할 것이기 때문이다.

2. 스크립트
이번에 새로 만든 스크립트는 없고 Monster 와 Player 를 수정했다.
2.1 Monster 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Monster : MonoBehaviour
{
public enum MoveDir
{
Left = -1, Idle = 0, Right = 1
}
[Header("Monster info")]
public float speed = 1f; // 이동 속도
public MoveDir dir = MoveDir.Idle; // 점토 몬스터가 움직이는 방향
public Coroutine monsterMoveCoroutine; // 몬스터는 계속해서 움직이는데 그 동작을 코루틴으로 만들 것. 코루틴 시작한 거 저장하기 위한 용도
public bool isDie = false;
private Animator anim;
private Rigidbody2D rigid;
private SpriteRenderer spriteRenderer;
private AudioSource monsterAudio;
private void Start()
{
// 컴포넌트 할당
anim = GetComponent<Animator>();
rigid = GetComponent<Rigidbody2D>();
spriteRenderer = GetComponent<SpriteRenderer>();
monsterAudio = GetComponent<AudioSource>();
monsterMoveCoroutine = StartCoroutine(MonsterMove());
}
private void Update()
{
if (!isDie)
{
// 몬스터가 땅에 있으면 이동 방향에 따라 움직일 수 있도록
transform.Translate(new Vector2((int)dir, 0) * speed * Time.deltaTime * 0.5f);
// 애니메이션 수행 & 스프라이트 관리
SetMonsterSprite();
// 몬스터 레이캐스트
MonsterRaycast();
}
}
private void MonsterRaycast()
{
// 방향에 맞게 레이 쏴야함
// 근데 아래 방향으로 쏴야함 -> 몬스터보다 조금 더 앞에서 밑에 방향으로 레이 쏠 것
Vector2 front = new Vector2(rigid.position.x + (int)dir, rigid.position.y);
Debug.DrawRay(front, Vector2.down, new Color(0, 1, 0));
RaycastHit2D rayHit = Physics2D.Raycast(front, Vector3.down, 1, LayerMask.GetMask("Ground"));
if (rayHit.collider == null)
{
Debug.Log("밑에 땅이 업어여;;");
// 자기 앞이 낭떠러지면 move 코루틴 중지하고 GotoReverse 시작
StopCoroutine(monsterMoveCoroutine);
StartCoroutine(GotoReverse());
}
}
private IEnumerator MonsterMove()
{
while (true)
{
dir = (MoveDir)Random.Range(-1, 2);
yield return new WaitForSeconds(2f); // 2초마다 방향 바꿀 수 있도록..
}
}
private IEnumerator GotoReverse()
{
// 이동 방향을 반대로 바꾸는 코루틴
int reverseDir = -(int)dir; // 현재 이동 방향에 반대
// 점토가 가는 방향의 밑이 낭떠러지면 방향 바꿔야함
dir = (MoveDir)reverseDir;
yield return new WaitForSeconds(2f); // 2초 기다령
monsterMoveCoroutine = StartCoroutine(MonsterMove()); // 코루틴 다시 시작
}
private void SetMonsterSprite()
{
// 애니메이션 수행
if ((int)dir == 0)
anim.SetBool("Move", false);
else
{
anim.SetBool("Move", true);
// 움직이는 방향이 오른쪽이면 스프라이트 뒤집도록..
if ((int)dir == -1)
spriteRenderer.flipX = false;
else
spriteRenderer.flipX = true;
}
}
public void Die()
{
StartCoroutine(DieAnim()); // 코루틴 시작
}
private IEnumerator DieAnim()
{
monsterAudio.Play(); // 오디오 재생
isDie = true; // 죽은 상태 알리기
anim.SetTrigger("Die"); // 죽는 애니메이션 수행
yield return new WaitForSeconds(1f); // 2초 기다려여
gameObject.SetActive(false); // 활성화 꺼야함
}
}
2.2 Monster 스크립트 변경 사항 설명
1. 변수
다음과 같은 변수가 추가됐다. isDie 는 몬스터의 생존 여부를 나타내기 위해, monsterAudio 는 몬스터가 죽을 때 소리를 내도록 하기 위해 선언했다.
public bool isDie = false;
private AudioSource monsterAudio;
2. Update()
기존의 로직을 if 문으로 감쌌다. 이유는 몬스터가 살아있을 때만 해당 로직을 수행할 수 있도록 하기 위함이다.
private void Update()
{
if (!isDie)
{
// 몬스터가 땅에 있으면 이동 방향에 따라 움직일 수 있도록
transform.Translate(new Vector2((int)dir, 0) * speed * Time.deltaTime * 0.5f);
// 애니메이션 수행 & 스프라이트 관리
SetMonsterSprite();
// 몬스터 레이캐스트
MonsterRaycast();
}
}
3. Die()
새로 추가된 메서드이다. Player 스크립트에서 호출해서 사용한다. 플레이어가 몬스터를 밟았을 때 Die() 메서드를 호출해서 몬스터가 비활성화 되도록 하는 것이다.
public void Die()
{
StartCoroutine(DieAnim()); // 코루틴 시작
}
4. DieAnim()
몬스터가 죽을 때 애니메이션을 수행하고 비활성화 하는 코루틴이다.
private IEnumerator DieAnim()
{
monsterAudio.Play(); // 오디오 재생
isDie = true; // 죽은 상태 알리기
anim.SetTrigger("Die"); // 죽는 애니메이션 수행
yield return new WaitForSeconds(1f); // 1초 기다려여
gameObject.SetActive(false); // 활성화 꺼야함
}
2.3 Player 스크립트
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using UnityEngine;
public class Player : MonoBehaviour
{
[Header("Player Info")]
public int heart = 3; // 목숨 3개
public float jumpPower = 500f; // 점프 힘
public int jumpCount = 0; // 누적 점프 횟수(최대 두번 뛸 수 있도록)
public bool isGrounded = false; // 바닥에 닿았는지 여부
public bool isDead = false; // 사망 여부
public bool isInvincibility = false; // 무적 여부
public Rigidbody2D rigid; // 리지드바디 컴포넌트
public Animator anim; // 애니메이터 컴포넌트
public AudioSource playerAudio; // 사용할 오디오 소스 컴포넌트
public SpriteRenderer spriteRenderer; // 스프라이트 렌더러
public AudioClip deathClip; // 죽을 때 나는 소리
public AudioClip jumpClip; // 점프할 때 나는 소리
public AudioClip attackedClip; // 맞을 때 나는 소리
public float gamePlayTime = 60f; // 일단 1분으로..
public float curTime = 0;
private void Start()
{
rigid = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
playerAudio = GetComponent<AudioSource>();
spriteRenderer = GetComponent<SpriteRenderer>();
}
private void Update()
{
if (curTime >= gamePlayTime)
{
// 플레이 시간 지나면 더이상 진행하지 않음
GameEnd();
}
if (isDead)
{
// 사망하면 더이상 진행하지 않음
return;
}
if (Input.GetMouseButtonDown(0) && jumpCount < 2)
{
// 점프 횟수 증가
jumpCount++;
// 점프 직전에 속도를 0 으로 변경
rigid.velocity = Vector3.zero;
// 위쪽으로 힘주기
rigid.AddForce(new Vector2(0, jumpPower));
// 오디오 소스 재생
playerAudio.clip = jumpClip;
playerAudio.Play();
}
else if (Input.GetMouseButtonUp(0) && rigid.velocity.y > 0)
{
// 마우스 왼쪽 버튼에서 손을 떼는 순간 && 속도의 y 값이 양수(위로 상승 중)
// 현재 속도를 절반으로 변경
rigid.velocity = rigid.velocity * 0.5f;
}
// 애니메이터의 Grounded 파라미터를 isGrounded 값으로 갱신
anim.SetBool("Grounded", isGrounded);
curTime += Time.deltaTime; // 시간 더해주기
}
public void GameEnd()
{
// 여기서 게임 매니저 게임 종료(성공) 메서드 호출
}
public void Die()
{
StartCoroutine(DieCoroutine());
}
private void OnTriggerEnter2D(Collider2D collision)
{
// Collider 를 통해 다른 객체가 경계를 통과했을 때 자동으로 호출되는 메서드
// 두 객체 중 적어도 하나에 Collider 가 있고, Is Trigger 옵션이 활성화 되어 있어야함.
// 체력 깎이는 부분은 무적 상태라면 수행 안 하고 걍 빠져나가기..
if (isInvincibility) return;
// 만약 플레이어가 체력 깎이는 바닥을 통과했을 때
if (collision.tag == "AttackGround")
{
Die(); // 죽어
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Ground")
{
isGrounded = true; // 땅에 닿았음 표시
jumpCount = 0; // 리셋
}
else if (collision.gameObject.tag == "Attack")
{
if (isInvincibility) return; // 무적 상태면 그냥 나가기..
// 장애물에 닿았으면 heart 값 -1 해주기..
Attacked();
}
else if (collision.gameObject.tag == "Monster")
{
if (isInvincibility) return; // 무적 상태면 그냥 나가기..
// 만약 플레이어가 몬스터보다 위에 있고, y 축의 속도가 감소하는 중이라면 몬스터를 밟은 거임
if (transform.position.y >= collision.gameObject.transform.position.y && rigid.velocity.y < 0)
{
// 몬스터의 Die 메서드 호출
collision.transform.GetComponent<Monster>().Die();
}
else
{
Attacked();
}
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if (collision.gameObject.tag == "Ground")
{
isGrounded = false;
}
}
private void Attacked()
{
// 효과음
playerAudio.clip = attackedClip;
playerAudio.Play();
// 피 깎기
heart--;
if (heart == 0)
{
Die(); // 죽어
return;
}
// 잠시 무적 상태로..
StartCoroutine(Invincibility());
}
private IEnumerator Invincibility()
{
isInvincibility = true;
spriteRenderer.color = new Color32(255, 0, 0, 180); // 반투명하게..(빨갛게)
gameObject.layer = 9; // PlayerDamaged 레이어는 9번임
anim.SetTrigger("Hit"); // 애니메이션 수행
yield return new WaitForSeconds(2f); // 2초 동안 무적
gameObject.layer = 8; // PlayerDamaged 레이어는 9번임
spriteRenderer.color = new Color32(255, 255, 255, 255); // 원래 상태로..
isInvincibility = false; // 다시 false 로 바꾸고 빠져나가기..
}
private IEnumerator DieCoroutine()
{
// 여기서 게임 종료(실패) 로직 수행할거임
// 애니메이터의 Die 트리거 파라미터를 세팅함
anim.SetTrigger("Die");
// 오디오 소스 클릅을 deathClip 으로 변경
playerAudio.clip = deathClip;
// 오디오 실행
playerAudio.Play();
// 속도를 제로로 변경
rigid.velocity = Vector2.zero;
// 사망 상태 true 로
isDead = true;
yield return new WaitForSeconds(5f); // 5초 기다리고 판넬 띄우고 끝내자
gameObject.SetActive(false); // 비활성화
}
}
2.4 Player 스크립트 변경 사항 설명
1. 변수
다음과 같은 변수가 추가됐다.
각각 무적여부를 나타내기 위한 변수와, 플레이어의 스프라이트 설정을 변경하기 위한 변수, 플레이어의 행동에 맞게 소리를 내기 위한 변수이다.
public bool isInvincibility = false; // 무적 여부
public SpriteRenderer spriteRenderer; // 스프라이트 렌더러
public AudioClip jumpClip; // 점프할 때 나는 소리
public AudioClip attackedClip; // 맞을 때 나는 소리
2. Start()
spriteRenderer 에 컴포넌트를 할당하는 코드가 추가됐다.
private void Start(){
rigid = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
playerAudio = GetComponent<AudioSource>();
spriteRenderer = GetComponent<SpriteRenderer>();
}
3. Update()
점프할 때 소리를 내기 위해 clip 을 jumpClip 으로 바꾸는 코드가 추가되었다.
private void Update(){
if (curTime >= gamePlayTime)
{
// 플레이 시간 지나면 더이상 진행하지 않음
GameEnd();
}
if (isDead)
{
// 사망하면 더이상 진행하지 않음
return;
}
if (Input.GetMouseButtonDown(0) && jumpCount < 2)
{
// 점프 횟수 증가
jumpCount++;
// 점프 직전에 속도를 0 으로 변경
rigid.velocity = Vector3.zero;
// 위쪽으로 힘주기
rigid.AddForce(new Vector2(0, jumpPower));
// 오디오 소스 재생
playerAudio.clip = jumpClip;
playerAudio.Play();
}
else if (Input.GetMouseButtonUp(0) && rigid.velocity.y > 0)
{
// 마우스 왼쪽 버튼에서 손을 떼는 순간 && 속도의 y 값이 양수(위로 상승 중)
// 현재 속도를 절반으로 변경
rigid.velocity = rigid.velocity * 0.5f;
}
// 애니메이터의 Grounded 파라미터를 isGrounded 값으로 갱신
anim.SetBool("Grounded", isGrounded);
curTime += Time.deltaTime; // 시간 더해주기
}
4. Die()
코루틴을 호출하도록 메서드 내용을 변경했다. 원래 Die 메서드에 있던 로직이 DieCoroutine 으로 넘어갔다.
public void Die(){
StartCoroutine(DieCoroutine());
}
5. OnTriggerEnter2D(Collider2D collision)
메서드의 내용을 수정했다.
플레이어가 현재 무적상태라면 그냥 메서드를 빠져나가도록 했다. 체력 깎이는 바닥을 통과하면 체력이 깎이는게 아니라 그냥 바로 죽도록 로직을 수정했다.
private void OnTriggerEnter2D(Collider2D collision){
// Collider 를 통해 다른 객체가 경계를 통과했을 때 자동으로 호출되는 메서드
// 두 객체 중 적어도 하나에 Collider 가 있고, Is Trigger 옵션이 활성화 되어 있어야함.
// 체력 깎이는 부분은 무적 상태라면 수행 안 하고 걍 빠져나가기..
if (isInvincibility) return;
// 만약 플레이어가 체력 깎이는 바닥을 통과했을 때
if (collision.tag == "AttackGround")
{
Die(); // 죽어
}
}
6. OnCollisionEnter2D(Collision2D collision)
만약 현재 플레이어가 무적상태라면 그냥 빠져나가도록 하는 로직을 추가했다.
플레이어가 몬스터를 밟는 기능을 구현한 부분이 있다. 만약 플레이어의 y 좌표가 몬스터의 y 좌표보다 크고, 현재 플레이어가 아래로 떨어지는 중이라면 몬스터를 밟은 것으로 처리하도록 했다. if 문에 진입하면 몬스터의 Die 메서드를 호출해서 죽게 만들었다.
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Ground")
{
isGrounded = true; // 땅에 닿았음 표시
jumpCount = 0; // 리셋
}
else if (collision.gameObject.tag == "Attack")
{
if (isInvincibility) return; // 무적 상태면 그냥 나가기..
// 장애물에 닿았으면 heart 값 -1 해주기..
Attacked();
}
else if (collision.gameObject.tag == "Monster")
{
if (isInvincibility) return; // 무적 상태면 그냥 나가기..
// 만약 플레이어가 몬스터보다 위에 있고, y 축의 속도가 감소하는 중이라면 몬스터를 밟은 거임
if (transform.position.y >= collision.gameObject.transform.position.y && rigid.velocity.y < 0)
{
// 몬스터의 Die 메서드 호출
collision.transform.GetComponent<Monster>().Die();
}
else
{
Attacked();
}
}
}
7. Attacked()
기존 로직을 메서드로 빼서 만들었다. 플레이어가 장애물에 닿으면 효과음을 내고, haert 를 깎도록 했다. 만약 heart 가 0이 되면 바로 Die 메서드를 호출해서 죽도록 할 것이다.
그리고 장애물에 닿았으면 플레이어를 무적상태로 만들어야 하기 때문에 해당 기능을 수행하는 코루틴을 시작시켰다.
private void Attacked()
{
// 효과음
playerAudio.clip = attackedClip;
playerAudio.Play();
// 피 깎기
heart--;
if (heart == 0)
{
Die(); // 죽어
return;
}
// 잠시 무적 상태로..
StartCoroutine(Invincibility());
}
8. Invincibility()
플레이어를 무적으로 만드는 코루틴이다. 2초 동안 무적상태에 있도록 만들었다.
private IEnumerator Invincibility()
{
isInvincibility = true;
spriteRenderer.color = new Color32(255, 0, 0, 180); // 반투명하게..(빨갛게)
gameObject.layer = 9; // PlayerDamaged 레이어는 9번임
anim.SetTrigger("Hit"); // 애니메이션 수행
yield return new WaitForSeconds(2f); // 2초 동안 무적
gameObject.layer = 8; // PlayerDamaged 레이어는 9번임
spriteRenderer.color = new Color32(255, 255, 255, 255); // 원래 상태로..
isInvincibility = false; // 다시 false 로 바꾸고 빠져나가기..
}
9. DieCoroutine()
Die 메서드에서 시작시키는 코루틴이다. 플레이어가 죽는 기능을 수행한다.
private IEnumerator DieCoroutine()
{
// 여기서 게임 종료(실패) 로직 수행할거임
// 애니메이터의 Die 트리거 파라미터를 세팅함
anim.SetTrigger("Die");
// 오디오 소스 클릅을 deathClip 으로 변경
playerAudio.clip = deathClip;
// 오디오 실행
playerAudio.Play();
// 속도를 제로로 변경
rigid.velocity = Vector2.zero;
// 사망 상태 true 로
isDead = true;
yield return new WaitForSeconds(5f); // 5초 기다리고 판넬 띄우고 끝내자
gameObject.SetActive(false); // 비활성화
}
3. 결과물
몬스터 또는 장애물에 닿으면 애니메이션이 수행되고, 무적상태가 되는 것을 확인할 수 있다. UI 에는 아직 반영을 안 해서 하트가 그대로 있지만 피가 깎이고 있다.
그리고 몬스터를 밟으면 몬스터가 죽는 기능도 확인할 수 있다.