[XR 전문 인력 과정] 유니티 타워디펜스 만들기(5) -프로젝트 최적화, 추가 기능 넣기(로비/로그인 씬 추가, 메뉴 UI 추가, 타워 체력 추가, GameOver UI 추가, 플레이어 점프 제한)

2023. 12. 13. 20:55Unity

728x90
반응형

https://create.unity.com/performance-optimization-e-book-console-PC?ungated=true

 

Optimize your console and PC game performance

 Country AfghanistanÅland IslandsAlbaniaAlgeriaAmerican SamoaAndorraAngolaAnguillaAntarcticaAntigua and BarbudaArgentinaArmeniaArubaAustraliaAustriaAzerbaijanBahamasBahrainBangladeshBarbadosBelarusBelgiumBelizeBeninBermudaBhutanBolivia (Plurinational Sta

create.unity.com

위의 링크에 들어가면 게임 성능 최적화에 대한 E-Book을 다운로드 받을 수 있다. 물론 게임뿐만 아니라 유니티 프로젝트 자체의 최적화에 두루 쓸 수 있는 내용이다.

 

 


 

 

프로젝트 최적화

Profiling, Memory, Assets, Graphics, GPU optimiztion 등에 대한 최적화 기능들이 책에 잘 나와있다.

 

 

현재는 가만히 있을 때 17 FPS, 화면을 움직일 때는 1~4 FPS까지 떨어진다.(다행히 Build를 하면 조금 낫다.)

 

 

[Window] - [Analysis] - [Frame Debugger]를 열어서 가장 많은 에셋들이 보일 때 Enable을 눌러보면, 현재 씬에서는 9.9백만 개의 Triangle이 보여지고 있고, 4146개의 Batches 수를 볼 수 있다. 이 상태에서의 DrawCall은 4171이 뜨는 것을 볼 수 있다.

드로우 콜을 줄이기 위해서 세부 항목을 열어보면, [Render.OpaqueGeometry] - [RenderDeferred.GBuffer]에서 Static Batch, Draw Mesh 수가 엄청 많은 것을 볼 수 있다.

 

 

[RenderDeferred.Lighting] 항목도 많은 드로우콜 수를 가지고 있는 것을 볼 수 있다. Realtime 라이트를 쓰는 것도 드로우콜 증가에 큰 몫을 한 것 같다.

 

Static Batching과 나뭇잎과 같은 중복 에셋들에 대한 최적화를 진행해야 될 것 같다. 라고 생각했지만 SRP Batcher를 사용하기 위해서는 URP 프로젝트로 만들어졌어야 했다.... 지금 프로젝트는 Built-In이다.

 

 

GPU Instancing은 사용이 가능할 것 같다.

재질 Inspector 창에 모든 메테리얼의 [Enable GPU Instancing]을 체크 한다.

 

 

중간에 Reflection Probe가 Realtime으로 설정 되어있어서 필요없는 건물 내부의 것들은 비활성화 시켰다.

 

 

이전과 비교하면 나름 많이 나아졌다!

최대한 같은 화면을 기준으로, 개선점은 다음과 같다.

프레임 수: 9.4fps -> 24.7fps

Batches: 4904 -> 2314

Tris: 11.0M -> 4.5M

Verts: 15.8M -> 6.3M

 

 


 

 

모델링 교체

적군 비행기 모델도 Triangles 수가 29432개라서 상당히 높아 보이긴 해서 낮은 모델로 바꾸면 좋을 것 같다.

폴리 수가 훨씬 적은 다른 기종으로 적의 모델을 바꾼다.

https://assetstore.unity.com/packages/3d/vehicles/air/super-spitfire-53217

 

Super Spitfire | 3D 항공 | Unity Asset Store

Elevate your workflow with the Super Spitfire asset from IGIIID. Find this & other 항공 options on the Unity Asset Store.

assetstore.unity.com

(좌) 이전 적 비행기, (우) 낮은 폴리의 적 비행기

 

 

스크립트에서 Hierarchy 상에서 데미지를 입을 때 이름에서 "Enemy"가 포함된 오브젝트를 받아오도록 해서 이름을 Enemy로 바꿔줘도 반응을 하지 않는다. 아마 이 프리팹의 트리 구조 때문인 것으로 보인다.

이 부분을 Enemy Tag를 인식 하는 것으로 바꿔본다.

PlayerFire 스크립트에서,

/*
//Ray 충돌 정보가 Enemy 이름을 포함하고 있다면, (프리팹은 뒤에 괄호로 숫자도 들어감)
if(hitInfo.transform.name.Contains("Enemy"))
{
    EnemyAI enemy = hitInfo.transform.GetComponent<EnemyAI>(); //EnemyAI 스크립트 가져오기
    if (enemy) //만약 enemy가 들어왔다면,
    {
        enemy.DamageProcess(); //enemy 스크립트의 DamageProcess 함수를 불러오기
    }
}
*/
//Ray 충돌 정보가 Enemy 태그를 포함하고 있다면,
if (hitInfo.transform.gameObject.CompareTag("Enemy"))
{
    EnemyAI enemy = hitInfo.transform.gameObject.GetComponent<EnemyAI>(); //EnemyAI 스크립트 가져오기
    if (enemy) //만약 enemy가 들어왔다면,
    {
        Debug.Log("적에게 발사");
        enemy.DamageProcess(); //enemy 스크립트의 DamageProcess 함수를 불러오기
    }
}

태그를 쓰는 것으로 바꿨지만 되지 않았다. Debug.Log("적에게 발사") 로그가 뜨지 않는다. 근데 과거에 쓰던 비행기 모델에 태그를 썼더니 적용이 되었다. 결국 날개, 동체,  프로펠러 등 모델이 다 나눠져 있던 것이 Ray가 맞지 않은 문제라고 생각된다.

 

 

블렌더를 이용해서 Mesh를 하나로 합친다. 원래는 많은 메시가 있었는데 Ctrl + J로 합쳐준다.

Mesh 합치기 전
Mesh 합친 후

 

 

유니티에서 프로펠러쪽이 땅에 박혀서 다니는 형상이어서 아예 블렌더에서 Rotation을 적용했다.

이 상태로 유니티에 적용하니 온전한 모습으로 맵을 돌아다니게 되었다. (나름 오래 걸림 ㅠㅠ)

 

 

다행히 Coroutine으로 잠시 멈춤도 적용되고, 비행기 폭발 효과도 잘 적용 되었다.

 

 


 

 

로비 씬 넣기

로비 씬 UI를 설정한다. 이전에 FPS 게임에서 썼던 로비씬 이미지와 같은데 뒷 배경이 이번 타워디펜스 게임에도 잘 어울릴 것 같아서 그대로 적용해본다.

BATTLE 버튼 부분은 알파 값을 높여 투명 처리하려 했는데 그러면 누르는 느낌이 들지가 않아서 해당 부분 스크린샷 스프라이트 이미지를 적용했다. 추가로 버튼 부분 좀 더 밝게하여 하이라이트 처리 하였다.

 

 

로비 씬도 Build Settings에 넣고 메인 씬 인덱스를 확인한 후 SceneChange 스크립트를 생성한다.

 

 

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

public class SceneChange : MonoBehaviour
{
    public void LoadScene()
    {
        //빌드 세팅 씬 넘버로 씬 불러오기
        SceneManager.LoadScene(1);
    }
}

 

 

조금의 로딩 시간이 필요하지만 씬 전환이 잘 된다.

 

 


 

 

Pause, Continue, Restart, Quit UI 설정

로비 씬에서 메인 씬으로 들어갈 때 게임이 바로 시작되면 마우스 움직임이 조금 불편해지기 때문에 메인 씬으로 전환 후 3초 정도 딜레이 타임을 넣고 이후에 게임이 시작되게 만들어야 할 것 같다.

추가로, 인 게임 화면에서 일시정지와 재시작, 나가기 버튼도 만들어서 적용한다.

 

 

UI를 먼저 만든다.

메뉴 폰트를 위해서 구글 폰트를 다운로드한다. 메뉴 이미지를 위해 검색해서 알맞은 이미지를 골라서 사용하였다.

 

Noto Sans - Google Fonts

Noto is a global font collection for writing in all modern and ancient languages. Noto Sans is an unmodulated (“sans serif”) design for texts in the Latin, Cyri

fonts.google.com

 

 

ButtonMananger 스크립트를 만들어서 아래의 코드를 사용할 수 있다. 메뉴 버튼을 눌렀을 때 Restart와 Quit에 대응하는 함수를 만들었다.

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

public class ButtonManager : MonoBehaviour
{
    public void RestartGame()
    {
        Time.timeScale = 1.0f;
        //현재 씬 리로드
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }

    public void QuitGame()
    {
        Application.Quit();
    }
}

 

 

GameManager에서는 옵션 패널 자체를 켰다 끄는 코드를 추가하였다.

public void OpenOption() //메뉴 옵션을 켜는 함수
{
    gameOption.SetActive(true);
    Time.timeScale = 0;
    //gameState = GameState.Pause;
}

public void CloseOption() //Continue를 눌렀을 때 메뉴 옵션을 끄는 함수
{
    gameOption.SetActive(false);
    Time.timeScale = 1.0f;
    //gameState = GameState.Go;
}

 

GameManager 전체 코드

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

public class GameManager : MonoBehaviour
{
    //적 오브젝트 가져오기
    public GameObject enemy;
    //적 생성 딜레이 시간
    float currentTime = 0;
    float delayTime = 3f;
    //UI 패널 가져오기
    public GameObject gameOption;

    void Update()
    {
        currentTime += Time.deltaTime;
        if(currentTime > delayTime)
        {
            //EnemyPoints 그룹의 자식들 위치 중 랜덤한 위치에 생성. 배열 사용. GetComponents를 사용해야함.
            Transform[] points = GameObject.Find("EnemyPoints").GetComponentsInChildren<Transform>();
            //랜덤 위치 시작은 1, (부모가 0) 끝은 포인트의 길이
            int idx = Random.Range(1, points.Length);
            //적 생성 위치와 회전도 같이 가져온다.
            Instantiate(enemy, points[idx].position, points[idx].rotation);
            currentTime = 0;
            
        }
    }

    public void OpenOption()
    {
        gameOption.SetActive(true);
        Time.timeScale = 0;
        //gameState = GameState.Pause; //나중에 코루틴 생성하면 쓸 예정
    }

    public void CloseOption()
    {
        gameOption.SetActive(false);
        Time.timeScale = 1.0f;
        //gameState = GameState.Go; //나중에 코루틴 생성하면 쓸 예정
    }
}

 

 

메뉴 옵션이 잘 적용되었다.

 

 


 

 

Gameover UI 만들기

이전에 MenuPanel을 복사해서 UI Panel을 하나 만든다. 여기서 Continue 버튼은 지운다. 대신 그 자리에 GameOver 텍스트를 넣는다.

 

 

UI는 만들었고, 플레이어의 체력이 0일 때 패널이 SetActive(true);가 되어야한다.

 

 

적의 공격력이 없었으므로 EnemyAI 스크립트에서 변수 선언을 한다.

//적 공격력 선언
public int attackPower = 10;
//플레이어 가져오기
GameObject player;

 

 

Start 함수에서 "Player"라는 이름으로 오브젝트를 가져온다.

//플레이어 게임오브젝트 선언
player = GameObject.Find("Player").gameObject;

 

 

그리고 코드의 밑으로 내려가서 Attack() 함수에서 플레이어 스크립트를 가져와서 DamageAction 함수를 실행하는 코드를 쓴다.

void Attack()
{
    currentTime += Time.deltaTime;
    if(currentTime > attackDelayTime)
    {
        Debug.Log("공격!");
        player.GetComponent<PlayerMove>().DamageAction(attackPower);
        currentTime = 0;
    }
}

 

 

현재까지 EnemyAI 전체 스크립트

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

public class EnemyAI : MonoBehaviour
{
    //상태 상수 정의
    enum EnemyState
    {
        Idle, Move, Attack, Damage, Die
    }
    //초기 시작 상태 설정
    EnemyState state = EnemyState.Idle;
    //대기 지속 시간
    public float idleDelayTime = 2f;
    //경과 시간
    float currentTime = 0;
    //이동 속도
    public float moveSpeed = 5f;
    //타워 위치
    Transform tower;
    //네비게이션 메시 에이전트 가져오기
    NavMeshAgent agent;
    //공격 범위
    public float attackRange = 30f;
    //공격 지연 시간
    public float attackDelayTime = 2f;
    //적 체력
    public int enemyHp = 3;
    //폭발 효과
    Transform explosion;
    //폭발 파티클 시스템 변수 선언
    ParticleSystem explosionEffect;
    //적 공격력 선언
    public int attackPower = 10;
    //플레이어 가져오기
    GameObject player;
    void Start()
    {
        //타워를 이름으로 가져오기
        tower = GameObject.Find("TowerObject").transform;
        agent = GetComponent<NavMeshAgent>();
        //처음 상태에서는 비활성화 되어야한다.
        agent.enabled = false;
        agent.speed = moveSpeed;
        //폭발 프리팹을 이름으로 가져오기
        explosion = GameObject.Find("Explosion").transform;
        //파티클 시스템 가져오기
        explosionEffect = explosion.GetComponent<ParticleSystem>();
        //플레이어 게임오브젝트 선언
        player = GameObject.Find("Player").gameObject;
    }
    void Update()
    {
        switch (state)
        {
            case EnemyState.Idle:
                Idle();
                break;
            case EnemyState.Move:
                Movement();
                break;
            case EnemyState.Attack:
                Attack();
                break;
        }
    }
    void Idle()
    {
        //시간의 경과
        currentTime += Time.deltaTime;
        if(currentTime > idleDelayTime)
        {
            //일정 시간 이상 지나면 이동 상태로 전환
            state = EnemyState.Move;
            print("이동 상태");
            //에이전트 활성화
            agent.enabled = true;
        }
    }
    void Movement()
    {
        agent.SetDestination(tower.position);

        //공격 범위 안에 들어오면 공격 상태로 전환
        if(Vector3.Distance(transform.position, tower.position) < attackRange)
        {
            state = EnemyState.Attack;
            //공격 상태일 때는 NavMesh 비활성화
            agent.enabled = false;
        }
    }
    void Attack()
    {
        currentTime += Time.deltaTime;
        if(currentTime > attackDelayTime)
        {
            Debug.Log("공격!");
            player.GetComponent<PlayerMove>().DamageAction(attackPower);
            currentTime = 0;
        }
    }
    public void DamageProcess()
    {
        //데미지를 맞으면 적의 체력이 깎임 PlayerFire 스크립트에서 접근 가능하도록 public 함수로 생성
        enemyHp--;
        Debug.Log("적 체력: " + enemyHp);
        if(enemyHp > 0) //적의 체력이 양수라면,
        {
            state = EnemyState.Damage;
            //밑에서 만든 코루틴 호출
            StopAllCoroutines(); //현재 재생 중인 모든 Coroutine 중지 초기화 느낌
            StartCoroutine(Damage());
        }
        else //적 체력이 0 이하일 때
        {
            //폭발 효과를 적의 현재 위치로 가져오기
            explosion.position = transform.position;
            Debug.Log(explosion.position);
            Debug.Log(transform.position);
            explosionEffect.Play();
            //적 제거
            Destroy(gameObject);
        }
    }
    //코루틴 생성
    IEnumerator Damage()
    {
        //피격되면 잠깐 Idle 상태로 전환 했다가 다시 원래대로 전환
        agent.enabled = false; //길찾기 중지
        yield return new WaitForSeconds(0.1f);
        state = EnemyState.Idle;
        currentTime = 0; //맞았으니까 시간 초기화
    }
}

 

 

PlayerMove 스크립트에서도 코드를 추가한다.

플레이어 체력 추가 (20번 정도 맞으면 GameOver)

//플레이어 체력
public int playerHP = 200;

 

 

Canvas는 public으로 직접 가져오고, GameOver 패널은 가져온 캔버스의 자식오브젝트로 찾을 것이다.

//캔버스 가져오기
public GameObject canvas;
//게임오버 패널
GameObject gameOverPanel;
void Start()
{
    //캐릭터 컨트롤러 가져오기
    cc = GetComponent<CharacterController>();
    //게임오버 패널을 자식 오브젝트 인덱스로 찾기
    gameOverPanel = canvas.transform.GetChild(2).gameObject;
}

 

 

DamageAction 함수를 추가했다. GameOver 패널 뜨면서 게임 멈춤.

public void DamageAction(int damage)
{
    playerHP -= damage; //플레이어 체력 감소
    if (playerHP < 0)
    {
        playerHP = 0;
        gameOverPanel.SetActive(true);
        Time.timeScale = 0;
    }
    Debug.Log("타워 체력: " + playerHP);
}

 

 

PlayerMove 전체 코드

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//XRInput 스크립트를 사용해서 플레이어 움직이기
public class PlayerMove : MonoBehaviour
{
    //이동 속도
    public float speed = 10f;
    //캐릭터 컨트롤러 컴포넌트를 변수로 가져오기
    CharacterController cc;
    //중력 변수
    public float gravity = -20f;
    //수직 속도 초기화
    float yVelocity = 0;
    //점프 파워
    public float jumpPower = 7f;
    //플레이어 체력
    public int playerHP = 20;
    //캔버스 가져오기
    public GameObject canvas;
    //게임오버 패널
    GameObject gameOverPanel;
    void Start()
    {
        //캐릭터 컨트롤러 가져오기
        cc = GetComponent<CharacterController>();
        //게임오버 패널을 자식 오브젝트 인덱스로 찾기
        gameOverPanel = canvas.transform.GetChild(2).gameObject;
    }
    void Update()
    {
        float horizontal = XRInput.GetAxis("Horizontal");
        float vertical = XRInput.GetAxis("Vertical");
        
        //방향 설정
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        
        //카메라가 바라보는 방향으로 가라
        dir = Camera.main.transform.TransformDirection(dir);

        //중력 적용
        yVelocity += gravity * Time.deltaTime;

        //점프 버튼을 누르면 점프 XRInput.cs의 191번째줄 참고
        if(XRInput.GetDown(XRInput.Button.Two, XRInput.Controller.RTouch))
        {
            //점프 파워 적용
            yVelocity = jumpPower;
        }

        dir.y = yVelocity;
        
        //이동
        cc.Move(dir * speed * Time.deltaTime);
    }

    public void DamageAction(int damage)
    {
        playerHP -= damage; //플레이어 체력 감소
        if (playerHP < 0)
        {
            playerHP = 0;
            gameOverPanel.SetActive(true);
            Time.timeScale = 0;
        }
        Debug.Log("타워 체력: " + playerHP);
    }
}

 

체력이 0 미만으로 떨어지면 GameOver 패널이 팝업 된다.

 

 


 

 

플레이어 점프 횟수 제한

지금은 플레이어가 무제한 점프를 할 수 있기 때문에 다소 어색할 수 있고 하늘까지 날아갈 수도 있다. 점프를 최대 2회까지만 하도록 제한한다.

PlayerMove 스크립트를 수정한다. isJumping 멤버 변수 추가

//점프 상태 변수, 처음엔 점프 안하므로 false
public int isJumping = 0;

 

바닥에 닿으면 yVelocity 초기화, isJumping 초기화

//캐릭터가 바닥에 착지 했다면 = 바닥면이 닿았다면
if (cc.collisionFlags == CollisionFlags.Below)
{
    isJumping = 0;
    yVelocity = 0; //값을 0으로 초기화
}

 

점프 누를 때 isJumping 값이 한 번도 뛰지 않았거나 한 번 뛰어서 1이라면 뛴다. 하지만 두 번 뛴 2값이 들어오면 더 뛰지 못한다.

//점프 버튼을 누르면 점프 XRInput.cs의 191번째줄 참고
if (XRInput.GetDown(XRInput.Button.Two, XRInput.Controller.RTouch))
{
    if(isJumping == 0 || isJumping == 1)
    {
        //점프 파워 적용
        yVelocity = jumpPower;
        isJumping += 1; //한 번 점프할 때마다 1씩 증가
    }
}

 

최대 두 번까지 점프되는 것이 잘 적용되었다.

 

 


 

 

Firebase로 로그인 씬 만들기

UI 설정

 

 

[Project Settings] - [Player]에서 CompanyName과 Product Name을 설정한다.

 

 

Firebase에서, Unity에서 설정했던 [Bundle Identifier] 이름으로 생성한다.

 

 

json을 받아 [Assets] - [StreamingAssets] 폴더에 json을 추가한다.

 

 

FrebaseAuth.unitypackage SDK를 import 한다. [Assets] - [Import Package] - [Custom Package...]

 

 

두 오류가 뜨는데 IOS 설치를 하지 않았기 때문이다.

다음 경로의 파일에서 체크박스 모두 해제하고 Apply.

 

 

다음 오류는 UNITY_IOS를 입력하고 Apply해서 해결한다.

 

 

FirebaseAuthManager 스크립트를 생성한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Firebase.Auth;
using TMPro;


public class FirebaseAuthManager : MonoBehaviour
{
    private FirebaseAuth auth; //인증을 위한 변수 선언
    public TMP_InputField email;
    public TMP_InputField password;
    void Start()
    {
        auth = FirebaseAuth.DefaultInstance;
    }

    public void Create() //계정 생성
    {
        auth.CreateUserWithEmailAndPasswordAsync(email.text, password.text).ContinueWith(task => //람다식, 결과는 task가 나온다.
        {
            if (task.IsFaulted) //계정을 만들지 못했을 경우,
            {
                Debug.Log("계정 생성 실패");
                return;
            }
            if (task.IsCanceled) //계정 생성을 실패했을 경우(네트워크 장애, 도중 취소)
            {
                Debug.Log("계정 생성 취소");
                return;
            }
            FirebaseUser newUser = task.Result.User; //계정을 만들지 못하거나 실패했을 경우가 아닐 경우
        });
    }

    public void LogIn() //로그인 함수 계정 생성과 동일한 코드 복붙, 수정
    {
        auth.SignInWithEmailAndPasswordAsync(email.text, password.text).ContinueWith(task => //람다식, 결과는 task가 나온다.
        {
            if (task.IsFaulted) //로그인을 못했을 경우,
            {
                Debug.Log("로그인 실패");
                return;
            }
            if (task.IsCanceled) //로그인을 실패했을 경우(네트워크 장애, 도중 취소)
            {
                Debug.Log("로그인 취소");
                return;
            }
            Debug.Log("로그인 성공");
        });
    }

    public void LogOut() //로그아웃 함수
    {
        auth.SignOut();
        Debug.Log("로그아웃"); //로그아웃 확인
    }
}

 

 

각 InputField에 맞는 값을 넣는다.

 

 

씬 전환을 위해 Build Settings에 Login 씬 추가

 

 

바로 위의 코드에서 로그인 성공하면 다음 씬으로 전환하는 코드를 넣는다.

public void LogIn() //로그인 함수 계정 생성과 동일한 코드 복붙, 수정
{
    auth.SignInWithEmailAndPasswordAsync(email.text, password.text).ContinueWith(task => //람다식, 결과는 task가 나온다.
    {
        if (task.IsFaulted) //로그인을 못했을 경우,
        {
            Debug.Log("로그인 실패");
            return;
        }
        if (task.IsCanceled) //로그인을 실패했을 경우(네트워크 장애, 도중 취소)
        {
            Debug.Log("로그인 취소");
            return;
        }
        Debug.Log("로그인 성공");
        SceneManager.LoadScene(1);
    });
}

 

 

버튼에 함수 매핑

 

 

실행하고 아이디와 비밀번호를 Create하면, Firebase에 생성이 된다.

 

 

그런데 로그인은 성공했지만 씬 전환이 되지 않아서 Coroutine을 이용해서 로그인 후 씬 전환에 딜레이 타임을 넣었다.

씬 전환이 되지 않은 이유는 Firebase의 SignInWithEmailAndPasswordAsync 함수는 비동기 함수이지만, 현재 코드에서는 즉시 다음 코드를 실행하고 있기 때문이었다. 코드를 다음과 같이 수정하였다.

public async void LogIn() //로그인 함수 계정 생성과 동일한 코드 복붙, 수정
{
    try
    {
        var signInTask = await auth.SignInWithEmailAndPasswordAsync(email.text, password.text);
        Debug.Log("로그인 성공");
        SceneManager.LoadScene(1);
    }
    catch (System.Exception e)
    {
        Debug.Log($"로그인 실패: {e.Message}");
    }
}

 

 

로그인 실패시, Text로 실패했다고 표시도 해준다.

텍스트를 가운데에 하나 만들고,

 

텍스트를 public으로 받아오고,

public TMP_Text loginFailedtext;

 

로그인 성공했을 때와 실패했을 때 각각 텍스트에 메시지를 출력한다.

public async void LogIn() //로그인 함수 계정 생성과 동일한 코드 복붙, 수정
{
    try
    {
        var signInTask = await auth.SignInWithEmailAndPasswordAsync(email.text, password.text);
        Debug.Log("로그인 성공");
        loginFailedtext.text = "Login Success!";
        SceneManager.LoadScene(1);
    }
    catch (System.Exception e)
    {
        Debug.Log($"로그인 실패: {e.Message}");
        loginFailedtext.text = "Login Failed";
    }
}

 

 

FirebaseAuthManager 전체 코드

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Firebase.Auth;
using TMPro;
using UnityEngine.SceneManagement;

public class FirebaseAuthManager : MonoBehaviour
{
    private FirebaseAuth auth; //인증을 위한 변수 선언
    public TMP_InputField email;
    public TMP_InputField password;
    public TMP_Text loginFailedtext;
    void Start()
    {
        auth = FirebaseAuth.DefaultInstance;
    }

    public void Create() //계정 생성
    {
        auth.CreateUserWithEmailAndPasswordAsync(email.text, password.text).ContinueWith(task => //람다식, 결과는 task가 나온다.
        {
            if (task.IsFaulted) //계정을 만들지 못했을 경우,
            {
                Debug.Log("계정 생성 실패");
                return;
            }
            if (task.IsCanceled) //계정 생성을 실패했을 경우(네트워크 장애, 도중 취소)
            {
                Debug.Log("계정 생성 취소");
                return;
            }
            FirebaseUser newUser = task.Result.User; //계정을 만들지 못하거나 실패했을 경우가 아닐 경우
        });
    }

    public async void LogIn() //로그인 함수 계정 생성과 동일한 코드 복붙, 수정
    {
        try
        {
            var signInTask = await auth.SignInWithEmailAndPasswordAsync(email.text, password.text);
            Debug.Log("로그인 성공");
            loginFailedtext.text = "Login Success!";
            SceneManager.LoadScene(1);
        }
        catch (System.Exception e)
        {
            Debug.Log($"로그인 실패: {e.Message}");
            loginFailedtext.text = "Login Failed";
        }
    }

    public void LogOut() //로그아웃 함수
    {
        auth.SignOut();
        Debug.Log("로그아웃"); //로그아웃 확인
    }
}

 

 

로그인 실패해서 Login Failed 텍스트 출력, 이후 성공해서 Lobby 씬으로 넘어간 후 Battle 버튼을 눌러 게임을 시작한다.

 

 


 

 

더 넣고 싶은 기능들...

적 HP 표시, 내 HP 표시

적 위치 표시 -미니맵

시작하고 ready, go!

728x90
반응형