기록 보관소

[Unity/유니티] 기초-2D 플랫포머: 스테이지를 넘나드는 게임 완성하기[BE2] 본문

유니티 프로젝트/2D 플랫포머

[Unity/유니티] 기초-2D 플랫포머: 스테이지를 넘나드는 게임 완성하기[BE2]

JongHoon 2022. 2. 15. 22:02

개요

유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.

현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.

https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2 

 

유니티 강좌 기초 채널 Basic

유니티 개발을 처음 시작하시는 입문자 분들을 위한 기초 채널. [ 프로젝트 ] B00 ~ B12 (BE1) : 유니티 필수 기초 B13 ~ B19 (BE2) : 2D 플랫포머 B20 ~ B26 (BE3) : 2D 탑다운 대화형 RPG B27 ~ B37 (BE4) : 2D 종스크롤

www.youtube.com


2D 플랫포머: 스테이지를 넘나드는 게임 완성하기[BE2]

1. 플레이어 이동 로직 수정

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMove : MonoBehaviour {
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void Update() {
        //점프
        if (Input.GetButtonDown("Jump") && !anim.GetBool("isJumping")) {    //스페이스바, 점프 애니메이션이 작동중이지 않다면
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            anim.SetBool("isJumping", true);    //Jump 애니메이션 추가
        }

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal")) {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate() {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed*(-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);

        //점프 후 착지시 애니메이션 전환(레이캐스트)
        if (rigid.velocity.y < 0) { //y축 속도 값이 0보다 클때만. 땅에 있으면 레이 표시 X
            Debug.DrawRay(rigid.position, Vector3.down, new Color(0, 1, 0)); //레이 표시
            RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Platform")); //레이에 닿는 물체
            if (rayHit.collider != null) //레이에 닿는 물체가 있다면
                if (rayHit.distance < 0.5f)
                    anim.SetBool("isJumping", false);
        }
    }

    void OnCollisionEnter2D(Collision2D collision) {    //충돌 발생시
        if (collision.gameObject.tag == "Enemy") {
            OnDamaged(collision.transform.position);
        }

    }

   void OnDamaged(Vector2 targetPos) {   //충돌 이벤트 무적 효과 함수
        //레이어 변경(무적)
        gameObject.layer = 11;  //11번 레이어, PlayerDamaged

        //충돌시 스프라이트 색상 변화
        spriteRenderer.color = new Color(1, 1, 1, 0.4f);    //Color(R,G,B,투명도)

        //충돌시 튕겨짐
        //목표물 기준 왼쪽에서 닿으면 왼쪽으로, 오른쪽에서 닿으면 오른쪽으로
        int dirc = transform.position.x - targetPos.x > 0 ? 1 : -1;
        rigid.AddForce(new Vector2(dirc, 1) * 7, ForceMode2D.Impulse);

        //애니메이션 변경
        anim.SetTrigger("doDamaged");

        Invoke("OffDamaged", 3);    //3초 뒤 OffDamaged 함수 실행
    }

    void OffDamaged() { //충돌 이벤트 무적 해제 함수
        //레이어 변경(원래대로)
        gameObject.layer = 10;  //10번 레이어, Player
        spriteRenderer.color = new Color(1, 1, 1, 1);
    }

}
  • 플레이어 방향 전환시 GetButtonDown은 키 입력이 겹치는 구간에서 문제가 발생할 수 있어서 GetButton을 사용한다. GetButtonDown을 사용하면 방향 전환 키를 급작스럽게 여러번 누르면 스프라이트가 Flip되지 않아서 이동 방향과 스프라이트가 반대되어 문워크처럼 이동할 수 있다.
  • 이 부분은 이전에 플레이어 이동을 구현할 때부터 GetButton을 사용해서 진행했으므로 넘어가도록 한다.

2. 몬스터 밟아서 잡기

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMove : MonoBehaviour {
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void Update() {
        //점프
        if (Input.GetButtonDown("Jump") && !anim.GetBool("isJumping")) {    //스페이스바, 점프 애니메이션이 작동중이지 않다면
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            anim.SetBool("isJumping", true);    //Jump 애니메이션 추가
        }

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal")) {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate() {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed*(-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);

        //점프 후 착지시 애니메이션 전환(레이캐스트)
        if (rigid.velocity.y < 0) { //y축 속도 값이 0보다 클때만. 땅에 있으면 레이 표시 X
            Debug.DrawRay(rigid.position, Vector3.down, new Color(0, 1, 0)); //레이 표시
            RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Platform")); //레이에 닿는 물체
            if (rayHit.collider != null) //레이에 닿는 물체가 있다면
                if (rayHit.distance < 0.5f)
                    anim.SetBool("isJumping", false);
        }
    }

    void OnCollisionEnter2D(Collision2D collision) {    //충돌 발생시
        if (collision.gameObject.tag == "Enemy") {
            //몬스터보다 위에 있고, 낙하 중이라면 밟음(공격)
            if (rigid.velocity.y < 0 && transform.position.y > collision.transform.position.y) {
                OnAttack(collision.transform);
            }
            //아니라면 피해 받기
            else
                OnDamaged(collision.transform.position);
        }

    }

    void OnAttack(Transform enemy) {    //몬스터 공격 함수
        //몬스터 밟았을때 반발력. 타격감?
        rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);

        //몬스터 사망
        EnemyMove enemyMove = enemy.GetComponent<EnemyMove>();
        enemyMove.OnDamaged();
    }

   void OnDamaged(Vector2 targetPos) {   //충돌 이벤트 무적 효과 함수
        //레이어 변경(무적)
        gameObject.layer = 11;  //11번 레이어, PlayerDamaged

        //충돌시 스프라이트 색상 변화
        spriteRenderer.color = new Color(1, 1, 1, 0.4f);    //Color(R,G,B,투명도)

        //충돌시 튕겨짐
        //목표물 기준 왼쪽에서 닿으면 왼쪽으로, 오른쪽에서 닿으면 오른쪽으로
        int dirc = transform.position.x - targetPos.x > 0 ? 1 : -1;
        rigid.AddForce(new Vector2(dirc, 1) * 7, ForceMode2D.Impulse);

        //애니메이션 변경
        anim.SetTrigger("doDamaged");

        Invoke("OffDamaged", 3);    //3초 뒤 OffDamaged 함수 실행
    }

    void OffDamaged() { //충돌 이벤트 무적 해제 함수
        //레이어 변경(원래대로)
        gameObject.layer = 10;  //10번 레이어, Player
        spriteRenderer.color = new Color(1, 1, 1, 1);
    }

}
  • PlayerMove 스크립트 파일. 몬스터를 밟았을때 처치하기 위한 함수 OnAttack()을 추가하고, 물리 충돌 인지 함수인 OnCollisionEnter2D를 수정했다. EnemyMove의 함수 OnDamaged()는 아래 스크립트에 작성되어있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyMove : MonoBehaviour {
    Rigidbody2D rigid;
    Animator anim;
    SpriteRenderer spriteRenderer;
    CapsuleCollider2D capsulecollider;
    public int nextMove;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        anim = GetComponent<Animator>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        capsulecollider = GetComponent<CapsuleCollider2D>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
        Invoke("Think", 5);
    }

    void FixedUpdate() {
        //이동
        rigid.velocity = new Vector2(nextMove, rigid.velocity.y);

        //Ray를 사용한 지형 체크
        Vector2 frontVec = new Vector2(rigid.position.x + nextMove * 0.2f, rigid.position.y);
        Debug.DrawRay(frontVec, Vector3.down, new Color(0, 1, 0));
        RaycastHit2D rayHit = Physics2D.Raycast(frontVec, Vector3.down, 1, LayerMask.GetMask("Platform"));
        if (rayHit.collider == null)
            Turn();

    }

    void Think() {
        //행동 지표 결정
        nextMove = Random.Range(-1, 2);

        //애니메이션 전환
        anim.SetInteger("WalkSpeed", nextMove);

        //애니메이션 방향 전환
        if (nextMove != 0)
            spriteRenderer.flipX = nextMove == 1;

        //재귀
        float nextThinkTime = Random.Range(2f, 5f);
        Invoke("Think", nextThinkTime);
    }

    void Turn() {
        //방향 전환
        nextMove *= -1;
        spriteRenderer.flipX = nextMove == 1;

        CancelInvoke();
        Invoke("Think", 2);
    }

    public void OnDamaged() {   //몬스터 공격 받았을때 함수
        //스프라이트 반투명화
        spriteRenderer.color = new Color(1, 1, 1, 0.4f);

        //스프라이트 Y축 뒤집기
        spriteRenderer.flipY = true;

        //물리 충돌 제거
        capsulecollider.enabled = false;

        //사망 모션(점프)
        rigid.AddForce(Vector2.up * 5, ForceMode2D.Impulse);

        //몬스터 제거
        Invoke("DeActive", 5);
    }

    void DeActive() {   //비활성화 함수
        gameObject.SetActive(false);
    }
}
  • MonsterMove 스크립트 파일. 플레이어에게 밟혀서 죽는 모션을 구현하기위해서 Collider 변수를 추가하여 OnDamaged() 함수와 DeActive()  함수를 만들었다.

몬스터를 밟으니 뒤집어지면서 아래로 떨어졌다


3. 아이템

Silver 스프라이트 생성
Gold 스프라이트 생성

  • Silver와 Gold 스프라이트를 생성해서 Circle Collider 2D 컴포넌트를 추가해 Redius 크기를 0.3으로 줄이고, 애니메이션 Silver_Coin과 Gold_Coin을 추가해서 Speed를 0.5로 모두 맞춰 주었다.

세가지 코인 모두 Tag를 Item으로 하고, Collider에 is Trigger을 체크해준다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMove : MonoBehaviour
{
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;

    void Awake()
    {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void Update()
    {
        //점프
        if (Input.GetButtonDown("Jump") && !anim.GetBool("isJumping"))
        {    //스페이스바, 점프 애니메이션이 작동중이지 않다면
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            anim.SetBool("isJumping", true);    //Jump 애니메이션 추가
        }

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal"))
        {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate()
    {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed * (-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);

        //점프 후 착지시 애니메이션 전환(레이캐스트)
        if (rigid.velocity.y < 0)
        { //y축 속도 값이 0보다 클때만. 땅에 있으면 레이 표시 X
            Debug.DrawRay(rigid.position, Vector3.down, new Color(0, 1, 0)); //레이 표시
            RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Platform")); //레이에 닿는 물체
            if (rayHit.collider != null) //레이에 닿는 물체가 있다면
                if (rayHit.distance < 0.5f)
                    anim.SetBool("isJumping", false);
        }
    }

    void OnCollisionEnter2D(Collision2D collision)
    {    //충돌 발생시
        if (collision.gameObject.tag == "Enemy")
        {
            //몬스터보다 위에 있고, 낙하 중이라면 밟음(공격)
            if (rigid.velocity.y < 0 && transform.position.y > collision.transform.position.y)
            {
                OnAttack(collision.transform);
            }
            //아니라면 피해 받기
            else
                OnDamaged(collision.transform.position);
        }

    }

    void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "Item") {
            //아이템 제거
            collision.gameObject.SetActive(false);
        }
    }

    void OnAttack(Transform enemy)
    {    //몬스터 공격 함수
        //몬스터 밟았을때 반발력. 타격감?
        rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);

        //몬스터 사망
        EnemyMove enemyMove = enemy.GetComponent<EnemyMove>();
        enemyMove.OnDamaged();
    }

    void OnDamaged(Vector2 targetPos)
    {   //충돌 이벤트 무적 효과 함수
        //레이어 변경(무적)
        gameObject.layer = 11;  //11번 레이어, PlayerDamaged

        //충돌시 스프라이트 색상 변화
        spriteRenderer.color = new Color(1, 1, 1, 0.4f);    //Color(R,G,B,투명도)

        //충돌시 튕겨짐
        //목표물 기준 왼쪽에서 닿으면 왼쪽으로, 오른쪽에서 닿으면 오른쪽으로
        int dirc = transform.position.x - targetPos.x > 0 ? 1 : -1;
        rigid.AddForce(new Vector2(dirc, 1) * 7, ForceMode2D.Impulse);

        //애니메이션 변경
        anim.SetTrigger("doDamaged");

        Invoke("OffDamaged", 3);    //3초 뒤 OffDamaged 함수 실행
    }

    void OffDamaged()
    { //충돌 이벤트 무적 해제 함수
        //레이어 변경(원래대로)
        gameObject.layer = 10;  //10번 레이어, Player
        spriteRenderer.color = new Color(1, 1, 1, 1);
    }

}

세 코인 모두 닿으니 비활성화 되었다

  • 아이템의 경우 트리거로 처리해서 각 아이템 Collider에 is Trigger를 체크해두어야하며, 스크립트에서도 OnTriggerEnter2D() 함수를 사용한다. 그리고 Tag를 사용해서 Item 태그에 닿으면 모두 비활성화되도록 하면 Player가 각 코인들에 닿을 때 게임상에서 보이지 않게 된다.

4. 결승점

결승점 역할을 할 Finish의 Tag를 Finish로 하고, Box Collider의 Size와 Offset도 변경하여 충돌 크기도 변경했다. is Trigger도 체크해야 한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMove : MonoBehaviour {
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void Update() {
        //점프
        if (Input.GetButtonDown("Jump") && !anim.GetBool("isJumping")) {    //스페이스바, 점프 애니메이션이 작동중이지 않다면
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            anim.SetBool("isJumping", true);    //Jump 애니메이션 추가
        }

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal")) {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate() {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed * (-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);

        //점프 후 착지시 애니메이션 전환(레이캐스트)
        if (rigid.velocity.y < 0) { //y축 속도 값이 0보다 클때만. 땅에 있으면 레이 표시 X
            Debug.DrawRay(rigid.position, Vector3.down, new Color(0, 1, 0)); //레이 표시
            RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Platform")); //레이에 닿는 물체
            if (rayHit.collider != null) //레이에 닿는 물체가 있다면
                if (rayHit.distance < 0.5f)
                    anim.SetBool("isJumping", false);
        }
    }

    void OnCollisionEnter2D(Collision2D collision) {    //적과 충돌시
        if (collision.gameObject.tag == "Enemy") {
            //몬스터보다 위에 있고, 낙하 중이라면 밟음(공격)
            if (rigid.velocity.y < 0 && transform.position.y > collision.transform.position.y) {
                OnAttack(collision.transform);
            }
            //아니라면, 피해를 받음
            else
                OnDamaged(collision.transform.position);

        }
    }

    void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "Item") {
            //점수 얻기
            

            //아이템 제거
            collision.gameObject.SetActive(false);
        }
        else if (collision.gameObject.tag == "Finish") {
            //다음 스테이지로 이동

        }
    }

    void OnAttack(Transform enemy) {    //몬스터 공격 함수
        //점수 얻기
        
        //몬스터 밟았을때 반발력
        rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);

        //몬스터 사망
        EnemyMove enemyMove = enemy.GetComponent<EnemyMove>();
        enemyMove.OnDamaged();
    }

    void OnDamaged(Vector2 targetPos) {   //충돌 이벤트 무적 효과 함수
        //레이어 변경(무적)
        gameObject.layer = 11;  //11번 레이어, PlayerDamaged

        //충돌시 스프라이트 색상 변화
        spriteRenderer.color = new Color(1, 1, 1, 0.4f);    //Color(R,G,B,투명도)

        //충돌시 튕겨짐
        //목표물 기준 왼쪽에서 닿으면 왼쪽으로, 오른쪽에서 닿으면 오른쪽으로
        int dirc = transform.position.x - targetPos.x > 0 ? 1 : -1;
        rigid.AddForce(new Vector2(dirc, 1) * 7, ForceMode2D.Impulse);

        //애니메이션 변경
        anim.SetTrigger("doDamaged");

        Invoke("OffDamaged", 3);    //3초 뒤 OffDamaged 함수 실행
    }

    void OffDamaged() { //충돌 이벤트 무적 해제 함수
        //레이어 변경(원래대로)
        gameObject.layer = 10;  //10번 레이어, Player
        spriteRenderer.color = new Color(1, 1, 1, 1);
    }

}
  • 결승점에 닿아 다음 스테이지로 가는 것과 몬스터 처치와 코인을 얻고 점수를 얻는 것은 이전 입문 프로젝트에서처럼 게임의 세부 사항들을 저장하고 처리하는 게임 매니저를 만들어서 해야한다. 따라서 일단은 OnTriggerEnter2D()등에 조건문 일부와 주석만 만들어 두고, 게임 매니저를 추가한 뒤에 완성하도록 한다.

5. 게임 매니저 추가

게임 매니저는 Create Empty로 생성한다
생성된 Empty의 이름을 바꾸고, 스크립트 파일을 만들어서 넣어준다

//GameManager 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour {
    public int totalPoint;  //총 점수
    public int stagePoint;  //스테이지 점수
    public int stageIndex;  //스테이지 번호

    public void NextStage() {
        stageIndex++;
        totalPoint += stagePoint;
        stagePoint = 0;
	}
	
	
	void Update () {
		
	}
}
//PlayerMove 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMove : MonoBehaviour {
    public GameManager gameManager;
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void Update() {
        //점프
        if (Input.GetButtonDown("Jump") && !anim.GetBool("isJumping")) {    //스페이스바, 점프 애니메이션이 작동중이지 않다면
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            anim.SetBool("isJumping", true);    //Jump 애니메이션 추가
        }

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal")) {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate() {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed * (-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);

        //점프 후 착지시 애니메이션 전환(레이캐스트)
        if (rigid.velocity.y < 0) { //y축 속도 값이 0보다 클때만. 땅에 있으면 레이 표시 X
            Debug.DrawRay(rigid.position, Vector3.down, new Color(0, 1, 0)); //레이 표시
            RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Platform")); //레이에 닿는 물체
            if (rayHit.collider != null) //레이에 닿는 물체가 있다면
                if (rayHit.distance < 0.5f)
                    anim.SetBool("isJumping", false);
        }
    }

    void OnCollisionEnter2D(Collision2D collision) {    //적과 충돌시
        if (collision.gameObject.tag == "Enemy") {
            //몬스터보다 위에 있고, 낙하 중이라면 밟음(공격)
            if (rigid.velocity.y < 0 && transform.position.y > collision.transform.position.y) {
                OnAttack(collision.transform);
            }
            //아니라면, 피해를 받음
            else
                OnDamaged(collision.transform.position);

        }
    }

    void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "Item") {
            //점수 얻기
            bool isBronze = collision.gameObject.name.Contains("Bronze");
            bool isSilver = collision.gameObject.name.Contains("Silver");
            bool isGold = collision.gameObject.name.Contains("Gold");

            if (isBronze)
                gameManager.stagePoint += 50;
            else if (isSilver)
                gameManager.stagePoint += 100;
            else if (isGold)
                gameManager.stagePoint += 300;

            //아이템 비활성화
            collision.gameObject.SetActive(false);
        }
        else if (collision.gameObject.tag == "Finish") {
            //다음 스테이지로 이동
            gameManager.NextStage();
        }
    }

    void OnAttack(Transform enemy) {    //몬스터 공격 함수
        //점수 얻기
        gameManager.stagePoint += 100;

        //몬스터 밟았을때 반발력
        rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);

        //몬스터 사망
        EnemyMove enemyMove = enemy.GetComponent<EnemyMove>();
        enemyMove.OnDamaged();
    }

    void OnDamaged(Vector2 targetPos) {   //충돌 이벤트 무적 효과 함수
        //레이어 변경(무적)
        gameObject.layer = 11;  //11번 레이어, PlayerDamaged

        //충돌시 스프라이트 색상 변화
        spriteRenderer.color = new Color(1, 1, 1, 0.4f);    //Color(R,G,B,투명도)

        //충돌시 튕겨짐
        //목표물 기준 왼쪽에서 닿으면 왼쪽으로, 오른쪽에서 닿으면 오른쪽으로
        int dirc = transform.position.x - targetPos.x > 0 ? 1 : -1;
        rigid.AddForce(new Vector2(dirc, 1) * 7, ForceMode2D.Impulse);

        //애니메이션 변경
        anim.SetTrigger("doDamaged");

        Invoke("OffDamaged", 3);    //3초 뒤 OffDamaged 함수 실행
    }

    void OffDamaged() { //충돌 이벤트 무적 해제 함수
        //레이어 변경(원래대로)
        gameObject.layer = 10;  //10번 레이어, Player
        spriteRenderer.color = new Color(1, 1, 1, 1);
    }

}

오브젝트 GameManager를 끌어다 놓는다
몬스터를 밟으니 Stage Point가 100점 올랐다
모든 코인을 다 먹으니 50점, 100점, 300점이 올라 총 550점이 되었다.
결승점 Finish에 닿으니 StageIndex가 1오르고, Total Point가 Stage Point를 저장하고, Stage Point는 0이 되었다.

  • PlayerMove 스크립트 파일의 변수창에 GameManager를 끌어다놓으면, 해당 스크립트 파일에 있는 변수들을 모두 사용할 수 있다.
//GameManager 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour {
    public int totalPoint;  //총 점수
    public int stagePoint;  //스테이지 점수
    public int stageIndex;  //스테이지 번호
    public int health;  //플레이어 체력
    public PlayerMove player;

    public void NextStage() {   //다음 스테이지 이동 함수
        stageIndex++;
        totalPoint += stagePoint;
        stagePoint = 0;
	}
	
    public void HealthDown() {
        if (health > 1)
            health--;
        else {
            //플레이어 사망 이펙트
            player.OnDie();

            //결과 UI
            Debug.Log("죽었습니다!");
            
            //재시작 버튼 UI

            
        }
    }
	
	void OnTriggerEnter2D(Collider2D collision) {
        //낙사할 경우
        if (collision.gameObject.tag == "Player") {
            //플레이어 위치 되돌리기
            if (health > 1) {
                collision.attachedRigidbody.velocity = Vector2.zero;    //낙하 속도 0으로 만들기
                collision.transform.position = new Vector3(0, 0, -1);   //플레이어 위치 이동
            }

            //체력 감소
            HealthDown();
        }
	}
}
//PlayerMove 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMove : MonoBehaviour {
    public GameManager gameManager;
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;
    CapsuleCollider2D capsulecollider;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();
        capsulecollider = GetComponent<CapsuleCollider2D>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void Update() {
        //점프
        if (Input.GetButtonDown("Jump") && !anim.GetBool("isJumping")) {    //스페이스바, 점프 애니메이션이 작동중이지 않다면
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            anim.SetBool("isJumping", true);    //Jump 애니메이션 추가
        }

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal")) {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate() {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed * (-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);

        //점프 후 착지시 애니메이션 전환(레이캐스트)
        if (rigid.velocity.y < 0) { //y축 속도 값이 0보다 클때만. 땅에 있으면 레이 표시 X
            Debug.DrawRay(rigid.position, Vector3.down, new Color(0, 1, 0)); //레이 표시
            RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Platform")); //레이에 닿는 물체
            if (rayHit.collider != null) //레이에 닿는 물체가 있다면
                if (rayHit.distance < 0.5f)
                    anim.SetBool("isJumping", false);
        }
    }

    void OnCollisionEnter2D(Collision2D collision) {    //적과 충돌시
        if (collision.gameObject.tag == "Enemy") {
            //몬스터보다 위에 있고, 낙하 중이라면 밟음(공격)
            if (rigid.velocity.y < 0 && transform.position.y > collision.transform.position.y) {
                OnAttack(collision.transform);
            }
            //아니라면, 피해를 받음
            else
                OnDamaged(collision.transform.position);

        }
    }

    void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "Item") {
            //점수 얻기
            bool isBronze = collision.gameObject.name.Contains("Bronze");
            bool isSilver = collision.gameObject.name.Contains("Silver");
            bool isGold = collision.gameObject.name.Contains("Gold");

            if (isBronze)
                gameManager.stagePoint += 50;
            else if (isSilver)
                gameManager.stagePoint += 100;
            else if (isGold)
                gameManager.stagePoint += 300;

            //아이템 비활성화
            collision.gameObject.SetActive(false);
        }
        else if (collision.gameObject.tag == "Finish") {
            //다음 스테이지로 이동
            gameManager.NextStage();
        }
    }

    void OnAttack(Transform enemy) {    //몬스터 공격 함수
        //점수 얻기
        gameManager.stagePoint += 100;

        //몬스터 밟았을때 반발력
        rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);

        //몬스터 사망
        EnemyMove enemyMove = enemy.GetComponent<EnemyMove>();
        enemyMove.OnDamaged();
    }

    void OnDamaged(Vector2 targetPos) {   //충돌 이벤트 무적 효과 함수
        //체력 감소
        gameManager.HealthDown();

        //레이어 변경(무적)
        gameObject.layer = 11;  //11번 레이어, PlayerDamaged

        //충돌시 스프라이트 색상 변화
        spriteRenderer.color = new Color(1, 1, 1, 0.4f);    //Color(R,G,B,투명도)

        //충돌시 튕겨짐
        //목표물 기준 왼쪽에서 닿으면 왼쪽으로, 오른쪽에서 닿으면 오른쪽으로
        int dirc = transform.position.x - targetPos.x > 0 ? 1 : -1;
        rigid.AddForce(new Vector2(dirc, 1) * 7, ForceMode2D.Impulse);

        //애니메이션 변경
        anim.SetTrigger("doDamaged");

        Invoke("OffDamaged", 3);    //3초 뒤 OffDamaged 함수 실행
    }

    void OffDamaged() { //충돌 이벤트 무적 해제 함수
        //레이어 변경(원래대로)
        gameObject.layer = 10;  //10번 레이어, Player
        spriteRenderer.color = new Color(1, 1, 1, 1);
    }

    public void OnDie() {  //플레이어 사망
        //스프라이트 반투명화
        spriteRenderer.color = new Color(1, 1, 1, 0.4f);

        //스프라이트 Y축 뒤집기
        spriteRenderer.flipY = true;

        //물리 충돌 제거
        capsulecollider.enabled = false;

        //사망 모션(점프)
        rigid.AddForce(Vector2.up * 5, ForceMode2D.Impulse);
    }
}

GameManager에 Health와 Box Collider 2D 추가
플레이어 좌표 변경 및 맵 수정
오브젝트 Player를 끌어다 놓는다
가시에 3번 닿으니 죽었다고 출력되면서 플레이어가 사망 모션을 보여준다
낙사를 3번하니 더이상 플레이어가 귀환하지 않고 죽었다고 출력되었다.

  • 몬스터에게 피해 받는 것을 구현하기 위해서 Health를 추가해서 값(3)을 주고, 낙사를 처리하기위해서 Box Collider 2D를 추가해 Offset과 Size를 조절한다. 또한 Is Trigger를 체크해둔다.
  • 낙사할 경우 체력을 1깎고 플레이어를 좌표 (0, 0, -1)로 이동시킨다. 부활 지점 시작 지점이 똑같아야 게임 진행이 자연스럽기때문에 플레이어 좌표를 (0, 0)으로 설정하고 맵도 함께 이동했다. 물론, 부활 지점을 그냥 플레이어와 같이 만들어도 되지만 보고 따라하는 입장에서 영상과 맞추는 것이 편하고 문제 발생률이 적다고 생각해서 바꿨다.

6. 스테이지 추가

정리를 위한 Empty 생성
생성한 Empty의 이름을 Stage 1으로 설정하고 플레이어와 관련된 요소를 제외한 것을 모두 집어넣어 정리했다
복사해서 붙여넣고 비활성화 한다
완성된 Stage 1
완성된 Stage 2
완성된 Stage 3

//GameManager 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour {
    public int totalPoint;  //총 점수
    public int stagePoint;  //스테이지 점수
    public int stageIndex;  //스테이지 번호
    public int health;  //플레이어 체력
    public PlayerMove player;
    public GameObject[] Stages;

    public void NextStage() {
        //다음 스테이지로 이동
        if (stageIndex < Stages.Length - 1) {
            Stages[stageIndex].SetActive(false);    //현재 스테이지 비활성화
            stageIndex++;
            Stages[stageIndex].SetActive(true); //다음 스테이지 활성화
            PlayerReposition();
        }
        else {  //게임 클리어시
            //플레이어 멈추기
            Time.timeScale = 0;

            //게임 결과 UI
            Debug.Log("게임 클리어!");
            
            //재시작 버튼 UI

            
        }

        //점수 계산
        totalPoint += stagePoint;
        stagePoint = 0;
	}
	
    public void HealthDown() {
        if (health > 1)
            health--;
        else {
            //플레이어 사망 이펙트
            player.OnDie();

            //결과 UI
            Debug.Log("죽었습니다!");
            
            //재시작 버튼 UI

            
        }
    }
	
	void OnTriggerEnter2D(Collider2D collision) {
        //낙사할 경우
        if (collision.gameObject.tag == "Player") {
            //플레이어 위치 되돌리기
            if (health > 1) {
                PlayerReposition();
            }

            //체력 감소
            HealthDown();
        }
	}

    void PlayerReposition() {   //플레이어 위치 되돌리기 함수
        player.transform.position = new Vector3(0, 0, -1);  //플레이어 위치 이동
        player.VelocityZero();  //플레이어 낙하 속도 0으로 만들기
    }
}
//PlayerMove 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMove : MonoBehaviour {
    public GameManager gameManager;
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;
    CapsuleCollider2D capsulecollider;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();
        capsulecollider = GetComponent<CapsuleCollider2D>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void Update() {
        //점프
        if (Input.GetButtonDown("Jump") && !anim.GetBool("isJumping")) {    //스페이스바, 점프 애니메이션이 작동중이지 않다면
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            anim.SetBool("isJumping", true);    //Jump 애니메이션 추가
        }

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal")) {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate() {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed * (-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);

        //점프 후 착지시 애니메이션 전환(레이캐스트)
        if (rigid.velocity.y < 0) { //y축 속도 값이 0보다 클때만. 땅에 있으면 레이 표시 X
            Debug.DrawRay(rigid.position, Vector3.down, new Color(0, 1, 0)); //레이 표시
            RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Platform")); //레이에 닿는 물체
            if (rayHit.collider != null) //레이에 닿는 물체가 있다면
                if (rayHit.distance < 0.5f)
                    anim.SetBool("isJumping", false);
        }
    }

    void OnCollisionEnter2D(Collision2D collision) {    //적과 충돌시
        if (collision.gameObject.tag == "Enemy") {
            //몬스터보다 위에 있고, 낙하 중이라면 밟음(공격)
            if (rigid.velocity.y < 0 && transform.position.y > collision.transform.position.y) {
                OnAttack(collision.transform);
            }
            //아니라면, 피해를 받음
            else
                OnDamaged(collision.transform.position);

        }
    }

    void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "Item") {
            //점수 얻기
            bool isBronze = collision.gameObject.name.Contains("Bronze");
            bool isSilver = collision.gameObject.name.Contains("Silver");
            bool isGold = collision.gameObject.name.Contains("Gold");

            if (isBronze)
                gameManager.stagePoint += 50;
            else if (isSilver)
                gameManager.stagePoint += 100;
            else if (isGold)
                gameManager.stagePoint += 300;

            //아이템 비활성화
            collision.gameObject.SetActive(false);
        }
        else if (collision.gameObject.tag == "Finish") {
            //다음 스테이지로 이동
            gameManager.NextStage();
        }
    }

    void OnAttack(Transform enemy) {    //몬스터 공격 함수
        //점수 얻기
        gameManager.stagePoint += 100;

        //몬스터 밟았을때 반발력
        rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);

        //몬스터 사망
        EnemyMove enemyMove = enemy.GetComponent<EnemyMove>();
        enemyMove.OnDamaged();
    }

    void OnDamaged(Vector2 targetPos) {   //충돌 이벤트 무적 효과 함수
        //체력 감소
        gameManager.HealthDown();

        //레이어 변경(무적)
        gameObject.layer = 11;  //11번 레이어, PlayerDamaged

        //충돌시 스프라이트 색상 변화
        spriteRenderer.color = new Color(1, 1, 1, 0.4f);    //Color(R,G,B,투명도)

        //충돌시 튕겨짐
        //목표물 기준 왼쪽에서 닿으면 왼쪽으로, 오른쪽에서 닿으면 오른쪽으로
        int dirc = transform.position.x - targetPos.x > 0 ? 1 : -1;
        rigid.AddForce(new Vector2(dirc, 1) * 7, ForceMode2D.Impulse);

        //애니메이션 변경
        anim.SetTrigger("doDamaged");

        Invoke("OffDamaged", 3);    //3초 뒤 OffDamaged 함수 실행
    }

    void OffDamaged() { //충돌 이벤트 무적 해제 함수
        //레이어 변경(원래대로)
        gameObject.layer = 10;  //10번 레이어, Player
        spriteRenderer.color = new Color(1, 1, 1, 1);
    }

    public void OnDie() {  //플레이어 사망
        //스프라이트 반투명화
        spriteRenderer.color = new Color(1, 1, 1, 0.4f);

        //스프라이트 Y축 뒤집기
        spriteRenderer.flipY = true;

        //물리 충돌 제거
        capsulecollider.enabled = false;

        //사망 모션(점프)
        rigid.AddForce(Vector2.up * 5, ForceMode2D.Impulse);
    }

    public void VelocityZero() {
        rigid.velocity = Vector2.zero;
    }
}

스테이지를 끌어다가 넣어둔다
스테이지 마지막에 도착했다
콘솔창에도 정상적으로 게임 클리어가 출력된다

  • 스테이지를 만드는데 대부분의 시간을 쓴것같다. 직접 해보니 컨트롤에 문제도 조금 있고해서 약간 스테이지들을 수정하고, 게임 클리어나 사망시 콘솔창에 출력하는 것이 아닌 직접적으로 보여주는 UI를 추가할 차례다.

7. 유저 인터페이스

UI 생성
앵커를 활용하면서 좌측 상단에 체력, 중앙에는 스테이지, 우측에는 점수 UI를 배치하고 재시작 버튼을 두었다
완성된 UI는 Game Manager에 넣는다

//GameManager 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;   //UI 사용을 위해서 추가
using UnityEngine.SceneManagement;   //Scene 전환을 위해서 추가

public class GameManager : MonoBehaviour {
    public int totalPoint;  //총 점수
    public int stagePoint;  //스테이지 점수
    public int stageIndex;  //스테이지 번호
    public int health;  //플레이어 체력
    public PlayerMove player;
    public GameObject[] Stages;

    public Image[] UIhealth;
    public Text UIPoint;
    public Text UIStage;
    public GameObject UIRestartBtn;

    void Update() {
        UIPoint.text = (totalPoint + stagePoint).ToString();

    }

    public void NextStage() {
        //다음 스테이지로 이동
        if (stageIndex < Stages.Length - 1) {
            Stages[stageIndex].SetActive(false);    //현재 스테이지 비활성화
            stageIndex++;
            Stages[stageIndex].SetActive(true); //다음 스테이지 활성화
            PlayerReposition();

            UIStage.text = "STAGE " + (stageIndex + 1);
        }
        else {  //게임 클리어시
            //멈추기
            Time.timeScale = 0;

            //재시작 버튼 UI
            UIRestartBtn.SetActive(true);
            Text btnText = UIRestartBtn.GetComponentInChildren<Text>();   //버튼 텍스트는 자식 오브젝트이므로 InChildren을 붙여야함
            btnText.text = "Clear!";
            UIRestartBtn.SetActive(true);
        }

        //점수 계산
        totalPoint += stagePoint;
        stagePoint = 0;
	}
	
    public void HealthDown() {
        if (health > 1) {
            health--;
            UIhealth[health].color = new Color(1, 0, 0, 0.4f);  //체력 UI 색상 변화
        }
        else {
            //체력 UI 끄기
            UIhealth[0].color = new Color(1, 0, 0, 0.4f);

            //플레이어 사망 이펙트
            player.OnDie();
            
            //재시작 버튼 UI
            UIRestartBtn.SetActive(true);
        }
    }
	
	void OnTriggerEnter2D(Collider2D collision) {
        //낙사할 경우
        if (collision.gameObject.tag == "Player") {
            //플레이어 위치 되돌리기
            if (health > 1) {
                PlayerReposition();
            }

            //체력 감소
            HealthDown();
        }
	}

    void PlayerReposition() {   //플레이어 위치 되돌리기 함수
        player.transform.position = new Vector3(0, 0, -1);  //플레이어 위치 이동
        player.VelocityZero();  //플레이어 낙하 속도 0으로 만들기
    }

    public void Restart() {
        Time.timeScale = 1; //재시작시 시간 복구
        SceneManager.LoadScene(0);
    }
}

GameManager Inspector창
Retry Button의 Inspector창

  • GameManager의 변수창에 이미지, 텍스트, 오브젝트(버튼)을 넣어줘야 작동한다.
  • Retry 버튼은 게임 시작 때는 필요 없으므로 비활성화한채로 시작하고, 아래 On Click ()에 GameManager를 넣어 버튼 클릭시 만들어두었던 Restart() 함수를 실행시키도록 한다.

스테이지 1에서 사망시
스테이지 2에서 사망시
모든 스테이지 클리어시
버튼을 클릭하니 모두 점수가 초기화되어 스테이지 1에서 시작되었다


8. 사운드

이전 프로젝트에서도 사용했던 Free Casual Game SFX Pack을 Import해서 사용한다
Player에 AudioSource 컴포넌트 추가

//PlayerMove 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMove : MonoBehaviour {
    public GameManager gameManager;
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;
    CapsuleCollider2D capsulecollider;
    AudioSource audioSource;

    //Audio Clip 변수들
    public AudioClip audioJump;
    public AudioClip audioAttack;
    public AudioClip audioDamaged;
    public AudioClip audioItem;
    public AudioClip audioDie;
    public AudioClip audioFinish;
    

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();
        capsulecollider = GetComponent<CapsuleCollider2D>();
        audioSource = GetComponent<AudioSource>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void PlaySound(string action) {
        switch(action) {
            case "JUMP" :
                audioSource.clip = audioJump;
                break;
            case "ATTACK":
                audioSource.clip = audioAttack;
                break;
            case "DAMAGED":
                audioSource.clip = audioDamaged;
                break;
            case "ITEM":
                audioSource.clip = audioItem;
                break;
            case "DIE":
                audioSource.clip = audioDie;
                break;
            case "FINISH":
                audioSource.clip = audioFinish;
                break;
        }
        
        audioSource.Play();
    }

    void Update() {
        //점프
        if (Input.GetButtonDown("Jump") && !anim.GetBool("isJumping")) {    //스페이스바, 점프 애니메이션이 작동중이지 않다면
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            anim.SetBool("isJumping", true);    //Jump 애니메이션 추가
            PlaySound("JUMP");  //사운드 재생
        }

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal")) {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate() {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed * (-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);

        //점프 후 착지시 애니메이션 전환(레이캐스트)
        if (rigid.velocity.y < 0) { //y축 속도 값이 0보다 클때만. 땅에 있으면 레이 표시 X
            Debug.DrawRay(rigid.position, Vector3.down, new Color(0, 1, 0)); //레이 표시
            RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Platform")); //레이에 닿는 물체
            if (rayHit.collider != null) //레이에 닿는 물체가 있다면
                if (rayHit.distance < 0.5f)
                    anim.SetBool("isJumping", false);
        }
    }

    void OnCollisionEnter2D(Collision2D collision) {    //적과 충돌시
        if (collision.gameObject.tag == "Enemy") {
            //몬스터보다 위에 있고, 낙하 중이라면 밟음(공격)
            if (rigid.velocity.y < 0 && transform.position.y > collision.transform.position.y) {
                OnAttack(collision.transform);
            }
            //아니라면, 피해를 받음
            else
                OnDamaged(collision.transform.position);

        }
    }

    void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "Item") {
            //점수 얻기
            bool isBronze = collision.gameObject.name.Contains("Bronze");
            bool isSilver = collision.gameObject.name.Contains("Silver");
            bool isGold = collision.gameObject.name.Contains("Gold");

            if (isBronze)
                gameManager.stagePoint += 50;
            else if (isSilver)
                gameManager.stagePoint += 100;
            else if (isGold)
                gameManager.stagePoint += 300;

            //아이템 비활성화
            collision.gameObject.SetActive(false);

            //사운드 재생
            PlaySound("ITEM");
        }
        else if (collision.gameObject.tag == "Finish") {
            //사운드 재생
            PlaySound("FINISH");

            //다음 스테이지로 이동
            gameManager.NextStage();
        }
    }

    void OnAttack(Transform enemy) {    //몬스터 공격 함수
        //점수 얻기
        gameManager.stagePoint += 100;

        //사운드 재생
        PlaySound("ATTACK");

        //몬스터 밟았을때 반발력
        rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);

        //몬스터 사망
        EnemyMove enemyMove = enemy.GetComponent<EnemyMove>();
        enemyMove.OnDamaged();
    }

    void OnDamaged(Vector2 targetPos) {   //충돌 이벤트 무적 효과 함수
        //체력 감소
        gameManager.HealthDown();

        //사운드 재생
        PlaySound("DAMAGED");

        //레이어 변경(무적)
        gameObject.layer = 11;  //11번 레이어, PlayerDamaged

        //충돌시 스프라이트 색상 변화
        spriteRenderer.color = new Color(1, 1, 1, 0.4f);    //Color(R,G,B,투명도)

        //충돌시 튕겨짐
        //목표물 기준 왼쪽에서 닿으면 왼쪽으로, 오른쪽에서 닿으면 오른쪽으로
        int dirc = transform.position.x - targetPos.x > 0 ? 1 : -1;
        rigid.AddForce(new Vector2(dirc, 1) * 7, ForceMode2D.Impulse);

        //애니메이션 변경
        anim.SetTrigger("doDamaged");

        Invoke("OffDamaged", 3);    //3초 뒤 OffDamaged 함수 실행
    }

    void OffDamaged() { //충돌 이벤트 무적 해제 함수
        //레이어 변경(원래대로)
        gameObject.layer = 10;  //10번 레이어, Player
        spriteRenderer.color = new Color(1, 1, 1, 1);
    }

    public void OnDie() {  //플레이어 사망
        //스프라이트 반투명화
        spriteRenderer.color = new Color(1, 1, 1, 0.4f);

        //스프라이트 Y축 뒤집기
        spriteRenderer.flipY = true;

        //물리 충돌 제거
        capsulecollider.enabled = false;

        //사운드 재생
        PlaySound("DIE");

        //사망 모션(점프)
        rigid.AddForce(Vector2.up * 5, ForceMode2D.Impulse);
    }

    public void VelocityZero() {
        rigid.velocity = Vector2.zero;
    }
}

사용할 음악들을 넣어둔다.
영상에선 나오지 않았지만 빌드도 해준다
빌드 후 실행
문제 없이 사운드도 잘 나오고 실행도 잘 된다

  • Player에 AudioSource 컴포넌트를 추가하고, 기본적으로 체크되어 있는 Play On Awake를 체크 해제한다. Play On Awake는 이름대로 사운드를 시작하자마자 실행하는 것인데, 여기서 추가하는 사운드는 배경 음악이 아닌 효과음이므로 필요가 없다.
  • 사운드는 점프 7번, 공격 22번, 피해 41번, 아이템 28번, 죽음 11번, 클리어 12번을 사용한다. 나머지 사운드는 사용하지 않으므로 Import할 때 이것만 해도 괜찮을듯 하다.