본문 바로가기

GAME

[Unity2D] 기초2D게임 만들기

728x90

앞부분과 내용이 이어집니다.

 

여태까지 해놓은 전체코드를 한번 올리고 시작하겠습니다

 

PlayerMove.cs : 캐릭터의 이동/점프 등에 관한 코드 

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

public class PlayerMove : MonoBehaviour
{
    public float maxSpeed; //최대 속력 변수 
    public float jumpPower;
    Rigidbody2D rigid; //물리이동을 위한 변수 선언 
    SpriteRenderer spriteRenderer; //방향전환을 위한 변수 
    Animator animator; //애니메이터 조작을 위한 변수 

    private void Awake() {
        
        rigid = GetComponent<Rigidbody2D>(); //변수 초기화 
        spriteRenderer = GetComponent<SpriteRenderer>(); // 초기화 
        animator = GetComponent<Animator>();
    }


    void Update(){

        // 버튼에서 손을 떄는 등의 단발적인 키보드 입력은 FixedUpdate보다 Update에 쓰는게 키보드 입력이 누락될 확률이 낮아짐


        //Jump
        if(Input.GetButtonDown("Jump") && !animator.GetBool("isJumping")){
            rigid.AddForce(Vector2.up* jumpPower , ForceMode2D.Impulse);
            animator.SetBool("isJumping",true);
        }

        //Stop speed 
        if(Input.GetButtonUp("Horizontal")){ // 버튼에서 손을 때는 경우 
            // normalized : 벡터 크기를 1로 만든 상태 (단위벡터 : 크기가 1인 벡터)
            // 벡터는 방향과 크기를 동시에 가지는데 크기(- : 왼 , + : 오)를 구별하기 위하여 단위벡터(1,-1)로 방향을 알수 있도록 단위벡터를 곱함 
            rigid.velocity = new Vector2( 0.5f * rigid.velocity.normalized.x , rigid.velocity.y);
        }

        //Direction Sprite
        if(Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;

        
        //Animation
        if( Mathf.Abs(rigid.velocity.x) < 0.35) //속도가 0 == 멈춤 
            animator.SetBool("isWalking",false); //isWalking 변수 : false 
        else// 이동중 
            animator.SetBool("isWalking",true);
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        float h = Input.GetAxisRaw("Horizontal");   
        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        if(rigid.velocity.x > maxSpeed)  //오른쪽으로 이동 (+) , 최대 속력을 넘으면 
            rigid.velocity= new Vector2(maxSpeed, rigid.velocity.y); //해당 오브젝트의 속력은 maxSpeed 
        
        else if(rigid.velocity.x < maxSpeed*(-1)) // 왼쪽으로 이동 (-) 
            rigid.velocity =  new Vector2(maxSpeed*(-1), rigid.velocity.y); //y값은 점프의 영향이므로 0으로 제한을 두면 안됨 


        //Landing Paltform
        Debug.DrawRay(rigid.position, Vector3.down, new Color(0,1,0)); //빔을 쏨(디버그는 게임상에서보이지 않음 ) 시작위치, 어디로 쏠지, 빔의 색 

        RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Platform"));
        //빔의 시작위치, 빔의 방향 , 1:distance , ( 빔에 맞은 오브젝트를 특정 레이어로 한정 지어야할 때 사용 ) // RaycastHit2D : Ray에 닿은 오브젝트 클래스 
    
        //rayHit는 여러개 맞더라도 처음 맞은 오브젝트의 정보만을 저장(?) 
        if(rigid.velocity.y < 0){ // 뛰어올랐다가 아래로 떨어질 때만 빔을 쏨 
            if(rayHit.collider != null){ //빔을 맞은 오브젝트가 있을때  -> 맞지않으면 collider도 생성되지않음 
                if(rayHit.distance < 0.5f) 
                    animator.SetBool("isJumping",false); //거리가 0.5보다 작아지면 변경

            }
        }
    }




    private void OnCollisionEnter2D(Collision2D collision) {
        
        if(collision.gameObject.tag == "Enamy"){
            OnDamaged(collision.transform.position); //현재 충돌한 오브젝트의 위치값을 넘겨줌  
        }

    }


    void OnDamaged(Vector2 tartgetPos){

        //Chnage Layer
        gameObject.layer = 11; //playerDamaged Layer number가 11로 지정되어있음 

        //View Alpha
        spriteRenderer.color = new Color(1,1,1,0.4f); //투명도를 0.4로 부여하여 지금이 무적시간으로 변경되었음을 보여줌

        //Reaction Force
        //맞으면 튕겨나가는 모션
        int dirc = transform.position.x-tartgetPos.x > 0 ? 1 : -1; 
        //튕겨나가는 방향지정 -> 플레이어 위치(x) - 충돌한 오브젝트위치(x) > 0: 플레이어가 오브젝트를 기준으로 어디에 있었는지 판별
        //> 0이면 1(오른쪽으로 튕김) , <=0 이면 -1 (왼쪽으로 튕김)
        rigid.AddForce(new Vector2(dirc,1)*7, ForceMode2D.Impulse); // *7은 튕겨나가는 강도를 의미 

        
        //Animation
        animator.SetTrigger("doDamaged");

        Invoke("OffDamaged",2); //2초의 딜레이 (무적시간 2초)
    }

    void OffDamaged(){ //무적해제함수 
        gameObject.layer = 10; //플레이어 레이어로 복귀함

        spriteRenderer.color = new Color(1,1,1,1); //투명도를 1로 다시 되돌림 

    }
}

 

MonsterMove.cs : 몬스터AI(떨어지지 않게 일정 범위내에서 움직이거나 멈추거나 하기)

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

public class MonsterMove : MonoBehaviour
{

    Rigidbody2D rigid;
    public int nextMove;//다음 행동지표를 결정할 변수
    Animator animator;
    SpriteRenderer spriteRenderer;

    // Start is called before the first frame update
    private void Awake() {
        animator = GetComponent<Animator>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        rigid = GetComponent<Rigidbody2D>();
        Invoke("Think", 5); // 초기화 함수 안에 넣어서 실행될 때 마다(최초 1회) nextMove변수가 초기화 되도록함 
        
    }


    // Update is called once per frame
    void FixedUpdate()
    {
        //Move
       rigid.velocity = new Vector2(nextMove,rigid.velocity.y); //nextMove 에 0:멈춤 -1:왼쪽 1:오른쪽 으로 이동 


       //Platform check(맵 앞이 낭떨어지면 뒤돌기 위해서 지형을 탐색)


       //자신의 한 칸 앞 지형을 탐색해야하므로 position.x + nextMove(-1,1,0이므로 적절함)
        Vector2 frontVec = new Vector2(rigid.position.x + nextMove*0.4f, rigid.position.y);

        //한칸 앞 부분아래 쪽으로 ray를 쏨
        Debug.DrawRay(frontVec, Vector3.down, new Color(0,1,0));

        //레이를 쏴서 맞은 오브젝트를 탐지 
        RaycastHit2D raycast = Physics2D.Raycast(frontVec, Vector3.down,1,LayerMask.GetMask("Platform"));

        //탐지된 오브젝트가 null : 그 앞에 지형이 없음
        if(raycast.collider == null){
            Turn();
        }

    }


    void Think(){//몬스터가 스스로 생각해서 판단 (-1:왼쪽이동 ,1:오른쪽 이동 ,0:멈춤  으로 3가지 행동을 판단)

        //Set Next Active
        //Random.Range : 최소<= 난수 <최대 /범위의 랜덤 수를 생성(최대는 제외이므로 주의해야함)
        nextMove = Random.Range(-1,2);

        //Sprite Animation
        //WalkSpeed변수를 nextMove로 초기화 
        animator.SetInteger("WalkSpeed",nextMove);


        //Flip Sprite
        if(nextMove != 0) //서있을 때 굳이 방향을 바꿀 필요가 없음 
            spriteRenderer.flipX = nextMove == 1; //nextmove 가 1이면 방향을 반대로 변경  


        //Recursive (재귀함수는 가장 아래에 쓰는게 기본적) 
        float time = Random.Range(2f, 5f); //생각하는 시간을 랜덤으로 부여 
        //Think(); : 재귀함수 : 딜레이를 쓰지 않으면 CPU과부화 되므로 재귀함수쓸 때는 항상 주의 ->Think()를 직접 호출하는 대신 Invoke()사용
        Invoke("Think", time); //매개변수로 받은 함수를 time초의 딜레이를 부여하여 재실행 
    }

    void Turn(){

        nextMove= nextMove*(-1); //우리가 직접 방향을 바꾸어 주었으니 Think는 잠시 멈추어야함
        spriteRenderer.flipX = nextMove == 1;

        CancelInvoke(); //think를 잠시 멈춘 후 재실행
        Invoke("Think",2);//  

    }

}

 

 

 

몬스터 사냥 + 몬스터 죽음

이제 플레이어가 몬스터의 머리를 밟으면 몬스터를 해치울 수 있도록 해봅시다 

조건은 "플레이어가 몬스터보다 위에 있음 && 아래로 낙하중 = 밟음"이라고 가정합시다

 

플레이어가 몬스터를 밟는 것도 충돌이벤트 이므로 OnCollisionEnter2D함수에 조건으로 구현해줍니다.

PlayerMove.cs

    private void OnCollisionEnter2D(Collision2D collision) {
        
        if(collision.gameObject.tag == "Enamy"){ //적과 충돌 시 
        
        
			//적의 윗쪽에서 충돌 && 아래로 하강중 : 밟는 모션일 때 
            if(rigid.velocity.y < 0 && transform.position.y > collision.transform.position.y){
                OnAttack(collision.transform); //적을 공격하는 함수
            }
            else{ // 밟는 모션이 아닌 적과의 충돌-> 데미지를 플레이어가 받음(데미지 받는함수) 
                OnDamaged(collision.transform.position); //현재 충돌한 오브젝트의 위치값을 넘겨줌  
            }
        }

    }

 

OnAttack함수를 구현해보도록 합시다.

플레이어의 반동을 표현해주고

몬스터의 클래스를 불러와 몬스터가 데미지를 입는 함수를 호출합니다.

주의*여기서 enemyMove.OnDamaged()의 OnDamaged()는 MonsterMove.cs의 OnDamaged()입니다.

PlayerMove.cs

void OnAttack(Transform enemy){

        
        
        //Reaction Force : 반동(플레이어가 튕겨져나감)
        rigid.AddForce(Vector2.up *5 , ForceMode2D.Impulse)

        //Enemy Die
        //몬스터에 적용한 스크립트의 함수를 사용하기위해 해당 클래스의 변수를 선언해서 초기화
        MonsterMove enemyMove = enemy.GetComponent<MonsterMove>();
        enemyMove.OnDamaged(); // 몬스터가 데미지를 입었을때 실행할 함수를 불러옴 


    }

 

 

이제 MonsterMove.cs에서 실행해줄 OnDamaged()를 구현합시다.

몬스터는 공격을 당하면 

흐려지고, 뒤집어진후,  아래로 떨어지고, 더이상 비활성화 되도록 구현합니다.

    public void OnDamaged(){ //몬스터가 데미지를 입었을때 


        //Sprite Alpha : 색상 변경 
        spriteRenderer.color = new Color(1,1,1,0.4f);
        
        //Sprite Flip Y : 뒤집어지기 
        spriteRenderer.flipY = true;

        //Collider Disable : 콜라이더 끄기 
        capcollider.enabled = false;

        //Die Effect Jump : 아래로 추락(콜라이더 꺼서 바닥밑으로 추락함 )
        rigid.AddForce(Vector2.up*5, ForceMode2D.Impulse);

        //Destroy 
        Invoke("DeActive",5);

    }

    void DeActive(){ //오브젝트 끄기 
        gameObject.SetActive(false);
    }

 

 

게임매니저GameManager

이는 3D게임에서 다루었듯이 "GameManger"라는 오브젝트를 이용하여 플레이어의 점수등을 관리합니다.

Create Empty를 통해 오브젝트를 생성하고 이름을 바꾸어줍니다.

GameManager.cs라는 스크립트를 생성하고 적용시켜줍니다.

 

총점 / 해당 스테이지의 점수 / 스테이지 변수를 만들어줍니다.

    public int totalPoint;
    public int stagePoint;
    public int stageIndex;

 

그 후 다양한 조작을 위해 플레이어의 스크립트(PlayerMove.cs) 클래스 변수를 public으로 만들어줍니다.

    public GameManager gameManager;

 

플레이어 오브젝트의 스크립트에 Game Manager부분이 None으로 되어있을탠데

그곳에 GameManager오브젝트를 드래그해서 넣어줍니다.

 

 

 

아이템 구현

일단 제공된 금색/은색/동색 동전을 각각 2D Object로 만든 후 애니메이션/Collider을 넣어줍니다.

Gold/Silver/Bronze 오브젝트를 만들었습니다.

 

Tag도 Item으로 만들어서 추가해줍니다.

Collider에는 Is Trigger에 체크해줍시다

 

이제 동전을 먹으면 없어지도록 만들어봅시다. ( 비활성화 )

Trigger이벤트로 설정해 주어야 하겠네요

PlayerMove.cs

    void OnTriggerEnter2D(Collider2D other) {
        
        if(other.gameObject.tag == "Item"){
            //포인트를 얻음

            //동전 사라지기(비활성화)
            other.gameObject.SetActive(false);
        }

    }

일단 점수에 관한 것은 빈칸으로 두고(GameManager를 만들어야함), 사라지는 것만 해보았습니다.

 

 

stagePoint증가

몬스터를 죽였을 때 점수 올리기

    void OnAttack(Transform enemy){

        //Point 점수 올리기
        gameManager.stagePoint +=100;
        
        //Reaction Force
        rigid.AddForce(Vector2.up*5, ForceMode2D.Impulse);

        //Enemy Die
        //몬스터에 적용한 스크립트의 함수를 사용하기위해 해당 클래스의 변수를 선언해서 초기화
        MonsterMove enemyMove = enemy.GetComponent<MonsterMove>();
        enemyMove.OnDamaged(); // 몬스터가 데미지를 입었을때 실행할 함수를 불러옴 


    }

 

동전 먹었을 때 점수 증가

    void OnTriggerEnter2D(Collider2D other) {
        
        if(other.gameObject.tag == "Item"){

            //Point
            //포인트를 얻음
            bool isBronze = other.gameObject.name.Contains("Bronze");
            bool isSilver = other.gameObject.name.Contains("Silver");
            bool isGold = other.gameObject.name.Contains("Gold");

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


            //동전 사라지기(비활성화)
            other.gameObject.SetActive(false);
        }

        else if(other.gameObject.tag == "Finish"){
            //Next Stage
        }

    }

 

gameObject.name.Contains("단어") // 입력한 단어가 오브젝트의 name에 포함되어있으면 true

 

결승점

오브젝트를 만들고, 콜라이더를 추가해서 조절해줍니다.

 

태그설정 

콜라이더 조절

 

이제 결승점오브젝트와 닿으면 NextStage로 넘어가도록 해봅시다

이는 3D게임에서 다루었듯이 "GameManger"라는 오브젝트를 이용하여 플레이어의 점수등을 관리합니다.

 

GameManager.cs 에 다음 Stage로 넘어가는 코드를 작성합니다.

스테이지를 증가시키고, 지역포인트를 전체 점수에 포함시킨후 지역포인트를 0으로 다시 초기화 시킵니다.

    public void NextStage(){

        stageIndex++; //스테이지 증가 

        totalPoint += stagePoint; // 얻은 지역포인트 전체점수에 포함시키기 
        stagePoint = 0; //지역 포인트 초기화
    }

그리고 결승점에 들어갔을 때 코드(PlayerMove.cs -> OnTriggerEnter2D에서 Finish tag와 닿았을 때)에서 이 함수를 실행시켜줍니다.

    void OnTriggerEnter2D(Collider2D other) {
        
        if(other.gameObject.tag == "Item"){
		// 아이템에 관한 코드
        }

        else if(other.gameObject.tag == "Finish"){
            //Next Stage
            gameManager.NextStage();
        }

    }

 

 

플레이어 체력

 

GameManager.cs 에 플레이어 생명 변수를 추가해줍니다.

public int health;

 

 

 

1)  지형 밑으로 떨어지면 생명 감소 

떨어지면 생명을 깍는 코드를 만들어봅시다

박스 콜라이더를 GameManager 오브젝트에 추가하여 떨어질 때 경계를 만들어줍니다.

통과해야하니까 on trigger 체크 

넓게 경계를 잡아줍니다

 

플레이어 오브젝트의 태그를 Player로 해주어야 합니다

private void OnTriggerEnter2D(Collider2D other) {
        
        if(other.gameObject.tag == "Player"){

            //체력감소
            health--; //경계에 부딪힌게 플레이어면 health 감소


            //떨어진 위치에서 플레이어 재생성
            other.attachedRigidbody.velocity  = Vector2.zero; 
            other.transform.position = new Vector3(-12,-2,-1); //플레이어의 시작위치로 되돌아오기
        } 
    }

new Vector3 는 자신의 기준에서 시작위치를 설정해주셔야 합니다.

 

 

2) 데미지를 입으면 생명 감소

Playermove.cs코드에서 OnDamaged 함수에 health를 --해주는 코드를 추가해줍니다.

    void OnDamaged(Vector2 tartgetPos){
		
        //다른 데미지를 입는 코드 생략

        //생명 감소
        gameManager.health--;
    }

 

 

3) health = 0이되면 죽음(?)

 

위에서 health -- 햇던 부분을 healthDown()함수로 변경하여 죽음까지 표현해보도록 합시다 

생명이 0개 이하 : 죽음 (죽음 이팩트 / ui에 적용 / 재시작 등)

생명 0개 이상 : 생명 감소 

 

health--;      ->      HealthDown(); 으로 변경합시다

 

 

몬스터가 죽었을때 이팩트처럼 플레이어 자체에도 죽었을때 이팩트를 추가해줍시다.

PlayerMove.cs

    public void OnDie(){ //죽음 effect(외적인거)

        //Sprite Alpha : 색상 변경 
        spriteRenderer.color = new Color(1,1,1,0.4f);
        
        //Sprite Flip Y : 뒤집어지기 
        spriteRenderer.flipY = true;

        //Collider Disable : 콜라이더 끄기 
        capcollider.enabled = false;

        //Die Effect Jump : 아래로 추락(콜라이더 꺼서 바닥밑으로 추락함 )
        rigid.AddForce(Vector2.up*5, ForceMode2D.Impulse);


    }

 

 

onDie()는 PlayerMove 의 함수이고, healthDown()은 GameManager()의 함수이므로,

GameManager가 PlayerMove객체를 표현하는 변수를 가져야합니다.  

public PlayerMove player;

 

유니티 상에서 GameManager의 Player변수에 자신의 플레이어 오브젝트를 끌어놔 초기화해줍니다

이제 GameManager.cs에서 PlayerMove.cs의 함수를 사용할 수 있게됐습니다.

GameManager.cs 

public void HealthDown(){

        if(health > 0) //생명이 0보다 크면 단순 생명 감소
            health--;

        else{// 생명이 0이하면 죽음

            //플레이어가 죽는 모션(이패트)
            player.OnDie();

            //결과 출력
            Debug.Log("죽었습니다.");

            //UI 다시 시작 버튼 



        }

 

그리고 플레이어 재생성 코드 또한 health가 1보다 클 때로 조건을 추가해주어야 합니다.

GameManager.cs

    private void OnTriggerEnter2D(Collider2D other) {
        
        if(other.gameObject.tag == "Player"){

            //체력감소
            HealthDown(); //경계에 부딪힌게 플레이어면 health 감소


            if(health>1){
            //떨어진 위치에서 플레이어 재생성 -> 플레이어가 죽지 않았을 떄만 해야함 
            other.attachedRigidbody.velocity  = Vector2.zero; 
            other.transform.position = new Vector3(-12,-2,-1); //플레이어의 시작위치로 되돌아오기
            }
        } 
    }

 

 

 

NextStage

다음 스테이지로 이동하도록 만들어봅시다.

 

Empty object를 만들어 Stage1이라고 이름 붙인 후,

플레이어/ 게임메니저/카메라를 제외한 나머지 모든 오브젝트들을 Stage1안에 넣어 정리해줍니다

 

이제 이 Stage1을 복사 붙여넣기 한 후 맵을 바꿔서 여러스테이지들을 구성해줍니다. (저는 귀찮아서 2까지만함)

스테이지 2도 이렇게 만들어 주었습니다 

 

실행해 보실 땐 여기 체크 해제해주셔야 인게임에서 두 스테이지가 나오는 일을 방지 할 수 있습니다.

(비활성화)

 

그렇다면 스테이지가 넘어가면 현재 스테이지 오브젝트를 비활성화하고

다음스테이지 오브젝트를 활성화 하면 다음 단계 맵으로 바꾸어줄 수 있겠네요

 

스테이지들을 하나로 관리하기 위해 배열을 선언해줍니다

스테이지는 오브젝트이므로 gameObject형 배열을 선언해줍니다.

public GameObject[] Stages; //스테이지를 오브젝트로 만들었기 떄문에 오브젝트 배열로 관리가능 

배열을 만들어 주었으므로 유니티 상에서 Stage1 , Stage2를 끌어와 array에 추가해주어야 합니다.

size는 자동으로 늘어납니다.

 

NextStage함수에 스테이지를 활성화/비활성화하는 코드를 작성합니다.

    public void NextStage(){

        //현재 스테이지 비활성화
        Stages[stageIndex].SetActive(false);
        stageIndex++; //스테이지 증가 
        Stages[stageIndex].SetActive(true); //다음 스테이지 활성화

        totalPoint += stagePoint; // 얻은 지역포인트 전체점수에 포함시키기 
        stagePoint = 0; //지역 포인트 초기화
    }

 

 

마지막 스테이지인 경우 

다음 스테이지로 넘어가는 것이 아닌 게임 클리어를 출력해주도록 설정합니다.

마지막 스테이지인지는 스테이지 배열의 길이로 계산할 수 있습니다. 

    public void NextStage(){

        if (stageIndex < Stages.Length-1){   // 마지막 스테이지 아닌 경우 -> 다음스테이지로 

            Stages[stageIndex].SetActive(false);
            stageIndex++; //스테이지 증가 
            Stages[stageIndex].SetActive(true); //다음 스테이지 활성화

            PlayerReposition(); //시작위치에서 플레이어를 태어나게?하는 함수 
        }
        else{ //마지막 스테이지인 경우 ->게임끝 

            //플레이어 컨트롤 막기 
            Time.timeScale = 0;

            //결과출력 
            Debug.Log("게임 클리어");

            //UI
            
        }


        //Calculate point
        totalPoint += stagePoint; // 얻은 지역포인트 전체점수에 포함시키기 
        stagePoint = 0; //지역 포인트 초기화
    }

 

다음 스테이지로 넘어갈 경우 플레이어를 시작위치에 배치해주어야 하므로

위에서 맵 밑으로 떨어질 경우 다시 생성하는 코드를 써주어야 하는데 자주사용하게 될 것 같은 코드는 함수로 만들어 주는 것이 좋아

PlayerReposition();이라는 함수로 만들어서 사용하였습니다. 

GameManager.cs

    void PlayerReposition(){
            
            player.VelocityZero();
            player.transform.position = new Vector3(-12,-2,-1); //플레이어의 시작위치로 되돌아오기
    }

 

플레이어 속도를 0으로 만드는 것도 함수로 따로 만들어서 

Playermove.cs에 만들어주었습니다.

    public void VelocityZero(){ //속력을 0으로 만드는 함수 
        rigid.velocity = Vector2.zero;
    }

 

 

 

 

UI

일단 생명 / 스테이지 표시 / 점수표시가 되도록 UI를 구성해봅시다 

 

Font 폴더를 Asset안에 만들어서 쿠키런체를 드래그해서 넣어줍니다 (무료베포임)

\

 

UI를 구성해줍니다 

 

이제 각각 UI들을 게임메니저에 넣어서 관리합시다

GameManager.cs //

using UnityEngine.UI; 써주셔야 됩니다 

    //UI 변수
    public Image[] UIhealth; //이미지는 3개이므로 배열 
    public Text UIPoint;
    public Text UIStage;

 

이제 유니티 내에서 드래그하여 초기화해줍니다.

 

점수 

점수는 Update()에 써야합니다.

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

 

 

 

스테이지 

스테이지가 바뀔 때 마다 변경되므로

NextStage()함수에 씁시다.

public void NextStage(){

        if (stageIndex < Stages.Length-1){   // 마지막 스테이지 아닌 경우 -> 다음스테이지로 

            UIStage.text = "STAGE "+(stageIndex + 1);

        }
        else{ //마지막 스테이지인 경우 ->게임끝 

        }

    }

스테이지가 변경될 때 이므로 마지막 스테이지가 아닐 때만 실행됨 

index는 인덱스이므로 0부터 시작하기 때문에 +1을 해주어야함 

 

 

생명

생명(체력)은 HealthDown() 시에 이미지가 사라져야하므로 해당 함수에 작성해줍니다. 

    public void HealthDown(){

        if(health > 1) {//생명이 0보다 크면 단순 생명 감소
            health--;
            UIhealth[health].color = new Color(1,0,0,0.3f);
        }
        else{// 생명이 0이하면 죽음



        }
    }

 

 

RETRY버튼

죽었을 경우 / 게임을 clear했을 경우 버튼이 보이도록 활성화해줌 

 

그럼 먼저 평소에는 retry button을 비활성화 해주셔야합니다.

 

 

죽었을 경우 / 게임을 clear했을 경우 버튼이 보이도록 활성화해줌  -> true

    public void NextStage(){

        if (stageIndex < Stages.Length-1){   // 마지막 스테이지 아닌 경우 -> 다음스테이지로 

			//생략

        }
        else{ //마지막 스테이지인 경우 ->게임끝 

            //플레이어 컨트롤 막기 
            Time.timeScale = 0; //플레이어가 이동되지 않게 함 

            //결과출력 
            Debug.Log("게임 클리어");

            //UI 다시시작버튼 
            UIRestartBtn.SetActive(true);

        }

    }

 

 

    public void HealthDown(){

        if(health > 1) {//생명이 0보다 크면 단순 생명 감소
            health--;
            UIhealth[health].color = new Color(1,0,0,0.3f);
        }
        else{// 생명이 0이하면 죽음

            //플레이어가 죽는 모션(이패트)
            player.OnDie();

            //UI에 결과 출력
            Debug.Log("죽었습니다.");

            //다시 시작 버튼 
            UIRestartBtn.SetActive(true);


        }
    }

 

버튼에 써있는 글씨를 변경하기 기능을 알아보기 위해서

게임을 clear했을때는 RETRY대신 Game Clear로 버튼의 글씨를 변경한 후 active하도록 합시다.

 

그러기위해선 텍스트 객체를 생성해야합니다.

NextStage()함수에 else부분 

            //UI 다시시작버튼 
            Text btnText = UIRestartBtn.GetComponentInChildren<Text>();
            btnText.text = "GameClear!";
            UIRestartBtn.SetActive(true);

GetComponent대신 GetComponentInChildren을 사용해주어야 합니다.

버튼에 쓰여있는 글씨는 Button내에 자식으로 속해있기 때문

 

 

이제 OnClick()이벤트 함수를 이용하여 버튼을 클릭했을 때 게임이 다시 시작되는 코드를 작성해봅시다 

using UnityEngine.SceneManagement; 추가해주셔야합니다.

    public void Restart(){ //재시작이므로 처음부터 다시시작이라 scene 0번 
        
        SceneManager.LoadScene(0);
    }

 

이제 버튼 오브젝트 인스펙터에서 +로 추가한후

게임매니저를 드래그해서 Restart함수를 적용시키면 됩니다 .

 

 

 

 

정상적으로 작동 되는 것을 볼 수 있습니다. 

 

 

 

죽었거나 게임을 클리어 했을 때

마지막 생명이 사라지지 않는 현상을 해결해보도록 하겠습니다. 

 

 

    public void HealthDown(){

        if(health > 1) {//생명이 0보다 크면 단순 생명 감소
            health--;
            UIhealth[health].color = new Color(1,0,0,0.3f);
        }
        else{// 생명이 0이하면 죽음

            //플레이어가 죽는 모션(이패트)
            player.OnDie();

            //UI에 결과 출력
            Debug.Log("죽었습니다.");

            //다시 시작 버튼 
            UIRestartBtn.SetActive(true);

            //죽었을 경우 모든 UI가 사라지도록 해야함 -> All Health UI Off
            UIhealth[0].color = new Color(1,0,0,0.3f);

        }
    }

맨 마지막 줄을 추가해줍니다

맨 마지막 생명이 없어지지 않으니 index는 0입니다.

 

 

또한 restart시 플레이어가 다시 동작하도록 해야하므로 timescale을 복구해줍니다.

    public void Restart(){ //재시작이므로 처음부터 다시시작이라 scene 0번 
        
        Time.timeScale = 1; //플레이어가 다시 움직일 수 있도록 함 
        SceneManager.LoadScene(0);
    }

 

이걸로 게임 코딩은 전부 완료되었습니다. 

사운드에 대한 부분은 생략합니다 -> 자세한건 골드메탈님 영상을 참고하면 훨씬 좋을 것 같음

 

 

전체 코드 

GameManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{


    //점수와 스테이지 이동관리하는 오브젝트(클래스)

    public int totalPoint;
    public int stagePoint;
    public int stageIndex;
    public int health;
    public PlayerMove player;

    public GameObject[] Stages; //스테이지를 오브젝트로 만들었기 떄문에 오브젝트 배열로 관리가능 


    //UI 변수
    public Image[] UIhealth; //이미지는 3개이므로 배열 
    public Text UIPoint;
    public Text UIStage;
    public GameObject UIRestartBtn;


    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    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; //플레이어가 이동되지 않게 함 

            //결과출력 
            Debug.Log("게임 클리어");

            //UI 다시시작버튼 
            Text btnText = UIRestartBtn.GetComponentInChildren<Text>();
            btnText.text = "GameClear!";
            UIRestartBtn.SetActive(true);


        }


        //Calculate point
        totalPoint += stagePoint; // 얻은 지역포인트 전체점수에 포함시키기 
        stagePoint = 0; //지역 포인트 초기화
    }

    private void OnTriggerEnter2D(Collider2D other) {
        
        if(other.gameObject.tag == "Player"){

            //체력감소
            HealthDown(); //경계에 부딪힌게 플레이어면 health 감소


            if(health>1){
            //떨어진 위치에서 플레이어 재생성 -> 플레이어가 죽지 않았을 떄만 해야함 
                PlayerReposition();
            }
        } 
    }

    public void HealthDown(){

        if(health > 1) {//생명이 0보다 크면 단순 생명 감소
            health--;
            UIhealth[health].color = new Color(1,0,0,0.3f);
        }
        else{// 생명이 0이하면 죽음

            //플레이어가 죽는 모션(이패트)
            player.OnDie();

            //UI에 결과 출력
            Debug.Log("죽었습니다.");

            //다시 시작 버튼 
            UIRestartBtn.SetActive(true);

            //죽었을 경우 모든 UI가 사라지도록 해야함 -> All Health UI Off
            UIhealth[0].color = new Color(1,0,0,0.3f);

        }
    }

    void PlayerReposition(){
            
            player.VelocityZero();
            player.transform.position = new Vector3(-12,-2,-1); //플레이어의 시작위치로 되돌아오기
    }


    public void Restart(){ //재시작이므로 처음부터 다시시작이라 scene 0번 
        
        Time.timeScale = 1; //플레이어가 다시 움직일 수 있도록 함 
        SceneManager.LoadScene(0);
    }
    

}

 

 

 

PlayerMove.cs

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 animator; //애니메이터 조작을 위한 변수 
    CapsuleCollider2D capcollider;



    private void Awake() {
        
        rigid = GetComponent<Rigidbody2D>(); //변수 초기화 
        spriteRenderer = GetComponent<SpriteRenderer>(); // 초기화 
        animator = GetComponent<Animator>();
        capcollider = GetComponent<CapsuleCollider2D >();
    }


    void Update(){

        // 버튼에서 손을 떄는 등의 단발적인 키보드 입력은 FixedUpdate보다 Update에 쓰는게 키보드 입력이 누락될 확률이 낮아짐


        //Jump
        if(Input.GetButtonDown("Jump") && !animator.GetBool("isJumping")){
            rigid.AddForce(Vector2.up* jumpPower , ForceMode2D.Impulse);
            animator.SetBool("isJumping",true);
        }

        //Stop speed 
        if(Input.GetButtonUp("Horizontal")){ // 버튼에서 손을 때는 경우 
            // normalized : 벡터 크기를 1로 만든 상태 (단위벡터 : 크기가 1인 벡터)
            // 벡터는 방향과 크기를 동시에 가지는데 크기(- : 왼 , + : 오)를 구별하기 위하여 단위벡터(1,-1)로 방향을 알수 있도록 단위벡터를 곱함 
            rigid.velocity = new Vector2( 0.5f * rigid.velocity.normalized.x , rigid.velocity.y);
        }

        //Direction Sprite
        if(Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;

        
        //Animation
        if( Mathf.Abs(rigid.velocity.x) < 0.35) //속도가 0 == 멈춤 
            animator.SetBool("isWalking",false); //isWalking 변수 : false 
        else// 이동중 
            animator.SetBool("isWalking",true);
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        float h = Input.GetAxisRaw("Horizontal");   
        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        if(rigid.velocity.x > maxSpeed)  //오른쪽으로 이동 (+) , 최대 속력을 넘으면 
            rigid.velocity= new Vector2(maxSpeed, rigid.velocity.y); //해당 오브젝트의 속력은 maxSpeed 
        
        else if(rigid.velocity.x < maxSpeed*(-1)) // 왼쪽으로 이동 (-) 
            rigid.velocity =  new Vector2(maxSpeed*(-1), rigid.velocity.y); //y값은 점프의 영향이므로 0으로 제한을 두면 안됨 


        //Landing Paltform
        Debug.DrawRay(rigid.position, Vector3.down, new Color(0,1,0)); //빔을 쏨(디버그는 게임상에서보이지 않음 ) 시작위치, 어디로 쏠지, 빔의 색 

        RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Platform"));
        //빔의 시작위치, 빔의 방향 , 1:distance , ( 빔에 맞은 오브젝트를 특정 레이어로 한정 지어야할 때 사용 ) // RaycastHit2D : Ray에 닿은 오브젝트 클래스 
    
        //rayHit는 여러개 맞더라도 처음 맞은 오브젝트의 정보만을 저장(?) 
        if(rigid.velocity.y < 0){ // 뛰어올랐다가 아래로 떨어질 때만 빔을 쏨 
            if(rayHit.collider != null){ //빔을 맞은 오브젝트가 있을때  -> 맞지않으면 collider도 생성되지않음 
                if(rayHit.distance < 0.5f) 
                    animator.SetBool("isJumping",false); //거리가 0.5보다 작아지면 변경

            }
        }
    }


    void OnTriggerEnter2D(Collider2D other) {

        if(other.gameObject.tag == "Item"){

            //Point
            //포인트를 얻음
            bool isBronze = other.gameObject.name.Contains("Bronze");
            bool isSilver = other.gameObject.name.Contains("Silver");
            bool isGold = other.gameObject.name.Contains("Gold");

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


            //동전 사라지기(비활성화)
            other.gameObject.SetActive(false);
        }

        else if(other.gameObject.tag == "Finish"){
            //Next Stage -> 게임 메니저에서 관리 
            gameManager.NextStage();
        }

    }

    private void OnCollisionEnter2D(Collision2D collision) {
        
        if(collision.gameObject.tag == "Enamy"){ //적과 충돌 시 

            if(rigid.velocity.y < 0 && transform.position.y > collision.transform.position.y){
                OnAttack(collision.transform);
            }
            else{
                OnDamaged(collision.transform.position); //현재 충돌한 오브젝트의 위치값을 넘겨줌  
            }
        }

    }

    void OnAttack(Transform enemy){

        //Point 점수 올리기
        gameManager.stagePoint +=100;
        
        //Reaction Force
        rigid.AddForce(Vector2.up*5, ForceMode2D.Impulse);

        //Enemy Die
        //몬스터에 적용한 스크립트의 함수를 사용하기위해 해당 클래스의 변수를 선언해서 초기화
        MonsterMove enemyMove = enemy.GetComponent<MonsterMove>();
        enemyMove.OnDamaged(); // 몬스터가 데미지를 입었을때 실행할 함수를 불러옴 


    }


    void OnDamaged(Vector2 tartgetPos){

        //Chnage Layer
        gameObject.layer = 11; //playerDamaged Layer number가 11로 지정되어있음 

        //View Alpha
        spriteRenderer.color = new Color(1,1,1,0.4f); //투명도를 0.4로 부여하여 지금이 무적시간으로 변경되었음을 보여줌

        //Reaction Force
        //맞으면 튕겨나가는 모션
        int dirc = transform.position.x-tartgetPos.x > 0 ? 1 : -1; 
        //튕겨나가는 방향지정 -> 플레이어 위치(x) - 충돌한 오브젝트위치(x) > 0: 플레이어가 오브젝트를 기준으로 어디에 있었는지 판별
        //> 0이면 1(오른쪽으로 튕김) , <=0 이면 -1 (왼쪽으로 튕김)
        rigid.AddForce(new Vector2(dirc,1)*7, ForceMode2D.Impulse); // *7은 튕겨나가는 강도를 의미 

        
        //Animation
        animator.SetTrigger("doDamaged");

        Invoke("OffDamaged",2); //2초의 딜레이 (무적시간 2초)

        //생명 감소
        gameManager.HealthDown();
    }

    void OffDamaged(){ //무적해제함수 
        gameObject.layer = 10; //플레이어 레이어로 복귀함

        spriteRenderer.color = new Color(1,1,1,1); //투명도를 1로 다시 되돌림 

    }

    public void OnDie(){
        
        //죽음 effect(외적인거)

        //Sprite Alpha : 색상 변경 
        spriteRenderer.color = new Color(1,1,1,0.4f);
        
        //Sprite Flip Y : 뒤집어지기 
        spriteRenderer.flipY = true;

        //Collider Disable : 콜라이더 끄기 
        capcollider.enabled = false;

        //Die Effect Jump : 아래로 추락(콜라이더 꺼서 바닥밑으로 추락함 )
        rigid.AddForce(Vector2.up*5, ForceMode2D.Impulse);


    }


    public void VelocityZero(){ //속력을 0으로 만드는 함수 
        rigid.velocity = Vector2.zero;
    }
}

 

MonsterMove.cs

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


public class MonsterMove : MonoBehaviour
{

    Rigidbody2D rigid;
    public int nextMove;//다음 행동지표를 결정할 변수
    Animator animator;
    SpriteRenderer spriteRenderer;
    CapsuleCollider2D capcollider;


    // Start is called before the first frame update
    private void Awake() {
        animator = GetComponent<Animator>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        rigid = GetComponent<Rigidbody2D>();
        capcollider = GetComponent<CapsuleCollider2D >();


        Invoke("Think", 5); // 초기화 함수 안에 넣어서 실행될 때 마다(최초 1회) nextMove변수가 초기화 되도록함 
        
    }


    // Update is called once per frame
    void FixedUpdate()
    {
        //Move
       rigid.velocity = new Vector2(nextMove,rigid.velocity.y); //nextMove 에 0:멈춤 -1:왼쪽 1:오른쪽 으로 이동 


       //Platform check(맵 앞이 낭떨어지면 뒤돌기 위해서 지형을 탐색)


       //자신의 한 칸 앞 지형을 탐색해야하므로 position.x + nextMove(-1,1,0이므로 적절함)
        Vector2 frontVec = new Vector2(rigid.position.x + nextMove*0.4f, rigid.position.y);

        //한칸 앞 부분아래 쪽으로 ray를 쏨
        Debug.DrawRay(frontVec, Vector3.down, new Color(0,1,0));

        //레이를 쏴서 맞은 오브젝트를 탐지 
        RaycastHit2D raycast = Physics2D.Raycast(frontVec, Vector3.down,1,LayerMask.GetMask("Platform"));

        //탐지된 오브젝트가 null : 그 앞에 지형이 없음
        if(raycast.collider == null){
            Turn();
        }

    }


    void Think(){//몬스터가 스스로 생각해서 판단 (-1:왼쪽이동 ,1:오른쪽 이동 ,0:멈춤  으로 3가지 행동을 판단)

        //Set Next Active
        //Random.Range : 최소<= 난수 <최대 /범위의 랜덤 수를 생성(최대는 제외이므로 주의해야함)
        nextMove = Random.Range(-1,2);

        //Sprite Animation
        //WalkSpeed변수를 nextMove로 초기화 
        animator.SetInteger("WalkSpeed",nextMove);


        //Flip Sprite
        if(nextMove != 0) //서있을 때 굳이 방향을 바꿀 필요가 없음 
            spriteRenderer.flipX = nextMove == 1; //nextmove 가 1이면 방향을 반대로 변경  


        //Recursive (재귀함수는 가장 아래에 쓰는게 기본적) 
        float time = Random.Range(2f, 5f); //생각하는 시간을 랜덤으로 부여 
        //Think(); : 재귀함수 : 딜레이를 쓰지 않으면 CPU과부화 되므로 재귀함수쓸 때는 항상 주의 ->Think()를 직접 호출하는 대신 Invoke()사용
        Invoke("Think", time); //매개변수로 받은 함수를 time초의 딜레이를 부여하여 재실행 
    }

    void Turn(){

        nextMove= nextMove*(-1); //우리가 직접 방향을 바꾸어 주었으니 Think는 잠시 멈추어야함
        spriteRenderer.flipX = nextMove == 1;

        CancelInvoke(); //think를 잠시 멈춘 후 재실행
        Invoke("Think",2);//  

    }

    public void OnDamaged(){ //몬스터가 데미지를 입었을때 


        //Sprite Alpha : 색상 변경 
        spriteRenderer.color = new Color(1,1,1,0.4f);
        
        //Sprite Flip Y : 뒤집어지기 
        spriteRenderer.flipY = true;

        //Collider Disable : 콜라이더 끄기 
        capcollider.enabled = false;

        //Die Effect Jump : 아래로 추락(콜라이더 꺼서 바닥밑으로 추락함 )
        rigid.AddForce(Vector2.up*5, ForceMode2D.Impulse);

        //Destroy 
        Invoke("DeActive",5);

    }

    void DeActive(){ //오브젝트 끄기 
        gameObject.SetActive(false);
    }

}

 

728x90

'GAME' 카테고리의 다른 글

[Unity2D] 대화창 구현  (0) 2020.08.29
[Unity2D]조사액션 / 조사창 구현  (0) 2020.08.28
[Unity2D] 적 몬스터 구현하기  (1) 2020.08.01
[Unity2D] 타일맵 Platform 만들기  (0) 2020.08.01
[Unity2D]플레이어 이동/점프  (0) 2020.08.01