공부/Game Bootcamp

[멋쟁이사자처럼 부트캠프 TIL] 유니티 게임 개발 3기 : 궤적, HpBar

Ail_ 2024. 12. 19. 16:10

오전에 Vector 외적...했음

 

궤적 그리기

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

public class Cannon : MonoBehaviour
{
    // 캐논의 발사력과 질량을 설정
    public float Power = 500.0f;  // 발사력
    public float Mass = 10.0f;    // 캐논볼의 질량
    public int maxStep = 20;      // 예측할 궤적의 최대 단계 수
    public float timeStep = 0.1f; // 시간 간격 (매 단계마다의 시간 차)

    // 발사체와 궤적을 표시할 오브젝트
    public GameObject CannonBall;  // 발사체 프리팹
    public GameObject Trajectory;  // 궤적 표시용 오브젝트

    // 궤적을 표시할 오브젝트 목록
    public List<GameObject> Objects = new List<GameObject>();

    // 예상 궤적을 계산하는 함수
    List<Vector3> PredictTrajectory(Vector3 force, float mass)
    {
        List<Vector3> trajectory = new List<Vector3>();
        
        // 현재 캐논의 위치와 초기 속도
        Vector3 position = transform.position;
        Vector3 velocity = force / mass;  // 힘 / 질량 = 속도

        trajectory.Add(position);  // 첫 번째 위치 (캐논의 현재 위치)

        // 궤적을 계산 (최대 단계 수만큼 반복)
        for (int i = 1; i <= maxStep; i++)
        {
            float timeElapsed = timeStep * i; // 시간 간격
            // 등가속도 운동 공식 적용: 거리 = 초기 위치 + 속도 * 시간 + 0.5 * 중력 * 시간^2
            trajectory.Add(position + 
                           velocity * timeElapsed + 
                           Physics.gravity * (0.5f * timeElapsed * timeElapsed));

            // 충돌 체크
            if (CheckCollision(trajectory[i - 1], trajectory[i], out Vector3 hitPoint))
            {
                trajectory[i] = hitPoint; // 충돌 지점으로 궤적 수정
                break; // 충돌 시 궤적 계산 중단
            }
        }

        return trajectory; // 계산된 궤적 반환
    }

    // 충돌 여부를 체크하는 함수
    private bool CheckCollision(Vector3 start, Vector3 end, out Vector3 hitPoint)
    {
        hitPoint = end; // 기본적으로 끝 지점으로 설정
        Vector3 direction = end - start; // 시작 지점과 끝 지점 사이의 방향
        float distance = direction.magnitude; // 두 지점 간 거리
        
        // Raycast로 충돌 검사 (Default 레이어에 대해 Raycast 진행)
        if (Physics.Raycast(start, direction.normalized, out RaycastHit hit, distance, 1 << LayerMask.NameToLayer("Default")))
        {
            hitPoint = hit.point; // 충돌 지점 저장
            return true; // 충돌 있음
        }
        
        return false; // 충돌 없음
    }

    // 매 프레임마다 호출되는 Update 함수
    void Update()
    {
        // 'W' 키를 눌렀을 때 캐논을 위로 회전
        if (Input.GetKey(KeyCode.W))
        {
            transform.rotation *= Quaternion.Euler(-90 * Time.deltaTime, 0, 0);
        }

        // 'S' 키를 눌렀을 때 캐논을 아래로 회전
        if (Input.GetKey(KeyCode.S))
        {
            transform.rotation *= Quaternion.Euler(90 * Time.deltaTime, 0, 0);
        }

        // 'Z' 키를 눌렀을 때 캐논볼 발사
        if (Input.GetKeyDown(KeyCode.Z))
        {
            // 캐논볼 생성 후 발사
            GameObject go = Instantiate(CannonBall, transform.position, transform.rotation);
            go.GetComponent<Rigidbody>().mass = Mass;  // 캐논볼의 질량 설정
            go.GetComponent<Rigidbody>().AddForce(transform.forward * Power, ForceMode.Impulse);  // 힘을 가하여 발사
            Destroy(go, 3.0f); // 3초 후 캐논볼 삭제
        }

        // 'Space' 키를 눌렀을 때 궤적 표시
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // 예측된 궤적 계산
            List<Vector3> trajectorys = PredictTrajectory(transform.forward * Power, Mass);

            // 기존의 궤적 오브젝트들 삭제
            foreach (var o in Objects)
            {
                Destroy(o);
            }
            
            Objects.Clear(); // 리스트 초기화

            // 새로 계산된 궤적에 대해 궤적 표시용 오브젝트 생성
            foreach (var trajectory in trajectorys)
            {
                var go = Instantiate(Trajectory, trajectory, Quaternion.identity);
                Objects.Add(go);
            }
        }

        // 기존에 생성된 궤적 오브젝트를 비활성화
        foreach (var o in Objects)
        {
            o.SetActive(false);
        }

        // 새로운 궤적 계산
        List<Vector3> trajectorys2 = PredictTrajectory(transform.forward * Power, Mass);

        // 궤적 개수가 같으면 오브젝트 활성화하고 위치 업데이트
        if (Objects.Count == trajectorys2.Count)
        {
            for (var index = 0; index < trajectorys2.Count; index++)
            {
                var trajectory = trajectorys2[index];
                Objects[index].SetActive(true);  // 궤적 오브젝트 활성화
                Objects[index].transform.position = trajectory;  // 위치 갱신
            }
        }
    }
}

 

HpBar 달기

 

 

일단 Image로 Background 하나 Gauge 하나 만든다

 

그리고 HpBar에 스크립트 추가해주고

private Camera camera;   // 메인 카메라
private Transform owner; // HP바가 따라다닐 대상 (플레이어)
private Camera ui_camera; // UI 카메라

public void UpdateOwner(Transform owner, Camera ui_camera)
{
    this.owner = owner;   // HP바의 소유자 설정
    this.ui_camera = ui_camera; // UI 카메라 설정
}

void LateUpdate()
{
    if (owner != null && camera != null) // 소유자와 메인 카메라가 존재할 때만 업데이트 실행
    {
        // 월드 좌표의 소유자 위치를 화면 좌표로 변환
        Vector3 screenPoint = camera.WorldToScreenPoint(owner.position);

        // 화면 좌표를 UI 캔버스의 로컬 좌표로 변환
        Vector2 localPoint;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            transform.parent.GetComponent<RectTransform>(), screenPoint, ui_camera, out localPoint);

        // HP바의 로컬 위치를 업데이트하여 소유자의 위치를 따라감
        transform.localPosition = localPoint;
    }
}

 

CharCharacter에도 코드 추가해준다(단 아래 코드는 권장되지 않음)

public Canvas _canvas; // HP바를 표시할 UI 캔버스
public HpBar _hpBar;   // HP바 프리팹
public Camera ui_camera; // UI 카메라
public Transform hpPosition; // HP바가 따라다닐 대상의 위치 (플레이어 캐릭터)

void Start()
{
    // ... 생략 ...

    // HP바를 생성하고 소유자 정보를 업데이트
    GameObject go = Instantiate(_hpBar.gameObject, _canvas.transform); // 캔버스의 자식으로 HP바 생성
    go.GetComponent<HpBar>().UpdateOwner(hpPosition, ui_camera); // HP바가 따라다닐 오브젝트와 UI 카메라 설정
}

 

캔버스는 안의 무언가가 바뀌면 통째로 리로드한다고 함