【斗地主代码分析】(1)

编程入门 行业动态 更新时间:2024-10-10 19:22:01

【<a href=https://www.elefans.com/category/jswz/34/1766157.html style=斗地主代码分析】(1)"/>

【斗地主代码分析】(1)

前言

这是个斗地主游戏,觉得这个Demo的框架挺不错,通俗易懂,本篇主要分析一下这个框架。

这个框架通过消息中心来调度所有功能,想个大号的中介模式,关于中介模式可以看看这篇博客:中介模式

源码地址:斗地主Demo

总览

先看一下这个框架的结构,如下图。

可以说这个框架就这么点东西,就这三个结构。

MsgCenter 是消息中心,根据传参调用对应的ManagerBase,而它又会根据参数执行对应的MonoBase

调用链:

MsgCenter.Dispatch(int areaCode, int eventCode, object message)|v
ManagerBase.Execute(int eventCode, object message)|v
MonoBase.Execute(int eventCode, object message)

消息中心

我们来自上而下看看这个调用链,先看MsgCenter 的实现。

public class MsgCenter : MonoBase
{public static MsgCenter Instance = null;void Awake(){Instance = this;gameObject.AddComponent<AudioManager>();gameObject.AddComponent<UIManager>();gameObject.AddComponent<NetManager>();gameObject.AddComponent<CharacterManager>();gameObject.AddComponent<SceneManagers>();DontDestroyOnLoad(gameObject);}/// <summary>/// 发送消息 系统里面所有的发消息 都通过这个方法来发///   怎么转发?根据不同的模块 来发给 不同的模块 ///     怎么识别模块呢?通过 areaCode/// ///     第二个参数:事件码 作用?用来区分 做什么事情的///         比如说 第一个参数 识别到是角色模块 但是角色模块有很多功能 比如 移动 攻击 死亡 逃跑...///             就需要第二个参数 来识别 具体是做哪一个动作/// </summary>public void Dispatch(int areaCode, int eventCode, object message){switch (areaCode){case AreaCode.AUDIO:AudioManager.Instance.Execute(eventCode, message);break;case AreaCode.CHARACTER:CharacterManager.Instance.Execute(eventCode, message);break;case AreaCode.NET:NetManager.Instance.Execute(eventCode, message);break;case AreaCode.GAME:break;case AreaCode.UI:UIManager.Instance.Execute(eventCode, message);break;case AreaCode.SCENE:SceneManagers.Instance.Execute(eventCode, message);break;default:break;}}
}

Awake 时把实现的ManagerBase 加进来,之后如果想调用某个功能,就调用MsgCenter.Dispatch(areaCode, eventCode, message)

通过这三个参数来区分所有功能,areaCode 区分这个功能所在的模块,eventCode 是对功能在模块内的具体区分,message 是这个事件需要的参数。

Dispatch 根据areaCode参数调用对应的模块的Execute 方法,接下来看看它是怎么实现的。

模块管理

/// <summary>
/// 每个模块的基类
///     1。保存自身注册的一系列消息
/// </summary>
public class ManagerBase : MonoBase
{/// <summary>/// 处理自身的消息/// </summary>/// <param name="eventCode">Event code.</param>/// <param name="message">Message.</param>public override void Execute(int eventCode, object message){if (!dict.ContainsKey(eventCode)){Debug.LogWarning("没有注册 : " + eventCode);return;}//一旦注册过这个消息 给所有的脚本 发过去List<MonoBase> list = dict[eventCode];for (int i = 0; i < list.Count; i++){list[i].Execute(eventCode, message);}}/// <summary>/// 存储 消息的事件码 和 哪个脚本 关联 的字典/// /// 角色模块 有一个动作是 移动///             移动模块需要关心这个事件 控制角色位置 进行移动///             动画模块 也需要关心  控制角色播放动画    ///             音效模块 也需要关心  控制角色移动的音效播放 走路声/// </summary>private Dictionary<int,List<MonoBase>> dict = new Dictionary<int, List<MonoBase>>();...
}

管理类有有一个成员变量dict,它存的是一个map类型:事件id->执行动作列表。

再看它的Execute 方法,找到eventCode 对应的执行动作列表,依次执行。也就是说,这个事件id可能对应多个执行动作,也可能多个事件id对应多个执行动作。

一个执行动作,就是一个MonoBase,这个比较简单,直接贴到下面。

/// <summary>
/// 为什么写着脚本?
///     我们想扩展 MonoBehaviour
/// </summary>
public class MonoBase : MonoBehaviour 
{/// <summary>/// 定义一个虚方法/// </summary>public virtual void Execute(int eventCode, object message){}
}

行了,整个框架就是这么三层,实现对应层的功能,由MsgCenter 统一调用,调到对应模块,再由模块调到具体功能。直接看这个框架有点儿抽象,接下来看看这个斗地主Demo是如何使用这个框架的,就能理解了。

登录功能的实现

我们通过登录功能的实现,来看这个框架如何使用。

首先分析一下登录功能的流程:

  1. 输入账号密码,点击登录。
  2. 去服务器校验,校验通过的话跳转到游戏界面。
  3. 校验失败的话提示。

一个简单的登录功能就这三步,来看这个Demo。

首先找到登录按钮的点击处理方法,在StartPanel.cs里。

    void OnLoginClick(){if (string.IsNullOrEmpty(inputAccount.text)){promptMsg.Text = "用户名不能为空!";Dispatch(AreaCode.UI,UIEvent.Prompt_Msg,promptMsg);return;}if (string.IsNullOrEmpty(inputPassword.text)){promptMsg.Text = "密码不能为空!";Dispatch(AreaCode.UI, UIEvent.Prompt_Msg, promptMsg);return;}if (inputPassword.text.Length < 4 || inputPassword.text.Length > 16){promptMsg.Text = "密码长度错误!应在4~16个字符之间";Dispatch(AreaCode.UI, UIEvent.Prompt_Msg, promptMsg);return;}UserInfoDto userInfo = new UserInfoDto{Account = inputAccount.text,Password = inputPassword.text};//发送数据Dispatch(AreaCode.NET, 0, new SocketMsg{OpCode = MsgType.Account,SubCode = AccountCode.Login,value = userInfo});}

前面进行一堆校验后,构造了userInfo,这是登录需要的信息。

最后调用了Dispatch() 方法。注意这里的三个参数。

第一个AreaCode.NET,表示要调用NET 模块的功能,这个模块也是我们自己实现的。
第二个0,在这个模块里对应的事件id是0,一会儿再看这个0在这个模块里是什么含义。
第三个是新构造的SocketMsg结构,把上面构造的userInfo 赋值给 value,它是一个自定义的网络消息格式,如下所示。

//
// 摘要:
//     网络消息
public class SocketMsg
{public SocketMsg();public SocketMsg(Enum state);public SocketMsg(MsgType opCode, Enum subCode, Enum state, object value = null);//// 摘要://     操作码public MsgType OpCode { get; set; }//// 摘要://     子操作public Enum SubCode { get; set; }//// 摘要://     参数public object value { get; set; }//// 摘要://     状态public Enum State { get; set; }
}

现在看对这个NET模块调用的实现,直接看NetManager.cs 里的Execute 方法,可以看到只处理了0 事件,做的操作就是发送了这个消息。

    public override void Execute(int eventCode, object message){switch (eventCode){case 0:client.Send(message as SocketMsg);break;default:break;}}

显而易见,接下来要看网络消息响应的处理了。还是这个文件,可以看到它的Update() 是一直在处理消息的,client 在收到消息后会解析并放入它的socketMsgQueue 队列里。

    private void Update(){if (client == null)return;while (client.socketMsgQueue.Count > 0){SocketMsg msg = client.socketMsgQueue.Dequeue();//处理消息ProcessSocketMsg(msg);}}

然后看ProcessSocketMsg 的实现,上面已经设置过网络消息的OpCodeMsgType.Account,这里也看对其的处理,调用了accountHandler.OnReceive(msg);

    HandleBase accountHandler = new AccoutHandle();HandleBase userHandler = new UserHandler();HandleBase matchHandler = new MatchHandler();HandleBase chatHandler = new ChatHandler();HandleBase fightHandler = new FightHandler();/// <summary>/// 接受网络的消息/// </summary>private void ProcessSocketMsg(SocketMsg msg){switch (msg.OpCode){case MsgType.Account:accountHandler.OnReceive(msg);break;case MsgType.User:userHandler.OnReceive(msg);break;case MsgType.Match:matchHandler.OnReceive(msg);break;case MsgType.Chat:chatHandler.OnReceive(msg);break;case MsgType.Fight:fightHandler.OnReceive(msg);break;}}

一步步跟着去看,最后会调到AccoutHandleLoginResponse 方法,它的实现如下,逻辑是:如果登录成功,就调用SCENE 模块的UIEvent.Scene 事件。若登录失败,则调用UI 模块的UIEvent.Prompt_Msg 事件。这里很容易猜到,前者是转换到游戏场景,后者是进行登录失败消息提示。

    /// <summary>/// 登录请求处理/// </summary>/// <param name="code"></param>private void LoginResponse(AccountCode code){if (code == AccountCode.Success){Dispatch(AreaCode.SCENE, UIEvent.Scene, new LoadSceneMsg{SceneIndex = 1,OnSceneLoaded = () =>{Dispatch(AreaCode.NET, 0, new SocketMsg{OpCode = MsgType.User,SubCode =UserCode.GetInfoRequest});}});return;}switch (code){case AccountCode.AccountDoesNotExist:promptMsg.Text = "账号不存在";break;case AccountCode.AccountOnline:promptMsg.Text = "账号在线";break;case AccountCode.AccountPasswordDoesNotMatch:promptMsg.Text = "账号密码不匹配";break;}Dispatch(AreaCode.UI, UIEvent.Prompt_Msg, promptMsg);}

到这里我想就不需要我再一步步分析了,如果要接着往下分析的话,就是去SCENE 模块看看这个事件对应的执行方法,看怎么实现的。再去UI模块看看这个消息提示是怎么处理的。

总之知道这个框架的调用流程后,代码就不难看懂了,剩下就是看代码逻辑是如何实现的。

事件的绑定

还有一点需要说一下,关于事件是如何绑定的,注意ManagerBase 是继承MonoBase 的,一个事件就是一个MonoBase

然后先看一个示例,就看SceneManagers 场景管理,在Start() 中调用了Add 方法,把UIEvent.Scene事件的处理绑定了this,也就是说如果有这个事件了,就会调用到SceneManagersExecute 方法。

public class SceneManagers : ManagerBase
{public static SceneManagers Instance;/// <summary>/// 加载场景委托/// </summary>private Action onSceneLoadAction;private void Awake(){Instance = this;SceneManager.sceneLoaded += SceneManager_sceneLoaded;}void Start(){Add(UIEvent.Scene, this);}public override void Execute(int eventCode, object message){switch (eventCode){case UIEvent.Scene:LoadSceneMsg msg = message as LoadSceneMsg;LoadScene(msg);break;}}...
}

如果事件比较少,都挺简单的话可以这么写,如果同一个模块事件比较多,那样全都写到一个文件里的话就会显得特别长,Execute 里的switch 也会特别长。

怎么分开呢,实际每个事件都是MonoBase,只要继承了这个接口,就可以把事件分开,而不用全部挤在一个ManagerBase 里处理,但是还需要和对应的ManagerBase关联起来。

这里可以看下UIManagerUIBaseStartPanel 这三者的实现,看完就能理解如何把模块内的各个功能拆分开了。这里就不再赘述了,如果上面都理解了,这个多看两边就看懂了。

本篇到这里算是全部讲完了,主要分析了这个消息中心模式的框架是如何实现和使用的,既然都看这个Demo了,下一篇就分析一下斗地主的功能逻辑吧。

更多推荐

【斗地主代码分析】(1)

本文发布于:2024-02-27 20:15:39,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1766151.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:斗地主   代码

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!