본문 바로가기

Development/[Game] Basic Game

[Game] #7. 게임 UI 생성 및 완성

728x90
반응형

게임 UI 생성하기

게임 UI는 다음 포스팅을 응용한다.

코딩은 다양한 방법을 응용하는 것보다, 자신의 방법을 정착하여 사용하는 편이 시간상 큰 절약을 가져다준다.

https://udangtangtang-cording-oldcast1e.tistory.com/114

 

[2D Game] #6. 게임 UI 만들기

게임 UI 만들기 점수와 게임 오버 메시지를 표시하는 UI를 만들어보자. UI를 구성하기 위해서는 먼저 캔버스 게임 오브젝트를 만들어야 한다. 캔버스 오브젝트: 모든 UI 요소를 다루는 루트 게임

udangtangtang-cording-oldcast1e.tistory.com

이전 포스팅에서 생성된 게임 개발 창

게임 UI 생성에 앞서, 우리는 캐릭터의 체력을 3개로 만들 예정이므로 게임 매니저 스크립트 창의 캐릭터의 HealthPoint를 3으로 설정한다.

캔버스 스케일러 설정

 '화면 크기에 따라 스케일' 모드를 사용해 640x360을 기준 해상도로 사용하여 UI를 배치한다. 캔버스 스케일 모드는 캔버스 스케일러 컴포넌트에서 설정한다.

  1. Canvas 컴포넌트 생성 (하이어 라키 > Create > UI > Canvas)
  2. Canvas Scaler 컴포넌트의 UI Scale Mode를 Scale With Screen Size로 변경
  3. Reference Resolution을 (640,360)으로 변경

플레이어의 라이프 포인트 이미지 생성

생성한 캔버스 스케일러에 이미지 컴포넌트를 생성한다.

  1. Canvas 컴포넌트에서 Create > UI > Image
  2. 생성한 이미지 파일에 플레이어 스프라이트를 선택한다.
  3. 생성한 플레이어 스프라이트를 3개가 되도록 복사&붙여넣기한다.

플레이어의 라이프 포인트 생성

플레이어의 스테이지 포인트 및 스코어 텍스트 생성

이때 using UnityEngine.UI; 메서드를 게임 매니저 스크립트에 작성하는 것을 잊지 않도록 한다! 해당 메서드는 유니티 상에서 UI를 관리하게 해주는 메서드이다.

using UnityEngine.UI;
  1. Canvas 컴포넌트에서 Create > UI > Text
  2. Rect Transform을 right top로 설정한다.
  3. 이전에 생성한 플레이어 스프라이트와 수형이 되도록 적절히 배치한다.
  4. 텍스트를 볼드체로 생성하고 폰트 크기를 30으로 설정한다.
  5. 텍스트의 위치를 가운데로 설정한다.

비슷한 방법으로, 스테이지 스코어 텍스트 오브젝트를 복사&붙여 넣기 후 Rect Transform을 middle top로 설정한다. 이후 텍스트를 STAGE 1로 설정한다.

스테이지 스코어 텍스트 생성(상단 좌측) 스테이지 포인트 텍스트 생성(상단 중간)

게임 리트라이 버튼 생성

캐릭터가 죽었을 때 혹은 게임을 재시작하고 싶을 때 게임을 재시작하는 RETRY 버튼을 생성하자.

  1. Canvas 컴포넌트에서 Create > UI > Button
  2. Rect Transform을 right bottom로 설정
  3. Button 오브젝트에 Create > UI > Text를 생성한다.
  4. Text의 이름을 RETRY로 변경한다.

게임 화면

게임 매니저 스크립트 수정

UI 속의 텍스트/이미지 컴포넌트를 담을 변수를 게임 메니저 스크립트에 생성한다.

//Game UI Object
public Image[] UIhealth;
public Text UIPoint;
public Text UIStage;
//Button
public GameObject Restart;

해당 코드를 입력한 후 저장해주면 유니티 인스펙터 창에서 해당 오브젝트를 관리할 수 있는 창이 뜨게 된다.

게임 화면

[플레이어가 사망한 경우]

public void HealthDown(){
    if(healthPoint > 1) {
        healthPoint --;
        UIhealth[healthPoint].color = new Color(1,0,0,0.4f);
    }
    else{
        //player Die Effect
        player.OnDie();
        //Result UI
        Debug.Log("죽었습니다!");
        //Retry Button UI
        Restart.SetActive(true);
    }
}

[스테이지 클리어 시]

버튼 텍스트는 버튼 오브젝트의 자식 오브젝트이므로 참조할 때 GetComponent 뒤에 InChildren를 붙여 GetComponentInChildren 로서 사용해 여한다.

...생략
else{
    Time.timeScale = 0;
    Debug.Log("게임 클리어!");

    Text btnText = Restart.GetComponentInChildren<Text>();
    //버튼 텍스트는 자식 오브젝트이므로 InChildren을 붙어야한다.

}

버튼을 누르는 함수는 OnClick 메서드를 이용한다. 이때 OnClick 매서드에 함수를 할당할 때에는 프로젝트 창의 GameManager를 드래그&드롭하는 것이 아닌 하이어 라키 창의 GameManager 오브젝트를 드래그&드롭함에 주의한다.

OnClick

게임 플레이 영상

게임 완성 스크립트

[PlayerMove]

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


    private AudioSource playerAudio; // 사용할 오디오 소스 컴포넌트
    private bool isDead = false; // 사망 상태

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


    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.GetButtonDown("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;

        
        //Animation
        if( Mathf.Abs(rigid.velocity.x) < 0.2) //속도가 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){ // 뛰어올랐다가 아래로 떨어질 때만 빔을 쏨 
            // Debug.Log("Drop!");
            if(rayHit.collider != null){ //빔을 맞은 오브젝트가 있을때  -> 맞지않으면 collider도 생성되지않음 
                // Debug.Log("Down!");
                if(rayHit.distance < 0.5f) 
                    animator.SetBool("isJumping",false); //거리가 0.5보다 작아지면 변경

            }
        }
    }

    private void OnTriggerEnter2D(Collider2D other) {
        if (other.tag == "Dead" && !isDead) Die();
        // Debug.Log("Player Dead");
    }

    private void OnCollisionEnter2D(Collision2D collision) {
        if (collision.gameObject.tag == "Enemy") {
            //Attack - 3.22
            // Debug.Log("Enemy Connect!");
            if(rigid.velocity.y<0 && transform.position.y > collision.transform.position.y){
                OnAttack(collision.transform);
                // Debug.Log("Attack to Enemy!");
            }
            else {
                OnDamaged(collision.transform.position); //Damaged - 3.22
                // Debug.Log("Too Sad...");
            }

        }//Coin Manager
        
        else if (collision.gameObject.tag == "Bronze") {
            gameManager.stagePoint += 50; 
            collision.gameObject.SetActive(false);
        }
        else if (collision.gameObject.tag == "Silver") {
            gameManager.stagePoint += 150;
            collision.gameObject.SetActive(false);
        }
        else if (collision.gameObject.tag == "Gold") {
            gameManager.stagePoint += 300;
            collision.gameObject.SetActive(false);
        }
        
        else if (collision.gameObject.tag == "Finish"){//Finish
            // Destroy(collision.gameObject);
            //NextStage
            // Debug.Log("스테이지 클리어!");
            gameManager.NextStage();

        }
    }
    void OnAttack(Transform enemy){
        //Point

        //Enemy Die
        EnemyMove enemyMove = enemy.GetComponent<EnemyMove>();
        enemyMove.OnDamaged();
    }

    private void Die() {

        animator.SetTrigger("Die");
        // // playerAudio.clip = deathClip;

        // // playerAudio.Play();
        rigid.velocity = Vector2.zero;

        isDead = true;
    }

    public void OnDamaged(Vector2 TargetPos){//-3.22
        //Health Down
        // gameManager.healthPoint --;
        gameManager.HealthDown();
        //Change Layer
        gameObject.layer = 11;
        //Sprite Alpha
        spriteRenderer.color = new Color(1,1,1,0.4f);
        //Readctio Force 3.22
        int dirc = transform.position.x - TargetPos.x > 0 ?1:-1;
        rigid.AddForce(new Vector2(dirc,1)*5,ForceMode2D.Impulse);

        animator.SetTrigger("Die"); //- 3.25
        Invoke("OffDamaged",3);
    }
    void DeActive(){
        gameObject.SetActive(false);
    }

    void OffDamaged(){
        gameObject.layer = 10;
        spriteRenderer.color = new Color(1,1,1,1);
    }

    public void OnDie(){
        //Sprite Alpha
        spriteRenderer.color = new Color(1,1,1,0.4f);
        //Sprite Flip Y
        spriteRenderer.flipY = true;
        //Collider Disable
        PlayerColider.enabled = false;
        //Die Effect Jump
        rigid.AddForce(Vector2.up * 5, ForceMode2D.Impulse);
        // //Destroy
        Invoke("DeActive",5);
    }

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

    
}

[EnemyMove]

using UnityEngine;

public class EnemyMove : MonoBehaviour
{
    // Start is called before the first frame update
    private Rigidbody2D EnemyRigd; //물리이동을 위한 변수 선언 
    private SpriteRenderer spriteRenderer; //방향전환을 위한 변수 
    private Animator Enemyanimator; //애니메이터 조작을 위한 변수 
    private Transform EnemyTransform; //애니메이터 조작을 위한 변수 
    private CircleCollider2D EnemyColider;

    public int NextMove;

    void Start()
    {
        EnemyRigd = GetComponent<Rigidbody2D>();
        EnemyTransform = GetComponent<Transform>();
        spriteRenderer = GetComponent<SpriteRenderer>(); // 초기화 
        EnemyColider = GetComponent<CircleCollider2D>();
        NextMove = Random.Range(-1,2);
        Invoke("Think",5); 
    }
    // Update is called once per frame
    void Update()
    {   
        //Move
        EnemyRigd.velocity = new Vector2(NextMove,EnemyRigd.velocity.y);

        //Platform Check
        Vector2 frontVec = new Vector2(EnemyRigd.position.x+NextMove,EnemyRigd.position.y);
        Debug.DrawRay(frontVec, Vector3.down, new Color(0,1,0)); //빔을 쏨(디버그는 게임상에서보이지 않음 ) 시작위치, 어디로 쏠지, 빔의 색 
        // float EnemyRay = (frontVec>0)?(frontVec-0.5f):frontVec+(0.5f);
        RaycastHit2D rayHit = Physics2D.Raycast(frontVec, Vector3.down, 1, LayerMask.GetMask("Platform"));
        //빔의 시작위치, 빔의 방향 , 1:distance , ( 빔에 맞은 오브젝트를 특정 레이어로 한정 지어야할 때 사용 ) // RaycastHit2D : Ray에 닿은 오브젝트 클래스 
        if(rayHit.collider == null){ //빔을 맞은 오브젝트가 있을때  -> 맞지않으면 collider도 생성되지않음 
            // Debug.Log("Danger!");
            NextMove *= -1;
            CancelInvoke();
            Invoke("Think",5);
        }

        //Diection
        if(NextMove==1) transform.localScale = new Vector3(-1, 1, 1); // 왼쪽 바라보기
        else if(NextMove== -1) EnemyTransform.localScale = new Vector3(1, 1, 1);
    }
    void Think(){
        NextMove = Random.Range(-1,2);

        // EnemyRigd.velocity = new Vector2(NextMove,EnemyRigd.velocity.y);
        
        float nextThinkTime = Random.Range(2f,5f);
        Invoke("Think",nextThinkTime);
    }

    public void OnDamaged(){
        //Sprite Alpha
        spriteRenderer.color = new Color(1,1,1,0.4f);
        //Sprite Flip Y
        spriteRenderer.flipY = true;
        //Collider Disable
        EnemyColider.enabled = false;
        //Die Effect Jump
        EnemyRigd.AddForce(Vector2.up * 5, ForceMode2D.Impulse);
        // //Destroy
        Invoke("DeActive",5);
        Destroy(gameObject);
    }

    void DeActive(){
        gameObject.SetActive(false);
    }

    
}

[GameManager]

using UnityEngine.SceneManagement;
using UnityEngine;
using UnityEngine.UI;
// using UnityEngine.UI.Image;

public class GameManager : MonoBehaviour
{
    public int totalPoint;
    public int stagePoint;
    public int stageIndex;

    public GameObject[] Stages;
    public PlayerMove player;

    public int healthPoint;
    //Game UI Object
    public Image[] UIhealth;
    public Text UIStagePoint; // 스테이지 카운트: 스테이지 클리어 시 변경
    public Text UIStagScore; // 스테이지에서 얻는 점수

    public GameObject RestartButton;

    void Update(){
        UIStagScore.text = (totalPoint + stagePoint).ToString();
        //3.29
    }

    public void NextStage()
    {
        Debug.Log("다음 스테이지 개방!");
        if(stageIndex < Stages.Length -1){
            Stages[stageIndex].SetActive(false);
            // Debug.Log("stageIndex:"+stageIndex);
            stageIndex ++;
            PlayerReposition();
            Stages[stageIndex].SetActive(true);
            // SceneManager.LoadScene(stageLevel, LoadSceneMode.Single);

            UIStagePoint.text = "STAGE" + (int)(stageIndex +1);
            //3.29
        }
        else{
            Time.timeScale = 0;
            Debug.Log("게임 클리어!");
            RestartButton.SetActive(true);
            Text btnText = RestartButton.GetComponentInChildren<Text>();
            //버튼 텍스트는 자식 오브젝트이므로 InChildren을 붙어야한다.

        }        
        totalPoint += stagePoint;
        stagePoint = 0;
    }
    void OnTriggerEnter2D(Collider2D collision){
        if (collision.gameObject.tag == "Player"){
            //HealthDown
            // healthPoint --;
            if(healthPoint> 1) PlayerReposition();
            HealthDown();
        }
    }

    public void HealthDown(){
        if(healthPoint > 1) {
            healthPoint --;
            UIhealth[healthPoint].color = new Color(1,0,0,0.4f);
        }
        else{
            //player Die Effect
            player.OnDie();
            //Result UI
            Debug.Log("죽었습니다!");
            //Retry Button UI
            RestartButton.SetActive(true);
        }
    }

    void PlayerReposition(){
        player.transform.position = new Vector3(0,0,-1);
        player.VelocityZero();
    }

    public void Restart(){
        Time.timeScale = 1;
        SceneManager.LoadScene(0);////
    }
}

 

소스 파일

PlayerMove.cs
0.01MB
CameraTarget.cs
0.00MB
EnemyMove.cs
0.00MB
GameManager.cs
0.00MB

728x90
반응형

'Development > [Game] Basic Game' 카테고리의 다른 글

[Game] #5. 게임 메니저  (0) 2022.03.29
[Game] #4. 충돌 및 사냥 구현  (0) 2022.03.28
[Game] #3. AI 구현  (0) 2022.03.20
[Game] #2. 게임 UI 및 팔레트  (0) 2022.03.20
[Game] #1. 새로운 프로젝트  (0) 2022.03.20
댓글