- 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大框架:
模块框架语言服务器SkynetC & Lua游戏逻辑DOTSC#UILuaFrameWorkLua协议sprotoLua & C#这两天作者大鹏又更新了很多干货,关于场景,关于怪物AI,有兴趣的朋友可以去了解一下。 特别感谢大鹏的无私分享,从他那里学了不少知识,很多问题他都不吝赐教,实在是一位非常热心的大佬。 下面继续UnityMMO的学习进度:
准备工作: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场景,打开该场景。
如上图,当我们点击开始游戏按钮时,触发的是:
--正式进入游戏场景
GlobalEventSystem:Fire(LoginConst.Event.SelectRoleEnterGame, ack_data.role_id)
与SelectRoleEnterGame事件绑定的回调方法在LoginController.lua脚本中:
local SelectRoleEnterGame = function ( role_id )
--激活UI加载视图
CS.UnityMMO.LoadingView.Instance:SetActive(true)
CS.UnityMMO.LoadingView.Instance:ResetData()
local on_ack = function ( ack_data )
if ack_data.result == 1 then
--进入游戏成功,先关掉所有界面
UIMgr:CloseAllView()
--请求角色信息和场景信息
self:ReqMainRole()
else
--进入游戏失败:Todo错误提示
end
end
--向服务器发送进入游戏的消息请求
NetDispatcher:SendMessage("account_select_role_enter_game", {role_id = role_id}, on_ack)
end
--绑定事件与回调函数
self.select_role_enter_game_handler = GlobalEventSystem:Bind(LoginConst.Event.SelectRoleEnterGame, SelectRoleEnterGame)
下一步:请求主角信息
function LoginController:ReqMainRole( )
local on_ack_main_role = function ( ack_data )
--加载其它系统的controller
print("Cat:LoginController [start:76] ack_data:", ack_data)
PrintTable(ack_data)
print("Cat:LoginController [end]")
local role_info = ack_data.role_info
local pos = Vector3.New(role_info.pos_x/GameConst.RealToLogic, role_info.pos_y/GameConst.RealToLogic, role_info.pos_z/GameConst.RealToLogic)
SceneMgr.Instance:AddMainRole(role_info.scene_uid, role_info.role_id, role_info.name, role_info.career, pos, role_info.cur_hp, role_info.max_hp)
-- SceneMgr.Instance:LoadScene(role_info.scene_id)
MainRole:GetInstance():SetBaseInfo(role_info)
GameVariable.IsNeedSynchSceneInfo = true
GlobalEventSystem:Fire(GlobalEvents.GameStart)
end
--向服务器发送主角信息请求
NetDispatcher:SendMessage("scene_get_main_role_info", nil, on_ack_main_role)
end
接下来回到ECS框架的C#代码中,首先进入的是SceneMgr.cs脚本:
///
/// 添加主角
///
/// 用户编号
/// 类型编号
/// 姓名
/// 职业
/// 位置
/// 当前血量
/// 最大血量
/// 角色实体
public Entity AddMainRole(long uid, long typeID, string name, int career, Vector3 pos, float curHp, float maxHp)
{
//把信息传递给角色管理器
Entity role = RoleMgr.GetInstance().AddMainRole(uid, typeID, name, career, pos, curHp, maxHp);
// entityDic.Add(uid, role);
//把实体交给字典缓存,方便复活
entitiesDic[SceneObjectType.Role].Add(uid, role);
//通过职业来初始化技能
SkillManager.GetInstance().Init(career);
return role;
}
接下来在RoleMgr.cs脚本中生成角色实体:
public Entity AddMainRole(long uid, long typeID, string name, int career, Vector3 pos, float curHp, float maxHp)
{
//GameObjectEntity是ECS和OOP混合开发模式的产物,用于游戏对象和实体的转换
//从资源管理器中获取预设并生成混合体
GameObjectEntity roleGameOE = m_GameWorld.Spawn(ResMgr.GetInstance().GetPrefab("MainRole"));
roleGameOE.name = "MainRole_"+uid;
roleGameOE.transform.SetParent(container);
roleGameOE.transform.localPosition = pos;
Entity role = roleGameOE.Entity;
RoleMgr.GetInstance().SetName(uid, name);
InitRole(role, uid, typeID, pos, pos, curHp, maxHp, false);
roleGameOE.GetComponent().Value = new UID{Value=uid};
EntityManager.AddComponentData(role, new PosSynchInfo {LastUploadPos = float3.zero});
EntityManager.AddComponent(role, ComponentType.ReadWrite());
var roleInfo = roleGameOE.GetComponent();
roleInfo.Name = name;
roleInfo.Career = career;
mainRoleGOE = roleGameOE;
SceneMgr.Instance.ApplyMainRole(roleGameOE);
return role;
}
这里已经涉及到ECS了,C如下:
///
/// C:用户编号
///
public struct UID : IComponentData
{
public long Value;
}
[DisallowMultipleComponent] //禁用多组件,被修饰的组件在每个实体上只能有一个
public class UIDProxy : ComponentDataProxy { }
///
/// C:位置同步信息
///
public struct PosSynchInfo : IComponentData
{
public float3 LastUploadPos;
}
///
/// C:玩家命令
///
[System.Serializable]
public struct UserCommand : Unity.Entities.IComponentData
{
///
/// 移动方向
///
public float moveYaw;
public float moveMagnitude;//移动量
public float lookYaw;//看的方向
public float lookPitch;//看的范围
public int jump;//跳
public int sprint;//冲刺
public int skill;//使用的技能索引,普攻也是技能来的
public static readonly UserCommand defaultCommand = new UserCommand(0);
private UserCommand(int i)
{
moveYaw = 0;
moveMagnitude = 0;
lookYaw = 0;
lookPitch = 90;
jump = 0;
sprint = 0;
skill = 0;
}
public void ClearCommand()
{
jump = 0;
sprint = 0;
skill = 0;
}
}
ECS混合开发是过渡阶段的无奈之举,很多东西还得依赖原来的组件和Mono,想要做纯粹的ECS开发,至少要等到明年。
主世界加載我們打開MainWorld脚本中:
///
/// 開始游戲
///
public void StartGame() {
//初始化主世界
Initialize();
if (GameVariable.IsSingleMode)//單機模式,用於測試
{
SceneMgr.Instance.AddMainRole(1, 1, "testRole", 2, Vector3.zero, 100, 100);
SceneMgr.Instance.LoadScene(1001);
}
else
{
//开始从后端请求场景信息,一旦开启就会在收到回复时再次请求
SynchFromNet.Instance.StartSynchFromNet();
}
}
在登陸成功以後,StartGame就被調用了,這個時候主世界開始初始化:
///
/// 初始化主世界
///
public void Initialize() {
//實例化一個游戲世界,這個是實體世界
m_GameWorld = new GameWorld("ClientWorld");
//初始化Timeline管理器,TimelineManager負責動畫相關
TimelineManager.GetInstance().Init();
//初始化場景管理器
SceneMgr.Instance.Init(m_GameWorld);
//初始化網絡同步
SynchFromNet.Instance.Init();
//初始化系統
InitializeSystems();
}
///
/// 初始化S
///
public void InitializeSystems() {
//實例化系統集合:把所有S添加到系統systems列表中
m_Systems = new SystemCollection();
//創建玩家輸入系統并添加到systems列表
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem());
//處理玩家視角,通過距離判斷,如果看到其他玩家就會生成對應的玩家實體
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
//通過玩家輸入生成對應的目標位置
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
//移動更新系統,朝著玩家輸入的位置移動,處理地面碰撞
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
//地面測試系統
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
//上傳主角位置系統
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
//技能生成系統
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
//Timeline生成系統
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
//更新動畫系統
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
//重置位置偏移量系統
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
//名稱面板系統和生成請求系統
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
//動作數據重置系統
m_Systems.Add(m_GameWorld.GetECSWorld().CreateSystem(m_GameWorld));
}
接下來網絡同步單例初始化,通過網絡同步單例向服務器請求場景信息:
///
/// 初始化網絡同步單例
///
public void Init()
{
//初始化改變方法字典,在服務器回調函數中調用
changeFuncDic = new Dictionary();
changeFuncDic[SceneInfoKey.PosChange] = ApplyChangeInfoPos;//位置改變
changeFuncDic[SceneInfoKey.TargetPos] = ApplyChangeInfoTargetPos;//目標位置信息改變
changeFuncDic[SceneInfoKey.JumpState] = ApplyChangeInfoJumpState;//跳躍狀態改變
changeFuncDic[SceneInfoKey.HPChange] = ApplyChangeInfoHPChange;//血量改變
//The main role may not exist until the scene change event is received
changeFuncDic[SceneInfoKey.SceneChange] = ApplyChangeInfoSceneChange;//場景改變
}
///
/// 開始網絡同步
///
public void StartSynchFromNet()
{
ReqSceneObjInfoChange();
ReqNewFightEvens();
}
///
/// 请求服务器场景对象信息改变
///
public void ReqSceneObjInfoChange()
{
// Debug.Log("GameVariable.IsNeedSynchSceneInfo : "+GameVariable.IsNeedSynchSceneInfo.ToString());
if (GameVariable.IsNeedSynchSceneInfo)//如果需要同步場景信息,則向服務器發送請求
{
SprotoType.scene_get_objs_info_change.request req = new SprotoType.scene_get_objs_info_change.request();
NetMsgDispatcher.GetInstance().SendMessage(req, OnAckSceneObjInfoChange);
}
else//否則注冊定時器,在0.5秒后繼續發送請求
{
Timer.Register(0.5f, () => ReqSceneObjInfoChange());
}
}
///
/// 請求新的戰鬥事件
///
public void ReqNewFightEvens()
{
// Debug.Log("GameVariable.IsNeedSynchSceneInfo : "+GameVariable.IsNeedSynchSceneInfo.ToString());
if (GameVariable.IsNeedSynchSceneInfo)//如果需要同步場景信息,則向服務器發送請求
{
SprotoType.scene_listen_fight_event.request req = new SprotoType.scene_listen_fight_event.request();
NetMsgDispatcher.GetInstance().SendMessage(req, OnAckFightEvents);
}
else//否則注冊定時器,在0.5秒后繼續發送請求
{
Timer.Register(0.5f, () => ReqNewFightEvens());
}
}
服務器收到消息后會調用回調函數:
///
/// 远程过程调用RPC处理函数
///
/// 結果
public void OnAckSceneObjInfoChange(SprotoTypeBase result)
{
SprotoType.scene_get_objs_info_change.request req = new SprotoType.scene_get_objs_info_change.request();
//遞歸請求并回調
NetMsgDispatcher.GetInstance().SendMessage(req, OnAckSceneObjInfoChange);
SprotoType.scene_get_objs_info_change.response ack = result as SprotoType.scene_get_objs_info_change.response;
if (ack==null || ack.obj_infos==null)//如果服務器沒有響應或響應信息則返回
return;
int len = ack.obj_infos.Count;
for (int i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?