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 = 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)!

  推荐阅读

  2D游戏开发套件指南(上)

  2D游戏开发套件指南(下)

  Cinemachine在2D游戏中的开发小技巧

  Unity中使用Tilemap快速创建2D游戏世界

  使用Unity 2D实现经典的扫雷游戏(上)

  使用Unity 2D实现经典的扫雷游戏(下)

  WebAssembly基准测试:加载时间和性能

  Unity Q&A 第5期 :Scriptable Build Pipeline及构建Assetbundle

  官方活动

  直播预告 | Unity面部捕捉解决方案课程(第一期)

  9月26日晚8点,Unity技术直播课程将带来Facial AR Remote面部捕捉解决方案系列课程的第一期,感兴趣的朋友赶紧预约吧![了解详情...]

  直播课程:Facial AR Remote面部捕捉解决方案课程(第一期)

  直播地址:https://connect.unity.com/events/unitychina-facialar

  Unity官方教师培训报名火热进行中

  Unity将在10月22-26日,举办为期5天的专业的Unity官方教师培训课程,诚邀广大教师与Unity一同学习分享最新技术![了解详情...]

  报名地址:

  https://connect.unity.com/events/2018jiaoshipeixun

  Unity开学季|重磅教育活动来袭

  九月开学季,Unity将在南京、南昌、上海开展重磅教育活动,欢迎教育领域的领导、专家、教师团队莅临交流![了解详情...]

  优惠活动|Unity订阅新起航,开启您的创作之旅

  现在访问Unity在线商店(store.unity.com),成功订阅Unity Pro专业版、Unity Plus加强版即可享受全新增值服务组合。11月18日之前订阅,更有指定插件资源限时赠送。[了解详情...]

  活动地址:https://store.unity.com/cn

  点击“阅读原文”访问Unity官方中文论坛

查看原文 >>
相关文章