02丨轲目苦津游戏代码规范:内核与外附系统

创建日期:2025-01-11

更新日期:2025-02-04

阅读次数:45

核心思想:内核与外附系统

项目分为一个内核与多个外附系统。 几万行代码的项目只需要几百行的内核,其余的都是外附系统。每个外附系统是一个文件夹,这个文件夹可以直接删除、不会有任何报错。 例如,成就系统是一个典型的外附系统。通过【让成就通过订阅事件来记录】以及【初始化函数特性】,可以实现【成就文件夹可以直接删除、不会有任何报错】。

案例:成就订阅事件

需求:按下WASD移动,累计移动十格解锁成就 一阶:耦合严重

public void 定向移动(Vector2 方向) {
    var A = 方向.单位向量() * 每秒速度 * 帧时长;
    坐标 += A;
    累计移动 += Mathf.Abs(A);
    if (累计移动 >= 10 && !已解锁累计移动成就) {
        Alert("解锁成就:移动十格!");
        已解锁累计移动成就 = true;
    }
}

二阶:使用成就类,并且让框架自动注册并统计每一个成就的解锁状况

public void 定向移动(Vector2 方向) {
    var A = 方向.单位向量() * 每秒速度 * 帧时长;
    坐标 += A;
    当移动时(A);
}
public class 移动成就 : I成就 {
    public float 累计移动;
    public string 解锁消息 => "解锁成就:移动十格!";
    public bool 解锁标准() => 累计移动 >= 10;
    public void Init() => 当移动时 += t => 累计移动 += Mathf.Abs(t);
}

三阶:A的存在太丑了,想办法删掉A

public void 定向移动(Vector2 方向) => 瞬移(方向.单位向量() * 每秒速度 * 帧时长);
public void 瞬移(Vector2 向量) {
    坐标 += 向量;
    当移动时(向量);
}
public class 移动成就 : I成就 {
    public float 累计移动;
    public string 解锁消息 => "解锁成就:移动十格!";
    public bool 解锁标准() => 累计移动 >= 10;
    public void Init() => 当移动时 += t => 累计移动 += Mathf.Abs(t);
}

四阶:不要专门给成就预留事件,改为使用通用事件。这样真正实现【内核与外附互不操心】。

public class 定向移动指令 : I指令 { (已屏蔽)
    public 角色类 角色;
    public Vector2 方向;
    public void _Exec() => new 瞬移法则 { 角色 = 角色, 向量 = 方向.单位向量() * 角色.每秒速度 * 帧时长}.Exec();(已屏蔽)
}
public class 瞬移法则 : I法则 { (已屏蔽)
    public 角色类 角色;
    public Vector2 向量;
    public void _Exec() => 角色.坐标 += 向量;
}
public class 移动成就 : I成就 { (已屏蔽)
    public float 累计移动;
    public string 解锁消息 => "解锁成就:移动十格!";
    public bool 解锁标准() => 累计移动 >= 10;
    public void Init() => OnExec<瞬移法则>(t => 累计移动 += Mathf.Abs(t.向量));
}
public static partial class LocalStorage { (已屏蔽)
    初始化函数
    public static void 初始化成就() {
        (已屏蔽)
    }
}

现在我们就实现了【成就外附】。整个成就系统可以作为一个文件夹,如果删掉这个文件夹、那么成就系统失效,如果加上这个文件夹、那么成就系统生效。 上面的结构有如下好处:

  • 适合分工,高内聚低耦合。写成就的人只需要写成就,写内核的人只需要写内核,提交的git代码永远不会有冲突,两个人工作永远在不同的文件夹之中。
  • 高扩展性。很多游戏公司都会面临一个令人崩溃的问题:游戏后期引入成就系统后,整个代码几乎都需要重构,不重构的话、成就系统会出现一堆bug,越改越乱,还会引发其他模块的bug。而使用上面的成就外附结构,成就的引入与修改都非常轻松,完全不需要修改内核。(我不希望看到:我提出一堆成就的需求后、程序告诉我【做不了】或者【做这些东西需要非常高的成本,十多天】或者【你怎么总修改成就?能不能一次定下来?】。使用本文的结构,程序应该不会说这些话,如果依然说,那么我在了解具体情况后会接受)
  • 适合外置记忆,避免大文件。大文件找代码会很难,因此文件最好一百行左右,两百行就是较多的了。每种成就一个类,方便分割文件;如果所有成就一个类,每种成就一个实例,那么不方便分割文件。
  • 方便接入符文。符文是内置的脚本,玩家可以通过符文来自动调用指令,实现自动挖矿、自动刷怪、自动交易等操作,但玩家不能作弊使得攻击力无限大。因此我拆分出了指令和法则两个东西。

案例:高级指令

指令并不都是内核。内核只需要一套最简单的指令,其余高级的指令作为外附系统、借助最简指令来实现。 需求:移动不只是WASD。可以右键点击地板,角色会自动移动过去 一阶:脱裤子放屁,每次写一个指令就改一遍内核?

public class 定点移动指令 : I指令 {
    public 角色类 角色;
    public Vector2 目标点;
    public void _Exec() {
        角色.目标点 = 目标点;(已屏蔽)
    }
}

二阶:新增外附指令时,只使用旧功能。

public class 定点移动指令 : I指令 {
    public 角色类 角色;
    public Vector2 目标点;
    public void _Exec() {
        角色.StartTaskLine(new TaskLine {
            终局 = () => 角色.坐标.IsAround(目标点), 
            每帧 = () => 角色.坐标 += 角色.坐标.计算方向(目标点) * 角色.每秒速度 * 帧时长,
        });
    }
}

三阶:不要有重复的代码。借助之前的定向移动指令。

public class 定点移动指令 : I指令 {
    public 角色类 角色;
    public Vector2 目标点;
    public void _Exec() {
        角色.StartTaskLine(new TaskLine {
            终局 = () => 角色.坐标.IsAround(目标点), 
            每帧 = () => new 定向移动指令 { 角色 = 角色, 方向 = 角色.坐标.计算方向(目标点)}.Exec(),
        });
    }
}
public class 定向移动指令 : I指令 {
    public 角色类 角色;
    public Vector2 方向;
    public void _Exec() => new 瞬移法则 { 角色 = 角色, 向量 = 方向.单位向量() * 角色.每秒速度 * 帧时长}.Exec();
}
public class 瞬移法则 : I法则 {
    public 角色类 角色;
    public Vector2 向量;
    public void _Exec() => 角色.坐标 += 向量;
}

宗旨:

  1. 定点移动之类的高级指令都是外附系统。写指令之前,先明确它是内核还是外附,明确方式是【能否用其他的来定义它】,能定义的话它就是外附。

  2. 不要有重复的代码。

好处:

  • 宗旨1可以实现最小内核,避免内核冗余。
  • 宗旨2可以避免【两处代码类似,导致当设计改动时,你需要同时修改这两个地方,但你忘了、只修改了一个】。嘉豪应该对这个感触很深:扫雷的【是否真实视野】使用了两个类似的代码,导致多个因为忘了同步修改而引起的bug。

扩展

整个前台都是外附系统。 读档存档是外附系统。 新手任务是外附系统。

FAQ

问:你要表达什么?你希望我(读者)做什么? 答:采纳本文所描述的编程思想。

问:做不到。本文太残缺了。就本文所描述出来的这些内容,设计的很好,但是本文没描述出来的内容,我(读者)没法自己推导出来。 答:没关系。你只需要做到这两点就够了: 第一,赞同本文,并时常回味。平时吃饭、散步、坐车的时候,想一想自己的代码,明确一下问题:我做到内核与外附分离了吗?内核是什么?外附是什么? 你并不需要去具体思考。你只需要明确问题,你的潜意识就会自动开始思考,这是一种很奇妙的感觉,不相信的话你可以试一试:吃饭的时候,明确问题,然后放空大脑、进入一种发呆的状态、机械的吃饭。等发呆结束,吃完饭,你会发现你仿佛突然变聪明了,有一堆关于内核与外附的灵感。 #关键词/自我管理/潜意识思考法 第二,写完代码之后,偶尔尝试去优化代码结构。你不需要强迫自己去做这个。结合【第一】,你只需要在你灵感爆发的时候去做。