using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
[Header("Movement")]
public float moveSpeed = 5f;
private Vector2 curMovementInput;
public float jumpPower = 100f;
public LayerMask groundLayerMask;
[Header("Look")]
public Transform cameraContainer;
public float minXLook;
public float maxXLook;
private float camCurXRot;
public float lookSensitivity;
private Vector2 mouseDelta;
private Rigidbody _rigidbody;
private void Awake()
{
_rigidbody = GetComponent<Rigidbody>();
}
private void Start()
{
Cursor.lockState = CursorLockMode.Locked;
}
private void FixedUpdate()
{
Move();
}
private void LateUpdate()
{
CameraLook();
}
void Move()
{
Vector3 dir = transform.forward * curMovementInput.y + transform.right * curMovementInput.x;
dir *= moveSpeed;
dir.y = _rigidbody.velocity.y;
_rigidbody.velocity = dir;
}
void CameraLook()
{
camCurXRot += mouseDelta.y * lookSensitivity;
camCurXRot = Mathf.Clamp(camCurXRot, minXLook, maxXLook);
cameraContainer.localEulerAngles = new Vector3(-camCurXRot, 0, 0);
transform.eulerAngles += new Vector3(0, mouseDelta.x * lookSensitivity, 0);
}
public void OnMoveInput(InputAction.CallbackContext context)
{
if (context.phase == InputActionPhase.Performed)
{
curMovementInput = context.ReadValue<Vector2>();
}
else if (context.phase == InputActionPhase.Canceled)
{
curMovementInput = Vector2.zero;
}
}
public void OnLookInput(InputAction.CallbackContext context)
{
mouseDelta = context.ReadValue<Vector2>();
}
public void OnJumpInput(InputAction.CallbackContext context)
{
if (context.phase == InputActionPhase.Started && IsGrounded())
{
_rigidbody.AddForce(Vector3.up * jumpPower, ForceMode.Impulse);
}
}
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++)
{
Debug.DrawRay(rays[i].origin, rays[i].direction * 0.5f, Color.red, 1f);
if (Physics.Raycast(rays[i], 0.5f, groundLayerMask))
{
return true;
}
}
return false;
}
}
오늘 배운 내용중 플레이어 컨트롤러 부분이다.
우선은 CameraLook이다
void CameraLook()
{
camCurXRot += mouseDelta.y * lookSensitivity;
camCurXRot = Mathf.Clamp(camCurXRot, minXLook, maxXLook);
cameraContainer.localEulerAngles = new Vector3(-camCurXRot, 0, 0);
transform.eulerAngles += new Vector3(0, mouseDelta.x * lookSensitivity, 0);
}
카메라의 상하 이동은 카메라 컨테이너의 오일러 각을 로테이션 하였다. 오일러 각이 이동하는 방향은 간단하게 생각하여 왼손으로 따봉을 한뒤 엄지를 그 축에 갖다대어 감기는 방향이 + 방향이라고 보면 된다 마우스의 델타값은 마우스의 위치 변화값인데 델타y가 +라면 x축을 기준으로 -방향으로 돌아가야 제대로된 방향으로 돌아간다
좌우 이동은 플레이어 오브젝트 자체를 돌리며 마우스가 오른쪽 +로 갈때 오일러각 y축 기준으로 +로 가게된다.
private Vector2 curMovementInput;
private Vector2 mouseDelta;
public void OnMoveInput(InputAction.CallbackContext context)
{
if (context.phase == InputActionPhase.Performed)
{
curMovementInput = context.ReadValue<Vector2>();
}
else if (context.phase == InputActionPhase.Canceled)
{
curMovementInput = Vector2.zero;
}
}
public void OnLookInput(InputAction.CallbackContext context)
{
mouseDelta = context.ReadValue<Vector2>();
}
인풋 시스템을 활용하여 만든 입력 조작 방식이다. 인풋시스템에서 vector2로 값을 받아와 wasd와 마우스 델타 값을 받아온다 이때 Input.get 과 마찬가지로 context.phase로 Started, Performed, Canceled 등의 상태에 따라 조정할 수 있다.
public void OnJumpInput(InputAction.CallbackContext context)
{
if (context.phase == InputActionPhase.Started && IsGrounded())
{
_rigidbody.AddForce(Vector3.up * jumpPower, ForceMode.Impulse);
}
}
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++)
{
Debug.DrawRay(rays[i].origin, rays[i].direction * 0.5f, Color.red, 1f);
if (Physics.Raycast(rays[i], 0.5f, groundLayerMask))
{
return true;
}
}
return false;
}
점프 부분이다. 여기선 rigidbody.AddForce를 사용하였는데 mode로는 4가지가 있다.
Force 연속적인 힘을 적용 질량에 영향받음 F=ma 적용
Acceleration 질량과 무관하게 가속도를 적용
Impulse 순간적인 힘을 한번만 적용 질량에 영향받음
VelocityChange 질량과 무관하게 즉각적으로 속도 변화 적용
여기서 바닥에 닿아 있는지 확인하기 위해 raycast를 활용했는데 player의 캡슐콜라이더 높이가 0.85이기에
Physics.Raycast(rays[i], 0.1f, groundLayerMask
로 는 바닥에 닿지 않아 false만 나왔다. 이때
Debug.DrawRay(rays[i].origin, rays[i].direction * 0.5f, Color.red, 1f);
를 통해 ray가 나가는것을 시각적으로 표현하여 디버깅할수 있엇다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DayNightCycle : MonoBehaviour
{
[Range(0, 1f)]
public float time;
public float fullDayLength;
public float startTime = 0.4f;
public float timeRate;
public Vector3 noon;
[Header("Sun")]
public Light sun;
public Gradient sunColor;
public AnimationCurve sunIntensity;
[Header("Moon")]
public Light moon;
public Gradient moonColor;
public AnimationCurve moonIntensity;
[Header("Other Lighting")]
public AnimationCurve lightingIntensityMultiplier;
public AnimationCurve reflectIntensityMultiplier;
private void Start()
{
timeRate = 1f / fullDayLength;
time = startTime;
}
private void Update()
{
time = (time + timeRate * Time.deltaTime) % 1.0f;
UpdateLighting(sun, sunColor, sunIntensity);
UpdateLighting(moon, moonColor, moonIntensity);
RenderSettings.ambientIntensity = lightingIntensityMultiplier.Evaluate(time);
RenderSettings.reflectionIntensity = reflectIntensityMultiplier.Evaluate(time);
}
void UpdateLighting(Light lightSource, Gradient gradient, AnimationCurve intensityCurve)
{
float intensity = intensityCurve.Evaluate(time);
lightSource.transform.eulerAngles = (time - (lightSource == sun ? 0.25f : 0.75f)) * noon* 4f;
lightSource.color = gradient.Evaluate(time);
lightSource.intensity = intensity;
GameObject go = lightSource.gameObject;
if (lightSource.intensity == 0 && go.activeInHierarchy)
go.SetActive(false);
else if (lightSource.intensity > 0 && !go.activeInHierarchy)
go.SetActive(true);
}
}
낮과 밤 코드이다.
AnimationCurve는 의외로 간단하다
AnimationCurve에서는 x축 (시간)과 y축(값)으로 구성되어 키프레임을 설정해주면 시간에 따른 값의 변화를 곡선으로 부드럽게 처리해준다.
여기서는 하루의 time를 1f로 설정하고 noon의 오일러각이 (90,0,0)일때 그에 알맞는 해의 각도를 세팅해주었다.
curve = new AnimationCurve(
new Keyframe(0f, 0f), // (시간, 값)
new Keyframe(1f, 1f) // (1초일 때 값이 1)
);
이와같이 키프레임을 코드로 작성하거나 keys AddKey(Keyframe key) Movekey(in index, Keyframe key) RemoveKey(int index) 메서드들 을 활용할수있으나 코드내에서는 Evalute만 활용하는것이 비교적 편하다.