基于Unity2019最新ECS架构开发MMO游戏笔记13
- UnityMMO选人流程
- 游戏杂谈
- 准备工作:
- 选人流程
- 选择角色+创建角色
- 小结
- 更新计划
- 作者的话
- ECS系列目录
- ECS官方示例1:ForEach
- ECS官方案例2:IJobForEach
- ECS官方案例3:IJobChunk
- ECS官方案例4:SubScene
- ECS官方案例5:SpawnFromMonoBehaviour
- ECS官方案例6:SpawnFromEntity
- ECS官方案例7:SpawnAndRemove
- ECS进阶:FixedTimestepWorkaround
- ECS进阶:Boids
- ECS进阶:场景切换器
- ECS进阶:MegaCity0
- ECS进阶:MegaCity1
- UnityMMO资源整合&服务器部署
- UnityMMO选人流程
- UnityMMO主世界
UnityMMO选人流程
游戏杂谈
官方MegaCity案例结束了,其实还有很多细节没有去细讲,因为我比较着急想要继续学习大鹏的UnityMMO案例,这个案例比较接近我想要开发的游戏。我和大鹏沟通过了,大鹏的UnityMMO是打算弄成塞达尔或者奥德赛那样,有多种多样的玩法。
而我,更想开发一款大世界生存动作手游,这款手游将结合《剑灵》的战斗模式设计,这是我比较喜欢动作设计模式。但是我会在操作上做大幅度减法,实际上是一种极简主义,因为手机的操作局限性,我不得不做这种大减法。
在做减法的同时让我想起了儿时常玩的街机三国,这款游戏对我影响很深,我至今仍然记得触发连招的快感。我企图还原那种快感,于是我的解决方案就出来了,街机三国的触发模式+剑灵的战斗机制=我的玩法。
在这基础上,我会利用方向键+按钮的组合键触发出无限连招,这套连招有无数分支,根据触发的前置技能的不同,而后续的连招走向一个独特的分支。例如,刺客的袖箭(→+A)触发,命中(所有无锁定,比较考验操作,也就是说技能完全可能丢空)敌人后会有几率致盲 or 流血 or 中毒 or 眩晕(不同的秘籍会有不同的效果),不同的效果成了下一套技能的前置buff,例如致盲会触发背刺(←+A+B),所以玩家需要关注敌人的状态,从而完成连招。
后续我会写详细的设计文档,把无限连招的玩法完善出来。
在故事和关卡设计上我还有很多想法,例如前所未闻的AI怪物,我们常规的玩法怪物都比较单纯,甚至你会觉得无聊,刷怪只是刷怪。但是我想在这方面做创新,例如怪物除了短期仇恨意外,还有长期仇恨。它们会记住你,并在见到你的时候回忆起来:哎哟,你小子又来找茬来了,上次从老子这里抢走的银鳞胸甲是时候物归原主了。
是的,我想设计与众不同的怪物,他们会有自己的情感(剧情),台词(个性),生存斗争(我会在游戏中设计生存模块,仿饥荒,饥荒中玩家面临生存危机,我将生存危机同样应用到怪物身上,这回是非常有意思的尝试)。
怪物还会主动参与玩家互动,例如抢夺玩家的食材,这是为了生存,饥饿值会促使怪物寻找食物。怪物还会抢夺装备,是的,它们也要武装自己,不仅如此,还会强化,甚至制作装备(根据智力来驱动,不同类型的怪物,特长不同)。
我还有很多想法,例如游戏的生态系统,食物链,轮回系统等等,这些设计,我想很多人想过,但是目前还没有成品的游戏做到。这些想法促使我不断学习和实践,我的内心深处特别想要完成这款特别的游戏,我常常想,这是款超级好玩的游戏。
啰嗦了这么多,想要做成却不是那么容易的事情,总而言之只要我们一步步向着目标前进,总有一天会实现的,一起加油吧!
准备工作:
0下载Unity编辑器(2019.1.4f1 or 更新的版本),if(已经下载了)continue;
1大鹏将项目代码和资源拆分成两部分,所以我们需要分别下载,然后再整合。
命令行下载UnityMMO,打开Git Shell输入:git clone https://github.com/liuhaopen/UnityMMO.git --recurse
下载完成后,继续输入:git clone https://github.com/liuhaopen/UnityMMO-Resource.git --recurse
or 点击UnityMMO和UnityMMO-Resource分别下载Zip压缩包
if(已经下载了)continue;
2如果下载的是压缩包,需要先将两个压缩包分别进行解压。然后打开UnityMMO-Resource并把Assets/AssetBundleRes及其meta文件复制到UnityMMO项目的Assets目录里,接下来将UnityMMO添加到Unity Hub项目中;
3用Unity Hub打开大鹏的开源项目:UnityMMO,等待Unity进行编译工作;
4打开项目后,我们发现还需要下载Third Person Controller - Basic Locomotion FREE插件,这个简单,直接在资源商店找到下载导入即可,然后在Assets/XLuaFramework下找到main场景,打开该场景。
选人流程
梳理下UnityMMO的进度好了,我们完成了游戏资源的整合和服务器部署,大鹏写了启动流程和登录流程,所以我们已经成功进入了游戏的选人场景,如下图所示:
我的Shader出了问题,因为使用的是Unity2019.1.12f,这和大鹏的版本不一致。所以角色变绿了,很尴尬,目前还没有解决,毕竟美术不是我的主攻方向。那么我们这一篇来解析一下选择角色的流程,当登录成功后会直接调用LoginController.lua下面的回调方法,因为对登录成功事件进行了绑定:
--绑定登录成功事件回调函数LoginSucceed
self.login_succeed_handler = GlobalEventSystem:Bind(LoginConst.Event.LoginSucceed, LoginSucceed)
这里要捎带提一下LuaFrameWork,非常成熟的热更新框架,后面会抽时间系统学习一下。这里的GlobalEventSystem:Bind方法可以将系统事件和回调函数绑定起来,在对应事件触发后,调用对应的回调函数。
登录成功后的回调函数如图所示,By the way,这里顺便推荐一下Anders研发的Lua IDE,名字叫做LuaPerfect,非常好用!
回到我们的代码,LoginSucceed的逻辑很清楚:
- 登录成功,向服务器请求角色列表:
--向服务器发送获取角色列表请求,回调函数on_ack
NetDispatcher:SendMessage("account_get_role_list", nil, on_ack)
- 服务器返回消息,触发回调函数
local on_ack = function ( ack_data )
print("Cat:LoginController [start:27] ack_data:", ack_data)
PrintTable(ack_data)
print("Cat:LoginController [end]")
--获取角色列表的数据
local role_list = ack_data.role_list
--设置角色列表数据,后面会在UI上显示
LoginModel:GetInstance():SetRoleList(role_list)
--如果登录窗口开着,则关闭登录窗口
if self.loginView then
UIMgr:Close(self.loginView)
self.loginView = nil
end
--如果角色列表不为空,且长度大于0
if role_list and #role_list > 0 then
--已有角色就先进入选择角色界面
local view = require("Game/Login/LoginSelectRoleView").New()
UIMgr:Show(view)
else
--还没有角色就先进入创建角色界面
local view = require("Game/Login/LoginCreateRoleView").New()
UIMgr:Show(view)
end
end
- 客户端显示选择角色界面或创建角色界面,代码如上
- 于是就进入了最开始截图的选人画面
我之前对lua不是很熟悉,这两天恶补了一下,总算是打通了Lua语言,算是入门级别吧。
选择角色+创建角色
创建角色的逻辑全部在LoginCreateRoleView.lua脚本中:
local LoginCreateRoleView = BaseClass()
--实例化的时候调用
function LoginCreateRoleView:DefaultVar( )、
--显示背景
require("Game/Login/LoginSceneBgView"):SetActive(true)
--返回配置
return {
UIConfig = {
prefab_path = "Assets/AssetBundleRes/ui/login/LoginCreateRoleView.prefab",
canvas_name = "Normal",
components = {
{UI.HideOtherView},
{UI.DelayDestroy, {delay_time=5}},
},
},
select_sex = 1,
is_first_select = true,
}
end
--加载后获取对应的UI组件
function LoginCreateRoleView:OnLoad( )
local names = {
"head_scroll/Viewport/head_con","close:obj","create_role:obj","item_con","head_scroll","role_tip:img","random:obj","role_name:input","effect","select_role_con:raw",
}
UI.GetChildren(self, self.transform, names)
self.transform.sizeDelta = Vector2.zero
--添加事件
self:AddEvents()
--初始化视图
self:InitView()
--加载角色模型
self:LoadRolesModel()
end
--随机加载角色模型
function LoginCreateRoleView:LoadRolesModel( )
local sex = math.random(1, 2)
self:SetCurSelectSex(sex)
end
--初始化创建角色的视图
function LoginCreateRoleView:InitView( )
self.sex_item_com = self.sex_item_com or UIMgr:AddUIComponent(self, UI.ItemListCreator)
local info = {
data_list = {1,2},
item_con = self.item_con,
prefab_path = ResPath.GetFullUIPath("login/LoginCreateRoleItem.prefab"),
item_height = 128,
space_y = 5,
scroll_view = self.item_con,
child_names = {"sex:txt","role_head:raw","bg:img:obj",},
on_update_item = function(item, i, v)
item.sex_txt.text = v==1 and "男" or "女"
item.sex_value = v
if not item.UpdateSelect then
item.UpdateSelect = function(item)
local cur_is_select = self.select_sex==v
UIHelper.SetImage(item.bg_img, cur_is_select and "login/login_role_item_bg_sel.png" or "login/login_role_item_bg_nor.png", true)
end
end
item:UpdateSelect()
if not item.UpdateHead then
item.UpdateHead = function(item)
local headRes = ResPath.GetRoleHeadRes(v, 0)
UIHelper.SetRawImage(item.role_head_raw, headRes)
end
end
item:UpdateHead()
if not item.had_bind_click then
item.had_bind_click = true
local on_click = function ( )
print('Cat:LoginCreateRoleView.lua[66] item.sex_value', item.sex_value)
self:SetCurSelectSex(item.sex_value)
end
UIHelper.BindClickEvent(item.bg_obj, on_click)
end
end,
}
self.sex_item_com:UpdateItems(info)
end
--设置当前选择角色的性别
function LoginCreateRoleView:SetCurSelectSex( sex )
print('Cat:LoginCreateRoleView.lua[75] sex', sex)
if self.select_sex == sex and not self.is_first_select then
return
end
self.is_first_select = false
self.select_sex = sex
self.sex_item_com:IterateItems(function(item, i)
if item.UpdateSelect then
item:UpdateSelect()
end
end)
-- self:UpdateRoleHead()
UIHelper.SetImage(self.role_tip_img, "login/login_role_tip_"..sex..".png", true)
self:OnClickRandomName()
self:UpdateRoleMesh(sex)
-- self:PlayRoleSound(sex)
end
--更新角色
function LoginCreateRoleView:UpdateRoleMesh( sex )
local show_data = {
showType = UILooksNode.ShowType.Role,
showRawImg = self.select_role_con_raw,
body = 1,
hair = 1,
career = sex==1 and 1 or 2,
canRotate = true,
}
self.roleUILooksNode = self.roleUILooksNode or UILooksNode.New(self.select_role_con)
self.roleUILooksNode:SetData(show_data)
end
--创建随机角色名
function LoginCreateRoleView:OnClickRandomName( )
local nick_name = nil
math.randomseed(os.clock() * 10000)
self.surname = self.surname or {"开朗哒","开朗的","可爱哒","可爱的","刻苦的",}
self.forename_man = self.forename_man or {"刚史","恭平","光希","光一","圭介",}
self.forename_woman = self.forename_woman or {"菜菜","成美","赤穗","初音","雏子",}
nick_name = self.surname[math.ceil(#self.surname * math.random())]
if self.select_sex == 1 then --临时随机名称,也可以用配置列表
nick_name = nick_name .. self.forename_man[math.ceil(#self.forename_man*math.random())]
else
nick_name = nick_name .. self.forename_woman[math.ceil(#self.forename_woman*math.random())]
end
print('Cat:LoginCreateRoleView.lua[106] nick_name', nick_name)
self.role_name_input.text = nick_name
end
--绑定点击事件,添加事件回调
function LoginCreateRoleView:AddEvents( )
local on_click = function ( click_btn )
print('Cat:LoginCreateRoleView.lua[29] click_btn', click_btn)
if click_btn == self.close_obj then
UIMgr:Close(self)--返回上个界面
elseif click_btn == self.random_obj then
self:OnClickRandomName()--随机姓名
elseif click_btn == self.create_role_obj then
--请求创建角色
local on_ack = function ( ack_data )
print("Cat:LoginCreateRoleView [start:35] ack_data:", ack_data)
PrintTable(ack_data)
print("Cat:LoginCreateRoleView [end]")
if ack_data.result == 0 then
UIMgr:CloseAllView()
require("Game/Login/LoginSceneBgView"):SetActive(false)
--正式进入游戏场景
GlobalEventSystem:Fire(LoginConst.Event.SelectRoleEnterGame, ack_data.role_id)
else
print('Cat:LoginCreateRoleView.lua[37] ack_data.result', ack_data.result)
--创建角色失败,显示消息提示
Message:Show("创建角色失败,错码码:"..ack_data.result)
end
end
--向服务器发送创建角色的消息
NetDispatcher:SendMessage("account_create_role", {name=self.role_name_input.text, career=self.select_sex}, on_ack)
end
end
UIHelper.BindClickEvent(self.close_obj, on_click)
UIHelper.BindClickEvent(self.create_role_obj, on_click)
UIHelper.BindClickEvent(self.random_obj, on_click)
end
function LoginCreateRoleView:OnClose( )
-- require("Game/Login/LoginSceneBgView"):SetActive(false)
end
return LoginCreateRoleView
选人的逻辑相对简单,我就节选部分LoginSelectRoleView.lua脚本好了:
--点击开始按钮后触发
local on_click = function ( click_btn )
if click_btn == self.start_obj then
--发射选择角色进入游戏事件
GlobalEventSystem:Fire(LoginConst.Event.SelectRoleEnterGame, self.select_role_id)
--保存角色编号
CookieWrapper:GetInstance():SaveCookie(CookieLevelType.Common, CookieTimeType.TYPE_ALWAYS, CookieKey.LastSelectRoleID, self.select_role_id)
end
end
--绑定按钮点击事件
UI.BindClickEvent(self.start_obj, on_click)
Ok,选择和创建角色学习完毕,下一篇正式进入游戏!
小结
这一篇的流程大体如下:
LoginSucceed
on_ack
有数据
无数据
on_click
on_click
登录成功
请求角色列表
角色列表
选择角色
创建角色
进入游戏
更新计划
Mon 12 Mon 19 Mon 26 1. ForEach 2. IJobForEach 3. IJobChunk 4. SubScene 5. SpawnFromMonoBehaviour 6. SpawnFromEntity 7. SpawnAndRemove 休息 修正更新计划 参加表哥婚礼 进阶:FixedTimestepWorkaround 进阶:BoidExample 初级:SceneSwitcher 我是休息时间 资源整合 部署服务器 启动流程 登录流程 MegaCity 选人流程 游戏主世界 待计划 待计划 待计划 待计划 我是休息时间 待计划 待计划 待计划 待计划 待计划 我是休息时间 读取Excel自动生成Entity 读取Excel自动生成Component 读取数据库自动生成Entity 读取数据库自动生成Component ESC LuaFrameWork Skynet DOTS 官方示例学习笔记 -----休息----- 基于ECS架构开发MMO学习笔记 LuaFrameWork学习笔记 -----休息----- 基于Skynet架构开发服务器学习笔记 制作代码自动生成工具 总结 基于Unity2019最新ECS架构开发MMO游戏笔记