공부/Game Bootcamp

[멋쟁이사자처럼 부트캠프 TIL] 유니티 게임 개발 3기 : 스킬 + 액션툴

Ail_ 2024. 12. 18. 22:32

스킬 사용 + 스킬 사용 시 효과

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

// 데미지 필드의 데이터를 정의하는 구조체 (Serializable로 설정해 인스펙터에서 수정 가능).
[Serializable]
public struct DamageFieldData
{
    public float distance; // 캐릭터로부터 데미지 필드가 생성될 거리.
}

// 캐릭터를 제어하는 메인 클래스
public class CharController : MonoBehaviour
{
    // 상수 및 애니메이터 파라미터 해시
    private const float jumpTestValue = 0.3f; // 점프 판정을 위한 테스트 값.
    private static readonly int Speed1 = Animator.StringToHash("Speed"); // 애니메이터 Speed 파라미터 해시.
    private static readonly int Ground = Animator.StringToHash("Ground"); // 애니메이터 Ground 파라미터 해시.

    // 캐릭터 움직임 관련 변수
    [SerializeField] private float Speed = 5.0f; // 캐릭터 이동 속도.
    [SerializeField] private float JumpSpeed = 15.0f; // 캐릭터 점프 속도.
    [SerializeField] private Camera _mainCamera; // 메인 카메라 참조.
    [SerializeField] private float CameraSpeed = 4.0f; // 카메라가 움직이는 속도.
    [SerializeField] private float MaxDistence = 4.0f; // 캐릭터와 카메라 사이 최대 거리.

    // 카메라와 캐릭터 간의 초기 거리 오프셋
    private Vector3 cameraOffset;

    // 입력 시스템 액션 정의
    InputAction Move_Input; // 이동 입력 액션.
    private Animator _animator; // 캐릭터 애니메이션을 관리하는 Animator 컴포넌트.
    private Rigidbody2D _rigidbody; // 2D 물리 동작을 위한 Rigidbody2D 컴포넌트.
    private SpriteRenderer _spriteRenderer; // 캐릭터 스프라이트 렌더링 컴포넌트.
    private InputAction Jump_Input; // 점프 입력 액션.

    // 스킬 버튼 및 데미지 필드 관련 변수
    public List<CButton> _buttons; // 캐릭터와 연결된 커스텀 버튼 리스트.
    public List<DamageField> _damageFields; // 데미지 필드(공격 이펙트) 프리팹 리스트.
    public List<DamageFieldData> _damageFieldDatas; // 데미지 필드 거리 정보 리스트.

    [NonSerialized] public int Grounded = 0; // 캐릭터가 땅에 닿아 있는지 여부 (점프 가능 여부 판단).

    // 게임 시작 시 실행되는 초기화 함수
    void Start()
    {
        // 주요 컴포넌트를 캐싱
        _animator = GetComponent<Animator>();
        _rigidbody = GetComponent<Rigidbody2D>();
        _spriteRenderer = GetComponent<SpriteRenderer>();

        // 입력 시스템 설정
        UnityEngine.InputSystem.PlayerInput Input = GetComponent<UnityEngine.InputSystem.PlayerInput>();
        Move_Input = Input.actions["Move"]; // 이동 입력 액션 연결.
        Jump_Input = Input.actions["Jump"]; // 점프 입력 액션 연결.

        // 초기 카메라와 캐릭터 간 오프셋 계산
        cameraOffset = _mainCamera.transform.position - transform.position;

        // 버튼마다 스킬 실행 함수 연결
        foreach (var cButton in _buttons)
        {
            cButton.AddListener(FireSkill);
        }
    }

    private bool canMove = true; // 캐릭터가 이동 가능한지 여부를 제어하는 플래그.

    // 특정 인덱스에 따라 데미지 필드(공격 이펙트)를 생성하는 함수
    void FireDamageField(int index)
    {
        GameObject go = Instantiate(_damageFields[index].gameObject); // 데미지 필드 프리팹 생성.
        go.transform.position = transform.position + transform.right * _damageFieldDatas[index].distance; // 캐릭터의 위치 기준으로 거리만큼 떨어진 위치에 생성.
        Destroy(go, 3.0f); // 3초 후 데미지 필드 파괴.
    }

    // 캐릭터 이동 가능 여부를 설정하는 함수
    void CanMove(int bMove)
    {
        canMove = bMove == 1; // bMove가 1이면 이동 가능, 그렇지 않으면 불가능.
    }

    // 캐릭터의 공격 동작을 실행하는 함수
    void FireSkill()
    {
        _animator.Rebind(); // 애니메이터 상태 초기화.
        _animator.Play("Attack"); // "Attack" 애니메이션 실행.
    }

    // 캐릭터 이동 및 물리 계산을 FixedUpdate에서 처리 (프레임에 독립적)
    void FixedUpdate()
    {
        Vector2 moveValue = Move_Input.ReadValue<Vector2>(); // 이동 입력 값을 가져옴.

        if (!canMove)
        {
            moveValue = Vector2.zero; // 이동이 비활성화된 경우 움직임을 중지.
        }

        // 입력에 따라 캐릭터 방향 전환
        if (moveValue.x != 0)
            _spriteRenderer.flipX = moveValue.x < 0; // 왼쪽으로 움직이면 스프라이트를 뒤집음.

        // 애니메이터의 Speed 파라미터 설정
        _animator.SetFloat(Speed1, Mathf.Abs(moveValue.x));

        // 입력이 없을 때 수평 속도를 0으로 설정
        if (moveValue.x == 0)
            _rigidbody.velocity = new Vector2(0, _rigidbody.velocity.y);

        // 캐릭터의 위치를 이동
        transform.position += new Vector3(moveValue.x * Speed * Time.deltaTime, 0, 0);
    }

    // 프레임마다 실행되는 함수로 입력 및 기타 상태를 처리
    void Update()
    {
        // 점프 입력이 발생하고 캐릭터가 땅에 닿아 있을 때 점프 실행
        if (Jump_Input.triggered && Grounded >= 1)
        {
            _rigidbody.AddForce(Vector2.up * JumpSpeed, ForceMode2D.Impulse); // 위쪽으로 힘을 가해 점프.
            _animator.Play("Alchemist_Jump"); // "Jump" 애니메이션 실행.
        }
    }

    // 카메라를 부드럽게 캐릭터를 따라오도록 설정
    private void LateUpdate()
    {
        var CharPosition = transform.position + cameraOffset; // 캐릭터와 오프셋을 더해 목표 카메라 위치 설정.
        float speed = CameraSpeed; // 카메라 이동 속도.

        Vector3 newPosition = Vector3.zero;

        // 카메라가 최대 거리를 초과했는지 체크
        if (Vector3.Distance(CharPosition, _mainCamera.transform.position) >= MaxDistence)
        {
            Vector3 Gap = ((_mainCamera.transform.position) - CharPosition).normalized * MaxDistence; // 거리 차이를 계산.
            newPosition = CharPosition + Gap; // 새로운 카메라 위치 설정.
        }
        else
        {
            // 카메라가 부드럽게 목표 위치로 이동
            newPosition = Vector3.MoveTowards(_mainCamera.transform.position,
                CharPosition,
                speed * Time.deltaTime);
        }

        _mainCamera.transform.position = newPosition; // 카메라 위치 업데이트.
    }
}

 

커스터마이징한 Action Tool 만들기

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;

// 이 클래스는 Unity 에디터에 커스터마이징된 "Action Tool" 창을 만드는 코드입니다.
// [CustomEditor]와 [MenuItem]을 사용해 Unity Editor 내부에 새로운 도구를 추가합니다.
[CustomEditor(typeof(ActionToolEditor))]
public class ActionToolEditor : EditorWindow
{
    // **사용자 입력 변수**
    public GameObject targetObject; // 사용자가 작업 대상 오브젝트를 선택할 수 있도록 하는 변수.
    private float timeScale = 100f; // 타임라인의 시간 배율 (1초를 몇 픽셀로 표현할지 설정).
    private float maxTime = 10.0f; // 타임라인의 최대 시간 길이.

    // **타임라인 표시 관련 변수**
    private float timelineWidth = 100f; // 타임라인의 너비.
    private float timelineHeight = 200f; // 타임라인의 높이.
    private float currentTime = 0f; // 현재 선택된 시간 (마우스 입력으로 변경 가능).
    private Vector2 scrollPosition; // 타임라인의 스크롤 위치.

    // Unity 에디터의 메뉴에 새로운 항목 추가
    [MenuItem("Window/Action/ActionTool")]
    public static void ShowWindow()
    {
        // 에디터 창 열기
        GetWindow<ActionToolEditor>("ActionTool"); // "ActionTool"이라는 이름의 창 생성.
    }

    // 에디터 창의 GUI를 그리는 함수
    private void OnGUI()
    {
        // 세로 레이아웃 시작
        EditorGUILayout.BeginVertical();

        // 대상 오브젝트를 선택하는 필드
        targetObject = EditorGUILayout.ObjectField(
            "Target Object", // 라벨
            targetObject,    // 현재 값
            typeof(GameObject), // 선택 가능한 오브젝트 타입 (GameObject)
            true // 씬 내 오브젝트 선택 허용
        ) as GameObject;

        // 시간 관련 옵션 입력
        timeScale = EditorGUILayout.FloatField("Time Scale", timeScale); // 시간 배율
        maxTime = EditorGUILayout.FloatField("Max Time", maxTime);       // 최대 시간

        // 빈 공간 추가 및 타임라인 제목
        EditorGUILayout.Space();
        EditorGUILayout.LabelField("Timeline", EditorStyles.boldLabel);

        // **스크롤 가능한 타임라인 시작**
        scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);

        // 타임라인의 영역(Rect) 설정
        Rect timelineRect = GUILayoutUtility.GetRect(timeScale * maxTime, timelineHeight);

        // 타임라인의 숫자 라벨 표시
        DrawTimelineText(timelineRect);

        // 타임라인 박스 영역 설정
        timelineRect.y += 20; // y축에서 약간 아래로 이동
        GUI.Box(timelineRect, ""); // 빈 박스 생성 (타임라인 시각적 구분용).

        // 타임라인 그리드와 현재 시간 표시
        DrawTimelineGrid(timelineRect); // 시간 단위로 그리드 선 그리기.
        DrawCurrentTimeline(timelineRect); // 현재 시간 위치를 빨간색 선으로 표시.

        // 타임라인 입력 처리 (마우스 클릭으로 시간 변경 가능)
        HandleTimelineInput(timelineRect);

        // 스크롤 뷰 종료
        EditorGUILayout.EndScrollView();

        // 세로 레이아웃 종료
        EditorGUILayout.EndVertical();

        // 창 다시 그리기 (실시간 업데이트)
        Repaint();
    }

    // 타임라인의 숫자를 그리는 함수
    void DrawTimelineText(Rect timelineRect)
    {
        float secondWidth = timeScale; // 1초당 그려질 픽셀 간격.
        float totalSeconds = maxTime;  // 타임라인의 총 시간 길이.

        for (int i = 0; i < totalSeconds; i++)
        {
            // 각 초 위치에 숫자 라벨 추가
            float x = timelineRect.x + (secondWidth * i); // x축 위치 계산.
            GUI.Label(
                new Rect(x - 15, timelineRect.y, 30, 15), // 라벨의 위치와 크기.
                i.ToString("F1"), // 라벨 텍스트 (소수점 1자리까지 표시).
                new GUIStyle(EditorStyles.miniLabel) { alignment = TextAnchor.MiddleCenter } // 중앙 정렬된 스타일.
            );
        }
    }

    // 타임라인 그리드 선을 그리는 함수
    void DrawTimelineGrid(Rect timelineRect)
    {
        float secondWidth = timeScale; // 1초당 그려질 픽셀 간격.
        float totalSeconds = maxTime;  // 타임라인의 총 시간 길이.

        for (int i = 0; i < totalSeconds; i++)
        {
            float x = timelineRect.x + (secondWidth * i); // 그리드 선의 x축 위치 계산.
            Handles.DrawLine(
                new Vector3(x, timelineRect.y, 0), // 시작점
                new Vector3(x, timelineRect.y + timelineRect.height, 0) // 끝점
            );
        }
    }

    // 현재 시간을 나타내는 빨간색 선을 그리는 함수
    void DrawCurrentTimeline(Rect timelineRect)
    {
        float x = timelineRect.x + (currentTime * timeScale); // 현재 시간의 x축 위치 계산.
        Handles.color = Color.red; // 선의 색상을 빨간색으로 설정.
        Handles.DrawLine(
            new Vector3(x, timelineRect.y), // 시작점
            new Vector3(x, timelineRect.y + timelineRect.height) // 끝점
        );
        Handles.color = Color.white; // 이후 색상을 기본값으로 복원.
    }

    // 타임라인에서 마우스 입력을 처리하는 함수
    void HandleTimelineInput(Rect timelineRect)
    {
        Event e = Event.current; // 현재 이벤트를 가져옴.

        if (timelineRect.Contains(e.mousePosition)) // 마우스가 타임라인 안에 있는지 확인.
        {
            // 마우스 클릭 또는 드래그 이벤트 처리
            if ((e.type == EventType.MouseDown || e.type == EventType.MouseDrag) && e.button == 0)
            {
                // 클릭한 위치를 기준으로 현재 시간을 계산.
                float clickPosition = e.mousePosition.x - timelineRect.x;
                currentTime = Mathf.Clamp(clickPosition / timeScale, 0f, maxTime); // 현재 시간을 0~최대 시간으로 제한.

                e.Use(); // 이벤트 소비.
                Repaint(); // 창 다시 그리기.
            }
        }
    }
}

 

 

과제로 캐릭터 스킬 3개 만들기 + 몬스터 공격으로 죽이기 + 몬스터 hpbar 표시

 

되긴 된다는 것에 의의를 두었다.........