await 关键字

在C#编程中,await 关键字用于异步编程,它通常与 async 关键字一起使用。await 用于暂停当前方法的执行,直到等待的异步任务完成。这样可以在不阻塞主线程的情况下执行耗时操作,如I/O操作或网络请求。

例如,假设有一个异步方法 GetDataAsync(),你可以这样使用 await:

1
2
3
4
5
6
public async Task<string> FetchDataAsync()
{
// 假设 GetDataAsync 是一个返回 Task<string> 的异步方法
string data = await GetDataAsync();
return data;
}

在这个例子中,await GetDataAsync() 会暂停 FetchDataAsync 方法的执行,直到 GetDataAsync 完成并返回结果。同时,控制权会返回给调用者,允许程序继续执行其他操作。

在C#中使用await时,有几个关键要点需要注意,以确保异步代码的正确性和高效性:

async 方法必须返回 Task 或 Task<T>

使用 await 的方法必须标记为 async,并且返回类型应为 Task(无返回值)或 Task<T>(有返回值)。

例如:

1
2
3
4
5
public async Task<int> CalculateAsync()
{
await Task.Delay(1000); // 模拟耗时操作
return 42;
}

避免阻塞调用

不要在 async 方法中使用 .Result 或 .Wait(),这会导致死锁或阻塞线程。

例如,避免以下写法:

1
var result = SomeAsyncMethod().Result; // 错误:可能导致死锁

正确处理异常

异步方法中的异常会被包装在 Task 中,可以通过 try-catch 捕获。

例如:

1
2
3
4
5
6
7
8
try
{
await SomeAsyncMethod();
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
阅读全文 »

Mesh 网格

Mesh Filter

官方手册地址:Mesh Filter

Mesh Filter 组件包含对网格的引用。该组件与同一个游戏对象上的 Mesh Renderer 组件配合使用;Mesh Renderer 组件渲染 Mesh Filt组件引用的网格。用于将网格数据应用到 3D 模型上。它是实现 3D 模型的重要组成部分之一,可以定义模型的形状和结构。

Mesh Filter 可以将网格数据应用到 3D 模型上,从而定义模型的形状和结构。它通过读取网格数据,包括顶点、三角形面片等信息,来定义模的形状和结构。这些网格数据可以通过外部工具(例如 Blender、Maya 等)创建,也可以通过代码动态生成。

Mesh Filter 提供了许多属性和方法,例如网格数据、法线、UV 坐标等,可以用来控制模型的形状和结构。另外,Mesh Filter
还支持动态修改网格数据,从而实现更加灵活的模型变形效果。

Mesh Renderer

官方手册地址:Mesh Renderer

Mesh Renderer 组件用于渲染网格。该组件与同一个游戏对象上的 Mesh Filter 组件配合使用;Mesh Renderer 组件渲染 Mesh Filter 组件引用的网格。

用于将 3D 模型渲染到屏幕上。它是游戏中 3D 模型的重要组成部分之一,可以让游戏中的角色、场景和物品等立体化呈现。

Skinned Mesh Renderer

官方手册地址:Skinned Mesh Renderer

Skinned Mesh Renderer 组件渲染一个可变形的网格。可变形网格包括蒙皮网格(具有骨骼和绑定姿势的网格)、具有混合形状的网格和运行布料模拟的网格。用于将骨骼动画应用到 3D 模型上。它是实现角色动画的重要组件之一,可以让游戏角色在运动中更加自然和流畅。

Skinned Mesh Renderer 可以在 3D 模型上应用骨骼动画,使得模型能够根据动画数据进行变形,从而实现角色的动画效果。它使用骨骼系统来管理骨骼和骨骼权重,并将这些信息应用到 3D 模型上,从而实现动画的变形效果。

Skinned Mesh Renderer 提供了许多属性和方法,例如骨骼系统、骨骼权重、动画剪辑、动画速度等,可以用来控制动画的播放和效果。另外,Skinned Mesh Renderer 还支持动态修改骨骼权重,从而实现更加灵活的动画效果。

Skinned Mesh Renderer 是 Unity 中实现角色动画的重要组件之一,可以让游戏角色在运动中更加自然和流畅。它是游戏开发中不可或缺的工具,可以提高游戏的可玩性和视觉效果。

Text Mesh

官方手册地址:Text Mesh

用于将文本转换为 3D 网格,以便进行高效的渲染和交互。与 Unity 自带的 Text 组件和 TextMeshPro-Text 组件相比,Text Mesh 更加轻量级和高效,适用于需要大量文本渲染的场景。

Text Mesh 可以用来渲染各种文本内容,例如游戏中的标签、计分板、物品名称等等。它能够将文本转换为 3D 网格,并使用 GPU 进行加速渲染,从而能够高效地处理大量文本内容。

Text Mesh 组件提供了许多属性,例如字体、字体大小、颜色、对齐方式、行距、字间距等,可以用来调整文本的排版和格式化。另外,Text Mesh 还支持动态生成文本,可以通过代码来实现动态更新文本内容,从而满足游戏中各种动态文本渲染需求。

Text Mesh 是 Unity 中非常高效的文本渲染组件,适用于需要大量文本渲染的场景,例如大量的标签、计分板、物品名称等等。

阅读全文 »

移动

当gameobject被加上rigidbody2d组件时,如果想移动它,不要这样写:

1
2
3
4
void Update()
{
this.transform.Translate (Vector3.left * speed * Time.deltaTime);
}

这种写法,主要针对无rigidbody2d的gameobject对象,否则,性能很差。
正确的写法:

1
2
3
4
5
6
7
8
9
void FixedUpdate()
{
rigidbody2D.MovePosition (rigidbody2D.position + Vector2.left * speed * Time.fixedDeltaTime);
}

void Start ()
{
rigidbody2D.velocity = Vector2.left * speed;
}

Body Type属性

rigidbody2d最为重要的一个属性,不同的选项,不同的物理效果:

Dynamic(动态的)

具有完全的物理特性;
会与所有类型的Rigidbody2D进行碰撞;
是最常用的Rigidbody2D类型、也是默认的类型;
是最耗费性能的类型;

Kinematic(运动学)

仅在用户控制下运动,不会受到重力和AddForce等力相关函数的影响;
只与Dynamic的Rigidbody2D发生碰撞(不勾选Use Full Kinematic Contacts);
不能通过力或碰撞改变速度,但是可以设置其速度和位置、旋转;
比Dynamic性能高;

Static (静态的)

具有无限质量、不可移动的物体;
velocity、AddForce、gravity、MovePosition、MoveRotation都不可用;
只与Dynamic的Rigidbody2D发生碰撞;
性能最高;

Simulated 物理模拟

阅读全文 »

游戏说明

游戏开始,下方随机生成不同品质小人,在场景中来回走动,不同品质的小人,移动速度不一样,被抓到后获得的积分也不一样。按住移动按钮,上方机械手臂会左右来回移动,当松开按钮,机械手臂会掉下来,当手臂下落碰到场景中小人时,检测当前手臂抓取范围内的小人,在抓取范围内的小人会被抓走。然后计算获得分数,如果机械手臂抓空,则碰到地面后手臂也会自动回弹,每轮游戏有4次抓取机会。

功能实现

移动的小人

创建移动小人AmusementParkDollMachineGameRole,跟据不同品质小人,移动速度不同实现小人在可控范围内随机位置移动。在移动过程中通过修改sortingOrder值来调整小人在不同位置的层级显示。小人在移动是播放walk动作,被抓住时拖放drag动作,同时将小人从场景节点移动到机械手臂抓取小人的节点内,当手臂上升时,小人跟着手臂一起向上移动,抓取结束移除被抓取的小人。

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
---待抓取的游戏小人
---@class AmusementParkDollMachineGameRole
local m = {
View = nil,
---@type AmusementParkDollMachineRoleData
RoleData = nil,
---@type Spine.Unity.SkeletonGraphic
Animation = nil,
---@type number
EntityId = 0,
---@type Vector3 下一个移动位置
NextPos = Vector3.New(0,0,0),
---@type boolean 是否在移动中
IsMoving = false,
---@type number 下一次开始移动的时间
NextMovieTimer = 0,
---@type boolean 小人是否已成功创建
IsCreateRole = false,
---@type boolean 小人是否被抓住
IsCatch = false,
---@type UnityEngine.Collider2D 小人Collider
RoleCollider = nil,
RoleObj = nil,
}

function m.New(view)
local obj = Clone(m)
obj:Init(view)
return obj
end

function m:Init(view)
self.View = view
LuaCodeInterface.BindOutlet(self.View, self)
self.OnShowSpineSuccessDelegate = function(sender, args) self:OnShowSpineSuccess(sender, args) end

self.RoleCollider = self.View:GetComponent(typeof(UnityEngine.Collider2D))

self:AddListener()
end

function m:AddListener()
GameEntry.LuaEvent:Subscribe(UnityGameFramework.Runtime.ShowEntitySuccessEventArgs.EventId, self.OnShowSpineSuccessDelegate)
end

function m:RemoveListener()
GameEntry.LuaEvent:Unsubscribe(UnityGameFramework.Runtime.ShowEntitySuccessEventArgs.EventId, self.OnShowSpineSuccessDelegate)
end

---界面轮询。
---@param elapseSeconds number
---@param realElapseSeconds number
---@return void
function m:OnUpdate(elapseSeconds, realElapseSeconds)
if not self.IsCreateRole then
return
end
if self.RoleData == nil then
return
end
if self.IsCatch then
return
end
if not self.IsMoving then
if PlayerModule.GetServerTime() > self.NextMovieTimer then
self:StartMove()
end
return
end
local direction = self.NextPos - self.View.transform.localPosition
if direction.magnitude > self.RoleData.roleRareSpeed then
self.View.transform:Translate(direction.normalized * self.RoleData.roleRareSpeed * UnityEngine.Time.deltaTime)
self:ChangeRoleOrderZ()
else
self:StopMove()
end
end

function m:ChangeOrder()
if self.RoleObj then
local canvas = self.View:GetComponentInParent(typeof(UnityEngine.Canvas))
setGameObjectLayers(self.RoleObj.transform, "UI")
local renderers = self.RoleObj:GetComponentsInChildren(typeof(UnityEngine.Renderer))
for i = 0, renderers.Length - 1 do
renderers[i].sortingLayerID = LuaCodeInterface.SortingLayerNameToID("Default")
renderers[i].sortingOrder = canvas.sortingOrder + 1
end
end
end

function m:OnShowSpineSuccess(sender, args)
if self.EntityId == args.Entity.Id then
self.RoleObj = args.Entity.gameObject
self.Animation = args.Entity.gameObject:GetComponentInChildren(typeof(Spine.Unity.SkeletonAnimation))
self.Animation.state:SetAnimation(0,"idle", true)
self:StartMove()
self.IsCreateRole = true
self:ChangeOrder()
end
end

---获取一个随机位置 坐标Z控制小人显示层级
function m:GetRandomPos()
local rx = math.random(-self.View.transform.parent.rect.w/2,self.View.transform.parent.rect.w/2)
local ry = math.random(-self.View.transform.parent.rect.h/2,self.View.transform.parent.rect.h/2)
return Vector3.New(rx,ry,0)
end

---开始移动
function m:StartMove()
self.IsMoving = true
self.NextPos = self:GetRandomPos()
if self.Animation then
self.Animation.state:SetEmptyAnimation(0, 0)
self.Animation.state:SetAnimation(0, "walk", true)
end
self:RefreshWalkFaceTo()
end

---移动到目标位置停止移动
function m:StopMove()
self.IsMoving = false
self.View.transform.localPosition = self.NextPos
self.NextMovieTimer = math.random(0,4) + PlayerModule.GetServerTime()
if self.Animation then
self.Animation.state:SetAnimation(0,"idle", true)
end
end

---播放抓住状态
function m:CatchState()
self.IsMoving = false
self.IsCatch = true
if self.Animation then
self.Animation.AnimationName = nil
self.Animation.state:SetEmptyAnimation(0, 0)
self.Animation.state:SetAnimation(0,"drag", true)
end
end

---刷新移动朝向
function m:RefreshWalkFaceTo()
local checkRet = self.NextPos.x - self.View.transform.localPosition.x
local localScale = self.View.transform.localScale
if checkRet > 0 then
localScale.x = -math.abs(localScale.x)
else
localScale.x = math.abs(localScale.x)
end
self.View.transform.localScale = localScale
end

---@param roleData AmusementParkDollMachineRoleData
function m:SetData(roleData)
self.RoleData = roleData
self.IsCreateRole = false
self.IsCatch = false
for i = 1, 4 do
self["Quality"..i]:SetActive(self.RoleData.roleRare == i)
end
self.View.name = self.RoleData.cid
local pos = self:GetRandomPos()
self.View.transform.localPosition = pos
self:ChangeRoleOrderZ()
self.NextPos = self:GetRandomPos()
self:ShowSpine()
end

---改变小人移动Z值(移动是层级关系)
function m:ChangeRoleOrderZ()
local pos = self.View.transform.localPosition
---设置小人移动的Z值
self.OrderZ.transform.localPosition = Vector3.New(0,0,(pos.y + 150)/100)
end

function m:ShowSpine()
self:HideEntity()
self.EntityId = GameEntry.Entity:GenerateEntityID()
GameEntry.Entity:ShowUIElement(self.EntityId, self.RoleData:GetCfgData().PlayerSpine, 0, self.SpineRoot.transform, Vector3.zero)
end

function m:HideEntity()
if self.EntityId ~= 0 then
GameEntry.Entity:HideEntity(self.EntityId)
self.EntityId = 0
end
end

function m:Dispose()
AssetUtil.UnloadAsset(self)
self:RemoveListener()
self:HideEntity()

LuaCodeInterface.ClearOutlet(self.View, self)
UnityEngine.Object.Destroy(self.View)
self.View = nil
end

return m
阅读全文 »

Lua实现Unity象素鸟小游戏要点记录:

游戏玩法简介

不用物理引擎实现像素鸟在障碍物之间飞行,通过配表配置小鸟向下的加速度和点击一次屏幕给小鸟赋值一次初始速度和向上加速度来模拟小鸟飞行。障碍物根据配置难度在固定距离随机生成。

功能实现简述

飞行障碍实现

创建AmusementParkBirdObstacleCell障碍控件。游戏运行时,根据固定的配置距离,生成不同难度的障碍实例。障碍物分为上,下两部份,中间有可穿行的空隙。

AmusementParkBirdObstacleCell

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
---@class AmusementParkBirdObstacleCell
local m = {
View = nil,
---@type number 移动速度
Speed = 0,
---@type UnityEngine.Collider2D
TopCollider2D = nil,
---@type UnityEngine.Collider2D
BottomCollider2D = nil,
}

function m.New(view)
local obj = Clone(m)
obj:Init(view)
return obj
end

function m:Init(view)
self.View = view
LuaCodeInterface.BindOutlet(self.View, self)

self.TopCollider2D = self.View.transform:Find("Top").gameObject:GetComponent(typeof(UnityEngine.Collider2D))
self.BottomCollider2D = self.View.transform:Find("Bottom").gameObject:GetComponent(typeof(UnityEngine.Collider2D))

self:AddListener()
end

function m:AddListener()

end

function m:RemoveListener()

end

---界面轮询。
---@param elapseSeconds number
---@param realElapseSeconds number
---@return void
function m:OnUpdate(elapseSeconds, realElapseSeconds)
self.View.transform:Translate(Vector3.New(self.Speed * UnityEngine.Time.deltaTime * -1, 0, 0))
end

---@param speed number
function m:SetData(speed)
self.Speed = speed
end

function m:Dispose()
AssetUtil.UnloadAsset(self)
self:RemoveListener()

LuaCodeInterface.ClearOutlet(self.View, self)
UnityEngine.Object.Destroy(self.View)
self.View = nil
end

return m
阅读全文 »

用Lua实现Unity一笔画小游戏要点记录:

游戏玩法简介

选择不同难度关卡开始游戏,每一关有多个方块小格,可以从任意一个方块格作为起点开始连接到下一个方块格,下一个方块格必须与起始格上下左右相邻。同一个方块格不能重复连接,成功连接后,目标方块格又成为新的起始方块格。以此类推直至将所有方块格连完。

功能实现简述

关卡难度

关卡难度选择,不同关卡制作不同prefab预设。prefab预设摆放所有要连接的格子位置信息节点。选择关卡开始游戏时,在每一个位置信息节点下生成一个游戏连接格实例对象。创建方格数据结构 AmusementParkOneConnectBlockData 和 视图显示控件AmusementParkOneConnectBlockCell

AmusementParkOneConnectBlockData

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
---@class AmusementParkOneConnectBlockData
local m = {
---@type number
Index = 0,
---@type number
X = 0,
---@type number
Y = 0,
---@type number 0 未连接,1 已连接
State = 0,
---@type boolean 最新连通
IsFirst = false,
}

function m.New(index,x,y)
local o = Clone(m)
o:Init(index,x,y)
return o
end

function m:Init(index,x,y)
self.Index = index
self.X = tonumber(x)
self.Y = tonumber(y)
end

return m

AmusementParkOneConnectBlockCell

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
---@class AmusementParkOneConnectBlockCell
local m = {
View = nil,
---@type AmusementParkOneConnectBlockData
BlockData = nil,
---@type Vector3
StartPos = nil,
---@type function
OnBeginDragCallBack = nil,
---@type function
OnDragCallBack = nil,
---@type UnityEngine.Collider2D
BlockCollider = nil,
---@type UnityEngine.Collider2D
DrgCollider = nil,
}

function m.New(view)
local obj = Clone(m)
obj:Init(view)
return obj
end

function m:Init(view)
self.View = view
LuaCodeInterface.BindOutlet(self.View, self)

self.DelegateBtnSelf = function() self:OnClickBtnSelf() end

self.BlockCollider = self.View:GetComponent(typeof(UnityEngine.Collider2D))
self.DrgCollider = self.ImgDrag:GetComponent(typeof(UnityEngine.Collider2D))

self.StartPos = self.ImgDrag.transform.position
self.ImgDrag:GetComponent("UIDrag").onBeginDrag = function(eventData) self:OnBeginDrag(eventData) end
self.ImgDrag:GetComponent("UIDrag").onDrag = function(pointerEventData) self:OnDrag(pointerEventData) end
self.ImgDrag:GetComponent("UIDrag").onReachTargetSuccess = function(eventData) self:OnDragSuccess(eventData) end
self.ImgDrag:GetComponent("UIDrag").onReachTargetFailure = function(eventData) self:OnFailure(eventData) end

self:AddListener()
end

function m:AddListener()
self.View:GetComponent("Button").onClick:AddListener(self.DelegateBtnSelf)
end

function m:RemoveListener()
self.View:GetComponent("Button").onClick:RemoveListener(self.DelegateBtnSelf)
end

function m:OnClickBtnSelf()
if self.OnBeginDragCallBack then
self.OnBeginDragCallBack(self)
end
end

function m:OnBeginDrag()
if self.OnBeginDragCallBack then
self.OnBeginDragCallBack(self)
end
end

function m:OnDrag(eventData)
if self.OnDragCallBack then
self.OnDragCallBack(self)
end
end

function m:OnDragSuccess(eventData)
self.ImgDrag.transform.position = self.StartPos
end

function m:OnFailure(eventData)
self.ImgDrag.transform.position = self.StartPos
end

function m:UpdateView()
self.ImgDefault:SetActive(self.BlockData.State == 0)
self.ImgCheck:SetActive(self.BlockData.State == 1)
self.ImgTag:SetActive(self.BlockData.IsFirst)
end

---@param blockData AmusementParkOneConnectBlockData
function m:SetData(blockData)
self.BlockData = blockData
self.View.name = self.BlockData.Index
self:UpdateView()
end

function m:Dispose()
AssetUtil.UnloadAsset(self)
self:RemoveListener()
self.OnBeginDragCallBack = nil
self.OnDragCallBack = nil

LuaCodeInterface.ClearOutlet(self.View, self)
UnityEngine.Object.Destroy(self.View)
self.View = nil
end

return m

阅读全文 »