Notifications
Article
使用Sprite Shape构建2D世界
Updated 2 months ago
65
0
Sprite Shape(精灵形状)可以帮助开发者在Unity中创建出各种形式的丰富2D环境,并通过直观的可视化工作流程根据需要装饰环境。该工具基于设定的角度范围集合,通过沿着样条曲线路径动态地平铺精灵。
本文将介绍如何使用Sprite Shape,为形状制作碰撞体并使用一些脚本构建特性功能环境。
请注意:Sprite Shape目前仍处于预览版阶段,将在未来发布可用于正式制作的版本。这意味着本文中介绍的工作流程和编程API可能会在之后版本中发生变化。

安装Sprite Shape

如果你使用Unity 2018.1或更高版本,可以通过使用资源包管理器Package Manager为项目安装Sprite Shape。
点击Window > Package Manager,然后选择All标签页找到2D Sprite Shape资源包,并将它添加到项目中。

创建形状

1.Sprite Shape配置文件

Sprite Shape通过在场景中创建的样条路径作为游戏对象来平铺精灵。要创建路径,首先必须设置Sprite Shape Profile(精灵形状配置),它用于定义和存储关于特定类型形状的信息。
在Sprite Shape Profile中,我们将分配需要使用的精灵,并告诉Sprite Shape如何渲染它们。例如:我们可以根据形状一部分所面对的方向,形状是否拥有填充纹理等来配置要显示的精灵。
现在创建配置文件。在项目视图的Assets文件夹窗口单击右键,选择Create > Sprite Shape Profile,其中有三种配置文件类型:Empty、Strip和Shape。不同类型的区别仅在于它们预设角度范围的数量。
我们先制作Strip配置文件,接下来处理角度范围。我们可以在检视窗口编辑配置文件。观察Angle Ranges下的圆圈会发现,它已被完全填充,这意味着它有一个预定义的角度范围。当曲线面向该特定方向时,角度范围决定渲染该路径的哪些精灵。角度范围覆盖整个圆圈表示同样的精灵会一直显示。
在检视窗口的Sprites属性下,可以点击“+”或“-”按钮添加或删除Angle Range中的新精灵,现在让我们给该形状添加精灵吧。

请注意:本示例及之后示例中,我们都将使用2D Game Kit中免费提供的精灵。

2.Sprite Shape游戏对象

我们已设置好配置文件,可以使用该配置文件创建Sprite Shape。为了在新建形状上自动使用该配置文件,请在Assets文件夹窗口中选中该文件,右键单击层级窗口,选择2D Object > Sprite Shape。
如果你不小心创建空白Sprite Shape或是打算使用不同的配置文件,可以在Sprite Shape Controller组件中指定或修改配置文件。
该游戏对象的另一组件是Sprite Shape Renderer,其作用类似Sprite Renderer组件,允许让我们修改精灵的材质、颜色和图层顺序。

3.编辑样条曲线(Spline)

现在可以在Sprite Shape Controller选项中点击Edit Spline来编辑形状。启用该功能后,我们可以在场景中重新安排、添加和删除样条曲线上的节点。添加新节点时,只要左键单击样条曲线上的位置即可。删除节点时,先选择节点,然后按下Delete删除。

现在介绍Sprite Shape Controller组件中的Point Modes。目前我们使用线性点模式(Linear point mode),这意味着节点不会形成曲线。如果切换为其它模式,例如镜像模式(Mirrored mode),选中节点时,将看到节点带有二条切线,移动切线时可以修改贝塞尔曲线的形状。
第三个模式是非镜像模式(Non-Mirrored mode)。启用该模式会解除二条切线的链接关系,在调整其中一条切线的时候不会影响另一条切线。

4.使用精灵编辑器

如果我们希望以特定方式平铺精灵,则需要手动调整精灵。在上面的示例中,由于使用了桥式精灵,会得到由桥体各部分组成的形状。如果我们想把它做成一条长的桥梁,要怎么做?
我们可以使用精灵编辑器(Sprite Editor),了解精灵编辑器的特定功能将更高效地使用Sprite Shape,并根据需要轻松调整精灵。
下面视频将介绍如何使用精灵编辑器。
首先编辑桥体精灵。选中该对象,在检视窗口中找到Sprite Editor按钮,点击该按钮会出现Sprite Editor窗口。
我们主要使用到的精灵编辑器功能是精灵周围的四个绿色控制点,以及右下角Sprite窗口的Border设置。使用Border设置可以告诉Sprite Shape要平铺形状的哪个部分以及要使用哪部分作为边界精灵,这些精灵只会在路径的开始和终点或角度边角进行渲染。我们可以调整桥体精灵的左右边界,确保只平铺桥体的中间部分。
另一个可以使用的设置是精灵轴心点(Pivot point)。轴心决定如何通过样条曲线渲染精灵。目前桥体精灵的轴心点在中心位置,这会使样条曲线在中心位置经过轴心点。如果将轴心点设在桥体顶部,该精灵会在样条曲线下渲染。
该方法非常实用,能够调整为形状自动生成碰撞体的相对位置,以及能够使用Sprite Shape更精确地装饰环境。

5.精灵变体

在勾勒环境时,Sprite Shape允许为每个角度范围指定多个精灵,并在形状上切换这些精灵。该功能可以为形状添加多样化视觉效果,或创建道具和装饰“画笔”。
首先准备了一个简单的悬挂苔藓配置文件,带有二个指定给单个角度范围的精灵。我们可以使用该配置文件给场景中的形状添加装饰苔藓。
得到满意的形状后,我们可以修改样条曲线上各部分需要渲染的精灵变体。选择要修改部分的起始点,设置Sprite Shape Controller组件上的Sprite Index为其它精灵。

添加碰撞

Sprite Shape能为形状自动生成碰撞体,我们可以手动调整自动生成的碰撞体。
对于目前的开放式Sprite Shape,可以使用Edge Collider 2D组件来处理。在检视窗口将该组件添加到Sprite Shape游戏对象。此时Sprite Shape Controller组件中会出现Update Collider设置,勾选该设置后,碰撞体会根据形状实时进行调整。
处理好形状后,如果我们打算手动修改碰撞体,请首先取消勾选Update Collider,否则该设置会重写修改内容。然后在Edge Collider 2D组件下,点击Edit Collider,然后根据需要调整碰撞体。
我们可以将游戏角色放入场景中测试新碰撞体。

封闭式形状

现在我们已经熟悉了Sprite Shape和Strip配置文件的基础知识,下面让我们了解如何使用多个角度范围创建封闭式形状。
首先我们简要回顾之前的二个Sprite Shape Profile。Empty配置文件没有任何预设角度范围,Shape配置文件有八个预设角度范围。我们的形状只需要四个角度范围,所以让我们使用Empty配置文件,学习如何添加定义角度范围。

1.定义角度范围

选中新配置文件,创建角度范围时,我们可以点击预览圆圈的空白位置或点击下方的Create Range按钮进行创建。
一旦创建好角度范围后,我们可以点击选中该角度范围,通过数值定义起始点或终点,或是拖拽范围上的图示到理想位置。
我们需要四个角度范围,所以设置每个角度范围为90度角。这一步完成后,可以分别为每个角度范围分配一个精灵,让形状看起来是个完整地形。
接下来,使用该配置文件来在场景中创建新形状。

由于定义了角度范围,形状会自动成为封闭式形状。我们可以随时在场景中选择形状,启用或禁用Sprite Shape Controller组件中的Open Ended复选框,从而修改形状类型。
另外我们的新形状有些问题:首先它没有填充纹理,其次形状的边角不会渲染任何精灵。我们可以在该Sprite Shape Profile中轻松修改这些内容。

2.添加填充纹理

为了填充形状的内部,我们必须在配置文件中加入填充纹理。这一步可以在检视窗口中的Fill选项下完成。我们还可以将它改为理想分辨率。
下面将使用2D Game Kit中的一个平铺作为纹理。

关于填充纹理需要了解几点内容。首先填充纹理必须作为独立文件导入,不能作为精灵图集的一部分导入。此外在导入设置中,必须将Wrap Mode设为Repeat。如果没有正确设置Wrap Mode,该纹理会产生瑕疵。

3.使用边角精灵

使用填充纹理后,形状的效果更好了,但我们还是缺少形状边角部分的精灵。Sprite Shape中的选项最多可以添加八个独立边角精灵,每个精灵对应形状的特定位置。
边角精灵可以在Sprite Shape Profile中的Corners部分指定。本示例中,我们将指定八个边角精灵中的六个精灵。形状使用的边角精灵数量可能会有所不同,具体取决于创建路径类型。
指定好边角精灵后,需要告诉形状这些边角精灵要用在什么位置。进入Edit Spline模式,在此选择形状上的独立边角节点,将节点的Corner Mode设为Automatic。

对样条曲线上的节点附加对象

我们介绍了Sprite Shape界面和工作流程的大部分内容,现在还可以通过脚本扩展该功能。
下面是一个示例脚本,该脚本能在场景中将对象附加到样条曲线的节点上。
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.U2D;
[ExecuteInEditMode]
public class NodeAttach : MonoBehaviour
{
public SpriteShapeController spriteShapeController;
public int index;
public bool useNormals = false;
public bool runtimeUpdate = false;
[Header("Offset")]
public float yOffset = 0.0f;
public bool localOffset = false;
private Spline spline;
private int lastSpritePointCount;
private bool lastUseNormals;
private Vector3 lastPosition;

void Awake()
{
spline = spriteShapeController.spline;
}

void Update()
{
if (!EditorApplication.isPlaying || runtimeUpdate)
{
spline = spriteShapeController.spline;
if ((spline.GetPointCount() != 0) && (lastSpritePointCount != 0))
{
index = Mathf.Clamp(index, 0, spline.GetPointCount() - 1);
if (spline.GetPointCount() != lastSpritePointCount)
{
if (spline.GetPosition(index) != lastPosition)
{
index += spline.GetPointCount() - lastSpritePointCount;
}
}
if ((index <= spline.GetPointCount() - 1) && (index >= 0))
{
if (useNormals)
{
if (spline.GetTangentMode(index) != ShapeTangentMode.Linear)
{
Vector3 lt = Vector3.Normalize(spline.GetLeftTangent(index) - spline.GetRightTangent(index));
Vector3 rt = Vector3.Normalize(spline.GetLeftTangent(index) - spline.GetRightTangent(index));
float a = Angle(Vector3.left, lt);
float b = Angle(lt, rt);
float c = a + (b * 0.5f);
if (b > 0)
c = (180 + c);
transform.rotation = Quaternion.Euler(0, 0, c);
}
}
else
{
transform.rotation = Quaternion.Euler(0, 0, 0);
}
Vector3 offsetVector;
if (localOffset)
{
offsetVector = (Vector3)Rotate(Vector2.up, transform.localEulerAngles.z) * yOffset;
}
else
{
offsetVector = Vector2.up * yOffset;
}
transform.position = spriteShapeController.transform.position + spline.GetPosition(index) + offsetVector;
lastPosition = spline.GetPosition(index);
}
}
}
lastSpritePointCount = spline.GetPointCount();
}
private float Angle(Vector3 a, Vector3 b)
{
float dot = Vector3.Dot(a, b);
float det = (a.x * b.y) - (b.x * a.y);
return Mathf.Atan2(det, dot) * Mathf.Rad2Deg;
}
private Vector2 Rotate(Vector2 v, float degrees)
{
float radians = degrees * Mathf.Deg2Rad;
float sin = Mathf.Sin(radians);
float cos = Mathf.Cos(radians);
float tx = v.x;
float ty = v.y;
return new Vector2(cos * tx - sin * ty, sin * tx + cos * ty);

当移动锚点节点或旋转其切线时,对象的Transform也会变化。
像这样的脚本可以用于创建动态环境,或让环境对玩家输入做出反应。它也能够让我们更轻松构建关卡原型,而且不必重新定位独立元素。
Sprite Shape脚本API仍处于开发阶段,如果你想要尝试使用Sprite Shape,可以通过Visual Studio Solution Explorer的Assembly Reference访问仍在编写的API文档,多数API与Unity.2D.SpriteShape.Runtime相关。

小结

后续我们还会介绍Sprite Shape的功能和UI,如果项目带有Sprite Shape资源包,你可以在项目中访问脚本API。
我们期待开发者能够使用Sprite Shape创作出精彩的作品,有关于此功能的反馈欢迎访问Unity官方中文论坛(UnityChina.cn)!

Unity China
379
Comments