按键精灵 识别图片形状

Sprite Shape gives you the freedom to create rich free-form 2D environments straight in Unity and decorate them as you see fit, with a visual and intuitive workflow. The tool works by dynamically tiling sprites along spline paths based on a given set of angle ranges. Read on to learn how you can work with Sprite Shape, get colliders for your shapes, and use a little bit of scripting to build on top of the features’ functionality.

Sprite Shape使您可以自由地在Unity中直接创建丰富的自由格式2D环境,并通过可视和直观的工作流程随意装饰它们。 该工具通过根据一组给定的角度范围沿样条曲线路径动态平铺精灵来工作。 继续阅读以了解如何使用Sprite Shape,如何为形状获取碰撞器,并使用一些脚本来构建功能的基础。

Sprite Shape is still in preview, with a production release planned at a later date. That means that the workflow and the coding API described in this article may change in the future. However, that doesn’t stop you from trying it now!

Sprite Shape仍在预览中,计划在以后发布产品。 这意味着本文中描述的工作流程和编码API将来可能会更改。 但是,这并不能阻止您立即尝试!

安装精灵形状 (Installing Sprite Shape)

If you’re using Unity 2018.1 or newer, you can get Sprite Shape for your project by using the Package Manager.

如果您使用的是Unity 2018.1或更高版本,则可以使用Package Manager为项目获取Sprite Shape。

Go to Window > Package Manager, then select the All tab. From there, you’ll be able to find the 2D Sprite Shape package and add it to your project.

转到窗口>软件包管理器 ,然后选择全部标签。 从那里,您将能够找到2D Sprite Shape包并将其添加到您的项目中。

创建形状 (Creating a Shape)

精灵形状轮廓 (The Sprite Shape Profile)

Sprite Shape works by tiling sprites along spline paths created in the scene as game objects. However, to start creating paths, we must first set up an asset called the Sprite Shape Profile. It is used to define and store information about a particular type of shape. Within this asset, we assign the sprites we wish to use and tell Sprite Shape how they should be rendered. For example, we can configure which sprites are to be displayed based on the direction a part of our shape is facing, whether the shape has a fill texture, and so on.

Sprite Shape通过沿场景中创建的作为游戏对象的样条路径平铺精灵来工作。 但是,要开始创建路径,我们必须首先设置一个名为Sprite Shape Profile的资源。 它用于定义和存储有关特定形状的信息。 在此资源中,我们分配希望使用的精灵,并告诉Sprite Shape应该如何渲染。 例如,我们可以根据形状的一部分面向的方向,形状是否具有填充纹理等来配置要显示哪些精灵。

Let’s create a profile right now! To do so, right-click in the Assets window of your project and go to Create > Sprite Shape Profile. You will see that there are three types of Profiles available to you: Empty, Strip, and Shape. The only difference between these profiles is the number of pre-set angle ranges they come with. Let’s start by making a Strip profile – we’ll get to angle ranges in a second.

让我们现在创建一个配置文件! 为此,在项目的Assets窗口中右键单击,然后转到Create> Sprite Shape Profile 。 您将看到有三种可用的轮廓: Empty,StripShape 。 这些轮廓之间的唯一区别是它们附带的预设角度范围的数量。 让我们从制作Strip轮廓开始-我们将在一秒钟内达到角度范围。

We can now edit our Profile in the Inspector window. If you take a look at the Angle Ranges circle, you notice that it’s completely filled, meaning that it has one pre-defined angle range. An angle range determines which sprites are rendered along the path when your curve is facing that particular direction. Having one angle range that covers the full circle means that the same sprites will be displayed at all times.

现在,我们可以在“检查器”窗口中编辑配置文件。 如果查看“角度范围”圆,您会注意到它已完全填充,这意味着它具有一个预定义的角度范围。 角度范围确定当曲线面向特定方向时沿路径渲染哪些精灵。 具有一个覆盖整个圆的角度范围意味着将始终显示相同的精灵。

Under Sprites in the Inspector window, we can add or delete new sprites to the Angle Range by using the ‘+’ and ‘-’ buttons below. Let’s go ahead and add our first sprite to this shape!  For this and all future examples, I’m going to be using sprites available for free from our 2D Game Kit.

在“检查器”窗口中的“精灵”下,我们可以使用下面的“ +”和“-”按钮在“角度范围”中添加或删除新的精灵。 让我们继续,将我们的第一个精灵添加到该形状中! 对于此示例以及所有以后的示例,我将使用2D游戏工具包中免费提供的精灵。

雪碧形状游戏对象 (The Sprite Shape Game Object)

Now that we have a profile set up, we can start creating shapes from it. To automatically use the profile in our new shape, make sure you have the profile selected in the Assets window; then right-click in the Hierarchy window and go to 2D Object > Sprite Shape.

现在我们已经设置了轮廓,我们可以开始从中创建形状。 要以我们的新形状自动使用配置文件,请确保在“资产”窗口中选择了配置文件; 然后在“层次结构”窗口中右键单击,然后转到2D对象>精灵形状

Note: if you’ve accidentally created an empty Sprite Shape or you would like to use a different profile, you can assign or change it within the Sprite Shape Controller component:

注意:如果您不小心创建了空的Sprite Shape或想要使用其他配置文件,则可以在Sprite Shape Controller组件中进行分配或更改:

The other component of the game object is the Sprite Shape Renderer, which acts similarly to a regular Sprite Renderer, allowing you to change the material, color, and layer order of the sprite.

游戏对象的另一个组件是Sprite Shape Renderer,其作用类似于常规的Sprite Renderer,允许您更改Sprite的材质,颜色和层顺序。

编辑样条线 (Editing the spline)

You can now start editing your shape by clicking Edit Spline in the Sprite Shape Controller options. Once you’ve got that enabled, within your scene you will be able to rearrange, add, and delete nodes on your spline. To add a new node, simply left-click anywhere on the spline. To delete a node, select it and press Delete.

现在,您可以通过单击Sprite Shape Controller选项中的Edit Spline开始编辑形状。 启用该功能后,您就可以在场景中重新排列,添加和删除样条线上的节点。 要添加新节点,只需在样条线上的任意位置单击鼠标左键。 要删除节点,请选择它,然后按Delete键。

Let’s talk a little bit about Point Modes available in the Sprite Shape Controller. Currently, we are in Linear point mode. This means that no curve is formed through our node. However, if we switch to one of the other two modes, such as the Mirrored mode, with a node selected, we will see that the node now has two tangents, and when we move them around, we can change the shape of the Bezier curve:

让我们来谈谈Sprite Shape Controller中可用的点模式。 目前,我们处于线性点模式。 这意味着没有曲线通过我们的节点形成。 但是,如果切换到其他两种模式之一(例如“镜像”模式)并选择了一个节点,我们将看到该节点现在具有两个切线,并且在四处移动它们时,我们可以更改Bezier的形状曲线:

The last mode is called Non-Mirrored mode. Enabling it unlinks the two tangents from each other, allowing you to adjust one at a time without affecting the other.

最后一种模式称为非镜像模式。 启用它可以使两个切线彼此取消链接,从而允许您一次调整一个切线而不影响另一个。

使用精灵编辑器 (Using the Sprite Editor)

Sometimes our sprites will need to be manually adjusted if we want them to be tiled in a particular way. In the examples above, you can see that since we are using a bridge sprite, we’re getting a shape that consists of bridge segments. What if we wanted to make it look like one long bridge strip instead?

有时,如果我们希望以特定方式平铺精灵,则需要手动调整它们。 在上面的示例中,您可以看到,由于我们使用的是桥精灵,因此我们得到的形状由桥段组成。 如果我们想使它看起来像一个长的桥条怎么办?

Fear not! The Sprite Editor is here to help. This blog post will not cover it in depth, but there is an excellent video tutorial on it on Unity’s YouTube channel. That said, knowing about certain parts of the Sprite Editor will allow you to use Sprite Shape more efficiently, and adjust sprites to suit your needs with ease.

不要怕! Sprite编辑器在这里为您提供帮助。 这篇博客文章不会深入介绍它,但是Unity的YouTube频道上有一个很棒的视频教程 。 就是说,了解Sprite Editor的某些部分将使您能够更有效地使用Sprite Shape,并轻松调整Sprite以适应您的需求。

Let’s edit our bridge sprite! With it selected, in the Inspector window, you will be able to find the Sprite Editor button. Click it, and the Sprite Editor window will be brought up.

让我们编辑桥精灵! 选中它,在“检查器”窗口中,您将能够找到“精灵编辑器”按钮。 单击它,将显示“ 精灵编辑器”窗口。

The main things of interest to us about the sprite editor will be the four green control points around the sprite, as well as the border settings in the Sprite window at the bottom right. By using borders, we can tell Sprite Shape which part of the shape we want to be tiled, and which parts we want to act as our border sprites – which will only be rendered at the start and end nodes of our path, or at angled corners. We can adjust our bridge sprite’s left and right borders to make sure only the middle section of the bridge is tiled.

关于精灵编辑器,我们感兴趣的主要内容是精灵周围的四个绿色控制点,以及右下方“精灵”窗口中的边框设置。 通过使用边界,我们可以告诉Sprite Shape我们要平铺形状的哪个部分,以及我们要用作边界sprite的哪些部分–仅在路径的起点和终点或成角度地呈现角落。 我们可以调整桥精灵的左右边界,以确保仅对桥的中间部分进行平铺。

One other setting that can be of use is the Pivot point of the sprite. The Pivot determines how the sprite is rendered relative to the spline. Currently, our bridge sprite has a pivot in the center, which makes the spline pass it right in the middle. If we set it to be at the top of the bridge, the sprite will render below the spline. This can be useful for adjusting the relative position of auto-generated colliders for your shape which we’ll cover in a bit, as well as for more precision when using Sprite Shape to decorate an environment.

可以使用的另一种设置是精灵的枢轴点。 枢轴确定如何相对于样条线渲染精灵。 目前,我们的桥精灵在中心具有一个枢轴,这使该样条曲线从中间穿过。 如果将其设置在桥的顶部,则精灵将在样条线下方渲染。 这对于调整将自动覆盖的形状的自动碰撞器的相对位置很有用,我们将稍作介绍,并在使用Sprite Shape装饰环境时提高精度。

精灵变化 (Sprite Variations)

On the note of decorating environments, Sprite Shape also allows you to assign more than one sprite per angle range, and switch between them on your shape. This can be used for adding visual variety to your shapes, or in creating prop and decoration ‘brushes’.

关于装饰环境,“精灵形状”还允许您为每个角度范围分配一个以上的精灵,并在形状上在它们之间切换。 这可用于为形状增加视觉效果,或用于创建道具和装饰“画笔”。

I’ve prepared a simple hanging moss profile with two sprites assigned to a single angle range. I can use this profile to add decorative moss to one of the other shapes in my scene.

我准备了一个简单的悬挂式苔藓轮廓,其中两个精灵分配了一个角度范围。 我可以使用此配置文件将装饰性苔藓添加到场景中的其他形状之一。

Once I have a shape I’m happy with, I can now change which of the sprite variants is rendered per each segment of the spline. To do so, I will select a starting node of the segment I wish to change. I can now set the Sprite Index in the Sprite Shape Controller to that of my other sprite.

拥有满意的形状后,现在就可以更改每个样条线段渲染哪个Sprite变体。 为此,我将选择要更改的段的起始节点。 现在,我可以将“精灵形状控制器”中的“精灵索引”设置为其他精灵的索引。

增加碰撞 (Adding collision)

One awesome feature that Sprite Shape comes with is the ability to auto-generate colliders for your shapes, also allowing you to manually adjust them later if required.

Sprite Shape附带的一项令人敬畏的功能是能够为您的形状自动生成碰撞器,还允许您以后根据需要手动调整它们。

For an open-ended Sprite Shape, which is what we have right now, we can use an Edge Collider 2D component. You can add it to your Sprite Shape game object from the Inspector window. You will notice that a new checkbox called Update Collider has now appeared in the Sprite Shape Controller. Checking it will start adjusting the collider to your shape in real-time:

对于我们现在拥有的开放式Sprite Shape,我们可以使用Edge Collider 2D组件。 您可以从“检查器”窗口中将其添加到“精灵形状”游戏对象中。 您会注意到,一个名为Update Collider的新复选框现在已经出现在Sprite Shape Controller中。 对其进行检查将开始实时根据您的形状调整对撞机:

Once you’ve perfected your shape, you might want to make some manual changes. To do so, make sure you untick Update Collider first – otherwise, it will keep rewriting your changes – and then under Edge Collider 2D, go to Edit Collider. You can now adjust the collider however you want!

完善形状后,您可能需要进行一些手动更改。 为此,请确保先取消选中Update Collider,否则它将继续重写您的更改,然后在Edge Collider 2D下转到Edit Collider。 现在,您可以根据需要调整对撞机!

We can test our new collider by putting a character in the scene:

我们可以通过在场景中放置角色来测试新的对撞机:

封闭形状 (Close-ended Shapes)

Now that you’re familiar with the basics of Sprite Shape and the Strip profile, let’s take a look at creating close-ended shapes with multiple angle ranges.

既然您已经熟悉了Sprite Shape和Strip轮廓的基础知识,让我们看一下创建具有多个角度范围的封闭形状。

Let’s briefly come back to the other two Sprite Shape Profiles we saw earlier. We have the Empty profile which comes with no pre-set angle ranges, and the Shape profile which comes with eight. For my shape, I only need four; so let’s start with an Empty profile and learn how to add our own angle ranges.

让我们简单地回到前面看到的其他两个Sprite Shape Profile。 我们有没有预置角度范围的Empty轮廓和带有八个的Shape轮廓。 对于我的身材,我只需要四个; 因此,让我们从“空”轮廓开始,学习如何添加我们自己的角度范围。

定义角度范围 (Defining Angle Ranges)

With the new profile selected, we can create a new angle range by either clicking on an empty spot on the preview circle or pressing the Create Range button below it. Once we have an angle range created, we can select it by clicking on it, and from here we can either define its start and end points numerically (see below) or by dragging the gizmos around the range to the desired position:

选择新的配置文件后,我们可以通过单击预览圆上的空白点或按下其下方的“创建范围”按钮来创建新的角度范围。 一旦创建了角度范围,就可以通过单击它来选择它,然后从这里我们可以用数字定义它的起点和终点(见下文),或者通过将范围周围的小控件拖动到所需位置:

I need four angle ranges, so I will make each one cover ninety degrees. Once that’s done, I can assign a sprite to each of the angle ranges separately and attempt to make my shape look like a complete piece of terrain. After that’s done, I can use my profile to create a new shape in the scene:

我需要四个角度范围,因此我将使每个角度都覆盖90度。 完成此操作后,我可以为每个角度范围分别指定一个精灵,并尝试使我的形状看起来像一块完整的地形。 完成之后,我可以使用个人资料在场景中创建新形状:

You will notice that the shape automatically becomes close-ended due to the nature of the angle ranges we have defined. You can change the type of your shape at any point by selecting it in the scene and enabling or disabling the Open Ended checkbox in the Sprite Shape Controller.

您会注意到,由于我们定义的角度范围的性质,形状会自动变为封闭的。 通过在场景中选择形状并启用或禁用“精灵形状控制器”中的“开放式”复选框,可以随时更改形状的类型。

You’ll also notice that several things are wrong with our new shape: first of all, it does not have a fill texture; and second, there are no sprites rendering at the corners of the shape. We can fix both of these things very easily back in our Sprite Shape Profile.

您还会注意到,我们的新形状有些问题:首先,它没有填充纹理; 其次,在形状的角点没有渲染精灵。 我们可以很容易地在Sprite Shape Profile中修复这两个问题。

添加填充纹理 (Adding a Fill Texture)

To fill the inside of our shape we have to include a fill texture in our Profile. This can be done under the Fill options in the Inspector window. We can also change it to the desired resolution. I will be using one of the tiles from the 2D Gamekit as my texture.

为了填充形状的内部,我们必须在Profile中包含填充纹理。 这可以在“检查器”窗口的“填充”选项下完成。 我们也可以将其更改为所需的分辨率。 我将使用2D Gamekit中的其中一张图块作为我的纹理。

There are several things to know about fill textures. First of all, they must be imported as individual files, and cannot be a part of a sprite atlas. Also, in the import settings, you have to make sure that the Wrap Mode is set to Repeat. If you fail to set the Wrap Mode correctly, the texture will create artifacts.

关于填充纹理,要了解几件事。 首先,它们必须作为单个文件导入,并且不能成为Sprite地图集的一部分。 另外,在导入设置中,必须确保将“包装模式”设置为“重复”。 如果无法正确设置“环绕模式”,则纹理将创建瑕疵。

使用角落精灵 (Using corner sprites)

Our shape is looking a lot better with a fill texture, but we are still missing sprites at the corners of our shape. Specifically for this purpose, Sprite Shape includes the option to add up to eight individual corner sprites, each of which corresponds to a specific location on the shape.

填充纹理使我们的形状看起来好多了,但是在形状的各个角落我们仍然缺少精灵。 专门为此目的,“精灵形状”包含一个选项,该选项最多可添加八个单独的角精灵,每个精灵对应于形状上的特定位置。

Corner sprites can be assigned within the Sprite Shape Profile, within the Corners section. For this example, I will assign six out of eight corner sprites. The amount that you use for your shapes may vary depending on the types of paths you plan to create.

可以在“圆角”部分的“子图形形状轮廓”中分配角精灵。 在此示例中,我将分配八个角精灵中的六个。 用于形状的数量可能会有所不同,具体取决于您计划创建的路径的类型。

Once the corner sprites are assigned, I need to tell the shape where they need to be used. To do so, I can go into Edit Spline mode, and from there select the individual corner nodes on my shape, and set their Corner Mode to Automatic:

分配了角精灵后,我需要告诉形状需要在哪里使用它们。 为此,我可以进入“编辑样条线”模式,然后从中选择形状上的各个角节点,并将其角模式设置为“自动”:

将对象附加到样条线上的节点 (Attaching objects to nodes on the spline)

We have now covered the majority of the Sprite Shape interface and workflow. You can also easily extend the feature with a little bit of scripting.

现在,我们已经涵盖了大多数Sprite Shape界面和工作流程。 您还可以通过少量脚本轻松扩展功能。

Here is an example of a script which will attach an object to a node on a select spline in the scene.

这是一个脚本示例,该脚本会将对象附加到场景中选择样条线上的节点上。

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); } } 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); } }

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

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);
}
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

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 ) ;
}
}

When the anchor node gets moved or its tangents get rotated, the object’s transform also changes:

当锚节点移动或其切线旋转时,对象的变换也会改变:

A script like this can be used to, for example, create dynamic environments, or make them react to player input. It can also make it easier for you to prototype levels without having to reposition individual elements.

这样的脚本可用于例如创建动态环境或使它们对玩家的输入做出React。 它还可以使您更容易为关卡创建原型,而不必重新定位各个元素。

The Sprite Shape scripting API is still under development. However, if you would like to experiment with Sprite Shape, you can access a work in progress version of the API documentation through the Assembly Reference through the Visual Studio Solution Explorer. Most of the API that will be relevant is under Unity.2D.SpriteShape.Runtime, however, please keep in mind that this is subject to change in the future.

Sprite Shape脚本API仍在开发中。 但是,如果您想尝试Sprite Shape,可以通过Visual Studio解决方案资源管理器中的“程序集引用”访问API文档的进行中版本。 大多数相关的API都位于Unity.2D.SpriteShape.Runtime下,但是请记住,将来可能会更改。

更多资源 (Further resources)

If you want to learn more about Sprite Shape, there are several resources you can currently make use of.

如果您想了解有关Sprite Shape的更多信息,目前可以使用多种资源。

Basic feature and UI documentation is available on the feature’s GitHub repository.

基本功能和UI文档可从该功能的GitHub存储库中获得 。

The scripting API can be accessed from within your project if it has the Sprite Shape package.

如果脚本API具有Sprite Shape包,则可以从您的项目中对其进行访问。

Finally, please do share your own Sprite Shape creations and feature feedback on The Sprite Shape forum. We are excited to see the awesome things you come up with!

最后,请在Sprite Shape论坛上共享您自己的Sprite Shape创作并提供功能反馈。 我们很高兴看到您提出的令人敬畏的事情!

翻译自: https://blogs.unity3d.com/2018/09/20/intro-to-2d-world-building-with-sprite-shape/

按键精灵 识别图片形状

按键精灵 识别图片形状_精灵形状的2D世界建筑物简介相关推荐

  1. python图片提取文字软件_python识别图片文字_图片文字识别软件,快速提取文字...

    图文识别是一种可以使你转换不同文档的技术,比如将扫描纸质文档,PDF文件或者数码相机拍摄的图片转换成可以编辑的文档. 假设你获得了一个纸质文件-比如,杂志.彩页或者你合作伙伴发给你的PDF合同.很明显 ...

  2. 易语言python识别图片验证码_图片识别-打码平台-打码网站-识别验证码-图鉴网络科技有限公司...

    Android脚本 Import "Cjson.lua" Import "ttddm.lua" Import "ShanHai.lua" / ...

  3. python动画精灵梦叶罗丽_精灵梦叶罗丽中出现过多少宝石盒子?灵犀阁的盒子造型最罕见...

    精灵梦叶罗丽第八季已经进入了暑期档的正式播出,不过每当想起要等待一周的时间才能看到下一集,也是有些心塞啊,所以说大家可以利用闲暇的时间回顾一下剧情,在细节中发现里面的一些有趣又好看好玩的事情或者是好看 ...

  4. python识别图片文字_使用百度文字识别API进行图片中文字的识别

    今天,为了满足我女朋友作业的需求,我使用Python制作了一个图片转文字的小应用. (当然,下面导入模块的问题我就不多说了,是非常简单的) 一. 申请百度通用文字识别接口. 1.先在百度AI开放平台注 ...

  5. python动画精灵梦叶罗丽_精灵梦叶罗丽:粉丝们误解最深的一位角色,文茜还能洗白么?...

    2020年搜狐动漫资讯 在<精灵梦叶罗丽>动漫当中角色的形象是对粉丝们影响比较大的,就像灵公主正是一位内自身的形象非常的好,并且还十分的善良,因此深得小伙伴们喜爱.然而冰公主在一开始的时候 ...

  6. python识别图片文字_如何利用Python识别图片中的文字

    一.前言 不知道大家有没有遇到过这样的问题,就是在某个软件或者某个网页里面有一篇文章,你非常喜欢,但是不能复制.或者像百度文档一样,只能复制一部分,这个时候我们就会选择截图保存.但是当我们想用到里面的 ...

  7. 识别图片噪声干扰_射频相位噪声介绍

    您可以偶然地意识到相位噪声是"是关于相位的噪声". 那么什么是相位. 同样,相位的数学定义仅来自高中数学,如下所示. 当我们改变信号的相位时,信号将如何改变. 以下图片将为您提供答 ...

  8. 设计师神器_随机形状生成器

    相信你看到过很多以不规则形状作为背景的设计图片,比如这样的 那这样的形状要怎么画?钢笔直接勾么?未免太麻烦,还不一定好看.今天,小编我就给大家推荐一个极其方便快捷的,正常网络状态可以访问的线上随机形状 ...

  9. 鼠标精灵对码软件_暗影精灵6游戏主机评测

    惠普在7月9日发布了旗下最新的暗影精灵6游戏本,全新的外观.极致的性能.升级的散热给我们留下了深刻的印象,堪称新一代国民游戏本.和暗影精灵6游戏本一同呈现的还有暗影精灵6游戏台式电脑至尊版(旗舰版), ...

最新文章

  1. python websocket例程_python 实现websocket
  2. psp能装安卓软件吗_王思聪:翻译软件能翻译出文化吗?
  3. IE9 CSS 因 Mime 类型不匹配而被忽略“问题
  4. 【C++基础】常见面试问题(二)
  5. php 与结合struts2,Struts2和Ajax数据交互示例详解
  6. 物业管理系统源码java_Java小区物业管理系统 源码报告下载
  7. 【转】推荐几本学习MySQL的好书-MySQL 深入的书籍
  8. 相对url和相对路径
  9. 统计学原理 数值型数据的整理与展示
  10. 普渡大学统计与计算机科学,普渡大学西拉法叶分校
  11. mysql对象资源管理器_使用对象资源管理器
  12. 计算机ctrl加什么作用,计算机中快捷键ctrl加什么是返回上一步
  13. Java:Java实现简单闹钟设计
  14. 如何实现app直播源代码,通过HLS进行直播观看
  15. PyQt5系列教程(二)利用QtDesigner设计UI界面
  16. 辐射剂量监测仪(一)
  17. 音视频开发为什么要学SRS流媒体服务器
  18. 【论文解读】利用高光谱图像对场景反射率进行有效估计(Efficient Estimation of Reflectance Parameters from Imaging Spectropy)
  19. 使用原生js实现复制剪贴板
  20. 计算机的专业的个人陈述,计算机专业留学个人陈述范文

热门文章

  1. Giscus,由 GitHub Discussions驱动的评论系统
  2. 【调剂】中科院自动化所医疗机器人课题组招收调剂研究生
  3. OLA端点问题实际应用效果
  4. Java的支持率让小伙伴们都惊呆了
  5. JavaApp自动化测试系列[v1.0.0][Appium开发环境搭建]
  6. 简单两步,将Windows11右键菜单修改为Windows10风格
  7. 有声小说php采集站源码下载,PTCMS小说站源码 可听书 可下载 带自动采集和搭建视频教程...
  8. 四、ESP32单片机wifi的AP与STA模式使用
  9. python中的计时模块:time.time()
  10. 1号店又要卖了?这在内部已不在是个秘密