프로젝트에 이런 연출이 들어가면 어떨까~ 싶어서 심심풀이로 만들어봤다.
using System.Collections;
using UnityEngine;
public class VoxelCube : MonoBehaviour
{
[SerializeField] private MeshRenderer meshRenderer;
private Transform rendererTransform;
private Coroutine coroutine;
public enum VoxelFieldType
{
Grass,
Stone,
}
public VoxelFieldType type;
[SerializeField] private Material[] materials;
private void Start()
{
meshRenderer.material = materials[(int)type];
rendererTransform = transform.GetChild(0);
}
public void OnPlayerSteps()
{
if (coroutine != null)
StopCoroutine(coroutine);
StartCoroutine(RendererPostionChange(rendererTransform.localPosition, new(0, -0.2f, 0), 0.3f));
}
public void ExitPlayerSteps()
{
if (coroutine != null)
StopCoroutine(coroutine);
StartCoroutine(RendererPostionChange(rendererTransform.localPosition, new(0, 0, 0), 0.3f));
}
private IEnumerator RendererPostionChange(Vector3 from, Vector3 to, float duration)
{
for (float t = 0; t < duration; t += Time.deltaTime)
{
rendererTransform.localPosition = Vector3.Lerp(from, to, t / duration);
yield return null;
}
rendererTransform.localPosition = to;
}
}
간단한 Voxel Cube를 만들어봤다.
OnPlayerSteps()
메서드가 호출되면 큐브가 살짝 내려앉고, ExitPlayerSteps()
가 호출되면 원상태로 복구된다.
using UnityEngine;
public class PlayerController : MonoBehaviour
{
private CharacterController controller;
private VoxelCube currentVoxel;
private void Start()
{
controller = GetComponent<CharacterController>();
}
private void Update()
{
Move();
VoxcelRay();
}
private void VoxcelRay()
{
var hits = Physics.RaycastAll(transform.position, Vector3.down, float.PositiveInfinity);
foreach (var hit in hits)
{
if (hit.collider.CompareTag("Player"))
continue;
if (hit.collider.TryGetComponent<VoxelCube>(out var voxel))
{
if (voxel != currentVoxel)
{
if (currentVoxel != null)
currentVoxel.ExitPlayerSteps();
voxel.OnPlayerSteps();
currentVoxel = voxel;
}
}
}
}
private void Move()
{
Vector3 moveVelocity = Vector3.zero;
moveVelocity.z = Input.GetAxisRaw("Vertical");
moveVelocity.x = Input.GetAxisRaw("Horizontal");
controller.Move(7f * Time.deltaTime * moveVelocity.normalized);
}
}
플레이어는 발밑으로 레이를 쏴서 탐지한 Voxel이 현재 밟고 있는 Voxel과 다르다면 새로운 Voxel를 밟은 것이다.
따라서 currentVoxel의 ExitPlayerSteps를 호출해주고 새롭게 탐지한 voxel에 OnPlayerSteps를 호출해준 다음, currentVoxel를 갱신해준다.
밟은 Voxel이 내려앉는다.
아무래도 실제 프로젝트에선 맵이 훨씬 거대할텐데, 어떤 방식으로 현재 밟고 있는 Voxel을 찾을지는 많은 고민이 필요할 것 같다.
Collision을 이용하는 건 아무래도 많은 연산이 필요할 것 같아서 제일 먼저 기각했다.
위의 테스트 프로젝트는 캐릭터 발밑으로 raycast를 쏴서 탐색했는데, 솔직히 이 방법도 맵이 커졌을 때 연산에 어떤 부하가 걸릴지는 잘 모르겠다.
또 다른 방법으로는 voxel들을 딕셔너리와 같은 컨테이너에 넣어두고, (x, z) 좌표값으로 참조가 가능하게 한 다음 플레이어의 x, z좌표를 이용하여 탐색하는 방법도 있을 수 있겠다.