抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

Hello world!

感谢

感谢bilibili视频网站M_Studio麦口老师系列视频教学,Unity2D都是从他这学的。

创建项目与导入素材

  1. 创建新项目
  2. 导入素材,点击菜单栏Assets -> ImportPackage

导入并编辑Tiles

  1. 点击菜单栏window -> 2D -> tile palette ,点击createNewPalette创建palette
  2. 导入Level文件夹下Spirit文件夹中的素材,并且适当编辑

TileMap

  1. 在Hierarchy窗口,创建所需要的2dObject -> tilemap
  2. 在Tile Palette窗口,选中相应Active Tile Map并绘制

image-20210818134102428

  1. 在Inspector窗口设置Sorting Layer,如下

image-20210818193555665

image-20210818193335243

​ Sorting Layer中Layer越靠下越显示在前方,位于同一Layer中Order in Layer数值越大越显示在前方

  1. 其中 Background Details用于在Background中再添加相应tile(在同一个TileMap上绘制只能显示一个tile,即使tile是含有透明背景,也会完全替换)

2D Extra插件

RuleTile

  1. 在Tiles文件夹下右键点击 Create -> tiles -> rule tile,并在Inspector窗口中设置 Default Sprite默认图片
  2. 在Inspector窗口中,设置Tiling Rules,并将新tile拖拽进Tile Palette并绘制,如下

image-20210818195734376

​ 每一个Tiling Rules中,右侧图片为符合该规则就显示的tile,九宫格中设置规则,绿色箭头表示在该方向上有tile,红色叉号表示不能有tile,同时列表中越靠上的rule优先级越高
​ 如上设置可快速填充封闭图形的对应tile

  1. 如下规则解决内角问题,注意这四个角的额外rule要放在横竖两个tile的上方

image-20210818200334242

Brush

想要添加贴合格子的物件prefab,可以使用创建brush的方式

  1. 创建brush,右键点击 create -> Brush -> prefab Brush
  2. 添加Element
  3. Tile Palette 窗口中左下角使用自定义brush即可
  4. 添加的对象别忘了设置Layer以判断,同时可以使用Override使得同一物品相同设置

Collider和Rigidbody

Platform设置Collider和Rigidbody

  1. 选中Platform对象,在Inspector窗口下方点击 add component -> Tilemap Collider 2D
  2. 勾选collider中的 user By Composite,即使用复合碰撞体,按照提示添加 Composite Collider 2D,则整个Platform为一个整体碰撞体
  3. 将随之添加的 rigidbody -> bodytype设置为static,即可保持Platform固定,而不会因为gravity重力下落
  4. 同时也可以将Platform本身设置为static

Player设置Collider和Rigidbody

  1. 选中Player对象,点击 add component -> rigidbody 2D,并设置参数如下

image-20210819114945303

​ 注意Colllision Detection 碰撞方式设置为Continuous,即可持续判断是否产生碰撞; Interpolate差值设置为Interpolate,即可当下落碰撞时对碰撞体产生一定效果(基于上一帧物体位置来插值模拟)

  1. 点击 add component -> Box Collider 2D,并点击Edit设置碰撞体范围
  2. 同时在 Constraints限制选项中选择Freeze Rotation Z,即可防止当角色处于Platform角上时会绕着Z轴旋转掉落
  3. 同时为Collider添加Materail

Layer

为Platform和Player添加不同的Layer,用于日后判断

image-20210819120442875

Player左右移动和形象翻转

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
// 通过 “Header”, “Tooltip” and “Space” 属性来组织Inspector中的属性显示,即在Inspector窗口上有小标题分区显示
// 创建rigidbody
private Rigidbody2D rb;

[Header("移动参数")]
public float speed = 8f; //
public float crouchSpeedDivisor = 3f; // 蹲下速度减缓量

[Header("状态")]
public bool isCrouch; // 是否下蹲

float xVelocity; // 轴速度

void Start()
{
// 获取rigidbody
rb = GetComponent<Rigidbody2D>();
}
void Update()
{ }
// 固定帧速率下每帧都调用该函数
private void FixedUpdate()
{
GroundMovement();
}

// 控制角色左右移动
void GroundMovement()
{
// 获取键盘输入并设置移动
xVelocity = Input.GetAxis("Horizontal"); // 返回值-1f ~ 1f,当不按下方向键时为0
rb.velocity = new Vector2(xVelocity * speed, rb.velocity.y);
// 控制朝向
FlipDiraction();
}
// 控制角色面向方向
void FlipDiraction()
{
if(xVelocity < 0)
{
transform.localScale = new Vector2(-1, 1);
}else if(xVelocity > 0)
{
transform.localScale = new Vector2(1, 1);
}
}
}

Player下蹲

点击 Edit -> Project Setting -> Input Manager -> 复制JumpElement并修改为Crouch

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
// 通过 “Header”, “Tooltip” and “Space” 属性来组织Inspector中的属性显示,即在Inspector窗口上有小标题分区显示

// rigidbody,collider
private Rigidbody2D rb;
private BoxCollider2D coll; // 使用BoxCollider可以直接获得其中的size、offset参数

[Header("移动参数")]
public float speed = 8f; //
public float crouchSpeedDivisor = 3f; // 蹲下时的速度减缓量

[Header("状态")]
public bool isCrouch; // 是否下蹲

float xVelocity; // 轴速度

// 碰撞体尺寸 (站立、下蹲时的大小、位置)
Vector2 colliderStandSize, colliderStandOffset,colliderCrouchSize, colliderCrouchOffset;

// 初始化参数
void Start()
{
// rigidbody,collider
rb = GetComponent<Rigidbody2D>();
coll = GetComponent<BoxCollider2D>();

// 碰撞体尺寸
colliderStandSize = coll.size;
colliderStandOffset = coll.offset;
colliderCrouchSize = new Vector2(coll.size.x, coll.size.y / 2f);
colliderCrouchOffset = new Vector2(coll.offset.x, coll.offset.y / 2f);
}

// Update is called once per frame
void Update()
{

}

// 固定帧速率下每帧都调用该函数
private void FixedUpdate()
{
GroundMovement();
}

// 控制角色左右移动
void GroundMovement()
{
// 获取键盘输入
xVelocity = Input.GetAxis("Horizontal"); // 返回值-1f ~ 1f,当不按下方向键时为0

// 判断是否按下下蹲按钮、是否未按下按钮并处于下蹲状态
if (Input.GetButton("Crouch"))
{
// 控制角色下蹲
Crouch();
}else if(isCrouch)
{
// 控制角色起身
StandUp();
}

// 控制角色速度以移动
rb.velocity = new Vector2(xVelocity * speed, rb.velocity.y);
// 控制朝向
FlipDiraction();
}

// 控制角色面向方向
void FlipDiraction()
{
if(xVelocity < 0)
{
transform.localScale = new Vector2(-1, 1);
}else if(xVelocity > 0)
{
transform.localScale = new Vector2(1, 1);
}
}

// 控制角色下蹲
void Crouch()
{
isCrouch = true;
// 下蹲时速度减缓
xVelocity /= crouchSpeedDivisor;

// 改变collider的尺寸
coll.size = colliderCrouchSize;
coll.offset = colliderCrouchOffset;
}

// 控制角色起立
void StandUp()
{
isCrouch = false;

// 改变collider的尺寸
coll.size = colliderStandSize;
coll.offset = colliderStandOffset;
}

}

Player跳跃

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
// 通过 “Header”, “Tooltip” and “Space” 属性来组织Inspector中的属性显示,即在Inspector窗口上有小标题分区显示

// rigidbody,collider
private Rigidbody2D rb;
private BoxCollider2D coll; // 使用BoxCollider可以直接获得其中的size、offset参数

[Header("移动参数")]
public float speed = 8f;
public float crouchSpeedDivisor = 3f; // 蹲下时的速度减缓量

[Header("跳跃参数")]
public float jumpForce = 6.3f; // 跳跃力
public float jumpHoldForce = 1.9f; // 长按跳跃的力
public float jumpHoldDuration = 0.1f; // 长按跳跃加成
public float crouchJumpBoost = 2.5f; // 下蹲跳跃的加成

[Header("状态")]
public bool isCrouch; // 是否下蹲
public bool isOnGround; // 是否接触地面
public bool isJump; // 是否跳跃

[Header("环境检测")]
public LayerMask groundLayer; // 地面层

float xVelocity; // 轴速度
float jumpTime; // 跳跃时间,与duration相关

// 按键
bool jumpPressed, jumpHeld, crouchHeld;

// 碰撞体尺寸 (站立、下蹲时的大小、位置)
Vector2 colliderStandSize, colliderStandOffset,colliderCrouchSize, colliderCrouchOffset;

// 初始化参数
void Start()
{
// rigidbody,collider
rb = GetComponent<Rigidbody2D>();
coll = GetComponent<BoxCollider2D>();

// 碰撞体尺寸
colliderStandSize = coll.size;
colliderStandOffset = coll.offset;
colliderCrouchSize = new Vector2(coll.size.x, coll.size.y / 2f);
colliderCrouchOffset = new Vector2(coll.offset.x, coll.offset.y / 2f);
}

// 获取GetButton最好放在此函数中
void Update()
{
// GetButton参数对应Project Setting -> Input Manager的设置
jumpPressed = Input.GetButtonDown("Jump");
jumpHeld = Input.GetButton("Jump");
crouchHeld = Input.GetButton("Crouch");
}

// 固定帧速率下每帧都调用该函数
private void FixedUpdate()
{
// 相关物理控制
GroundMovement();
PhysicsCheck();
MidAirMovement();
}

// 环境检测
void PhysicsCheck()
{
// 是否接触地面层
if (coll.IsTouchingLayers(groundLayer))
{
isOnGround = true;
}
else
{
isOnGround = false;
}
}

// 控制角色左右移动
void GroundMovement()
{
// 获取键盘输入
xVelocity = Input.GetAxis("Horizontal"); // 返回值-1f ~ 1f,当不按下方向键时为0

// 判断是否
if (crouchHeld && !isCrouch && isOnGround)
{
// 按下下蹲按钮,不处于下蹲状态,接触地面 --》 角色下蹲
Crouch();
}
else if(!crouchHeld && isCrouch)
{
// 未按下按钮并处于下蹲状态 --》 角色起身
StandUp();
}
else if(!isOnGround && isCrouch)
{
// 不接触地面但是处于下蹲状态 ——》 处于空中 --》 角色起身
StandUp();
}

// 控制角色速度以移动
rb.velocity = new Vector2(xVelocity * speed, rb.velocity.y);
// 控制朝向
FlipDiraction();
}

// 控制角色跳跃
void MidAirMovement()
{
if(jumpPressed && isOnGround && !isJump)
{
// isOnGround && !isJump 防止蹭墙无限跳
// 按下了跳跃,接触地面,未处于跳跃状态 ——》 控制跳跃

// 下蹲状态跳跃,有额外力的加成
if (isCrouch)
{
// 控制起身
StandUp();
// 添加额外力
rb.AddForce(new Vector2(0f, crouchJumpBoost), ForceMode2D.Impulse);
}

isJump = true;
isOnGround = false;

// 计算跳跃时的时间,Time.time获取游戏真实持续时间(不断增长)
jumpTime = Time.time + jumpHoldDuration;

// 修改rb.velocity也可以实现
rb.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);
}
else if (isJump)
{
// 长按跳跃,获得额外的力jumpHoldForce加成
if (jumpHeld)
{
rb.AddForce(new Vector2(0f, jumpHoldForce), ForceMode2D.Impulse);
}
// 当Time.time获取的时间比jumpTime大,即之间的时间差已经大于jumpHoldDuration的值时,即可恢复isJump
if (jumpTime < Time.time)
{
isJump = false;
}
}
}

// 控制角色面向方向
void FlipDiraction()
{
if(xVelocity < 0)
{
transform.localScale = new Vector2(-1, 1);
}else if(xVelocity > 0)
{
transform.localScale = new Vector2(1, 1);
}
}

// 控制角色下蹲
void Crouch()
{
isCrouch = true;
// 下蹲时速度减缓
xVelocity /= crouchSpeedDivisor;

// 改变collider的尺寸
coll.size = colliderCrouchSize;
coll.offset = colliderCrouchOffset;
}

// 控制角色起立
void StandUp()
{
isCrouch = false;

// 改变collider的尺寸
coll.size = colliderStandSize;
coll.offset = colliderStandOffset;
}

}

Raycast射线检测

实现移动跳跃下蹲检测

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
// 通过 “Header”, “Tooltip” and “Space” 属性来组织Inspector中的属性显示,即在Inspector窗口上有小标题分区显示

// rigidbody,collider
private Rigidbody2D rb;
private BoxCollider2D coll; // 使用BoxCollider可以直接获得其中的size、offset参数

[Header("移动参数")]
public float speed = 8f;
public float crouchSpeedDivisor = 3f; // 蹲下时的速度减缓量

[Header("跳跃参数")]
public float jumpForce = 6.3f; // 跳跃力
public float jumpHoldForce = 1.9f; // 长按跳跃的力
public float jumpHoldDuration = 0.1f; // 长按跳跃加成
public float crouchJumpBoost = 2.5f; // 下蹲跳跃的加成

[Header("状态")]
public bool isCrouch; // 是否下蹲
public bool isOnGround; // 是否接触地面
public bool isHeadBlocked; // 是否头顶遮挡
public bool isJump; // 是否跳跃

[Header("环境检测")]
public float footOffset = 0.35f; // 单脚的偏移值,即整体xoffset的一半
public float headClearance = 0.5f;
public float groundDistance = 0.2f;
public LayerMask groundLayer; // 地面层

float xVelocity; // 轴速度
float jumpTime; // 跳跃时间,与duration相关

// 按键
bool jumpPressed, jumpHeld, crouchHeld;

// 碰撞体尺寸 (站立、下蹲时的大小、位置)
Vector2 colliderStandSize, colliderStandOffset,colliderCrouchSize, colliderCrouchOffset;

// 初始化参数
void Start()
{
// rigidbody,collider
rb = GetComponent<Rigidbody2D>();
coll = GetComponent<BoxCollider2D>();

// 碰撞体尺寸
colliderStandSize = coll.size;
colliderStandOffset = coll.offset;
colliderCrouchSize = new Vector2(coll.size.x, coll.size.y / 2f);
colliderCrouchOffset = new Vector2(coll.offset.x, coll.offset.y / 2f);
}

// 获取GetButton最好放在此函数中
void Update()
{
// GetButton参数对应Project Setting -> Input Manager的设置
jumpPressed = Input.GetButtonDown("Jump");
jumpHeld = Input.GetButton("Jump");
crouchHeld = Input.GetButton("Crouch");
}

// 固定帧速率下每帧都调用该函数
private void FixedUpdate()
{
// 相关物理控制
GroundMovement();
PhysicsCheck();
MidAirMovement();
}

// 环境检测
void PhysicsCheck()
{
/* // 当前角色的位置和单脚左右点的offset
Vector2 pos = transform.position;
Vector2 offset = new Vector2(-footOffset, 0f);
// leftCheck/rightCheck即单脚左右点检测
RaycastHit2D leftCheck = Physics2D.Raycast(pos+offset,Vector2.down,groundDistance,groundLayer);
// 显示射线
Debug.DrawRay(pos + offset, Vector2.down, Color.red, 0.2f);
*/
// 1、左右脚检测是否接触地面
// 获得射线检测
RaycastHit2D leftCheck = Raycast(new Vector2(-footOffset, 0f), Vector2.down, groundDistance, groundLayer);
RaycastHit2D rightCheck = Raycast(new Vector2(footOffset, 0f), Vector2.down, groundDistance, groundLayer);

// 是否接触地面层
//if (coll.IsTouchingLayers(groundLayer))
if (leftCheck || rightCheck)
{
isOnGround = true;
}
else
{
isOnGround = false;
}

// 2、头顶检测是否有阻挡
RaycastHit2D headCheck = Raycast(new Vector2(0f, coll.size.y), Vector2.up, headClearance, groundLayer);
if (headCheck)
{
isHeadBlocked = true;
}
else
{
isHeadBlocked = false;
}

}

// 控制角色左右移动
void GroundMovement()
{
// 获取键盘输入
xVelocity = Input.GetAxis("Horizontal"); // 返回值-1f ~ 1f,当不按下方向键时为0

// 判断是否
if (crouchHeld && !isCrouch && isOnGround)
{
// 按下下蹲按钮,不处于下蹲状态,接触地面 --》 角色下蹲
Crouch();
}
else if(!crouchHeld && isCrouch && !isHeadBlocked)
{
// 未按下按钮,处于下蹲状态,头顶无遮挡 --》 角色起身
StandUp();
}
else if(!isOnGround && isCrouch)
{
// 不接触地面但是处于下蹲状态 ——》 处于空中 --》 角色起身
StandUp();
}

// 控制角色速度以移动
rb.velocity = new Vector2(xVelocity * speed, rb.velocity.y);
// 控制朝向
FlipDiraction();
}

// 控制角色跳跃
void MidAirMovement()
{
if(jumpPressed && isOnGround && !isJump)
{
// isOnGround && !isJump 防止蹭墙无限跳
// 按下了跳跃,接触地面,未处于跳跃状态 ——》 控制跳跃

// 下蹲状态跳跃,有额外力的加成
if (isCrouch)
{
// 控制起身
StandUp();
// 添加额外力
rb.AddForce(new Vector2(0f, crouchJumpBoost), ForceMode2D.Impulse);
}

isJump = true;
isOnGround = false;

// 计算跳跃时的时间,Time.time获取游戏真实持续时间(不断增长)
jumpTime = Time.time + jumpHoldDuration;

// 修改rb.velocity也可以实现
rb.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);
}
else if (isJump)
{
// 长按跳跃,获得额外的力jumpHoldForce加成
if (jumpHeld)
{
rb.AddForce(new Vector2(0f, jumpHoldForce), ForceMode2D.Impulse);
}
// 当Time.time获取的时间比jumpTime大,即之间的时间差已经大于jumpHoldDuration的值时,即可恢复isJump
if (jumpTime < Time.time)
{
isJump = false;
}
}
}

// 控制角色面向方向
void FlipDiraction()
{
if(xVelocity < 0)
{
transform.localScale = new Vector2(-1, 1);
}else if(xVelocity > 0)
{
transform.localScale = new Vector2(1, 1);
}
}

// 控制角色下蹲
void Crouch()
{
isCrouch = true;
// 下蹲时速度减缓
xVelocity /= crouchSpeedDivisor;

// 改变collider的尺寸
coll.size = colliderCrouchSize;
coll.offset = colliderCrouchOffset;
}

// 控制角色起立
void StandUp()
{
isCrouch = false;

// 改变collider的尺寸
coll.size = colliderStandSize;
coll.offset = colliderStandOffset;
}

// 增强Raycast函数
RaycastHit2D Raycast(Vector2 offset, Vector2 rayDirection, float length, LayerMask layer)
{
// 当前角色的位置
Vector2 pos = transform.position;
// 创建射线
RaycastHit2D hit = Physics2D.Raycast(pos + offset, rayDirection, length, layer);

// 显示射线,hit返回是否碰撞布尔值
Color color = hit ? Color.red : Color.green;
Debug.DrawRay(pos + offset, rayDirection, color);

return hit;
}

}

实现悬挂功能以及下蹲跳跃的优化

判断Raycast射线是否为TRUE的条件:为射线中有接触碰撞体,但是射线起点没有接触碰撞体;

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
// 通过 “Header”, “Tooltip” and “Space” 属性来组织Inspector中的属性显示,即在Inspector窗口上有小标题分区显示

// rigidbody,collider
private Rigidbody2D rb;
private BoxCollider2D coll; // 使用BoxCollider可以直接获得其中的size、offset参数

[Header("移动参数")]
public float speed = 8f;
public float crouchSpeedDivisor = 3f; // 蹲下时的速度减缓量

[Header("跳跃参数")]
public float jumpForce = 6.3f; // 跳跃力
public float jumpHoldForce = 1.9f; // 长按跳跃的力
public float jumpHoldDuration = 0.1f; // 长按跳跃加成
public float crouchJumpBoost = 2.5f; // 下蹲跳跃的加成
public float hangingJumpForce = 15f; // 悬挂时跳跃的力

[Header("状态")]
public bool isCrouch; // 是否下蹲
public bool isOnGround; // 是否接触地面
public bool isHeadBlocked; // 是否头顶遮挡
public bool isJump; // 是否跳跃
public bool isHanging; // 是否悬挂

[Header("环境检测")]
public LayerMask groundLayer; // 地面层
public float headClearance = 0.25f;
public float groundDistance = 0.2f;
public float grabDistance = 0.4f; // 抓取距离
public float reachOffset = 0.7f; // 接触偏移值


float footOffset; // 单脚的偏移值,即整体xsize的一半
float xVelocity; // 轴加速度
float jumpTime; // 跳跃时间,与duration相关
float playerHeight; // 角色高度
float eyeHeight; // 角色眼睛高度

// 按键
bool jumpPressed, jumpHeld, crouchHeld, crouchPressed;

// 碰撞体尺寸 (站立、下蹲时的大小、位置)
Vector2 colliderStandSize, colliderStandOffset,colliderCrouchSize, colliderCrouchOffset;

// 初始化参数
void Start()
{
// rigidbody,collider
rb = GetComponent<Rigidbody2D>();
coll = GetComponent<BoxCollider2D>();

footOffset = coll.size.x / 2;
playerHeight = coll.size.y;
eyeHeight = playerHeight - 0.4f;

// 碰撞体尺寸
colliderStandSize = coll.size;
colliderStandOffset = coll.offset;
colliderCrouchSize = new Vector2(coll.size.x, coll.size.y / 2f);
colliderCrouchOffset = new Vector2(coll.offset.x, coll.offset.y / 2f);
}

// 获取GetButton最好放在此函数中
void Update()
{
// GetButton参数对应Project Setting -> Input Manager的设置
jumpPressed = Input.GetButtonDown("Jump");
jumpHeld = Input.GetButton("Jump");
crouchHeld = Input.GetButton("Crouch");
crouchPressed = Input.GetButtonDown("Crouch");

playerHeight = coll.size.y;
eyeHeight = playerHeight - 0.4f;
}

// 固定帧速率下每帧都调用该函数
private void FixedUpdate()
{
// 相关物理控制
GroundMovement();
PhysicsCheck();
MidAirMovement();
CrouchMovement();
HangdingMovement();
}

// 环境检测
void PhysicsCheck()
{
/* // 当前角色的位置和单脚左右点的offset
Vector2 pos = transform.position;
Vector2 offset = new Vector2(-footOffset, 0f);
// leftCheck/rightCheck即单脚左右点检测
RaycastHit2D leftCheck = Physics2D.Raycast(pos+offset,Vector2.down,groundDistance,groundLayer);
// 显示射线
Debug.DrawRay(pos + offset, Vector2.down, Color.red, 0.2f);
*/
// 1、左右脚射线检测 是否接触地面
// 获得射线检测
RaycastHit2D leftCheck = Raycast(new Vector2(-footOffset, 0f), Vector2.down, groundDistance, groundLayer);
RaycastHit2D rightCheck = Raycast(new Vector2(footOffset, 0f), Vector2.down, groundDistance, groundLayer);

// 是否接触地面层
//if (coll.IsTouchingLayers(groundLayer))
if (leftCheck || rightCheck)
isOnGround = true;
else
isOnGround = false;

// 2、头顶射线检测 是否有阻挡
RaycastHit2D headCheck = Raycast(new Vector2(0f, playerHeight), Vector2.up, headClearance, groundLayer);
if (headCheck)
isHeadBlocked = true;
else
isHeadBlocked = false;

// 3、头前方区域射线检测 是否悬挂
float diraction = transform.localScale.x;
RaycastHit2D blockedCheck = Raycast(new Vector2(footOffset * diraction, playerHeight), new Vector2(diraction,0f), grabDistance, groundLayer);
RaycastHit2D wallCheck = Raycast(new Vector2(footOffset * diraction, eyeHeight), new Vector2(diraction, 0f), grabDistance, groundLayer);
RaycastHit2D ledgeCheck = Raycast(new Vector2(reachOffset * diraction, playerHeight), Vector2.down, grabDistance, groundLayer);

if (!isOnGround && rb.velocity.y < 0f && ledgeCheck && wallCheck && !blockedCheck)
{
// 将player固定在一个位置
Vector3 pos = transform.position;
pos.x += (wallCheck.distance - 0.05f) * diraction; // RaycastHit2D下的distance为起始点到接触点的距离
pos.y -= ledgeCheck.distance;
transform.position = pos;

rb.bodyType = RigidbodyType2D.Static; // 让角色静止
isHanging = true;
}

}

// 控制角色左右移动
void GroundMovement()
{
// 悬挂时不允许移动
if (isHanging)
return;

// 获取键盘输入
xVelocity = Input.GetAxis("Horizontal"); // 返回值-1f ~ 1f,当不按下方向键时为0
// 控制角色速度以移动
rb.velocity = new Vector2(xVelocity * speed, rb.velocity.y);
// 控制朝向
FlipDiraction();
}

// 控制角色下蹲起立
void CrouchMovement()
{
if (crouchHeld && !isCrouch && isOnGround)
{
// 按下下蹲按钮,不处于下蹲状态,接触地面 --》 角色下蹲
Crouch();
}
else if (!crouchHeld && isCrouch && !isHeadBlocked)
{
// 未按下按钮,处于下蹲状态,头顶无遮挡 --》 角色起身
StandUp();
}
else if (!isOnGround && isCrouch)
{
// 不接触地面但是处于下蹲状态 ——》 处于空中 --》 角色起身
StandUp();
}
}

// 控制角色跳跃
void MidAirMovement()
{
// 跳跃检测
if(jumpPressed && isOnGround && !isJump && !isHeadBlocked)
{
// isOnGround && !isJump 防止蹭墙无限跳
// 按下了跳跃,接触地面,未处于跳跃状态 ——》 控制跳跃

// 下蹲状态跳跃,有额外力的加成
if (isCrouch && !isHeadBlocked)
{
// 控制起身
StandUp();
// 添加额外力
rb.AddForce(new Vector2(0f, crouchJumpBoost), ForceMode2D.Impulse);
}

isJump = true;
isOnGround = false;

// 计算跳跃时的时间,Time.time获取游戏真实持续时间(不断增长)
jumpTime = Time.time + jumpHoldDuration;

// 修改rb.velocity也可以实现
rb.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);
}
else if (isJump)
{
// 长按跳跃,获得额外的力jumpHoldForce加成
if (jumpHeld)
{
rb.AddForce(new Vector2(0f, jumpHoldForce), ForceMode2D.Impulse);
}
// 当Time.time获取的时间比jumpTime大,即之间的时间差已经大于jumpHoldDuration的值时,即可恢复isJump
if (jumpTime < Time.time)
{
isJump = false;
}
}
}

// 控制角色悬挂时跳跃与下蹲
void HangdingMovement()
{
// 悬挂时检测
if (isHanging)
{
if (jumpPressed)
{
rb.bodyType = RigidbodyType2D.Dynamic;
rb.AddForce(new Vector2(0f, hangingJumpForce), ForceMode2D.Impulse);
isHanging = false;
}
else if (crouchPressed)
{
rb.bodyType = RigidbodyType2D.Dynamic;
isHanging = false;
}
}
}

// 控制角色面向方向
void FlipDiraction()
{
if(xVelocity < 0)
{
transform.localScale = new Vector2(-1, 1);
}else if(xVelocity > 0)
{
transform.localScale = new Vector2(1, 1);
}
}

// 控制角色下蹲
void Crouch()
{
isCrouch = true;
// 下蹲时速度减缓
xVelocity /= crouchSpeedDivisor;

// 改变collider的尺寸
coll.size = colliderCrouchSize;
coll.offset = colliderCrouchOffset;
}

// 控制角色起立
void StandUp()
{
isCrouch = false;

// 改变collider的尺寸
coll.size = colliderStandSize;
coll.offset = colliderStandOffset;
}

// 增强Raycast函数
RaycastHit2D Raycast(Vector2 offset, Vector2 rayDirection, float length, LayerMask layer)
{
// 当前角色的位置
Vector2 pos = transform.position;
// 创建射线
RaycastHit2D hit = Physics2D.Raycast(pos + offset, rayDirection, length, layer);

// 显示射线,hit返回是否碰撞布尔值
Color color = hit ? Color.red : Color.green;
Debug.DrawRay(pos + offset, rayDirection, color);

return hit;
}

}

2D透视效果与摄像机

  1. PackageManager -> CineMachines插件导入,添加Follow选项
  2. body -> dead zone锁死范围,在该范围内移动不会跟着移动摄像机
  3. body -> Screen 设置follow角色在摄像机的哪个位置
  4. body -> CameraDistance 设置摄像机包括范围
  5. Main Camera -> Projection -> Perspective,即可模拟正常摄像机效果(切换Scene 2D即可查看)
  6. Extension -> Confiner添加摄像机移动边界,为Background添加Collider,注意一定要点选is trigger选项

灯光

  1. 对Object右键light -> point light 添加点光源
  2. 为Plateform、Background添加materail
  3. Window -> Rendering ->light ->Enviroment -> Ambient Color,设置主场景灯光

动画

  1. Window -> Animator/Animation,打开窗口
  2. 添加script:PlayerAnimation如下
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerAnimation : MonoBehaviour
{
Animator animator;
PlayerMovement movement;
int groundID;
void Start()
{
animator = GetComponent<Animator>();
groundID = Animator.StringToHash("isOnGround");
// 获取父组件的脚本
movement = GetComponentInParent<PlayerMovement>();
}

void Update()
{
// 设置animatior的状态值
animator.SetFloat("speed", Mathf.Abs(movement.xVelocity));
// 使用编号对应赋值、字符串对应赋值
animator.SetBool(groundID, movement.isOnGround);
animator.SetBool("isJumping", movement.isJump);
animator.SetBool("isCrouching", movement.isCrouch);
animator.SetBool("isHanging", movement.isHanging);
}
}

使用BlendTree

  1. 在Animator的BaseLayer中右键 Create State -> From New Blend Tree
  2. 点击新的BlendTreeState,在Inspector -> Motion 添加Motion,添加每一个motion对应的animation,且将Parameter设置为自定义的state
  3. Threshold临界值选项,当对应parameter值复合某一threshold值时会播放该animation,注意垂直向上水平向右为正

image-20210820202829801

​ 上图中MidAir1~7为起跳到下落的动画

  1. 修改动画脚本如下
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerAnimation : MonoBehaviour
{
Animator animator;
PlayerMovement movement;
Rigidbody2D rb;
int speedID, groundID, jumpingID, crouchingID, hangingID, fallId;
void Start()
{
animator = GetComponent<Animator>();

// animatior parameter对应编号
speedID = Animator.StringToHash("speed");
groundID = Animator.StringToHash("isOnGround");
jumpingID = Animator.StringToHash("isJumping");
crouchingID = Animator.StringToHash("isCrouching");
hangingID = Animator.StringToHash("isHanging");
fallId = Animator.StringToHash("verticalVelocity");
// 获取父组件的脚本
movement = GetComponentInParent<PlayerMovement>();
rb = GetComponentInParent<Rigidbody2D>();
}

void Update()
{
// 设置animatior的状态值,两种方法(使用编号对应赋值、字符串对应赋值)
// animator.SetFloat("speed", Mathf.Abs(movement.xVelocity));
animator.SetFloat(speedID, Mathf.Abs(movement.xVelocity));
animator.SetBool(groundID, movement.isOnGround);
animator.SetBool(jumpingID, movement.isJump);
animator.SetBool(crouchingID, movement.isCrouch);
animator.SetBool(hangingID, movement.isHanging);
animator.SetFloat(fallId, rb.velocity.y);
}
}

音效

  1. 注意Animation窗口中对应动画,含有特定的animation event需要添加
  2. 添加AudioManager(Prefab),并为其添加script如下
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;

public class AudioManager : MonoBehaviour
{
static AudioManager current;

[Header("环境声音")]
public AudioClip ambientCilip;
public AudioClip musicClip;

[Header("角色音效")]
public AudioClip[] walkStepClips;
public AudioClip[] crouchStepClips;
public AudioClip jumpClip;
public AudioClip jumpVoiceClip;

public void Awake()
{
current = this;
}

}

​ 同时在AudioManager(Prefab)中选择相应的音效

  1. 添加AudioSource
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;

public class AudioManager : MonoBehaviour
{
// ...

AudioSource ambientSource, musicSource, fxSource, playerSource, voiceSource;

public void Awake()
{
// 单例模式
if(current != null)
{
Destroy(gameObject);
return;
}
// 方便在static方法中使用this
current = this;

// 防止场景加载时被销毁
DontDestroyOnLoad(gameObject);

// 使用代码添加AudioSource组件
ambientSource = gameObject.AddComponent<AudioSource>();
musicSource = gameObject.AddComponent<AudioSource>();
fxSource = gameObject.AddComponent<AudioSource>();
playerSource = gameObject.AddComponent<AudioSource>();
voiceSource = gameObject.AddComponent<AudioSource>();
}

public static void PlayFootstepAudio()
{
// 获得随机数
int index = Random.Range(0, current.walkStepClips.Length);
// 指定source的clip声音片段
current.playerSource.clip = current.walkStepClips[index];
// 播放
current.playerSource.Play();
}
}
  1. PlayerAnimation中调用方法
1
2
3
4
public void StepAudio()
{
AudioManager.PlayFootstepAudio();
}
  1. 其他音效同理,完整如下

AudioManager

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

public class AudioManager : MonoBehaviour
{
static AudioManager current;

[Header("环境声音")]
public AudioClip ambientClip;
public AudioClip musicClip;

[Header("角色音效")]
public AudioClip[] walkStepClips;
public AudioClip[] crouchStepClips;
public AudioClip jumpClip;
public AudioClip jumpVoiceClip;

// clip声音片段,source声音源
AudioSource ambientSource, musicSource, fxSource, playerSource, voiceSource;

public void Awake()
{
// 单例模式
if(current != null)
{
Destroy(gameObject);
return;
}
// 方便在static方法中使用this
current = this;

// 防止场景加载时被销毁
DontDestroyOnLoad(gameObject);

// 使用代码添加AudioSource组件
ambientSource = gameObject.AddComponent<AudioSource>();
musicSource = gameObject.AddComponent<AudioSource>();
fxSource = gameObject.AddComponent<AudioSource>();
playerSource = gameObject.AddComponent<AudioSource>();
voiceSource = gameObject.AddComponent<AudioSource>();

// 背景音乐不需要触发,直接播放
StartLevelAudio();
}

// 背景音乐
void StartLevelAudio()
{
// 打开循环
current.ambientSource.clip = current.ambientClip;
current.ambientSource.loop = true;
current.ambientSource.Play();

current.musicSource.clip = current.musicClip;
current.musicSource.loop = true;
current.musicSource.Play();
}

// 角色走动音效
public static void PlayFootstepAudio()
{
// 获得随机数
int index = Random.Range(0, current.walkStepClips.Length);
// 指定source的clip声音片段
current.playerSource.clip = current.walkStepClips[index];
// 播放
current.playerSource.Play();
}

// 角色下蹲音效
public static void PlayCrouchFootstepAudio()
{
int index = Random.Range(0, current.crouchStepClips.Length);
current.playerSource.clip = current.crouchStepClips[index];
current.playerSource.Play();
}

// 角色跳跃音效
public static void PlayJumpAudio()
{
current.playerSource.clip = current.jumpClip;
current.playerSource.Play();

current.voiceSource.clip = current.jumpVoiceClip;
current.voiceSource.Play();
}

}

PlayerAnimation

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerAnimation : MonoBehaviour
{
Animator animator;
PlayerMovement movement;
Rigidbody2D rb;
int speedID, groundID, jumpingID, crouchingID, hangingID, fallId;
void Start()
{
animator = GetComponent<Animator>();

// animatior parameter对应编号
speedID = Animator.StringToHash("speed");
groundID = Animator.StringToHash("isOnGround");
jumpingID = Animator.StringToHash("isJumping");
crouchingID = Animator.StringToHash("isCrouching");
hangingID = Animator.StringToHash("isHanging");
fallId = Animator.StringToHash("verticalVelocity");
// 获取父组件的脚本
movement = GetComponentInParent<PlayerMovement>();
rb = GetComponentInParent<Rigidbody2D>();
}

void Update()
{
// 设置animatior的状态值,两种方法(使用编号对应赋值、字符串对应赋值)
// animator.SetFloat("speed", Mathf.Abs(movement.xVelocity));
animator.SetFloat(speedID, Mathf.Abs(movement.xVelocity));
animator.SetBool(groundID, movement.isOnGround);
animator.SetBool(jumpingID, movement.isJump);
animator.SetBool(crouchingID, movement.isCrouch);
animator.SetBool(hangingID, movement.isHanging);
animator.SetFloat(fallId, rb.velocity.y);
}

public void StepAudio()
{
AudioManager.PlayFootstepAudio();
}

public void CrouchStepAudio()
{
AudioManager.PlayCrouchFootstepAudio();
}
}

​ 由于Animation中没有添加jump的event,所以直接在playerMovement中调用音效即可

死亡机制

  1. 添加新的cs代码如下,应用于角色,用于死亡条件判断
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerHealth : MonoBehaviour
{
// 预制死亡特效
public GameObject deathVFXPrefab;

int trapsLayer;

void Start()
{
// 获得对应Layer的编号
trapsLayer = LayerMask.NameToLayer("Traps");
}

// 如果一个碰撞体进入了触发器(当前角色已经被设定为isTrigger)则调用
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.layer == trapsLayer)
{
// Instantiate实例化,此处让特效显示
Instantiate(deathVFXPrefab,transform.position,transform.rotation);
// 让角色消失
gameObject.SetActive(false);
// 播放死亡音效
AudioManager.PlayDeathAudio();
}
}

}
  1. 添加死亡音效,注意在Inspector窗口中选择对应片段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    [Header("角色音效")]
public AudioClip[] walkStepClips;
public AudioClip[] crouchStepClips;
public AudioClip jumpClip;
public AudioClip jumpVoiceClip;
public AudioClip deathClip;
public AudioClip deathVoiceClip;

[Header("特效音效")]
public AudioClip deathFXClip;

// 死亡音效
public static void PlayDeathAudio()
{
current.playerSource.clip = current.deathClip;
current.playerSource.Play();

current.voiceSource.clip = current.deathVoiceClip;
current.voiceSource.Play();

current.fxSource.clip = current.deathFXClip;
current.fxSource.Play();
}
  1. 设置场景重置,在file -> build settings 中设置scene
1
2
3
4
5
6
using UnityEngine.SceneManagement;
private void OnTriggerEnter2D(Collider2D collision)
{
// 重置场景为当前场景
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}

视觉效果与相机抖动

Post Processing

  1. Window -> PackageManager -> 搜索插件Post Processing
  2. 为 Main Camera 添加Component -> Post-process Layer,创建一个专门的Layer并添加
  3. 创建一个Empty Object,命名为Global Post Processing同时添加上一步骤中的Layer,用于承载视觉效果(如果是单独物件添加特效直接添加即可)
  4. 为该对象添加组件 Post-process Volume,其中is Global 用于影响全局,否则添加collider设置影响范围即可
  5. 为该对象添加Profile,同时Add effect

Camera Shake

  1. 在之前添加的Cinemachine中, Inspector -> Extension ->Cinemachine Impulse Listener
  2. 在触发相机抖动效果的物件上添加组件 Cinemachine Collision Impulse Source(此为碰撞触发,可选其他source),
  3. 为该组件添加Raw Signal即触发抖动时的效果,可以自行设置,选择TriggerObjectFilter中的LayerMask,即碰撞当前对象的Layer
  4. 其余如SignalShape中的FrequencyGain越大速度越快……

GameManager

死亡过渡以及相关计数

  1. 添加Script,一定命名为GameManager,如下编写
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
static GameManager gm;

private void Awake()
{
if(gm != null)
{
Destroy(gameObject);
return;
}
gm = this;
DontDestroyOnLoad(this);
}

public static void PlayerDied()
{
// 经过一段时间后调用方法
gm.Invoke("RestartScene", 1.5f);
}

// 重启场景
void RestartScene()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
  1. 添加Empty Object并应用GameManager Script
  2. 添加SceneFader Script,编写如下,应用于Fader(UI -> Image对象,含有过渡动画,其中Animator中有Fade参数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SceneFader : MonoBehaviour
{
Animator anim;
int faderID;

private void Start()
{
anim = GetComponent<Animator>();
faderID = Animator.StringToHash("Fade");
}
public void fadeOut()
{
anim.SetTrigger(faderID);
}
}
  1. 在GameManager中添加如下代码,这里mark一下,上网搜了其他的模式和方法,都可以实现,视频里讲的是观察者模式
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
static GameManager gm;
SceneFader sceneFader;

private void Awake()
{
if(gm != null)
{
Destroy(gameObject);
return;
}
gm = this;
DontDestroyOnLoad(this);

}

// 用于通知GameManager接收一个SceneFader实例
//public static void RegisterSceneFader(SceneFader fader)
//{
// gm.sceneFader = fader;
//}

public static void PlayerDied()
{
//SceneFader.sf.fadeOut();
SceneFader.sf.fadeOut();

// 以下为另一种调用其他类的方法实现方法,还有其他方法或模式可以实现
//GameObject.Find("Fader").SendMessage("fadeOut");

// 经过一段时间后调用方法
gm.Invoke("RestartScene", 1.2f);
}

// 重启场景
void RestartScene()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}

}
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SceneFader : MonoBehaviour
{
// 这里使用单例模式,能让其他类调用
public static SceneFader sf;

Animator anim;
int faderID;

private void Awake()
{
if (sf != null)
{
return;
}
sf = this;
}

private void Start()
{
anim = GetComponent<Animator>();
faderID = Animator.StringToHash("Fade");

//GameManager.RegisterSceneFader(this);
}
public void fadeOut()
{
anim.SetTrigger(faderID);
}
}
  1. 添加收集物的相关脚本
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
static GameManager gm;
//SceneFader sceneFader;
List<Orb> orbs;

public int orbNum,deathNum;

private void Awake()
{
if(gm != null)
{
Destroy(gameObject);
return;
}
gm = this;
orbs = new List<Orb>();
DontDestroyOnLoad(this);

}

private void Update()
{
orbNum = gm.orbs.Count;
}

// 用于通知GameManager接收一个SceneFader实例
//public static void RegisterSceneFader(SceneFader fader)
//{
// gm.sceneFader = fader;
//}

public static void RegisterOrb(Orb orb)
{
if (!gm.orbs.Contains(orb))
{
gm.orbs.Add(orb);
}
}

public static void PlayerDied()
{
//SceneFader.sf.fadeOut();
SceneFader.sf.fadeOut();

// 以下为另一种调用其他类的方法实现方法,还有其他方法或模式可以实现
//GameObject.Find("Fader").SendMessage("fadeOut");

// 经过一段时间后调用方法
gm.Invoke("RestartScene", 1.2f);

gm.deathNum++;
}

public static void PlayerGetOrb(Orb orb)
{
if (gm.orbs.Count == 0 || !gm.orbs.Contains(orb))
return;
gm.orbs.Remove(orb);
}

// 重启场景
void RestartScene()
{
gm.orbs.Clear();
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}

}
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Orb : MonoBehaviour
{
int playerLayer;

public GameObject explosionVFXPrefab;

void Start()
{
playerLayer = LayerMask.NameToLayer("Player");
GameManager.RegisterOrb(this);
}

private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.layer == playerLayer)
{
GameManager.PlayerGetOrb(this);
Instantiate(explosionVFXPrefab, transform.position, transform.rotation);
gameObject.SetActive(false);
AudioManager.PlayOrbAudio();
}
}

}

Door机关

收集物品到一定数量后打开机关

  1. 编写Door Script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Door : MonoBehaviour
{
Animator anim;
int openID;

void Start()
{
anim = GetComponent<Animator>();
openID = Animator.StringToHash("Open");
GameManager.RegisterDoor(this);
}

public void Open()
{
anim.SetTrigger(openID);
AudioManager.PlayOpenDoorAudio();
}


}
  1. 编写AudioManager
1
2
3
4
5
6
// 打开门音效
public static void PlayOpenDoorAudio()
{
current.fxSource.clip = current.doorFXClip;
current.fxSource.PlayDelayed(1f);
}

UI Manager

显示text

  1. 编写一个UI Manager Object,添加script如下
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class UIManager : MonoBehaviour
{
static UIManager instance;

// 获得Text文本框,记得在Inspector中选择相应的text
public TextMeshProUGUI orbText, timeText, deathText, gameOverText;

private void Awake()
{
if(instance != null)
{
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(this);
}

// 更新Orb数量
public static void UpdateOrbUI(int orbCount)
{
// 设置text
instance.orbText.text = orbCount.ToString();
}

// 其余text类似
}
  1. 在GameManager中适当位置调用UIManager相应方法即可
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class UIManager : MonoBehaviour
{
static UIManager instance;

// 获得Text文本框,记得在Inspector中选择相应的text
public TextMeshProUGUI orbText, timeText, deathText, gameOverText;


private void Awake()
{
if(instance != null)
{
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(this);
}

// 更新Orb数量
public static void UpdateOrbUI(int orbCount)
{
// 设置text
instance.orbText.text = orbCount.ToString();
}

// 更新Death数量
public static void UpdateDeathUI(int deathNum)
{
// 设置text
instance.deathText.text = deathNum.ToString();
}

// 更新Time
public static void UpdateTimeUI(int time)
{
int minutes = time / 60;
int seconds = time % 60;

// 设置text
instance.timeText.text = minutes.ToString("00")+":"+seconds.ToString("00");
}
}

评论




🧡💛💚💙💜🖤🤍