你见过最烂的代码长什么样子?
你见过最烂的代码长什么样子? 一年前的事。前同事给我展示了我本来最初写的代码,后来可能是被外包的人改的面目全非,然后他又把它改规范了,让我意思意思review下,并且吐槽下外包的代码。我拿起代码和git提交历史一看,一口老血喷在地上。我写的初版是这样的(不喜欢直接展示原代码,所以这边是抽象掉+集中展示槽点的修改版):
public bool ValidateUser(long userId)
{
int userScore = 100;
if (!CheckCondition1(userId)) userScore -= 10;
if (!CheckCondition2(userId)) userScore -= 15;
......
return userScore >= 60;
}
这个代码我写的时候没有留意magic number的问题,但是,可读性上是OK的。
然后,翻git看到了第一版改动。应该是有需求提出,有一个condition,是一票否决的,这个为false直接结束。
代码变成了这样:
public bool ValidateUser(long userId)
{
int userScore = 100;
bool b = CheckYCDYCondition(userId);
if (!CheckCondition1(userId)) userScore -= 10;
if (!CheckCondition2(userId)) userScore -= 15;
......
if (b == true)
goto end;
if (b == false)
return false;
:end
return userScore >= 60;
}
b== true, goto, 还有你都上来一票否决了,还在check剩下的条件,这我就不说了。YCDY是啥呢?我问前同事,他也不知道,回头去问。第二天他告诉了我:
此时一万把锤子在我的心中呼啸而过。
(这边说点正经的,看到有几个评论问这个正常应该怎么写。个人觉得表现出高阶覆盖/起决定作用的意思即可。因此,写Ultimate, SuperOverride, FirstPriority等都可以)
第二版:
public bool ValidateUser(long userId)
{
int userScore = 100;
bool b = CheckYCDYCondition(userId);
if (!CheckCondition1(userId)) userScore -= 10;
if (!CheckCondition2(userId)) userScore -= 15;
......
if (!b == true)
return userScore >= 60;
else if (!b == false)
return false;
else
return false;
}
嗯好,goto没了,变成如果b既不等于true也不等于false还可以进行下去了。YDCY听同事解释是里面逻辑改了,本来false变成true,本来true变false(喷血)。改就改吧,我也没办法,但是对应措施是把b改成!b么?然后!b == false???我在草丛里蹲你,你知道我在草丛里蹲你,我知道你知道我在草丛里蹲你?
我问同事这代码有人review吗,他说这段时间就那几个外包的在搞这个模块,我们这边就看跑起来功能正确就可以了。
第三版:
const static int fillScore = 100;
const static int passScore = 60;
public static bool ValidateUser(long userId)
{
int userScore = fillScore ;
int Score10 = 12;
int Score15 = 15;
bool b = CheckYCDYCondition(userId);
if (!CheckCondition1(userId)) userScore -= Score10 ;
if (!CheckCondition2(userId)) userScore -= Score15 ;
......
if (!b == true)
return userScore >= passScore;
else if (!b == false)
return false;
else
return false;
}
fillScore 是满分的意思,也算了,没写成manScore就谢天谢地了。看出来好像也在解决魔数问题。但是Score10是啥啊,说明如果condition1不过,要扣10分么?那你赋值10就行了,变量写10是啥意思?看,这里被要求改成12了吧?
还有,怎么就变static了?这个原因忘了,好像说是要new什么静态类的。new静态类就除了把整个方法都静态化之外没其他办法了吗???
第四版:
bool b = !CheckYCDYCondition(userId);
if (!CheckCondition1(userId)) userScore -= Score10 ;
if (!CheckCondition2(userId)) userScore -= Score15 ;
......//后面又加过好几个条件,不仅仅这一版
System.threading.thread.sleep(3000);
if (!b == true)
return userScore >= passScore;
else if (!b == false)
return false;
else
return false;
}
一锤定音看起来又反回来了(第一行),所以逻辑现在变成你知道我知道你知道我在草丛里蹲你了;(这个我觉得情有可原吧,毕竟bool b 和if (b)间隔超过一屏了) 一看到sleep(3000),我就想起了这个段子——这个函数性能不好,加钱给你优(sleep后面的数字改小)化。问他,是不是当初是这个意思,结果他说,哦,我知道,他们是拿这个来debug的,单独函数跑太快了,直接执行就完毕了,所以要时间给你点debug的暂停键。但是这个可能最后漏删了。
最后,这个代码还是被前同事改正常了。
更:刚又和前同事提YCDY了,结果前同事一笑说:你以为YCDY就没了吗?你知道那个库里还有一个叫CheckECDYCondition的函数,虽然好像没用到么?
大锤80小锤40大锤80小锤40……
追更一个,也是布尔类型的闹剧:
背景:项目中有一个IsIncludeTrade的函数,用来判定是否需要最终报表包括某些数据。然而各级领导意见不一,有时候说要包括有时候不要,今天上了包括版明天又说不包括过两天又说包括。
于是程序里这个代码是这样的:
if(! !!!! !!!!IsIncludeTrade) //记录这个到底要改多少次不过这个程序员还是很有节操的,除了这里代码都是规范的,!也是四个一分隔便于计数。
更2,改写自朋友口述,以下均为伪代码。
前端页面需要实行一个功能,将传进来的Int秒数转换成分:秒的形式;据说这个前端语言并没有类似于{0:mm:ss}之类的内置转换功能。
我是觉得这样写不就行了嘛:
int seconds = 12345;
string str = (string)(seconds / 60) + (seconds % 60) >= 10 ? ":" : ":0" + (string)(seconds % 60);结果这个代码思路是这样的:
int seconds = 12345;
int min = 0, sec;
while (seconds > 60)
{
seconds -= 60;
min += 1;
}
sec = seconds;
//然后开始拼min和sec为什么这个代码问题被发现了呢,因为传秒数的函数出了个bug,往里面传了一个十亿多的数字。 有次帮小姐姐修电脑,偶然看到这幕,这应该也算吧...
世界上最简洁易懂的代码,是自己刚写出来的代码。
世界上最烂的代码,就是自己一年前写出来的代码。 看到这代码后隔天就辞职了 目前深陷一个火坑中,代码之烂,令人瞠目结舌,让人发指!!!!
1.奇葩的命名方式。一般编码,命名方式都是英文驼峰法或者下划线分割。然而该项目命名规范是拼音首字母缩写。诸如yzjd,mc,tj,sj之类的变量名随处可见,还几乎没注释。现在告诉各位上述四个变量代表叶子节点,名称,条件,数据。有没有砸烂他们狗头的冲动?此外,本工程中代表着时间,事件的变量也是sj,定项目编码规范的人,说他是智障都是抬举他了。
然而,这还不是全部。假设已知yh代表用户,那各位请猜一猜lstyh代表啥?这个问题曾经困扰了我很久,导致我完全不知道代码的业务逻辑想表达啥,甚至不确定这玩意和yh有没有关系。直到最后我反复求证,才得以确认,lstyh是用户数组的意思,即我们正常写法的userList。天杀的居然把list缩写成lst!!!就为了少些一个i!!!
当然,这还不是最瞎的,最瞎的是诸如fghjk,wert之类的函数名,如果各位不知道这些函数名怎么来的,看看键盘就懂了!!!!!
2.混乱的代码结构,在controller里完成所有业务处理,一个js或者java文件上千行是常有的事。
3.前端页面中,充斥着无数内联样式和内联js响应函数。
4.奇葩的测试方式。有一天某技术主管召集了大概15人,给我们说,过会我数123,你们一起点击网站,我们来测试一下系统的并发性!!!!!
补充:
5,脱裤子放屁的一些代码。比如客户端c++代码,typedef int INT,然后在INT a=10。还有服务器端的java代码,final string SYLJ = “sylj”,请问以上这些代码有什么意义!!!(经各位知友提醒,之前typedef写成了typeof,已修改)
6,假设我司的名称是abcd公司。然后在代码里,我发现这么一个自定义异常类,AbcdException。。。。嗯,终于没把exception缩写成yc,但我还是不知道这个异常想表达什么!!!
补充:
看了评论不少人认为5和6还是有一定意义的,这里说下我的看法:
定义常量,主要是为了避免魔数的使用,便于日后的修改,同时也提升代码可读性。因而常量的命名,应该告诉人这个常量表示什么意思。final string SYLJ = “sylj”这段代码,常量名和值是一样的,所以依旧不知道SYLJ 这个常量表示什么。倘若以后值修改了,确实只用修改一处代码,但代码里到处都是的SYLJ ,反而可能给人造成误导,可能接手的人还会认为它就代表“sylj”这玩意。
同理,自定义异常应该告诉人们这是什么异常,比如java里的NullPointException,ArrayIndexOutOfBoundsException,一看其名就知道是什么原因造成的异常。abcd公司自定义的AbcdException反映不出任何问题,没有意义。
7,另一个项目里,让我参考原来老员工的代码。我只看了两眼就没看了,因为能写出这种代码的,已经没法用智障来形容了。为什么?因为在所有的catch块中,都写着system.gc!!!!甚至好多处地方,连异常信息都可以不打印,就一个光秃秃的gc!!
8,跟新补充,我在前司时参与的第一个项目,先从代码看起,最早的注释是2012年的,那么至少这个代码已经慢慢开发了4年。这是一个web工程,我学习了一天这个代码,发现了一个可怕,让我瞬间感觉公司极其不靠谱的事情,一个web工程,前后端交互,居然没有一处用到ajax!!!!比如说登录页面密码错误,嗯,直接跳转到一个新页面,新页面上就如下几个字,“用户名或者密码错误!”(而且这几个字还不带布局设置的,没有放在页面中央,就在页面左上角。。。。。)我找到所谓带我的“导师”,(为啥要带引号?因为这货的水平压根不配当我导师),问她为什么web工程不用ajax?她一脸懵逼,什么是ajax?我们为什么要用ajax?接下来就是例行教育,你们年轻人才进来多虚心学习,我们做了这么久,肯定比你们考虑的周全。。。。。。。。。
9,同一个工程,数据库表主键生成策略,是在代码里写的一个计数器,写死的从0开始计数,来一条记录就自增1,然后计数器值作为记录的主键。我就想砸烂他们的狗头,先不说他们用的int没法应对并发访问,单说这么多年他们就没发现这样写如果出现重启服务的情况,就会出现计数器归0,主键冲突的情况吗???后面我发现,嗯,他们发现了这个记录插入失败的现象,但是不晓得原因。。。。每次一出问题,就把表里数据清空。。。反正这东西从来没有实际用过
新增:
10,在这个混乱无比的web工程里,某些业务功能设计了用户上传文件的功能。用户上传的文件如何保存?可能你已经想到了对象存储等技术,哪怕是web服务器下创建专门用于存储用户文件的区域也好,业务表里记录存储路径即可。然而本工程里,存储文件的方式居然是在业务表里设计blob字段,把文件转换成二进制流写进数据库里!!!!!我问为什么要这样设计,得到的答复更是无比奇葩:为了保护用户隐私。在服务器下直接放文件万一被别人下载下来怎么办?转成二进制流至少第一眼绝对不知道文件内容。。。。。。
11,第8,9条提的那个web工程要用户验收了,有一条指标是界面点击响应时间。我们没有专业测试软件,要获取响应时间,我说日志输出吧。项目负责人逮着我一顿喷,“你让用户看日志???要是日志里输出个异常怎么办??傻逼吧!”。我说,那我把界面响应改一改,每次操作后在页面最下方显示个响应时间。负责人又逮着我一顿喷,“你这样会破坏界面的协调性!”。我有些火了,说那我没辙,你负责人给拿个方案。这憨批想了一下午,甚至还召集了所谓的技术元老开了个会讨论这事,最后的方案绝对超出各位的想像
“经过讨论决定,客户验收时,我们用秒表来计时!到时你就在旁边给客户按秒表!”
先想到这么多,以后再补充 12.1补充:
让Unreal来告诉大家什么叫做AAA Math:
DXMath:We're DONE.
我来接着 @MaxwellGeng 的这个回答,深挖一下材质的屎:
你见过最烂的代码长什么样子?上面这个回答从功能设计层面给大家展示了Material的究极耦合,那么下面我们再来看一看MaterialInstance的花式操作。
在开喷源码之前,首先我们分析一下这个东西。MaterialInstance,其实就是份数据变体,引擎里面打开一个材质实例,呈现的就是一个经典的配表面板:
不知道的还以为这是个json或者xml
按照Unreal的光荣传统,它是一个多级继承的Object。序列化成人类不可编辑的binary之后,它的体积达到了惊人的:100KB!(当然大也是因为里面有shader变体拉。。。)
よし、気合いが勝っとる!
下面我们从写代码的角度分析一下这个东西。
MaterialInstance作为一个经典的数据变体, 我们碳基生物一般会这么做:
塞一个变体数据,在原材质基础上做modify,构造一个特化的材质对象出来供游戏逻辑使用。序列化这个MaterialInstanceArgs,就可以在编辑器更改存盘,在运行时反序列化了。在运行时直接更改RuntimeMaterial下的data成员,也就可以实现动态的效果变化。
这么简单的东西,UE4咋写的呢?下面就从Runtime和Editor两个角度带你大饱眼福。
1. 多态 & Runtime---UMaterialInterface
Material和Instance里面有好多共享的property啊!咋办呢,来个接口把!
这个接口写了900行
且不说Instance的数据和Material是包含关系,为什么一个接口继承了实体类????
罢了,接着看Instance把:
懵逼,为什么Instance还是个abstract?
原来,因为我们的材质可能会需要参数的动态绑定,材质实例还要分 动态 和 静态 两种 需求!
里面居然还有蓝图编辑器和VM的 帮助代码!
因为和Streaming耦了,里面还有Streaming相关的成员:
各种功能耦合在一起,Runtime和Editor不分,虚幻味十足!
2.Editor --- UMaterialEditorInstanceConstant
为了在Editor下面更改MaterialInstance,Unreal专门写了个专用的面板编辑器!太贴心拉!
写一个上面那个改表的Detail面板需要多少代码呢?
1300多行,哇!!!!
代码有点奇怪?如果你第一次见到有把各种广播回调露在外面的GUI框架,那还只能说你太嫩了!我们天龙虚幻人一直都是这么写的!
接下来发生的事就牛逼大了,面板上的值咋映射到MaterialInstance上呢?虚幻写了个类UMaterialEditorInstanceConstant:
为什么一个叫Constant的东西要继承自Object?常量对象吗?
我们要修改EditorInstanceConstant中一个叫做FMaterialInstanceBasePropertyOverrides的结构,这里面的值会被最终提交到对应的MaterialInterface上。
FMaterialInstanceBasePropertyOverrides长下面这样。
那我们怎么才能触发这一步提交呢??????我们接着看编辑器的代码:
魔法发生在红色高亮的这个FunctionCall!它的实现,要溯源到UObject:
在UMaterialEditorInstanceConstant中,我们发现PostEditChangeProperty被覆写了!
在这个覆写函数里面,我们终于看到了最终的属性修改。
回调两步,贯穿四层继承链,踏遍四层引用,深入引擎基类,只为设置一个值!我称其为:
U N R E A L 消息循环! 我第一份工作清理的代码。十几年依然没见过更烂的。
3500行的主逻辑代码,只有一个函数!!!
函数的开头声明了一百多个变量,包括i, j, k这种计数器(C++代码哦)。
里面是一个奇大无比的连续if。
每一个else if代表一个逻辑分支。一共判断了十几次。
整个项目有十几万行。
工具类绝不抽离出来做函数,最夸张的在同一个函数中都能写四五次。有的逻辑在整个系统中重复写了不下十几次。
神秘数字多如牛毛。绝不用宏或者常量替代。
注释基本上只有一种//xxx modified at yyyy/mm/dd
跑起来的效果是每一两个小时就崩溃一次。
当时也是初生牛犊不怕虎。刚好住在公交的始发站附近,每天都有位子。于是买了本《代码大全》,每天上下班就抱着啃。啃完了就照着书里说的原则去改。
花了小半年吧。
把所有的工具代码都抽出来做了工具类。
把大函数都拆成几十行的小函数。
用VC全项目一个数字,一个数字的搜索,把所有能找到的神秘数字都抽出来用宏或者常量来替代。保证代表同一件事物的神秘数字,只出现一次。
那个巨大的if,改成了switch,并且主函数里面,几乎只有这一个switch和针对不同逻辑需要的参数变量,每一个开关下,直接去调用一个逻辑函数。
在所有清理好的重要的地方,都加了伪代码注释。
最终削减了近三分之一的代码。还顺手增加了几个小功能。
测试一个月,7*24小时运转,崩溃三次。
我本来还不满足。结果老板告诉了我一个重要的人生哲理。
客户签的合同要求没这么高。你费那个劲干嘛。那几个功能已经白送了。他又不给咱们加钱。
交差。 翻硬盘时翻出来个小玩意,试了试 现在还能跑起来,哈哈。
当时答主事带一学生,很有精神;刚学一点西加加;头发浓密。萌新的憧憬之下,用devcpp这个瞎眼ide 写了个控制台坦克大战。3k来行
那会刚看完少战,中二附体max
手打像素矩阵
欢乐の小曲儿
穿甲爆炸燃烧 瞬间完成,事战车中的豪杰
用txt文件储存地图,玩家可自己设计地图,哈哈。当时还想弄地图编辑器的,最后还是咕咕咕了
相当搞笑的一点:前面有段代码是获取地图文件夹下所有文件名,存入字符数组。当时不知道字符串尾部要加结束符,导致后面使用该字符串读入文件时屡屡崩溃(没有结束符嘛)。
于是,宇宙大聪明答主写了这么一段处理代码,大概思路是将这个字符串复制一遍,检查复制后字符串的长度是否异常,如果异常则再复制一遍……(笑哭.jpg)
当时win7和现在win10的控制台空格长度不相同,现在运行会字符错位,地图看上去乱糟糟。正常应该是没有这些缝缝的。
穿甲弹,击中墙壁后产生2格杀伤区
穿甲弹杀伤大,但是有几率跳弹。有效击穿时会在对方炮塔处显示“#”。
高爆弹,产生3x3爆炸范围
“谢尔曼M1型”自带速射主炮,每分钟350发(大雾)。可惜为了平衡,并没有采用原版APHEI(高爆穿甲燃烧弹),而是杀伤力很低的糖豆炮。
炮射导弹,射出去能拐个弯。cd长 伤害低,但打掩体后的敌人是为一绝。
炸药桶地图块,被击中产生5x5爆炸范围。灰色地图块无法被破坏,浅黄色的可以。
坦克被击毁时有几率殉爆,产生5x5爆炸范围,而且持续时间比炸药桶长得多。
程序的稳定性相当差劲,截这些图的时候答主n次卡死或控制台弹窗报错了……
是的,当时连printf都不知道,打印字符用效率奇低的cout;而且一帧里逻辑处理和渲染混杂执行,导致画面容易闪烁甚至掉帧……
这是敌人AI的一部分,用n个套娃循环实现类似A*算法的寻路。AI寻找能射击到敌人的最近位置,还会根据自身血量等计算攻击欲望,躲避可能伤害到自己的炮弹,拾取有用的道具。整套算法效率特别特别低,每帧都要完整执行一次;容易导致掉帧。所以场上只能有一只精英怪使用完整的AI,其他小怪用随机函数xjb乱逛。
小怪:不是很聪明的亚子
类似移动、碰撞检测之类的部分,每一份代码都要手动复制四次,对应上下左右四个方向。
再放开头定义的一段吧:
#include <iostream>
#include <windows.h>
#include <fstream>
//#include <iomanip>
//#include <string.h>
//#include <stdio.h>
//#include <conio.h>
#include <sstream>
#include <cstdlib>
#include <ctime>
#include <io.h>//文件
#include <vector>//文件
#include<Mmsystem.h>//音频相关
#pragma comment(lib,&#34;winmm.lib&#34;)//音频
#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0)
using namespace std;
int map = {0};//储存地图
int mapX = 55;//地图x大小
int mapY = 38;//地图y大小
int allX = 30;//整体x位移
int allY = 0;//整体y位移
int circlet = 0;//循环次数
int model;//当前游玩模式
int buff = {0};//0[储存道具:1动能弹,2高爆弹,9生命值 ] ,1x,2y
int tank = {0};//储存坦克:[编号],
//武器:0普通炮,1速射炮,2高爆弹,3破甲弹,4跟踪弹
int shell = {0};//储存炮弹:[所属坦克],[编号],
int smoke = {0};//储存硝烟&#34;#&#34;:[编号],
//**********基础功能**********
void ProgramBegin();//初始化程序
void FuZhi();//初始赋值
void gotoxy(int,int);//跳转光标
void color(int);//颜色
void getFiles(string, vector<string>&);//获取目标文件夹内所有文件名
void InMap(char*);//将地图导入数组
void Tout(int);//打印地图方块
void PrintMap();//打印地图
void PrintTank(int,int,int,int);//打印坦克,朝向,x,y,类型
void DelTank(int,int);//屏幕删除坦克
void sound(int);//音效
//**********菜单界面**********
int MenuModel();//模式菜单
char* MenuMap();//选图菜单
void MenuTank1vN();//选车菜单1vN模式
void MenuTank1v1();//选车菜单1v1模式
void Interface();//玩家状态界面
void NewUI();//刷新状态界面
int MenuPveWin(int);//结束人机战局
int MenuPvpWin(int);//结束双人战局
//**********战斗功能**********
void CtrlTank(int,int);//坦克控制
void InShell(int);//载入炮弹
void SkyShell();//炮弹移动
bool HitTank(int,int,int);//判断坦克是否受到伤害
void HitShell(int,int,int,int,int,int);//炮弹命中.朝向,x,y,弹药类型,被命中者 ,发射者
void Boom(int,int,int);//爆炸。半径,x,y
void InBuff();//载入道具
void OffBuff(int);//使用(消除)道具
void InSmoke(int,int);//载入硝烟
void OffSmoke();//删除硝烟
//**********AI功能**********
int FindMap(int,int,int,int);//寻路。起点x,y,终点x,y。返回方向,1234上下左右。
void AiTank();//坦克AI
void XieTank(int);//屑AI
void Born(int,int);//生成新车,编号、难度
//**********模式**********
void annihilate();//歼灭模式
void pve();//人机
void pvp();//双人对战
//**********附加**********
void op();//片头
void loading();//伪加载界面
void xy();//显示硝烟参数
void pd();//显示炮弹参数
void MenuZhanYi();//战役是不是已经想吐了~3.5k行代码塞在一个.cpp里
有没有留意到,上面那一堆全局变量的数据结构只有int数组。怎么用它储存数量和参数不等的坦克、炮弹、粒子(爆炸火光、烟雾……)等东西的呢?答主“发明”了这样一种方法:
一个数组储存一种类的物体,第一维记录该类所有物体。如坦克tank数组第一维长度是10,那地图上最多只能同时存在10辆坦克;tank 表示编号为5的坦克。数组第二维储存这个物体的个体属性。如tank代表5号坦克的朝向,、代表其坐标,等。
游戏中每一帧,依次遍历坦克、炮弹、特效等数组,提取数组中每一位物体,根据玩家输入和游戏逻辑更新该物体的参数(也就是更新这个数组)。
关键的来了:怎么实现生成和销毁物体,比如炮弹命中时销毁自身?很简单,要销毁物体,将那个物体所在的数组第二维清零就行了。刷新物体遍历数组时,遇到这种“空槽”就跳过。要增加物体,也遍历一遍该数组,找到一个“空槽”,将新物体的初始数据写入。
一段时间后,答主知道了有种东西叫结构体,有门课叫数据结构,有种数据结构叫链表,有种对象叫面向对象。可惜到现在也没找到对象,唉。
最烂的代码果然还是自己写的代码,这简直是个珠穆奥力给峰。好在没有人需要为这个“项目”负责,没有人需要维护它,它只需要静静地躺在硬盘深处的角落就行了。
总结:
后来呢我还是一直热衷于游戏开发。坦克大战后不久,写的第二个“大工程”是基于qt的2d海战游戏,有点类似顶视角的wows。为啥当时突然想学qt?也许是前一个学期写作业用mfc太痛苦了吧。总之,按我的坏习惯,看了两三天文档就兴冲冲开工写demo了。后期的代码和工程现在已经找不到了,只找到一些前期的素材:
按照舰船的真实顶视图 用ps画svg矢量图精灵
代码风格也是极其奥力给,回忆一下大概像这样:
thisGuanQia->ui->myship->labelPao->setText(&#34;主炮0开火cd:&#34;+QString::number(thisGuanQia->jianDui->ship->my->ship->wuQiZu->pao->cd->nowCD));//在标签上打印 我方舰队 第一只船 第一个武器组 第一门炮的cd
thisGuanQia->ui->myship->labelKey->setText(&#34;W&#34;+QString::number(ctrl.keyW)+&#34;S&#34;+QString::number(ctrl.keyS)+&#34;A&#34;+QString::number(ctrl.keyA)+&#34;D&#34;+QString::number(ctrl.keyD)+&#34;↑&#34;+QString::number(ctrl.keyUp)+&#34;↓&#34;+QString::number(ctrl.keyDown)+&#34;←&#34;+QString::number(ctrl.keyLeft)+&#34;→&#34;+QString::number(ctrl.keyRight)+&#34;GN&#34;+QString::number(ctrl.keyShift)+QString::number(ctrl.keyCtrl)+QString::number(ctrl.keyAlt)); //打印键盘事件那时不会调编辑器缩进,现在也忘了qt原生编辑器是长啥样的。反正基本上整段代码写在一行里…………看代码要按紧shift+滚轮,对小拇指可是个考验。
终于会用队列了,比如开炮时的火光就是将特效序列帧存入一个队列,然后每帧读取一张。还写了一套几何碰撞检测,虽然只支持点、线、椭圆、长方形这四种元素。逻辑和渲染终于分开了。还写了一段帧率控制算法,按本帧逻辑部分的执行时间决定接下来sleep多久,使帧率尽量稳定在30。(然而加这玩意使帧率更不稳定,后来全删了)
最后先帝创业未半而中道崩黜,加了很多功能后帧率实在太低。用的应该是2代i5m集显,帧率只有12不到……
再后来接触了“真正”的游戏引擎,发现啊这个也太方便太易用了。原来之前花了很多时间瞎折腾的烂代码,在引擎里竟然全有对应功能的接口?我想到的,前人全都想到过啊。
随便聊一下,引擎里最让我愉悦的是cocos creator。新手教程级的文档,极其舒服的环境。对比隔壁unity,去unity和ccc的api文档用中文搜点东西,ccc大多是你想要的,unity文档搜索是什么玩意。ccc的中文社区建设的也挺好,活跃度高,干货多;而且中文搜索引擎能直接搜到帖子内容。unity中文社区,??? 可惜ccc本质还是个轻(棋)量(牌)级引擎,论功能跟unity、ue暂时还没得比。 点名某宇宙战舰级开源游戏引擎,此帖常年更新。
贴心小公告:
评论区好热闹,列几个见过不知道多少次的给该引擎洗地的评论,如果作为看官您正好想要这么说,希望这个小公告可以帮您省下打字的时间,直接摁数字提交选项就可以了:
大家都这样。你行你上。这样肯定有它的设计哲学,你不懂。这么成功的引擎,设计肯定没问题。
都这么多次的老朋友了,当然也没什么好反驳或者互怼的,大家和和气气坐下喝杯茶,你吹你的,我喷我的,其乐融融,多好。
富强 民主 文明 和谐 自由 平等 公正 法治 爱国 敬业 诚信 友善
将会分选里边几个部分阐述这座山的构造。
你猜猜一个class究竟能做多少种功能?
在打开工程时,我从来没有认真的思考过这个问题,在我这么一个普通的程序猿眼里看来,类型要尽可能高聚合低耦合,一个类完成一个功能就够了,实在不行写个组件管理类包一下嘛,结果发现。我 太 年 轻 了
首先,当我看到一个接口用多继承,继承了1个基类和2个接口时,碳基生物求生的本能在用最大音量警告我危险:
看类名,不难推断出,这个东西是干嘛的。游戏引擎嘛,材质总共就两种,一种管渲染的材质,里边包一堆数据和着色器引用,一种管物理的材质,基本是纯数据(质量,摩擦力等)。抱着这种心情我们开始继续往下看,映入眼帘的是一个屏幕空间次表面,这基本锁定了这是一个渲染材质的事实,虽然把一个屏幕特效放在材质里稍微有点丑,但并不是不可以接受:
接下来,内心产生了一点小小的疑惑,它既然是一个渲染材质,储存各种参数是正常的,但是为什么下面居然有一个这个?
按理说,这是一个编译相关的应该是静态的数据,应该是编译期完成序列化的,怎么会出现在一个纯动态的材质类里?
先不管,继续往下看,这时画风开始不对劲了起来,在这个管渲染的类里居然出现了物理!
发现这确实是货真价实的摩擦力之类的力学物理,和光学物理没有任何关系:
心态已经出现了一丝丝的波澜,但是问题并不是很大,继续看:
地形!材质中居然出现了地形!!!
看到这时我内心产生了一个疑问:这个类到底还能做多少事情??!
搜一下Mesh看看,都这么NB了,和模型耦合一下正常的很啊对不对?
诶,还真有!虽然只是编译器内用的,但是也是有的嘛!
那么最后,问题来了,这个接口类(注意只是个接口类啊),究竟能完成多少功能?和多少个领域耦合了?
没错,这个神奇的“类”,如果还可以称之为一个类型的话,总共涉及到了:
模型贴图加载地形系统力学物理光照静态编译动态序列化布料曲线2D图片水渲染毛发渲染点云
从此对“耦合”一词有了更深层次的理解。