목차
게임에 필요한 상식
25.04.04
Character Animation
>> Hierarchy에 Ellen Prefab 추가
>> PlayerController.cs 생성
: Ellen에 추가
--> Input Manager를 통해 입력값 받는 방식으로 구현
>> Ellen에 'Character Controller' Component 추가
: 캐릭터를 이동시킬 뿐만 아니라 경사로나 계단도 오르도록 도와준다.
- Slope Limit : 오를 수 있는 경사로 각도
- Skin Width : 충돌감지영역 크기
- Min Move Distance : 움직임의 최소 거리
- Step Offset : 오를 수 있는 계단의 높이
--> Character에 맞게 Center 조정
※ Layer는 32Bit 비트 마스크를 사용하여 Layer를 구분한다.
>> 이동 테스트 할 때, 캐릭터가 살짝 뜨는 문제 해결
: Character Controller의 Collider 부분이 땅과 닿아서 생기는 문제 --> Skin Width 값을 줄여서 해결
>> Animation Controller 생성
: 이름은 'Ellen', 이후 Hierarchy에 있는 Ellen에 바인딩
※ Animation을 미리 보고 다운 받을 수 있는 사이트
https://www.mixamo.com/#/?page=1&type=Character
Mixamo
www.mixamo.com
>> Animator
: 'Ellen' Animation Controller
--> EllenIdle은 Idle로 이름을 변경
>> Walk 추가
>> Create State Empty로 'Jump', 'Attack', 'Hit' 추가한 뒤, Has Exit Time 체크 해제
: Walk -> Idle도 Has Exit Time 체크 해제
--> Jump, Attack, Hit은 나중에 구현하려고 임시로 만든 것
>> Parameters에서 Bool 타입으로 'Move' 생성 후 바인딩
: Walk -> Idle은 반대로 Move가 False일 때로 설정
>> Apply Root Motion 해제
>> Jump 구현
: (개인적으로 구현하도록)
>> Run 구현
: Blend Tree 활용 --> 기존의 Walk는 삭제
1. Blend Tree 생성
2. Blend Tree 내부로 들어가서 Motion Field 생성
: 2개 생성
3. Ellen의 Walk와 Run 바인딩 및 Parameter 수정
4. Blend Tree의 이름을 Move로 변경하고 Conditions 추가 및 Has Exit Time 체크 해제
: Move -> Idle도 마찬가지로 Has Exit Time 체크 해제하고 Condition의 Move를 false로 설정
>> Shift를 눌렀을 때, 서서히 속도가 증가해서 Animation을 자연스럽게 만들기
: 개인적으로 구현하도록
└ Apply Root Motion을 Script에서 조절하여 구현
※ Update Mode
: Animation의 계산을 언제 시행할지 결정하는 것
- Normal : Update()와 같은 횟수로 호출 --> 타임 스케일의 영향을 받는다.
- Animate Physics : Fixed Update()와 같은 횟수로 호출 --> Unity가 다루는 물리 계산과 Animation을 동기화할 수 있다.
- Unscaled Time : Update()와 같은 횟수로 호출되지만 타임스케일의 영향을 받지 않는다 --> 게임이 슬로우 모션 중일 때에도 정상적으로 Animation 시키고자 할 때 도움이 된다.
>> Jump 구현
: Sub-State Machine을 활용하여 구현
1. Sub-State Machine 생성
: 이름은 'Jump'
2. Jump 내부로 들어가서 Blend Tree 생성 및 'EllenIdleLand' 추가 후, 'Make Transition'
: Blend Tree의 이름은 'OnJump'
3. Base Layer에서 Idle과 Jump 연결
: StateMachine - Jump로 연결
※ 아직 구현이 완료되지 않은 상태로 수업이 마무리 됨
Camera
>> 구면 좌표계
- r: 원점에서 점까지의 거리 (반지름)
- θ: 방위각 (azimuthal angle, x축 기준 회전각)
- φ: 고도각 (polar angle, y축 방향으로 올라가는 각)
- b: xz 평면에 투영된 거리 (r의 수평 성분)
>> CameraController.cs 생성
: 이후 Main Camera에 추가
>> Ellen의 자식으로 빈 게임 오브젝트 'Camera Target' 생성
: 이후 CameraController.cs에 바인딩
--> 그냥 Ellen을 Camera의 Target으로 넣으면 Pivot이 발에 있기 때문에 발을 기준으로 Camera가 움직이게 되므로 이를 방지하기 위해서
최종 코드
>> PlayerController.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(Animator))]
public class PlayerController : MonoBehaviour
{
private static readonly int Move = Animator.StringToHash("Move");
private static readonly int Speed = Animator.StringToHash("Speed");
[SerializeField] private float rotateSpeed = 100f;
[SerializeField] private float jumpForce = 5f;
private CharacterController _characterController;
private Animator _animator;
private float _gravity = -9.81f;
private Vector3 _velocity;
private float _groundDistance;
// Root Motion On
private float _groundedMinDistance = 0.1f;
private float _speed = 0f;
private bool IsGrounded
{
get
{
var distance = GetDistanceToGround();
return distance < _groundedMinDistance;
}
}
private void Awake()
{
_characterController = GetComponent<CharacterController>();
_animator = GetComponent<Animator>();
}
private void Start()
{
// 커서 설정
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
}
private void Update()
{
// 커서 Rock 해제
if (Input.GetKeyDown(KeyCode.Escape))
{
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
}
HandleMovement();
// ApplyGravity(); // Root Motion Off
CheckRun();
}
// 사용자 입력 처리 함수
private void HandleMovement()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
if (vertical > 0)
{
_animator.SetBool(Move, true);
}
else
{
_animator.SetBool(Move, false);
}
_animator.SetFloat(Speed, _speed);
#region Root Motion Off
// // 달리기
// float speed = 0;
// if (Input.GetKey(KeyCode.LeftShift))
// {
// speed = 1;
// }
//
// _animator.SetFloat("Speed", speed);
#endregion
Vector3 movement = transform.forward * vertical;
transform.Rotate(0, horizontal * rotateSpeed * Time.deltaTime, 0);
#region Root Motion Off
// _characterController.Move(movement * Time.deltaTime);
// _groundDistance = GetDistanceToGround();
// if (Input.GetButtonDown("Jump"))
// {
// _velocity.y = Mathf.Sqrt(jumpForce * -2f * _gravity);
// }
#endregion
}
#region Root Motion Off
// 중력 적용 함수
private void ApplyGravity()
{
_velocity.y += _gravity * Time.deltaTime;
_characterController.Move(_velocity * Time.deltaTime);
}
#endregion
// 달리기 처리
private void CheckRun()
{
if (Input.GetKey(KeyCode.LeftShift))
{
_speed += Time.deltaTime;
_speed = Mathf.Clamp01(_speed); //Mathf.Clamp(_speed, 0f, 1f);와 동일
}
else
{
_speed -= Time.deltaTime;
_speed = Mathf.Clamp01(_speed);
}
}
// 바닥과 거리를 계산하는 함수
private float GetDistanceToGround()
{
float maxDistance = 10f;
if (Physics.Raycast(transform.position, Vector3.down, out RaycastHit hit, maxDistance))
{
return hit.distance;
}
else
{
return maxDistance;
}
}
#region Animator Method
private void OnAnimatorMove()
{
Vector3 movePosition;
movePosition = _animator.deltaPosition;
// 중력 적용
_velocity.y += _gravity * Time.deltaTime;
movePosition.y = _velocity.y;
_characterController.Move(movePosition);
}
#endregion
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, transform.position + Vector3.down * _groundDistance);
}
}
>> CameraController.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Camera))]
public class CameraController : MonoBehaviour
{
[SerializeField] private Transform target;
[SerializeField] private float rotationSpeed = 1000f;
[SerializeField] private float distance = 5f; // target과 camera의 거리
private float _azimuthAngel;
private float _polarAngle = 45f;
private void Start()
{
var cartesianPosition = GetCameraPosition(distance, _polarAngle, _azimuthAngel);
var cameraPosition = target.position - cartesianPosition;
transform.position = cameraPosition;
transform.LookAt(target);
}
private void LateUpdate()
{
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
Debug.Log($"## X : {mouseX} Y : {mouseY}");
_azimuthAngel += mouseX * rotationSpeed * Time.deltaTime;
_polarAngle -= mouseY * rotationSpeed * Time.deltaTime;
_polarAngle = Mathf.Clamp(_polarAngle, 10f, 45f);
var cartesianPosition = GetCameraPosition(distance, _polarAngle, _azimuthAngel);
var cameraPosition = target.position - cartesianPosition;
transform.position = cameraPosition;
transform.LookAt(target);
}
Vector3 GetCameraPosition(float r, float polarAngle, float azimuthAngle)
{
float b = r * Mathf.Cos(polarAngle * Mathf.Deg2Rad);
float z = b * Mathf.Cos(azimuthAngle * Mathf.Deg2Rad);
float y = r * Mathf.Sin(polarAngle * Mathf.Deg2Rad) * -1;
float x = b * Mathf.Sin(azimuthAngle * Mathf.Deg2Rad);
return new Vector3(x, y, z);
}
}
'Development > C#' 카테고리의 다른 글
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 91일차 (0) | 2025.04.22 |
---|---|
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 90일차 (0) | 2025.04.17 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 88일차 (0) | 2025.04.03 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 87일차 (1) | 2025.04.02 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 79, 80일차 (0) | 2025.03.21 |