스킬 사용 + 스킬 사용 시 효과
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 표시
되긴 된다는 것에 의의를 두었다.........
'공부 > [TIL] Game Bootcamp' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL] 유니티 게임 개발 3기 : 리더보드 구현 (0) | 2025.03.06 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL] 유니티 게임 개발 3기 : 궤적, HpBar (0) | 2024.12.19 |
[멋쟁이사자처럼 부트캠프 TIL] 유니티 게임 개발 3기 : Curve, 몬스터 생성, 몬스터와 충돌 이벤트 (1) | 2024.12.17 |
[멋쟁이사자처럼 부트캠프 TIL] 유니티 게임 개발 3기 : 애니메이션을 더한 캐릭터 점프, 아이템 먹기 (1) | 2024.12.16 |
[멋쟁이사자처럼 부트캠프 TIL] 유니티 게임 개발 3기 : Input System, TileMap 등 (4) | 2024.12.13 |