
원래 현재 게임 뷰를 보여주는 FreeLookCamera가 필드의 가운데 노드인 Node(2)를 보도록 해놨는데 추후에 노드가 많아지고 Field가 많아지면 그 때 마다 가운데 노드를 찾아주기가 쉽지 않아서 코드로 필드의 가운데를 바라보도록 구현해보자.
public class CMmainFreeLookCameraSetting : MonoBehaviour
{
[Space(5)]
[Header("Camera Settings")]
public CinemachineFreeLook mainFreeLookCamera;
public float zoomSpeed = 2.5f;
[Space(5)]
[Header("Objects & Transform")]
public GameObject CameraTarget;
public Transform field;
private void Start()
{
mainFreeLookCamera = GetComponent<CinemachineFreeLook>();
if (field == null)
return;
Vector3 centerPos = CalculateCameraTarget(field);
SetCameraTarget(centerPos);
mainFreeLookCamera.LookAt = CameraTarget.transform;
mainFreeLookCamera.Follow = CameraTarget.transform;
}
Vector3 CalculateCameraTarget(Transform parent)
{
Vector3 totalPosition = Vector3.zero;
int childCount = 0;
foreach (Transform child in parent)
{
if (child.CompareTag("node"))
{
totalPosition += child.position;
childCount++;
}
}
if (childCount > 0)
{
return totalPosition / childCount;
}
else
{
return parent.position;
}
}
private void SetCameraTarget(Vector3 centerPos)
{
if (CameraTarget != null)
CameraTarget.transform.position = centerPos;
}
// update문 생략
}
카메라가 필드 내부에 있는 node들의 좌표 평균을 내서 centerPoint 오브젝트를 해당 위치에 배치한 다음 해당 오브젝트를 바로보도록 설정했다.

노드 갯수를 늘려도 카메라가 자연스럽게 노드들의 중앙을 바라보도록 되었다.
카메라가 자연스럽게 endPoint를 클로즈업하면서 endPoint 주면을 회전하도록 구현한다.
using Cinemachine;
using System;
using System.Collections;
using System.Runtime.CompilerServices;
using Unity.VisualScripting;
using UnityEngine;
public class GameManager : MonoBehaviour
{
[Space(5)]
[Header("Manager")]
public BuildManager buildManager;
public Validator validator;
[Space(5)]
[Header("Tag & Name")]
public string startTag = "startPoint";
public string endTag = "endPoint";
public string BulbName = "Bulb";
[Space(5)]
[Header("endPoint")]
public GameObject endPoint;
public Animator endAnimator;
public float endAnimationDuration = 1.0f; // Current Animation Duration
[Space(5)]
[Header("Material")]
[SerializeField]
public Material PillarMaterial = null;
[Space(5)]
[Header("mainCamera")]
public GameObject mainCamera = null;
[Space(5)]
[Header("State")]
public GameState gameState;
public static GameManager Instance { get; private set; }
void Awake()
{
// 싱글톤 인스턴스 설정
if (Instance == null)
{
Instance = this;
}
else
{
Debug.LogWarning("Error - Only 1 instance - GameManager.");
Destroy(gameObject);
}
validator = GetComponent<Validator>();
buildManager = GetComponent<BuildManager>();
endAnimator = endPoint.GetComponent<Animator>();
startTag = "startPoint";
endTag = "endPoint";
}
void Start()
{
gameState = GameState.PLAY;
// Validation
if (!validator.ValidateInitialization())
QuitGame();
}
/// <summary>
/// endPoint's state is now ON. Ending the stage.
/// </summary>
public void Clear()
{
gameState = GameState.CLEAR; // game Clear
float delayTime = 1.0f;
float finishDuration = 2.0f;
StartCoroutine(FinishCameraSetting(delayTime, finishDuration));
StartCoroutine(FinishStage(delayTime, finishDuration));
}
/// <summary>
/// Clear State, Finish Stage
/// </summary>
IEnumerator FinishStage(float delayTime, float finishDuration)
{
yield return new WaitForSeconds(delayTime);
endAnimator.speed = endAnimationDuration / delayTime;
endAnimator.SetTrigger("endStateOn"); // play endPoint Animation
}
IEnumerator FinishCameraSetting(float delayTime, float finishDuration)
{
yield return new WaitForSeconds(delayTime);
mainCamera.GetComponent<CMmainFreeLookCameraSetting>().ClearGame(endPoint.transform, finishDuration);
}
/// <summary>
/// Something has happened and shutting down the game
/// </summary>
public void QuitGame()
{
Debug.Log("Quitting Game...");
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
}
아래와 같이 애니메이션이 일정한 속도로 시작해서 천천히 가속하다가 끝에 다가가면 다시 천천히 감속하는 효과를 얻을 수 있습다.
float t = Mathf.Clamp01(timer / duration); // 0에서 1 사이로 정규화
float smoothT = Mathf.SmoothStep(0.0f, 1.0f, t); // 부드러운 보간값 계산
// 애니메이션의 현재 위치 계산
Vector3 currentPosition = Vector3.Lerp(startPoint.position, endPoint.position, smoothT);
// 오브젝트 위치 업데이트
transform.position = currentPosition;
GameManager에 gameState로 게임 상태를 유지하고 있다. PLAY, PAUSE, CLEAR를 enum형으로 선언해놓았다. GameState가 CLEAR로 바뀌면서 동시에 건설 및 카메라 제어를 종료한다.
카메라 회전만 추가해보자
/// <summary>
/// When Clear Stage, GameManager called this method
/// </summary>
public void ClearGame(Transform endPoint, float transitionDuration)
{
mainFreeLookCamera.m_YAxis.m_InputAxisName = "";
mainFreeLookCamera.m_XAxis.m_InputAxisName = "";
mainFreeLookCamera.m_XAxis.m_MaxSpeed = 20f;
mainFreeLookCamera.m_XAxis.m_InputAxisValue = 1f;
StartCoroutine(MoveTargetToPosition(endPoint, transitionDuration));
StartCoroutine(CloseUpEndPoint(endPoint, transitionDuration));
}
CLEAR 하면 이제 endPoint가 클로즈업 되면서 한쪽 방향으로 천천히 회전이 된다.
게임이 클리어되면 건설 예정 위치에 출력되는 반투명한 블럭을 비활성화해주어야 해서 코루틴으로 게임 상태가 CLEAR로 바뀌면 투명 블럭을 비활성화 시켜주도록 했다.
public void OnMouseEnter() // When the mouse passes or enters an object collider
{
if(isBuildable && GameManager.Instance.gameState == GameState.PLAY)
{
StartCoroutine(transparentBlockNodeControl());
rend.material.color = hoverColor; // change color to hoverColor
}
}
IEnumerator transparentBlockNodeControl()
{
transBlockOnNode.SetActive(true);
while (transBlockOnNode.activeSelf && GameManager.Instance.gameState == GameState.PLAY)
{
yield return null;
}
transBlockOnNode.SetActive(false);
}
다이나믹함을 추가해주기 위해서 Block Build Animation을 추가해주자.

localScale 에러가 발생했다. Node를 수정해주자. 노드 블럭을 노드 scale 1, 1, 1 안의 자식 오브젝트로 넣어주고 블럭을 그 밖의 scale이 1인 오브젝트의 자식으로 넣어주도록 계층 구조를 바꿔주었다.

이제 scale이 잘 유지되면서 건설된다.
HUD 및 스테이지, 맵 정보 저장 및 로드, 기타 음악 효과 등, 등을 구현할 예정이다.