골인 지점에 도착했을때와 게임오버시의 충돌처리를 작성합니다.

gameState라는 게임 상태를 나타내는 정적변수를 선언해서

player가 죽었을때는 "gameover", Goal에 도착했을대는 "gameclear"상태가 되어

각 이벤트호출시 gameState가 "playing"상태가 아니면 처리하지 않게해서 플레이어를 멈추게 합니다.

보통의 게임오브젝트(인스턴스)는 씬이 바뀔경우 없어지게 되는데 gameState는 정적변수라 씬이 바뀌어도 게임 전체에 계속 남아 있을 수 있다.

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

public class PlayerController : MonoBehaviour{
	~생략~
    public static string gameState = "playing"; //게임중
		~생략~
    void Start(){
		~생략~
        gameState = "playing";
    }

    // Update is called once per frame
    void Update(){
        if(gameState != "playing") {
            return;
        }
		~생략~
    }
    private void FixedUpdate() {
        if(gameState != "playing") {
            return;
        }
       		~생략~
    }
    public void Jump() {
       		~생략~
    }
    private void OnTriggerEnter2D(Collider2D collision) {
       		~생략~
    }
    public void Goal() {
        		~생략~
        gameState = "gameClear";
        GameStop(); //게임중지;
    }
    public void GameOver() {
       		~생략~
        gameState = "gameOver";
        GameStop();   //게임중지
        GetComponent<CapsuleCollider2D>().enabled = false;  //플레이어 판정 비활성화
        rbody.AddForce(new Vector2(0, 5), ForceMode2D.Impulse);  //한번 튕겨줍니다.
    }
    void GameStop() {
        Rigidbody2D rbody = GetComponent<Rigidbody2D>();
        rbody.velocity = Vector2.zero;
    }
}

PlayerController.cs
0.00MB

이제 애니메이션 전화을 위한 PlayerController 스크립트를 수정합니다.

이번에 제일 중요한 부분은 충돌 판정입니다. Goal과 Dead존에 들어가면 알맞은 함수를 실행합니다.

  private void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "Goal") {
            Goal();
        } else if(collision.gameObject.tag =="Dead") {
            GameOver();
        }
    }

FixedUpdate()에서는 이동중이면 PlayerMove, 정지시 PlayerStop, 하늘에 떠 있다면 PlayerJump 애니메이션을 실행합니다.

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

public class PlayerController : MonoBehaviour{
		// 생략
		//애니메이션처리
    Animator animator;  //애니메이터 컨트롤러 참조
    public string stopAnim = "PlayerStop";
    public string moveAnim = "PlayerMove";
    public string jumpAnim = "PlayerJump";
    public string goalAnim = "PlayerGoal";
    public string deadAnim = "PlayerOver";
    string nowAnim = "";
    string oldAnim = "";
    void Start(){
        rbody = GetComponent<Rigidbody2D>();
        animator = GetComponent<Animator>();
        nowAnim = stopAnim;
        oldAnim = stopAnim;
    }

    // Update is called once per frame
    void Update(){
 		// 생략 
    }
    private void FixedUpdate() {
     	//생략
        if(onGround) {
            if(axisH == 0) {
                nowAnim = stopAnim;
            } else {
                nowAnim = moveAnim;
            }
        } else {
            nowAnim = jumpAnim;
        }
        if(nowAnim != oldAnim) {
            oldAnim = nowAnim;
            animator.Play(nowAnim);
        }
    }
    public void Jump() {
        goJump = true;
    }
    private void OnTriggerEnter2D(Collider2D collision) {
        if (collision.gameObject.tag == "Goal") {
            Goal();
        } else if(collision.gameObject.tag =="Dead") {
            GameOver();
        }
    }
    public void Goal() {
        animator.Play(goalAnim);
    }
    public void GameOver() {
        animator.Play(deadAnim);
    }
}

PlayerController.cs
0.00MB

 

점프 애니메이션을 이전 처럼 여러장의 스프라이트로 자동으로 만들어도 되지만 이번에는 수동으로 만들어 보겠습니다.

하이라키에서 player를 선택하고 Ctrl+6를 클릭해서 애니메이션 창을 엽니다. 메뉴 Windows>Animation>Animation을 클릭해서도 열수 있습니다.

현재는 PlayerMove하나만 있습니다. PlayerMove>Create New Clip을 선택한후 PlayerJump로 바꿉니다.

Add Property를 클릭해 Sprite Renderer>Sprice옆 +를 클릭합니다.

기본적으로 player_stop이 1초간 적용되어 있는데 Assets>Player폴더내 Player_Jump1,2로 변경해보겠습니다.

Player_Jump1을 0프레임에 끌어다 놓습니다. Player_Jum2를 0.2초 1초 2군데 끌어다 놓습니다.

 

대기, 클리어, 게임오버 애니메이션 만들기

New Animation Clip을 PlayerStop이라는 이름으로 만들고 Player폴더에서 Player_Stop 스프라이트를 끌어다 0프레임에 놓으면 자동으로  Sprite프라퍼티가 추가되면서 다이아몬드가 생깁니다.

PlayerGoal이라는 이름으로 새로운 Clip을 만들고 Player_Clear라는 스프라이트를 0프레임과 60프레임에 끌어다 놓습니다. Add Property>Sprite Color를 추가하고 60프레임의 Color.a를 0으로 변경합니다.

지금까지 Move, Jump, Stop, Clear, Over 애니메이션클립을 만들었습니다. 이들이 상황에 맞게 전화되도록 해보겠습니다.

Animations 폴더의 Player Anim을 열어봅니다. 그동안 만들었더 애니메이션들이 모여있습니다.

현재  제일 먼저 시작되는 Entry에서 PlayerMove가 디폴트로 설정되여 있는데 PlayerStop으로 바꿔보겠습니다. PlayerStop위를 우클릭하고 Set as Layer Default State를 선택합니다. PlayerStop이 디폴트가 됩니다.

이제 플레이 해보면 PlayerStop이 디폴트이므로 아무런 동작도 안하고 움직여도 애니메이션이 없습니다.

애니메이션은 유니티의 메카님 기능을 활용합니다. 여러장의 스프라이트를 순차적으로 플레이해서 애니메이션을 보여줍니다.

 

이동 애니메이션 만들기

프로젝트뷰 player폴더내 player_run1부터 run7까지를 모드 선택후 Pivot이 Bottom인지 확인후 , 씬뷰로 드래그&드롭 합니다. 저장할건지 물어보면 PlayerAnim으로 저장합니다.

하이라키에 보면 player_run1이 생겼는데 레이어오더 문제로 보이지 않습니다. Order를 3으로 변경합니다.

저장후 폴더에는 삼각형과 사각형모양의 아이콘이 2개 생깁니다. 이름을 아래와 같이 변경하고 Animations폴더를 만들어 이동 시킵니다. 삼각형은 이동동작을 표현하난 애니메이션클립이고 사각형은 여러가지 애니메이션을 조건에 맞게 플레이 해주는 애니메이터(컨트롤러)입니다. 새로만들어진 player_run1의 인스펙터뷰를 보면 Animator컴포넌트가 추가되어 있고 Controller는 PlayerAnim가 연결되어 있습니다.

Player Move를 선택하고  Samples가 12로 되어 있습니다. 7을 입력하고 엔터하면 조금 느리게 재생됩니다. 플레이가 재생이 안되면 하이라키의 player_run1을 클릭하면 잘 움직입니다.

이제 하이라키의 player_run1을 지웁니다. 씬뷰에서 방금 만든 캐릭터를 지워도 됩니다.

이제 프로젝트뷰 Animations폴더내 PlayAnim을 끌어다 하이라키의 player에 놓습니다. 자동으로 애니메이션 컴포넌트가 추가됩니다.

 

플레이어 캐릭터가 점프를 할 수 있는데 벽이나 블록에 닿으면 붙어서 떨어지지 않습니다. 이는 캐릭터의 마찰력 때문입니다.  마찰을 0으로 바꾸어 보겠습니다.

 프로젝트뷰의 +를 눌러 2D> physics Material 2D를 클릭합니다. 이름을 PM2D라고 변경합니다.

Fricsion을 0으로 바꿉니다.

이 매터리얼을 Player에 적용합니다. 

Player게임오브젝트의 인스펙터뷰> Rigidbody2D > Material 검색브라우저를 눌러 방금만든 PM2D를 적용시킵니다. 파일을 끌어다 여기다 놔도 됩니다.

 

Vector2 jumpPw = new Vector2(0, jump);  // 점프벡터 생성
rbody.AddForce(jumpPw, ForceMode2D.Impulse); //순간적인 힘 가하기
goJump = false; // 점프플래그 끄기

플레이어 Tag지정하기

플레이어를 선택하고 Tag를 player를 선택합니다. player Tag는 기본적으로 있습니다.

 

점프 구현하기

스페이스바를 눌렀을때 캐릭터를 점프하게 만들어 보겠습니다.

Update()함수에 스페이스키를 체크되면 Jump()함수를 호출하는 코드가 추가되었습니다. 

Jump() 함수는 goJump 플래그를 true로 할 뿐입니다.

public void Jump() {
    goJump = true;
}

실제 점프 동작은 FixedUpdate()에서 if(onGround && goJump) 때 이루어 집니다. 

Vector2 jumpPw = new Vector2(0, jump);  // 점프벡터 생성
rbody.AddForce(jumpPw, ForceMode2D.Impulse); //순간적인 힘 가하기
goJump = false; // 점프플래그 끄기

플레이 해보면 스페이스바를 눌러도 캐릭터가 점프를 안하는데 LayerMask 변수인 groundLayer를 인스펙터뷰 playerController 스크립트 창에서 ground로 설정해야 합니다.

public LayerMask groundLayer; //착지할 수 있는 레이어
bool onGround = false;  //지면에 서 있는 플래그

Linecast가 캐릭터위치에서 0.1f 아래로 광선을 쏴서 goundLayer가 감지될경우만 onGround를 true시킵니다.

onGround = Physics2D.Linecast(transform.position, 
					transform.position - (transform.up * 0.1f), groundLayer);

Linecast(시작점, 종료점, 마스크)

 

아래는 playerController.cs 전체코드입니다.

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

public class PlayerController : MonoBehaviour{
    // Start is called before the first frame update
    Rigidbody2D rbody;  // Rigidbody 참조
    float axisH = 0.0f; //입력
    public float speed = 3.0f;  //이동속도

    public float jump = 9.0f; //점프력
    public LayerMask groundLayer; //착지할 수 있는 레이어
    bool goJump = false; //점프개시 플래그
    bool onGround = false;  //지면에 서 있는 플래그
    void Start(){
        rbody = GetComponent<Rigidbody2D>();
    }

    // Update is called once per frame
    void Update(){
        axisH = Input.GetAxisRaw("Horizontal");
        if(axisH > 0.0f) {
            transform.localScale = new Vector2(1, 1);
        } else if (axisH <0.0f) {
            transform.localScale = new Vector2(-1, 1);
        }
        if (Input.GetButtonDown("Jump")) {
            Jump();
        }
    }
    private void FixedUpdate() {
        //착지판정
        onGround = Physics2D.Linecast(transform.position, transform.position - (transform.up * 0.1f), groundLayer);
        if(onGround || axisH !=0) {  //점프상태일 때는 전진 안함.
            rbody.velocity = new Vector2(speed * axisH, rbody.velocity.y);
        }
        if(onGround && goJump) {
            Vector2 jumpPw = new Vector2(0, jump);  // 점프벡터 생성
            rbody.AddForce(jumpPw, ForceMode2D.Impulse); //순간적인 힘 가하기
            goJump = false; // 점프플래그 끄기
        }
    }
    public void Jump() {
        goJump = true;
    }
}

 

이번에 만들 게임요소를 정리해 보겠습니다.

  • 규칙 : 오른쪽 끝의 골인 지점에 도착하면 게임 클리어
  • 적과 장애물 : 함정에 떨어지면 게임 오버
  • 간섭과 변화 : 점프로 함정 뛰어넘기
  • 보상 : 함정을 피해서 목표 지점에  도착

 

4.1 사이드뷰게임

사이드뷰는 게임 속 세상을 옆에서 바라본 시점의 게임 시스템입니다. 이동은 좌우나 점프를 위해 상하로 움직일 수 있습니다. 

 

사용할 게임 오브젝트와 스크립트 생각해보기

  • 플레이어 캐릭터 : 좌우이동과 점프를 할 수 있습니다. 이동이나 점프를 묘사할 애니메이션을 적용합니다.
  • 지면과 블록 :  플레이어가 올라가서 이동할 수 있는 지면과 점프해 올라갈 수 있는 발판이 되는 블록입니다.
  • 골인 지점과 게임 오버 : 게임 스테이지의 오른쪽 끝에 게임 오브젝트를 배치해 이곳에 접촉하면 스테이지 클리어되도록 목표지점을 만듭니다. 지면 아래로 떨어지면 게임 오버되는 처리도 추가합니다.
  • 상태 표시와 재시작 : 게임으 시작및 게임오버 클리어했을 때의 화면을 표시하고 다시 처음부터 시작할 수 있게 합니다. 

4.2 샘플 게임 실행해보기  

Jewlry Hunter폴더내의 프로젝트를 플레이 해보시면 됩니다.

4.3 게임 스테이지 만들기

지면 블록 만들기

이전에 연습했던 프로젝트를 사용합니다.

프로젝트뷰의 Block들을 끌어다 씬뷰에 배치합니다.

위와 같이 보이지 않을겁니다. 왜냐하면 Order in Layer가 밑에 있기 때문에 가려져서 그렇습니다.

하이라키에서 방금 끌어온 block들을 선택해고 인스펙터뷰의 Additional Settings의 Order in Layer를 2로 하면 위와 같이 블록들이 보입니다.

레이어로 게임 오브젝트를 그룹으로 분류하기 (레이어)

유니티는 게임오브젝트를 이름으로 식별할 수 있지만 "레이어"를 사용해 그룹으로 묶을 수도 있습니다. 바닥과 블록을 Ground라는 이름으로 그룹화해 플레이어 캐릭터가 레이어에 접촉할 때만 점프할 수 있도록 합니다.

인스펙터뷰의 레이어 Item을 눌러 Add Layer로 Ground 레이어를 추가합니다.  보통 0~5까지는 유니티가 사용중이므로 레이어6에 새로 만듭니다.

레이어 추가 메뉴는 게임오브젝트 메뉴와 독립적이라 돌아갈 수 없고 다시 하이라키의 게임오브젝트를 선택해야 합니다.

한꺼번에 레이어를 바꾸기 위해 하이라키에서 ground 와 블록3가지를 선택하고 인스펙트뷰에서 레이어를 6으로 바꿔줍니다.

씬뷰에 goal팻말을 배치하고 인스펙터뷰에서 Order in Layer를 2로 바꿔줍니다.

골인 지점 도달 판별하기

목표에 도달한것을 판별하기 위해 goal에 Box Collider2D 컴포넌트를 추가하고 Is Trigger를 체크합니다. Is Trigger는 물리적인 충돌없이 충돌 이벤트만 만듭니다.

 

게임오브젝트를 구별하는 방법(태그)

골인 지점에 도달했는지 판별하는 방법중 태그(Tag)도 있습니다. 

하이라키에서 goal을 선택후 인스펙트뷰에서 Tag를 눌러 Add Tag에서 Goal을 만들고 저장합니다. 이후 Goal에서 지정합니다.

게임오브젝트 재사용하기

유니티에서 프리팹은 재사용가능한 "클래스"같은 개념입니다. 하이라키의 ground,block1,2,3,goal등을 끌어다 프로젝트뷰에 끌어다 놓으면 자동으로 프리팹이 만들어 집니다. 하이라키의  ground,block1,2,3,goal등은 이미 파란색으로 바뀌었는데 이건 프리팹의 인스턴스라는 이야기 입니다. 프리팹을 사용하고 싶으면 아이콘을 끌어다 하이라키나 씬뷰에 놓으면 인스턴스가 만들어집니다.

프로젝트뷰의 프리팹은 런타임시 존재하지 않습니다. 씬뷰에서 인스턴스화 되었을때 비로서 런타임에서 존재하고 접근할 수 있습니다. 이점이 중요합니다. 클래스나 타입도 변수화 하지 않으면 아무것도 아닌것 처럼요.

프리팹을 변경하면 기본에 만들었던 모든 인스턴스에 영향을 미칩니다. 반대로 인스턴스를 변경해면 유니티에서 프리팹을 변경할건지 물어보는 개념도 있습니다. 이걸 오버라이드라고 합니다.

프리팹을 정리하기 위해 프로젝트뷰 Assets 폴더안에 Prefabs폴더를 만들고 만들어진 프리팹들을 끌어다 놓습니다.

 

프리팹을 배치해 지면 만들기

프리팹 폴더안의 프리팹들을 이용해 다음과 같이 지면과 블록을 더 배치 합니다. (이미지 폴더가 아닙니다.)

백그라운드는 back_title에서 back으로 바꾸었습니다. 길게보이는 그라운드는 확대한게 아니라 같은 그라운드를 2개 연결한것입니다.

 

게임 오브젝트 정렬하기

옮길 오브젝트를 살짝 클릭해서 선택해줍니다. 마우스클릭하지 않고 키보드 V를 눌러 맞추고 싶은 중앙이나 모서리를 클릭하면서 맞출곳에 가져가면 자석같이 붙습니다. 화면을 좀 크게하시는게 잘되고 한번에 안되면 옮긴후 다시 시도 하면 됩니다.

 

보이지 않는 충돌 판정 만들기

하이라키의 +를 눌러 Create Empty를 클릭합니다. 이름을 Wall로 합니다. Box Collider 2D를 2개 추가합니다.

Edit  Collider를 눌러 충돌영역의 위치를 화면 양옆으로 이동시킵니다. BoxCollider2D를 2개 추가했기 때문에 영역이 2개 입니다. 

플레이해보면 캐릭터가 왼쪽벽에 부딪쳐 가지 못합니다. 

 

게임오버 판별하기

현재는 구덩이쪽으로 가면 캐릭터가 빠져 계속 떨어지기 때문에 게임진행이 더 이상 불가능 합니다.

DeadZone이라는 빈게임오브젝트를 만들고 BoxCollider2D컴포넌트를 추가하고 Is Trigger를 체크합니다.  Edit Collider를 누루고 다음과 같이 화면 아래 배치합니다. 

Dead라는 Tag를 만들어 지정해 줍니다.

 

 

 

 

 

+ Recent posts