一个二维角色如果生活在三维世界里会是什么样子的?
今天带来的作品是一款名叫Flip的平台跳跃解谜游戏,耗时1个月开发,是在皮皮关学习的毕业作业。感谢马遥老师和沈琰老师的帮助。
游戏采用了2D和3D视角切换的方法,玩家需要转动3D平台,从而让2D角色能够到达终点。基于这套极简规则展开了一系列谜题(关卡),并额外添加了一些变量。
本文章将介绍核心代码:使用EzySlice插件对3D物体进行切割。
将EzySlice插件导入工程里,具体教程可以看这个视频。
新建名为Splitter的代码,挂在切割平面下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using EzySlice;
using UnityEngine.Rendering;
public class Splitter : MonoBehaviour
{
public Material hullMat, transMat;
public Vector3 myScale;
List<GameObject> slicedHulls;
public GameObject platformPivot;
List<Vector3> vertsGenerated;
List<int> indiceGenerated;
public GameObject paintedPlace;
List<Vector2> colliderPoints;
List<GameObject> polyColliders;
List<GameObject> meshObjs;
void Start()
{
meshObjs = new List<GameObject>();
polyColliders = new List<GameObject>();
vertsGenerated = new List<Vector3>();
indiceGenerated = new List<int>();
colliderPoints = new List<Vector2>();
slicedHulls = new List<GameObject>();
PlaneSliceNew();
}
//每次将摄像机调回3D透视视角时调用,Destroy掉所有切割的物体及其碰撞体
public void ClearObjs()
{
foreach (var temp in slicedHulls)
{
Destroy(temp);
}
slicedHulls.Clear();
foreach (var temp in polyColliders)
{
Destroy(temp);
}
polyColliders.Clear();
foreach(var temp in meshObjs)
{
Destroy(temp);
}
meshObjs.Clear();
}
//每次将摄像机调回2D正交视角时调用,切割3D物体
public void PlaneSliceNew()
{
Collider[] colliders = Physics.OverlapBox(transform.position, new Vector3(200, 200, 0.01f), Quaternion.identity, ~LayerMask.GetMask("Solid"));
foreach (Collider c in colliders)
{
//使用EzySlice的Slice方法,切割物体
SlicedHull hull = c.gameObject.Slice(transform.position, transform.up);
if (hull != null)
{
//使用EzySlice的CreateLowerHull方法,获取到LowerHull。按F12跳转到插件源代码,将此方法中的transform.localPosition改为transform.position
GameObject lower = hull.CreateLowerHull(c.gameObject, hullMat);
slicedHulls.Add(lower.gameObject);
lower.GetComponent<MeshRenderer>().material = transMat;
//获取到LowerHull所有顶点vertices数据
Vector3[] vertices = hull.lowerHull.vertices;
for (int i=0; i<vertices.Length; i++)
{
vertices[i] = lower.transform.TransformPoint(vertices[i]);//将坐标从局部坐标系转换到世界坐标系,以便后续PolygonCollider2D的正确生成
}
int[] indices = hull.lowerHull.GetIndices(1);//获取到LowerHull序号为[1]的submesh所有顶点vertices数据,此submesh就是切开的截面
//生成polygon collider2d
DrawPolygon(indices, vertices);
//生成截面mesh
GameObject twoDHull = Instantiate(paintedPlace);
DrawMesh(indices, vertices, twoDHull, c.gameObject);
lower.gameObject.layer = LayerMask.NameToLayer("Obstacle");
twoDHull.transform.position = new Vector3(twoDHull.transform.position.x, twoDHull.transform.position.y, -0.01f);
}
}
}
public void DrawPolygon(int[] indices, Vector3[] vertices)
{
colliderPoints.Clear();
GameObject obj = new GameObject();
obj.transform.name = "PolygonColliderArea";
obj.transform.SetParent(null);
obj.tag = "LowerHull";
int start = indices[0];
for(int i = start; i < vertices.Length; i++)
{
colliderPoints.Add(obj.transform.InverseTransformVector(vertices[i]));
}
var pc = obj.AddComponent<PolygonCollider2D>();
pc.points = colliderPoints.ToArray();
polyColliders.Add(obj);
}
void DrawMesh(int[] indices, Vector3[] vertices, GameObject meshObj)
{
int start = indices[0];
for (int i = start; i < vertices.Length; i++)
{
vertsGenerated.Add(vertices[i]);
}
for (int i = 0; i <= indices.Length - 3; i += 3)
{
indiceGenerated.AddRange(new int[] { indices[i] - start, indices[i + 1] - start, indices[i + 2] - start });
}
//画一个mesh
Mesh mesh = new Mesh();
mesh.vertices = vertsGenerated.ToArray();
mesh.triangles = indiceGenerated.ToArray();
mesh.RecalculateNormals();
mesh.RecalculateBounds();
meshObj.GetComponent<MeshFilter>().mesh = mesh;
meshObj.GetComponent<MeshRenderer>().material = hullMat;
meshObjs.Add(meshObj);
vertsGenerated.Clear();
indiceGenerated.Clear();
}
}
这一块就是最核心的代码,能够实现切割2D物体并正确生成2D物理检测的碰撞体。完整的实现,需要在挂在摄像机下的脚本中调用,即在摄像机切换正交\透视视角时调用。