编程 | 切割3D物体代码

一个二维角色如果生活在三维世界里会是什么样子的?

今天带来的作品是一款名叫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物理检测的碰撞体。完整的实现,需要在挂在摄像机下的脚本中调用,即在摄像机切换正交\透视视角时调用。

发表评论

%d