文章目录
一. 安装SpriteShape
打开Unity菜单Window-Package Manager
因为我用的Unity版本是2019.2.14f1
,2D SpriteShape
插件还属于preview
阶段,所以我要打开Show preview packages
,
搜索spriteshape
即可看到插件,点击install
即可。
安装后,还可以导入工程例子和拓展资源
二. 创建Sprite Shape Profile
Sprite Shape Profile
是用于配置特定形状类型的所有选项的资源。 通过它我们可以分配要使用的精灵,并告诉Sprite Shape
如何渲染它们。
在Project新建一个目录SpriteShapeRes
,然后在这个目录上右键菜单Create-Sprite Shape Profile
。
可以看到有两个子菜单:Open Shape
和Closed Shape
。
这两个看名字就可以理解了,如下图:
比如这里创建一个Open Shape
的profile
文件
为其添加一个精灵图
三. 制作一个Open Shape地形
在场景中创建一个空物体
给这个物体添加Sprite Shape Controller
组件
赋值Profile
属性
此时即可看到场景中出现了一个闭合的形状
把Is Open Ended
勾选上,就不是闭环的了
四. 编辑地形
点击形状编辑按钮,即可在场景中对形状进行编辑
这里我们发现路径是一段一段的,我们想要衔接处是那种圆滑过渡,只需把每个点的Tangent Mode
改成Continuous
即可
这时发现衔接是圆滑的,但是图片是断开的,这是因为我们没有设置border
。
选中精灵图,点击Sprite Editor
设置左右两边的border
,最后点击Apply
此时场景中的就得到了我们想要的效果了
五. 添加地形碰撞
想要角色可以在地形上行走,我们还要给地形添加碰撞。
添加Polygon Collider 2D
(2D多边形碰撞),此时看到的碰撞是这样的
可以看到碰撞的形状是默认的一个五角星,我们不可能挨个点挨个点去编辑这个碰撞提让它和路面形状一致。怎么办呢,通过脚本来搞定。
挂上Geometry Collider
脚本(这个脚本是上面Extras工具包中的,代码见文章末尾),然后勾选一下Update Collider
碰撞体形状对了,但是碰撞体的边太多了,可以通过Composite Collider 2D
组件来合并。
挂上Composite Collider 2D
组件,然后勾选Polygon Collider 2D
的Used By Composite
碰撞体合并成一个,得到了我们想要的效果
还可以调节Offset
,可以调整碰撞的整体偏移,
六. Geometry Collider代码
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Experimental.U2D;
using UnityEngine.U2D;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteAlways]
public class GeometryCollider : MonoBehaviour
{
[SerializeField]
bool m_UpdateCollider = false;
int m_HashCode = 0;
void Start()
{
}
// Update is called once per frame
void Update()
{
if (m_UpdateCollider)
Bake(gameObject, false);
}
static public void Bake(GameObject go, bool forced)
{
var spriteShapeController = go.GetComponent<SpriteShapeController>();
var spriteShapeRenderer = go.GetComponent<SpriteShapeRenderer>();
var polyCollider = go.GetComponent<PolygonCollider2D>();
var geometryCollider = go.GetComponent<GeometryCollider>();
if (spriteShapeController != null && polyCollider != null)
{
var spline = spriteShapeController.spline;
if (geometryCollider != null)
{
int splineHashCode = spline.GetHashCode();
if (splineHashCode == geometryCollider.m_HashCode && !forced)
return;
geometryCollider.m_HashCode = splineHashCode;
}
NativeArray<ushort> indexArray;
NativeSlice<Vector3> posArray;
NativeSlice<Vector2> uv0Array;
NativeArray<SpriteShapeSegment> geomArray;
spriteShapeRenderer.GetChannels(65536, out indexArray, out posArray, out uv0Array);
geomArray = spriteShapeRenderer.GetSegments(spline.GetPointCount() * 8);
NativeArray<ushort> indexArrayLocal = new NativeArray<ushort>(indexArray.Length, Allocator.Temp);
List<Vector2> points = new List<Vector2>();
int indexCount = 0, vertexCount = 0, counter = 0;
for (int u = 0; u < geomArray.Length; ++u)
{
if (geomArray[u].indexCount > 0)
{
for (int i = 0; i < geomArray[u].indexCount; ++i)
{
indexArrayLocal[counter] = (ushort)(indexArray[counter] + vertexCount);
counter++;
}
vertexCount += geomArray[u].vertexCount;
indexCount += geomArray[u].indexCount;
}
}
Debug.Log(go.name + " : " + counter);
OuterEdges(polyCollider, indexArrayLocal, posArray, indexCount);
}
}
// Generate the outer edges from the Renderer mesh. Based on code from www.h3xed.com
static void OuterEdges(PolygonCollider2D polygonCollider, NativeArray<ushort> triangles, NativeSlice<Vector3> vertices, int triangleCount)
{
// Get just the outer edges from the mesh's triangles (ignore or remove any shared edges)
Dictionary<string, KeyValuePair<int, int>> edges = new Dictionary<string, KeyValuePair<int, int>>();
for (int i = 0; i < triangleCount; i += 3)
{
for (int e = 0; e < 3; e++)
{
int vert1 = triangles[i + e];
int vert2 = triangles[i + e + 1 > i + 2 ? i : i + e + 1];
string edge = Mathf.Min(vert1, vert2) + ":" + Mathf.Max(vert1, vert2);
if (edges.ContainsKey(edge))
{
edges.Remove(edge);
}
else
{
edges.Add(edge, new KeyValuePair<int, int>(vert1, vert2));
}
}
}
// Create edge lookup (Key is first vertex, Value is second vertex, of each edge)
Dictionary<int, int> lookup = new Dictionary<int, int>();
foreach (KeyValuePair<int, int> edge in edges.Values)
{
if (lookup.ContainsKey(edge.Key) == false)
{
lookup.Add(edge.Key, edge.Value);
}
}
// Create empty polygon collider
polygonCollider.pathCount = 0;
// Loop through edge vertices in order
int startVert = 0;
int nextVert = startVert;
int highestVert = startVert;
List<Vector2> colliderPath = new List<Vector2>();
while (true)
{
// Add vertex to collider path
colliderPath.Add(vertices[nextVert]);
// Get next vertex
nextVert = lookup[nextVert];
// Store highest vertex (to know what shape to move to next)
if (nextVert > highestVert)
{
highestVert = nextVert;
}
// Shape complete
if (nextVert == startVert)
{
// Add path to polygon collider
polygonCollider.pathCount++;
polygonCollider.SetPath(polygonCollider.pathCount - 1, colliderPath.ToArray());
colliderPath.Clear();
// Go to next shape if one exists
if (lookup.ContainsKey(highestVert + 1))
{
// Set starting and next vertices
startVert = highestVert + 1;
nextVert = startVert;
// Continue to next loop
continue;
}
// No more verts
break;
}
}
}
#if UNITY_EDITOR
[MenuItem("SpriteShape/Generate Geometry Collider", false, 358)]
public static void BakeGeometryCollider()
{
if (Selection.activeGameObject != null)
GeometryCollider.Bake(Selection.activeGameObject, true);
}
#endif
}