Unity_개발일지_20

홍신영·2024년 11월 1일
0

Unity

목록 보기
22/62

Player Condition

플레이어를 설계를 해보자.
먼저 싱글톤으로 전역으로 접근할 CharacterManager를 만들어주자.
여기서 Player를 관리할 것.

using UnityEngine;

public class CharacterManager : MonoBehaviour
{
    private static CharacterManager _instance;
    public static CharacterManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<CharacterManager>();
                if (_instance == null)
                {
                    _instance = new GameObject("CharacterManager").AddComponent<CharacterManager>();
                }
            }
            return _instance;
        }
    }

    private Player _player;
    public Player Player
    {
        get { return _player; }
        set { _player = value; }
    }

    private void Awake()
    {
        if (_instance == null)
        {
            _instance= this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

}

그러고 이제 플레이어와 컨트롤러를 만들어 줄건데, 컨트롤러 부분은 지난번에도 다룬적이 있기에 코드만 보고 넘어가자.

Player

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

public class Player : MonoBehaviour
{
    public PlayerController controller;
    public PlayerCondition condition;

    private void Awake()
    {
        CharacterManager.Instance.Player = this;
        controller = GetComponent<PlayerController>();
        condition = GetComponent<PlayerCondition>();
    }
}

PlayerController

using System;
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
    private Rigidbody rb;

    [Header("Move")]
    [SerializeField][Range(1, 10)] private float moveSpeed;
    private Vector2 moveInput;

    [Header("Look")]
    [SerializeField] private Transform camContainer;
    [SerializeField] private float lookSensitively;
    [SerializeField] private float maxXLook;
    [SerializeField] private float minXLook;
    private Vector2 lookInput;
    private float curCamXRot;
    private bool isInvenOpen;

    [Header("Jump")]
    [SerializeField] private float jumpPower;
    [SerializeField] private LayerMask groundLayer;
    [SerializeField] private float maxRayDistance;

    [Header("Inventory")]
    public Action onOpenInventory;

    [Header("Interact")]
    public Action onInteraction;


    private void Awake()
    {
        rb = GetComponent<Rigidbody>();
        Cursor.lockState = CursorLockMode.Locked;
    }

    private void FixedUpdate()
    {
        Move();
    }

    private void LateUpdate()
    {
        if (isInvenOpen = Cursor.lockState == CursorLockMode.Locked)
        {
            Look();
        }
    }

    private void Move()
    {
        Vector3 dir = (transform.forward * moveInput.y + transform.right * moveInput.x).normalized;
        dir *= moveSpeed;
        dir.y = rb.velocity.y;
        rb.velocity = dir;
    }

    private void Look()
    {
        curCamXRot += lookInput.y * lookSensitively;
        curCamXRot = Mathf.Clamp(curCamXRot, minXLook, maxXLook);
        camContainer.localEulerAngles = new Vector3(-curCamXRot, 0, 0);
        transform.eulerAngles += new Vector3(0, lookInput.x * lookSensitively, 0);
    }

    private bool isGrounded()
    {
        Ray[] rays = new Ray[4]
        {
            new Ray(transform.position + (transform.forward * 0.2f) + (transform.up * 0.01f), Vector3.down),
            new Ray(transform.position + (-transform.forward * 0.2f) + (transform.up * 0.01f), Vector3.down),
            new Ray(transform.position + (transform.right * 0.2f) + (transform.up * 0.01f), Vector3.down),
            new Ray(transform.position + (-transform.right * 0.2f) + (transform.up * 0.01f), Vector3.down)
        };

        for (int i = 0; i < rays.Length; i++)
        {
            if (Physics.Raycast(rays[i], maxRayDistance, groundLayer))
            {
                return true;
            }
        }
        return false;
    }

    public void OnMove(InputAction.CallbackContext context)
    {
        if (context.phase == InputActionPhase.Performed)
        {
            moveInput = context.ReadValue<Vector2>();
        }
        else if (context.phase == InputActionPhase.Canceled)
        {
            moveInput = Vector2.zero;
            rb.velocity = new Vector3(0, rb.velocity.y, 0);
        }
    }

    public void OnLook(InputAction.CallbackContext context)
    {
        lookInput = context.ReadValue<Vector2>();
    }

    public void OnJump(InputAction.CallbackContext context)
    {
        if (context.phase == InputActionPhase.Started && isGrounded())
        {
            rb.AddForce(Vector2.up * jumpPower, ForceMode.Impulse);
        }
    }

    public void OnInteract(InputAction.CallbackContext context)
    {
        if (context.phase == InputActionPhase.Started)
        {
            onInteraction?.Invoke();
        }
    }

    public void OnInventory(InputAction.CallbackContext context)
    {
        if (context.phase == InputActionPhase.Started)
        {
            OnToggle();
            onOpenInventory?.Invoke();
        }
    }

    private void OnToggle()
    {
        Cursor.lockState = isInvenOpen ? CursorLockMode.None : CursorLockMode.Locked;
    }
}

이제 컨디션을 만들어줄건데 적도 만들예정이니, Condition클래스를 만들고, PlayerCondition에서 Condition에 공통 데이터를 가져와 사용하자.

먼저 Condition이다.

using UnityEngine;

[System.Serializable]
public class Condition
{
    public float curValue;
    public float passiveValue;
    [SerializeField] private float maxValue;
    [SerializeField] private float startValue;

    public Condition(float hp = 0)
    {
        maxValue = hp;
        startValue = hp;
        curValue = startValue;
    }

    public float GetPercentage()
    {
        return curValue / maxValue;
    }

    public void Add(float amount)
    {
        curValue = Mathf.Min(curValue + amount, maxValue);
    }

    public void Subtract(float amount)
    {
        curValue = Mathf.Max(curValue - amount, 0);
    }
}

컨디션에 있을 데이터를 생각해봤을 때,
1. 체력
2. 배고픔
3. 스테미나

였고, 위 3개 모두의 공통된 부분을 로직으로 구현하면,
123의 value 감소, 증가, 초기세팅등이 있었다.

이제 PlayerCodition도 생각해보자.
컨디션에서 공통로직은 처리되었으니, Condition타입으로 1,2,3(체,배,스)를 정의해주고, 플레이어에게만 적용되는 추가적인 로직을 구현해보자.

using UnityEngine;

public class PlayerCondition : Unit
{
    public UICondition uiCondition;

    private Condition mainBoard { get { return uiCondition.mainBoard; } } //health
    private Condition memory { get { return uiCondition.memory; } } // hunger
    private Condition clock { get { return uiCondition.clock; } } //stamina

    [SerializeField] private float noMemoryMainBoardDecay;

    private void Update()
    {
        memory.Subtract(memory.passiveValue * Time.deltaTime);
        clock.Add(clock.passiveValue * Time.deltaTime);

        if (memory.curValue <= 0)
        {
            mainBoard.Subtract(noMemoryMainBoardDecay * Time.deltaTime);
        }
    }

    public override void Ondamage(float damage)
    {
        mainBoard.Subtract(damage);
        if (mainBoard.curValue <= 0)
        {
            Die();
        }
    }

    public void HealMainBoard(float amount)
    {
        mainBoard.Add(amount);
    }

    public void HealMemory(float amount)
    {
        memory.Add(amount);
    }


    public void Die()
    {
        Debug.Log("Die");
        Time.timeScale = 0;
    }




}

메모리는 계속 줄어들고, 스테미나는 계속 오르도록 업데이트에 넣어주었다.
추가적으로, 힐,죽기 기능 메소드도 만들어주었다. ui부분은 이어서 정리하도록 하겠다. 생각보다 구조짜기가 어렵다..

profile
게임 클라이언트 개발자

0개의 댓글