三年来的写的代码真的很多,有必要试着理清一下思路,找到不依赖公司的框架,能够带走的东西。

三年来主要的工作是完成手机游戏的功能需求。公司的游戏客户端是使用lua语言。服务器是使用c++。以下举例的游戏主要基于ARPG的,SLG的部分另外有一篇 http://www.cnblogs.com/yao2yaoblog/p/6723621.html。

客户端

1.mvc

有借鉴意义的一部分是客户端view的管理。分三层:viewctrldata。是一个mvc的思想。view是加载面板layout,展示游戏画面的。data是存储服务端下发的数据。ctrl是负责调度的。ctrl通常可以写成单例,作为全局可方便调用的。

我拿下图的一个业务来举例。福利大厅里面领取奖励。

1) 服务器下发数据之后,首先是用data来存放这些数据(可领取,不可领取)。通常会调用ctrl里面的函数来把数据传到data,类似这样

LevelRewardCtrl:Instance():SavaRewardData()

2)页面上要展示的内容,首先是由layout布局决定的,view加载这个layout。再取data里的数据对页面上的内容进行update。

local data = LevelRewardCtrl:Instance():GetRewardData()

mvc这种结构,很有借鉴意义,会使得逻辑清晰很多。

2.NGUIDrawCall优化。

NGUI是Unity一个开源的制作UI的插件。DrawCall是NGUI里面的一个概念。

Unity准备好数据通知GPU绘制的过程叫DrawCall。至于准备哪些数据,没有深入过源码就不深究了。一次DrawCall会消耗大量GPU资源,所以通常制作UI时需要减少DrawCall的数量。

所有的UI组件都有个UIWidget的脚本。每个UIWidget的显示渲染顺序都是由其面板UIPaneldepth和自己的depth共同决定的。

1)面板深度加权重。假设有UIPanel:P1,P2,UIWidget:W1,W2。W1在P1里,W2在P2里,P1的depth大于P2的depth,那么无论W1,W2的depth哪个大,W1最后算出来的depth都比W2大。

2)尽量避免图集交叉。假设有图集A,B。UIWidget:WA1,WA2使用图集A。UIWidget:WB1,WB2使用图集B。深度从小到大如果是WA1,WA2,WB1,WB2。那么会是2个DrawCall。如果是WA1,WB1,WA2,WB2,那么会变成4个DrawCall。相邻的深度如果是同一个图集,通常会合并成一个DrawCall。

DrawCall合并算法:先把UIPanel中的Widget按depth从小到大排序,如果depth相同那按照material的ID来排序。然后遍历每个元素,把material相同的Widget归类到同一个drawCall。

3)动态元素和静态元素区分。也不能一味的合并DrawCall。游戏里有一些元素是长时间不动的,比如背景的一些元素。有一些元素是经常需要变动的。如果把大量静态元素和动态元素合并成了一个DrawCall,也会浪费开销。

4)改变Position代替SetActive。SetActive需要做大量工作,如果只是改变Position,开销会小很多。

5)文字置顶。UILabel,UIRichlabel的深度置顶。会使得关于文字的drawcall全部合并。

3.lua的闭包

http://www.cnblogs.com/yao2yaoblog/p/6413190.html

4.内存池。

在游戏里的物品格子使用了内存池的策略。物品格子在很多地方需要使用,而且里面的组件不少。sprite,texture,label组合都有。在经历了多个游戏,多个版本之后最终采用的是内存池的方法。

也就是说使用不需要业务手动创建,而是从内存池取得,销毁改为放回内存池。

服务端

c++是属于我的编程母语。不过确实是一门深不见底的语言,无论看多少资料,总会新的东西展现在眼前。三年的代码量只能保证业务需求变化不大的情况下,尽量精简,不要犯低级错误。用最稳妥的写法,不要秀语法,不要秀语法,不要秀语法。能上篮别扣。

因为服务器是c++的,所以空指针,数组越界的问题很容易就让服务器崩溃了。崩溃了需要重启,对游戏运营会造成损失,玩家会流失的。肯定要尽可能少重启。

服务器的所有进程如图:

1.个人系统。

指的是玩家自己的系统,只涉及客户端和GameWorld进程,加上数据库存储的操作。

可以拿坐骑进阶系统举例,简化而言,服务器有个数据ride_level。客户端根据服务器下发的这个ride_level,在场景上,UI上展示不同的模型。

ride_level是玩家登陆的时候,从数据库取上来,放到内存里,组织在userlogic类里面。userlogic类声明在Role类里。这样通过Role的实例就可以拿到这个ride_level。于是各个人系统之间都可以通过role实例互相取得数据。

比如坐骑系统的等级是根据翅膀系统的等级而变(瞎编的需求),可以这样在翅膀系统写代码(伪代码),翅膀升级的时候导致坐骑升级。

void Wing::Levelup()
{wing_level++;role->GetRide()->Levelup();
}void Ride::Levelup()
{ride_level++;
}

这些个人系统的数据会在玩家下线的时候存储到数据库,或者是服务器进程退出的时候存储到数据库,或者每隔一段时间存储到数据库。

至于数据库的存取使用的是json,存到mysql里的是一个json的字符串。

2.全服系统

比如帮派系统,比如结婚系统,比如全服的活动。数据区别与玩家自己的数据,在数据库中存在全服的数据表里,全服的功能在一个Global进程里。

Globa里通常会有全服的在线玩家list,每当一个玩家登录时,会从GW同步数据到Global,用一个简化的用户信息结构GlobalUser管理。

通常情况,如果需要得到其他玩家的数据,就必须经过Global得到。既是考虑多人的功能时,需要考虑Global和GW的通信问题。副本比较特殊,副本可以在GW管理多人信息。

举个例子。帮派神树的浇灌,大意是说整个帮派所有玩家共有一颗树,大家可以消耗自己的物品或者帮贡,来对树升级。

首先可能在客户端判断自己的帮贡是否足够,发消息到GW,判断自己的帮贡是否足够,发消息到Global对神树升级,在发消息回GW对自己的帮贡进行扣除。伪代码如下:

// client lua
function ProtocalArmy::ReqUpTree(need_data)
{Protocal.Begin(1000)//协议号Protocal.SetData("i", need_data)    //设置传输数据Protocal.Send(NetId)    //根据Ip地址发到服务端
}// GW c++
RoleArmy::ReqUpTree(need_data)
{if (m_data < need_data){// 帮贡不够return;}// 发消息到Global 带着数据或不带
    TreeUpReqStruct turs;turs.need_data = need_data;SendToGlbal(net_id, (const char*)&turs, sizeof(TreeUpReqStruct));
}// Global c++
ArmyManager::ReqUpTree(need_data)
{// 神树升级
    TreeUp();// 发消息回GW 带着数据或不带
    TreeUpReqSucBackStruct tursbs;tursbs.need_data = need_data;SendToGW(net_id, (const char*)&tursbs, sizeof(TreeUpReqSucBackStruct));
}// GW c++
RoleArmy::ReqUpTreeSucBack(need_data)
{// 减帮贡m_data -= need_data;// 发消息回客户端 给玩家反馈
}

整个流程大概如上,可见涉及Global的功能已经比只涉及GW难一些,因为涉及到GW和Global之间消息互发,如果思路不是很清晰,容易混乱。做功能之前需要弄清楚哪些数据在GW有,哪些数据在Globa有,数据会怎样改变。

在这个需求里面,为什么不直接从客户端发消息到Global呢,而要经过GW。是因为个人的帮贡信息是放在GW的,Global没有,先要在GW用帮贡数据做个判断。这个时候需要扣除的帮贡还不能直接扣除,需要等Global的神树数据改变后,再返回来改变帮贡。

其中有一个异常问题值得思考。假如Global数据变化之后,发消息会GW时,两个进程断开了,会怎样。岂不是会造成数据错乱。导致神树升级,但是帮贡没扣。

这样讲可能感受不深。借用数据库”事务“的概念来说。银行转账的例子,银行A转账到银行B,实际上是银行A减,银行B加,必须保证两者都完成,才算一次转账操作,否则要回滚。

刚才讲的例子是Global进程的神树数据和GW进程的帮贡数据也要同时改变,否则理论上应该回滚。但是我们的服务器好像没做这方面的考虑。因为GW和Global通常在一台服务器上,通信时间可以忽略不计。几乎不可能出现上述情况。

但是在我做过的另一个SLG服务器架构里面就有可能出这个问题。是一个悬而未决的问题。记录在这里,也许以后会得到答案。

3.跨服系统

跨服副本。

4.技能系统

技能系统是已有的基础模块了,不过我做过一个功能叫魔神系统。简而言之,是人物可以变身,变身之后人物的技能列表换掉,等变身时间到技能列表再还原回来。

这就涉及到技能模块和个人系统模块。个人系统模块可能是控制变身的条件,状态,cd等等。真正的大头在技能模块。

技能大致可以分为,被动和主动。

5.aoi模块

6.副本管理

副本管理全部在GW进程。副本其实就是涉及到场景管理。所有场景都是副本,有一个logic作为基类。如果是普通场景,没有什么特殊操作,那么用一个default子类即可。如果需要特殊操作则用子类,重载基类的方法来实现。

要创建一个场景,通常需要3个变量,scend_id,scene_key,logic_type。

bool CreateFb(int scene_id, int scene_key, int logic_type);

scene_id是由配置而来,scene_key用来唯一标志这个场景,logic_type代表这个副本的玩法。其中单人副本中,scene_key通常采用自增方法得到,而多人副本中,scene_key通常需要记录下来,以保证多人进入的是同一副本。

副本玩法比较重要的几个,需要重载的方法。心跳,人物进入,人物退出,人物死亡,人物被攻击等等。

virtual void Update(unsigned long interval, time_t now_second){}

首先心跳,重载这个函数,可以来控制副本的状态。interval是两次调用的间隔,now_second是现在的时间戳。例如如果副本有准备,开始,结束3个状态。可以在副本初始化的时候,算好这2个关键时间点m_begin_time,m_end_time。在update里分别与now作比较:

switch(m_status)
{case FB_READY:{if (now_second > m_begin_time){m_status = FB_BEGIN;}break;      }
    case FB_BEGIN:{if (now_second > m_end_time){m_status = FB_END;}break;      }
    case FB_END:{// destroyfb(); break;      }
} 

从而切换副本的状态。

人物的进出。副本通常会有一个玩家列表,来管理这个副本里玩家的信息。单人副本比较简单,多人副本需要根据需求在进出副本的时候写逻辑。比如玩家出了副本,在副本记录的信息需不需要清空。通常是会清空的。

清空和不清空的区别,在于一套对象管理的机制。场景里新创建一个人物,会有一个role对象产生,通常用一个obj_id标志这个对象。这个obj_id产生的策略,我理解为抢占式的。

如果A进入场景,那么obj_id = 1给A,B再进入,那么obj_id = 2给B,这时副本管理列表里obj_id = 1, 2分别是A,B玩家。这时A玩家推出了副本,C玩家进来了,这时obj_id = 1就给了C。同时列表里的信息覆盖掉A的信息。

如果需求是清空的,那就没任何问题。新进来的玩家覆盖旧玩家的信息。但是如果需求是清空的,那么A的第一次进入副本的信息怎么记录呢,比如他杀了个怪。这就需要另一个表了。

一个表是正在副本里的玩家信息m_on_fb_user_map,一个表是进来过副本又出去了的玩家信息m_out_fb_user_map。通常这个结构是写成一个std::map< UserId, UserInfo >的map。

通信相关

1.服务器进程间通信

2.服务端客户端通信。

转载于:https://www.cnblogs.com/yao2yaoblog/p/7623071.html

三年的总结(技术篇)相关推荐

  1. @Android程序员今年必看!!拖更了三年带回了一个抖音,虎牙,哔哩哔哩都在用的库|墙裂推荐

    作者:yummyLau 链接:https://juejin.im/post/5eddf8456fb9a04804041738 文章是转载的哦 ,感兴趣的可以看看作者其他的文章,我个人觉得挺不错的. 起 ...

  2. 北航机器人研究所 裴旭_三年亏损近29亿!北航硕士痴迷平衡车,让“中国智造”走向世界...

    九号机器人成立于2014年12月,公司专注于科技产品的设计.研发.生产.销售及服务.九号机器人在全球有纳恩博.赛格威(Segway).九号联合.鼎力联合等22家控股子公司,子公司区域遍布中国.美国.荷 ...

  3. AI视频行为分析系统项目复盘——技术篇4:deepsort原理图

    0 背景 见<AI视频行为分析系统项目复盘--技术篇1> 1 目标 尽力绘制详尽的原理图,弄懂deepsort的全局和细节,希望具备以下能力: 精通--能够魔改算法. 一叶知秋--深入了解 ...

  4. AI视频行为分析系统项目复盘——技术篇3:tensorRT技术梳理

    0 背景 见<AI视频行为分析系统项目复盘--技术篇1> 1 tensorRT 介绍 NVIDIA®TensorRT™是一个深度学习平台,用于模型推理加速(仅支持NVIDIA自家GPU,C ...

  5. AI视频行为分析系统项目复盘——技术篇2:视频流GPU硬解码

    0 项目背景 见<AI视频行为分析系统项目复盘--技术篇1> https://blog.csdn.net/weixin_42118657/article/details/118105545 ...

  6. Java程序员三年的工作经验,却不如一个新人的工资高???

    文章目录 一.关于程序员的几个阶段 第一阶段:三年 第二阶段:五年 第三阶段:十年 二.关于项目经验 三.关于专业技能 1.基本语法 2.集合 3.设计模式 4.多线程 5.JDK源码 6.框架 7. ...

  7. ios技术篇-CoreData

    ios技术篇-CoreData 上一篇: iOS技术篇-CocoaPods       目录        下一篇:

  8. iOS技术篇1-CocoaPods

    iOS技术篇1-CocoaPods 上一篇:         目录        下一篇:ios技术篇2-CoreData

  9. 三年程序学习之三:(失业了)

    老板的同学从广州过来的,然后不会写代码.曾经是学计算机硬件的.他过来主要是抓公司业务,公司想走正轨,想做站点建设外包工作.话说这人27和老板一样的年龄,当时就数落了他,为什么多年后你就成为了他的员工. ...

最新文章

  1. LeetCode 829. Consecutive Numbers Sum--笔试题--C++解法
  2. 2018年终总结之访问量较大的原创文章
  3. 霍尼韦尔epks 操作 组态 维护 使用 硬件 手册_标准四合一气体检测仪霍尼韦尔MiniMAX X4...
  4. Spring Cloud Alibaba基础教程:使用Sentinel实现接口限流
  5. 洛谷P1939 【模板】矩阵加速(数列)
  6. dmesg与printk命令
  7. Eclipse Neon安装指导
  8. spark集群启动正常,但是提交任务后只有一个节点(slave/worker)参与集群计算
  9. 我做计算机视觉工程师的第一个月都学会了什么?
  10. Matlab 显示图像太慢
  11. 中国象棋ai人工智能(网页版)
  12. linux关闭防火墙时出现问号乱码,linux文件名乱码问题的解决方...-tcp_wrappers防火墙配置方法-su 与 su - 的比较_169IT.COM...
  13. selenium-对指定区域截图
  14. 【SMTP】服务端口
  15. CE游戏修改器制作游戏修改器傻瓜教程
  16. Discuz修改导读设置,显示更多热帖和精华帖
  17. Java开发Telegram机器人
  18. CSS(二)——Flex布局 边框 渐变 过渡 动画
  19. C++ push_back()和back() 、pop()、push()、emplace() 和 emplace_back()
  20. python作排产计划表_排产计划表

热门文章

  1. 植物病害分类的深度可解释体系结构(github源码)
  2. Tensorboard可视化具体做法
  3. ORB_SLAM3在ubuntu18.04安装和初步测试+轨迹评估
  4. python requests网页爬取初探
  5. 详解机器学习的凸优化、图神经网络、强化学习、贝叶斯方法等四大主题
  6. 机器学习应用量化投资没啥用?那是你方法不对!
  7. android simple-xml,使用Maven构建Android项目-dexer在simple-xml依赖项上失败
  8. 原始图像扩展_Resize Sense for Mac(图像处理软件)
  9. android 开发中判断网络是否连接的代码
  10. C++中的接口继承和实现继承