[SKKU DT] 12일차 -FPS 게임 만들기(4) -체력 바 UI 구현

2023. 11. 14. 18:20SKKU DT

728x90
반응형

체력 바에 UI - 슬라이더를 사용한다.

Transform 값을 0으로 바꾼다.

 

 

Fill의 색도 바꾼다.

 

Slider의 Value로 조절할 수 있다.(현재 체력 / 전체 체력)

 

 


 

 

PlayerMove 스크립트에서 체력을 적용한다.

using UnityEngine.UI;로 UI 네임스페이스 사용

최대 체력, 체력 슬라이더 변수 추가

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

public class PlayerMove : MonoBehaviour
{
    public float moveSpeed = 7f;
    //캐릭터 컨트롤러 컴포넌트 변수
    CharacterController cc;
    //중력 변수
    float gravity = -20f;
    //수직 속력 변수 초기화
    float yVelocity = 0;
    //점프력 변수
    public float jumpPower = 7f;
    //점프 상태 변수 처음엔 점프 안하므로 0
    int isJumping = 0;
    //플레이어 체력 변수
    public int hp = 100;
    //최대 체력
    int maxHP;
    //체력 슬라이더 변수
    public Slider hpSlider;

    private void Start()
    {
        //컴포넌트 변수는 Start 함수에서 할당을 해주어야 한다.
        cc = GetComponent<CharacterController>();
        maxHP = hp;
    }
    void Update()
    {
        //키보드 입력 받기
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");

        //이동 방향 설정
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        dir = dir.normalized;

        //바라보는 기준 방향으로 움직이기
        dir = Camera.main.transform.TransformDirection(dir);

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

        //점프 후 중력값 적용을 위해 중력 값 적용 위에 쓴다. Space 누르면 점프
        if (Input.GetButtonDown("Jump"))
        {
            if(isJumping == 0 || isJumping == 1)//만약 0또는 1인 상태이면 뛴다.
            {
                yVelocity = jumpPower;
                isJumping += 1;
            }
        }

        //수직 속도에 중력 값 적용
        yVelocity += gravity * Time.deltaTime;
        dir.y = yVelocity;

        //이동
        cc.Move(dir * moveSpeed * Time.deltaTime);

        //현재 체력을 슬라이더의 Value에 반영
        hpSlider.value = (float)hp / (float)maxHP;
    }
    //플레이어 피격 함수
    public void DamageAction(int damage) //argument로 int 형의 damage를 받아 온다.
    {
        //적의 공력력 만큼 플레이어 체력 감소
        hp -= damage;
        //체력이 음수이면 0으로 초기화
        if(hp < 0)
        {
            hp = 0;
        }
        Debug.Log("플레이어 체력: " + hp);
    }
}

 

 


 

 

적 체력 바 만들기

-새 캔버스 만들고 [RenderMode] - [World Space]

 

 

Enemy 오브젝트 밑에 적 캔버스 위치

 

 

캔버스 스케일 줄이기

 

 


 

 

EnemyFSM 스크립트에서 적 체력 바 조절

using UnityEngine.UI; 추가, 적 체력 추가

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

public class EnemyFSM : MonoBehaviour
{
    //적 상태 상수 6개 생성
    enum EnemyState
    {
        Idle,
        Move,
        Attack,
        Return,
        Damaged,
        Die
    }
    //적 상태 변수
    EnemyState m_State;
    //플레이어 발견 범위
    public float findDistance = 10f;
    //플레이어 Transform Component 적이 많이 생성되면 끌어 넣는게 비어있을 수 있다. Find로 찾아서 적용
    Transform player;
    //공격 범위
    public float attackDistance = 4f;
    //적 이동 속도
    public float moveSpeed = 5f;
    //적 캐릭터 컨트롤러 컴포넌트
    CharacterController cc;
    //적 공격 누적 시간
    float currentTime = 0;
    //적 공격 딜레이 시간
    float attackDelay = 2f;
    //적 공격력
    public int attackPower = 10;
    //적이 다시 되돌아갈 초기 위치 저장
    Vector3 originPos;
    //적 이동 가능 범위
    public float moveDistance = 20f;
    //적 체력 설정
    public int hp = 50;
    //적 최대 체력
    public int maxHP = 50;
    //슬라이더 변수 가져오기
    public Slider enemyHPSlider;
    void Start()
    {
        //플레이어 Transform Component 할당
        player = GameObject.Find("Player").transform;
        //최초의 적 상태를 대기로 설정
        m_State = EnemyState.Idle;
        //적 캐릭터 컨트롤러 컴포넌트 할당
        cc = GetComponent<CharacterController>();
        //적의 초기 위치 저장
        originPos = transform.position;
    }
    void Update()
    {
        //적의 상태를 체크해서 상태별로 정해진 기능을 수행 Switch문 / Damaged와 Die는 AnyState이기 때문에 case에 들어가지 않는다.
        switch (m_State)
        {
            case EnemyState.Idle:
                Idle();
                break;
            case EnemyState.Move:
                Move();
                break;
            case EnemyState.Attack:
                Attack();
                break;
            case EnemyState.Return:
                Return();
                break;
        }
        //적 슬라이더에 값 반영
        enemyHPSlider.value = (float)hp / (float)maxHP;
    }
    void Idle()
    {
        //만약 플레이어와 적의 거리가 발견 범위 이내라면 Move 상태로 전환
        if(Vector3.Distance(transform.position, player.position) < findDistance)
        {
            m_State = EnemyState.Move;
            Debug.Log("발견!");
        }
    }
    void Move()
    {
        //만약 현재 위치와 초기 위치 거리가 이동 가능 범위보다 크면 복귀
        if(Vector3.Distance(transform.position, originPos) > moveDistance)
        {
            m_State = EnemyState.Return;
            Debug.Log("상태 전환");
        }
        //만약 플레이어와 적의 거리가 공격 범위보다 크다면 플레이어를 향해 이동
        else if (Vector3.Distance(transform.position, player.position) > attackDistance)
        {
            //이동 방향
            Vector3 dir = (player.position - transform.position).normalized;
            //이동
            cc.Move(dir * moveSpeed * Time.deltaTime);
        }
        else
        {
            m_State = EnemyState.Attack;
            Debug.Log("공격!");
            //공격이 바로 안되므로 공격 딜레이 시간 만큼 미리 진행을 시켜놓아야 한다.
            currentTime = attackDelay;
        }
    }
    void Attack()
    {
        //플레이어와 적의 거리가 공격 범위 이내라면 공격
        if(Vector3.Distance(transform.position, player.position) < attackDistance)
        {
            //일정 시간마다 플레이어 공격
            currentTime += Time.deltaTime;
            if(currentTime > attackDelay)
            {
                player.GetComponent<PlayerMove>().DamageAction(attackPower);
                print("공격!");
                currentTime = 0;
            }
        }
        //그렇지 않다면, Move();
        else
        {
            m_State = EnemyState.Move;
            Debug.Log("이동");
            currentTime = 0;
        }
    }
    void Return()
    {
        //오리지널 방향으로 돌아가기 특정 좌표값보다 범위로 나타내는게 에러가 덜 날 가능성이 높다.
        if(Vector3.Distance(transform.position, originPos) > 0.1f)
        {
            //방향 구하기 (목적지 - 내 위치)
            Vector3 dir = (originPos - transform.position).normalized;
            //이동하기
            cc.Move(dir * moveSpeed * Time.deltaTime);
        }
        else  //그렇지 않다면 적 위치를 초기 위치로 조정하고, 대기 상태로 전환한다.
        {
            transform.position = originPos;
            m_State = EnemyState.Idle;
            Debug.Log("상태 전환: Return -> Idle");
        }
    }
    //데미지 처리 Coroutine 함수
    IEnumerator DamageProcess()
    {
        //피격 모션만큼 기다림 애니메이션 고려
        yield return new WaitForSeconds(0.5f);
        //이동 상태 전환
        m_State = EnemyState.Move;
    }

    void Damaged()
    {
        //피격 코루틴 실행
        StartCoroutine(DamageProcess());
    }
    //데미지 실행 함수
    public void HitEnemy(int hitPower)
    {
        //적이 피격 상태이면 플레이어를 때릴 수 없게
        if(m_State == EnemyState.Damaged)
        {
            return;
        }
        //플레이어의 공격력만큼 적 체력 감소
        hp -= hitPower;
        //적 체력이 0보다 크면 피격
        if(hp > 0)
        {
            m_State = EnemyState.Damaged;
            Damaged();  //코루틴 호출
            Debug.Log("적 체력 표시: " + hp);
        }
        //0보다 작으면 죽음
        else
        {
            m_State = EnemyState.Die;
            Debug.Log("죽음");
            Die();
        }
        IEnumerator DieProcess()
        {
            //적 캐릭터 컨트롤러 컴포넌트 비활성화
            cc.enabled = false;
            //2초 대기
            yield return new WaitForSeconds(3f);
            //적 제거
            Destroy(gameObject);
        }
        void Die()
        {
            //실행 중인 이전 Coroutine 중지
            StopAllCoroutines();
            //죽음 Coroutine 실행
            StartCoroutine(DieProcess());
        }
    }
}

 

 


 

 

적 체력 바가 계속 나를 바라보게 하기

Billboard 스크립트 생성

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

public class Billboard : MonoBehaviour
{
    public Transform target;

    void Update()
    {
        transform.forward = target.forward;
    }
}

 

 


 

 

728x90
반응형