드래그로 블럭을 한 칸 움직였지만, 마우스를 놓았을 때 오른쪽으로 움직일지, 왼쪽으로 움직일지 예상할 수가 없다.
아래와 같이 BoundBox를 이용하여 블럭이 어디로 이동하는지 미리 계산하고 보여주자.
BoundBox는 아래의 무료 에셋을 이용하였다.
Runtime에 볼 수 있는 Collider의 느낌이 나는 괜찮은 에셋이다.
https://assetstore.unity.com/packages/tools/utilities/boundboxes-10962
BoundBox는 일반 큐브이고 콜라이더와 Mesh Renderer를 제거하였다.
에셋에 있는 BoundBox.cs에서 namespace를 제거하였고, 아래의 코드를 추가하여 싱글턴으로 만들었다.
static BoundBox instance = null;
public static BoundBox Instance
{
get
{
if (null == instance) instance = FindObjectOfType<BoundBox>();
return instance;
}
}
void Awake()
{
if (null == instance) instance = this;
}
그리고 setActive를 할 수 있도록 함수를 추가하였다.
public void boundSetActive(bool set)
{
this.gameObject.SetActive(set);
}
이제 Block의 OnMouseDown에서 BoundBox를 켜주고, 현재의 위치를 기억해둔다.
void OnMouseDown()
{
/* 코드 추가 */
BoundBox.Instance.boundSetActive(true);
BoundBox.Instance.transform.position = this.transform.position;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
hitPos = new GameObject("Empty");
hitPos.transform.position = hit.point;
this.transform.SetParent(hitPos.transform);
}
}
OnMouseDrag에서 반올림을 이용하여 다음의 위치를 미리 계산해둔다.
void OnMouseDrag()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
int layer = 1 << LayerMask.NameToLayer(HIDDEN_PLANE);
if (Physics.Raycast(ray, out hitLayerMask, Mathf.Infinity, layer))
{
...
Vector3 boundTemp = BoundBox.Instance.transform.position;
boundTemp.x = Mathf.Round(this.transform.position.x);
BoundBox.Instance.transform.position = boundTemp;
}
}
OnMouseUp가 moveBlock을 호출한다.
moveBlock이 종료될 때, BoundBox를 꺼주면 된다.
void OnMouseUp()
{
...
moveBlock(tempPos);
}
...
private IEnumerator moveBlockCoroutine(Vector3 targetPosition)
{
...
transform.position = targetPosition;
BoundBox.Instance.boundSetActive(false);
}
Block.cs의 최종 코드는 아래와 같다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Block : MonoBehaviour
{
const string HIDDEN_PLANE = "HiddenPlane";
public float blockMoveTime = .3f;
RaycastHit hit, hitLayerMask;
GameObject hitPos;
Vector3 getContactPoint(Vector3 normal, Vector3 planeDot, Vector3 A, Vector3 B)
{
Vector3 nAB = (B - A).normalized;
return A + nAB * Vector3.Dot(normal, planeDot - A) / Vector3.Dot(normal, nAB);
}
void OnMouseDown()
{
BoundBox.Instance.boundSetActive(true);
BoundBox.Instance.transform.position = this.transform.position;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
hitPos = new GameObject("Empty");
hitPos.transform.position = hit.point;
this.transform.SetParent(hitPos.transform);
}
}
void OnMouseDrag()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
int layer = 1 << LayerMask.NameToLayer(HIDDEN_PLANE);
if (Physics.Raycast(ray, out hitLayerMask, Mathf.Infinity, layer))
{
Vector3 normal = hitLayerMask.transform.up;
Vector3 planeDot = hitPos.transform.position;
Vector3 A = Camera.main.transform.position;
Vector3 B = hitLayerMask.point;
Vector3 temp = getContactPoint(normal, planeDot, A, B);
Vector3 setPos = hitPos.transform.position;
setPos.x = temp.x;
hitPos.transform.position = setPos;
Vector3 boundTemp = BoundBox.Instance.transform.position;
boundTemp.x = Mathf.Round(this.transform.position.x);
BoundBox.Instance.transform.position = boundTemp;
}
}
void OnMouseUp()
{
this.transform.parent = null;
Destroy(hitPos);
Vector3 tempPos = this.transform.position;
tempPos.x = Mathf.Round(tempPos.x);
moveBlock(tempPos);
}
public void moveBlock(Vector3 targetPosition)
{
StartCoroutine(moveBlockCoroutine(targetPosition));
}
private IEnumerator moveBlockCoroutine(Vector3 targetPosition)
{
float elapsedTime = 0.0f;
Vector3 currentPosition = transform.position;
while (elapsedTime < blockMoveTime)
{
elapsedTime += Time.deltaTime;
transform.position = Vector3.Lerp(currentPosition, targetPosition, elapsedTime / blockMoveTime);
yield return null;
}
transform.position = targetPosition;
BoundBox.Instance.boundSetActive(false);
}
}
에셋스토어의 BoundBox.cs는 아래와 같이 변경하였다. (불필요한 내용 삭제)
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class BoundBox : MonoBehaviour
{
static BoundBox instance = null;
public static BoundBox Instance
{
get
{
if (null == instance) instance = FindObjectOfType<BoundBox>();
return instance;
}
}
void Awake()
{
if (null == instance) instance = this;
}
public enum BoundSource
{
meshes,
boxCollider,
}
public BoundSource boundSource = BoundSource.meshes;
//public bool setupOnAwake = false;
public Material lineMaterial;
[HideInInspector]
public Color wireColor = new Color(0f, 1f, 0.4f, 0.74f);
[HideInInspector]
public bool line_renderer = false;
[HideInInspector]
public Object linePrefab;
[HideInInspector]
[Range(0.005f, 0.25f)] public float lineWidth = 0.03f;
[HideInInspector]
public Color lineColor = Color.red;
[HideInInspector]
public int numCapVertices = 0;
protected Bounds bound;
protected Vector3 boundOffset;
[HideInInspector]
public Bounds colliderBound;
[HideInInspector]
public Vector3 colliderBoundOffset;
[HideInInspector]
public Bounds meshBound;
[HideInInspector]
public Vector3 meshBoundOffset;
protected Vector3[] corners = new Vector3[0];
protected Vector3[,] lines = new Vector3[0, 0];
private Quaternion quat;
// private DimBoxes.DrawLines cameralines;
protected LineRenderer[] lineList;
//private MeshFilter[] meshes;
//private Material lineMaterial;
private Vector3 topFrontLeft;
private Vector3 topFrontRight;
private Vector3 topBackLeft;
private Vector3 topBackRight;
private Vector3 bottomFrontLeft;
private Vector3 bottomFrontRight;
private Vector3 bottomBackLeft;
private Vector3 bottomBackRight;
[HideInInspector]
public Vector3 startingScale;
private Vector3 previousScale;
private Vector3 startingBoundSize;
private Vector3 startingBoundCenterLocal;
private Vector3 previousPosition;
private Quaternion previousRotation;
public void boundSetActive(bool set)
{
this.gameObject.SetActive(set);
}
void Reset()
{
calculateBounds();
Start();
}
void Start()
{
/*cameralines = FindObjectOfType(typeof(DimBoxes.DrawLines)) as DimBoxes.DrawLines;
if (!cameralines)
{
Debug.LogError("DimBoxes: no camera with DimBoxes.DrawLines in the scene", gameObject);
return;
}*/
previousPosition = transform.position;
previousRotation = transform.rotation;
startingBoundSize = bound.size;
startingScale = transform.localScale;
previousScale = startingScale;
startingBoundCenterLocal = transform.InverseTransformPoint(bound.center);
init();
}
public void init()
{
SetPoints();
SetLines();
SetLineRenderers();
}
void LateUpdate()
{
if (transform.localScale != previousScale)
{
SetPoints();
}
if (transform.position != previousPosition || transform.rotation != previousRotation || transform.localScale != previousScale)
{
SetLines();
previousRotation = transform.rotation;
previousPosition = transform.position;
previousScale = transform.localScale;
}
}
void calculateBounds()
{
quat = transform.rotation;//object axis AABB
Vector3 locScale = transform.localScale;
meshBound = new Bounds();
MeshFilter[] meshes = GetComponentsInChildren<MeshFilter>();
transform.rotation = Quaternion.Euler(0f, 0f, 0f);
transform.localScale = Vector3.one;
for (int i = 0; i < meshes.Length; i++)
{
Mesh ms = meshes[i].sharedMesh;
int vc = ms.vertexCount;
for (int j = 0; j < vc; j++)
{
if (i == 0 && j == 0)
{
meshBound = new Bounds(meshes[i].transform.TransformPoint(ms.vertices[j]), Vector3.zero);
}
else
{
meshBound.Encapsulate(meshes[i].transform.TransformPoint(ms.vertices[j]));
}
}
}
transform.rotation = quat;
transform.localScale = locScale;
meshBoundOffset = meshBound.center - transform.position;
}
void SetPoints()
{
if (boundSource == BoundSource.boxCollider)
{
BoxCollider bc = GetComponent<BoxCollider>();
if (!bc)
{
Debug.LogError("no BoxCollider - add BoxCollider to " + gameObject.name + " gameObject");
return;
}
bound = new Bounds(bc.center, bc.size);
boundOffset = bc.center;
}
else
{
bound = meshBound;
boundOffset = meshBoundOffset;
}
//bound.size = new Vector3(bound.size.x * transform.localScale.x / startingScale.x, bound.size.y * transform.localScale.y / startingScale.y, bound.size.z * transform.localScale.z / startingScale.z);
//boundOffset = new Vector3(boundOffset.x * transform.localScale.x / startingScale.x, boundOffset.y * transform.localScale.y / startingScale.y, boundOffset.z * transform.localScale.z / startingScale.z);
topFrontRight = boundOffset + Vector3.Scale(bound.extents, new Vector3(1, 1, 1));
topFrontLeft = boundOffset + Vector3.Scale(bound.extents, new Vector3(-1, 1, 1));
topBackLeft = boundOffset + Vector3.Scale(bound.extents, new Vector3(-1, 1, -1));
topBackRight = boundOffset + Vector3.Scale(bound.extents, new Vector3(1, 1, -1));
bottomFrontRight = boundOffset + Vector3.Scale(bound.extents, new Vector3(1, -1, 1));
bottomFrontLeft = boundOffset + Vector3.Scale(bound.extents, new Vector3(-1, -1, 1));
bottomBackLeft = boundOffset + Vector3.Scale(bound.extents, new Vector3(-1, -1, -1));
bottomBackRight = boundOffset + Vector3.Scale(bound.extents, new Vector3(1, -1, -1));
corners = new Vector3[] { topFrontRight, topFrontLeft, topBackLeft, topBackRight, bottomFrontRight, bottomFrontLeft, bottomBackLeft, bottomBackRight };
}
protected virtual void SetLines()
{
if (line_renderer)
{
lineList = GetComponentsInChildren<LineRenderer>();
if (lineList.Length == 0)
{
lineList = new LineRenderer[12];
for (int i = 0; i < 12; i++)
{
#if UNITY_EDITOR
GameObject go = PrefabUtility.InstantiatePrefab(linePrefab) as GameObject;
go.transform.SetParent(transform);
go.transform.position = Vector3.zero;
go.transform.rotation = Quaternion.identity;
#else
GameObject go = (GameObject)Instantiate(linePrefab, transform, false);
#endif
lineList[i] = go.GetComponent<LineRenderer>();
}
}
else
{
for (int i = 0; i < lineList.Length; i++)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
if (PrefabUtility.GetCorrespondingObjectFromSource(lineList[i].gameObject) == linePrefab)
{
lineList[i].enabled = true;
}
else
{
lineList[i].gameObject.SetActive(false);
Debug.Log("BB");
GameObject go = PrefabUtility.InstantiatePrefab(linePrefab) as GameObject;
go.transform.SetParent(transform);
go.transform.position = Vector3.zero;
go.transform.rotation = Quaternion.identity;
lineList[i] = go.GetComponent<LineRenderer>();
}
}
#endif
lineList[i].enabled = true;
}
}
for (int i = 0; i < 4; i++)
{
//width
lineList[i].SetPositions(new Vector3[] { transform.TransformPoint(corners[2 * i]), transform.TransformPoint(corners[2 * i + 1]) });
//height
lineList[i + 4].SetPositions(new Vector3[] { transform.TransformPoint(corners[i]), transform.TransformPoint(corners[i + 4]) });
//depth
lineList[i + 8].SetPositions(new Vector3[] { transform.TransformPoint(corners[2 * i]), transform.TransformPoint(corners[2 * i + 3 - 4 * (i % 2)]) });
}
}
}
public void SetLineRenderers()
{
Gradient colorGradient = new Gradient();
colorGradient.SetKeys(new GradientColorKey[] { new GradientColorKey(lineColor, 0.0f)}, new GradientAlphaKey[] { new GradientAlphaKey(lineColor.a, 0.0f) });
foreach (LineRenderer lr in GetComponentsInChildren<LineRenderer>(true))
{
lr.startWidth = lineWidth;
lr.enabled = line_renderer;
lr.numCapVertices = numCapVertices;
lr.colorGradient = colorGradient;
lr.material = lineMaterial;
}
}
}
LineMaterial은 아래와 같다.
BoundBox의 자식으로 12개의 Line Renderer가 있다.
여기 있는 BoundBox를 복사하는 것보다 Asset Store에서 가져온 후, 수정하는 것이 편할 수 있다.
이 글에서 사용한 BoundBox는 아래의 unitypackage를 import 하면 된다.
Unity Plus:
Unity Pro:
Unity 프리미엄 학습:
'개발 > Unity' 카테고리의 다른 글
유니티 - OnGUI로 실시간 초당 프레임 수 확인하기 (FPS Status based on OnGUI) (0) | 2022.06.17 |
---|---|
유니티 쉐이더 - 카메라가 바라보는 방향으로 밝아지는 큐브 (0) | 2022.06.14 |
유니티 - 드래그로 블럭 옆으로 한 칸 움직이기 (Drag GameObject Snapped to a Grid) (0) | 2022.06.12 |
유니티 쉐이더 - 카메라 흑백 효과로 게임 오버 이펙트 만들기 (Camera Game Over Effect) (0) | 2022.06.11 |
유니티 - 전처리기 지시문을 사용하여 조건부 컴파일하기 (Scripting Define Symbols) (0) | 2022.06.10 |
댓글