您当前的位置: 首页 >  unity

CloudHu1989

暂无认证

  • 4浏览

    0关注

    89博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

UnityMMO主世界

CloudHu1989 发布时间:2019-08-16 08:41:22 ,浏览量:4

基于Unity2019最新ECS架构开发MMO游戏笔记14
  • 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主世界

总结一下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             
关注
打赏
1664096582
查看更多评论
0.2587s