UGUI并不适合开发2D游戏,因为角色移动的话,他所在的canvas每一帧都会进行Mesh合并操作,这是没有必要的。

Unity提供了Sprite Renderer组件,它可以配合Animator组件来控制播放2D精灵动画。此外还有2D物理和碰撞事件

Sprite

Sprite Renderer是2D游戏开发最基础的组件。使用的是将类型标记为Sprite的图片资源。

Sprite Renderer和UI一样,都可以使用Atlas进行图集合并。

将图片资源选择为Sprite后,一共有3个模式可以选择

  • Single:表示它是一张单张图
  • Multiple:可以把一张图分拆成多个Sprite。可以自动分拆,也可以手动在Sprite Editor中编辑出每个sprite的区域。
  • Polygon:自定义Sprite的多边形形状,例如,在不需要美术人员修改的情况下,可以让它变成圆形

Sprite中可能有部分是透明的,如果按矩形渲染的话,会需要多余的渲染资源来渲染透明部分,所以我们把Mesh Type改为Tight,这样Unity会自动把这张图生成网格后再渲染,减少需要渲染的透明像素。Extrude Edges可以调节网格的数量。相反地,如果设置为Rect,就是矩形渲染会渲染整个图片

我们在Scene窗口下选择Wireframe渲染方式,即可看到网格的信息

网格信息

图片设置

2D摄像机与分辨率自适应

首先,需要确认开发分辨率。我们暂定开发分辨率是1136*640。先设置Orthographic正交摄像机,里面Size是的含义是屏幕的一半,也就是640/2=320.由于Sprite默认的Pixels Per Unit设置的是100,所以320/100=3.2

如果Unity当前分辨率大于开发分辨率,它会自动缩放。但是如果当前分辨率小于开发分辨率,需要我们手动处理。

新建CameraAutoScale脚本,挂载在场景的主camera上,把主camera拉进去

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
using UnityEngine;

public class CameraAutoScale : MonoBehaviour
{
public Camera camera;


private void Update()
{
float designWidth = 1136f, designHeight = 640f, designOrthographicSize = 3.2f, desiginScale = designWidth/designHeight;
float scaleRate = (float)Screen.width / (float)Screen.height;
if (scaleRate < desiginScale)
{
float scale = scaleRate / desiginScale;
camera.orthographicSize = 3.2f / scale;//orthographic越大,camera渲染场景就越小
}
else
{
camera.orthographicSize = 3.2f;
}
}
private void OnGUI()
{
GUILayout.Label(string.Format("<size=60><color=red>{0} X {1}</color></size>", Screen.width, Screen.height));
}
}

这样的话当我们的实际分辨率长宽比变小,会动态调整游戏整体画面,保持原长宽比

Sprite Renderer排序

在UGUI实例中,我们学习过粒子特效UI排序的操作,当时实现的原理是在粒子的父对象添加canvas组件,canvas可以设定粒子的order。

在Sprite渲染中,同样有相似的设定粒子order的组件,是Sorting Group,可以覆盖当前对象和子对象的Order

显示

排序始终是先看Sorting Layer,然后才看Sorting Order的。

在Project Settings——Graphics——Camera Settings里面,将Transparency Sort Mode设为Custom Axis、Transparency Sort Axis设为0,-1,0

这表示我们用自定义Y轴来排布相同order下的sprite顺序

排序原则

相反,当Transparency Sort Axis设为0,1,0,越比Y=1大,越在后面,越比Y=1小,越在前面,这个比较常用

这里的比大比小是指sprite相对数值,并不是指Y=1或Y=-1这个固定的轴

Sprite Mask

Sprite Renderer也提供了Mask,但是和UGUI不同的是,它不需要被裁切的元素必须挂载在裁切组件的下面。

在hierarchy窗口右键——2D Object——Sprite Mask。

inspector面板

sprite是裁切样板图,点击可以选择默认的裁切样板,在scene窗口把shaded改为sprite mask即可看到样板图

点击打开custom range,表示比front小,比back大的order in layer可以被裁切

sprite mask一直生效,需要被裁切的sprite的sprite Renderer组件需要将mask interaction设置为visible inside mask或者visible outside mask

Sprite Mask 是不能裁切带特效的Sprite Renderer的。如果需要裁切的话,参考UGUI实例——粒子的裁切

Sprite动画

使用unity的动画系统来混合动画,但是unity默认的动画编辑器没有骨骼信息,我们可以通过2D Animationpackage或者Spine来实现2D骨骼,3D骨骼也可以在Unity Store购买一些开发工具来实现。

创建2D动画

首先在Windows中打开Animation,选中场景中的sprite,点击animation窗口中的create按钮,会自动为当前对象生成animator和anim clip,然后做好命名和文件夹整理即可。

Animation窗口

在本例中我们使用素材中提供的sprite切片来制作动画(除了拖拽,还可以全选后右键,新建animation),根据需要设置sample

制作方法

新建动画切片

如果要实现换装功能,可以把人物的脑袋、身体、胳膊、腿和武器等都拆成散图,此时就需要使用编辑器将每一帧的散图组合拼接在一起,从而实现最终动画效果。这样做可以节省图片内存。

2D动画控制器

Unity的AnimationController是一个强大的状态机,提供了子状态层、子状态机以及混合树等功能,第七章将重点介绍

Entry表示状态机的入口(无法删除),Any state表示任意状态,例如角色死亡了,无论当前处于什么状态,都可以立即进入死亡状态,exit表示退出当前状态机,如果有子状态机,则表示返回上一层状态机。

我们按照下图创建状态机,设置两个bool类型参数,分别命名为run和jump。然后分别设定好他们的transition

状态图

在transition面板中,Has Exit Time表示切换状态的时候是否需要等上一个动画播放完过度动画,过度动画的时候在Settings下拉菜单中配置,然后在最下方的Condition中设置自定义的参数变量

在开发后期,状态机会越来越复杂,这个时候我们选中一个状态或者transition,就可以编辑它们的solo和mute,勾选solo的transition在条件满足时只会播放勾选solo的,勾选mute的transition在条件满足时也不会播放

动画设置

新建PlayerController脚本

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
80
81
82
83
84
85
86
87
88
89
90
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
public Animator animator;
public SpriteRenderer playerRenderer;
private State m_State = State.Idle;
// Start is called before the first frame update
void Start()
{
Idle();
}

// Update is called once per frame
void Update()
{
float y = Input.GetAxis("Vertical");
float x = Input.GetAxis("Horizontal");

if (y > 0)
{
Run(Direction.Up, Vector3.up);
}else if (y < 0)
{
Run(Direction.Down, Vector3.down);
}

if (x > 0)
{
Run(Direction.Right, Vector3.right);
}else if (x < 0)
{
Run(Direction.Left, Vector3.left);
}

if(m_State == State.Run)
{
if(Mathf.Approximately(x,0f) && Mathf.Approximately(y, 0f))
{
Idle();
}
}

if (Input.GetKey(KeyCode.Space))
{
Jump();
}else if (Input.GetKeyUp(KeyCode.Space))
{
Idle();
}
}
void Idle()
{
SetState(State.Idle);
}
void Run(Direction dir,Vector3 position)
{
SetState(State.Run);

playerRenderer.flipX = (dir == Direction.Left);
playerRenderer.transform.position += (position * 0.001f);
}
void Jump()
{
SetState(State.Jump);
}
void SetState(State newState)
{
m_State = newState;
animator.SetBool("run", m_State == State.Run);
animator.SetBool("jump", m_State == State.Jump);
}
enum State
{
Idle = 1,
Run,
Jump
}
enum Direction
{
None = 1,
Up,
Down,
Left,
Right
}
}