项目说明

项目概况

  • 本项目基于 Cocos Creator 2.4.15 开发。
  • 主要开发语言为 TypeScript
  • Android 平台使用 Android Studio 进行构建。
  • iOS 平台使用 Xcode 进行构建。

协作约定

  • 所有改动应尽量兼容 Cocos Creator 2.4.15 的工作流和项目结构。
  • 优先遵循仓库现有的 TypeScript 编码风格与目录组织方式。
  • 避免引入当前 Cocos Creator 2.4.15 环境不支持的工具链、语法或特性。
  • 涉及移动端构建调整时,Android 相关改动应保持兼容 Android Studio 工程,iOS 相关改动应保持兼容 Xcode 工程。

构建背景

  • Cocos Creator 是游戏逻辑开发与资源集成的主要编辑环境。
  • 导出或生成原生工程后,移动端的打包、调试与发布流程继续在 Android StudioXcode 中完成。

提交约定

  • 如果用户输入 commit,默认表示要直接提交当前已暂存的内容
  • 只允许基于当前 git add 过的 staged diff 整理改动并生成简短 commit message
  • 默认不要自动把未暂存文件加入提交
  • 如果 staged 为空,应明确提示无法提交
  • 如果 staged 内容明显混入无关改动,应先提醒用户再决定是否继续提交
  • commit message 默认保持简短、直接,优先使用类似 feat: ...fix: ...refactor: ... 的格式
  • 除非用户明确要求,否则不要改写历史,不要 commit --amend,不要执行破坏性 git 操作
  • 用中文提交

开发习惯规范

1. Button 事件监听

开发时如果遇到 XQButtoncc.Button 类型,添加点击监听事件时统一使用以下写法:

1
this.btn.node.on("click", this.onClick, this);

约束

  • 统一使用 on("click", handler, this) 形式绑定。
  • 第三个参数必须传 this,保证回调上下文正确。
  • 禁止省略上下文参数或改为匿名函数包装。

2. @property 节点空值检查

对于使用 @property 装饰器声明的属性(通常是编辑器绑定的节点或组件),在代码中使用时不要添加空值判断

规范

  • 错误写法

    1
    2
    3
    4
    5
    6
    7
    8
    @property(cc.Node)
    myNode: cc.Node = null;

    start() {
    if (this.myNode) { // ❌ 禁止这种防御性编程
    this.myNode.active = true;
    }
    }
  • 正确写法

    1
    2
    3
    start() {
    this.myNode.active = true; // ✅ 直接调用,如果为空直接报错
    }

原因

  • 这些属性预期必须在编辑器中挂载。
  • 如果运行时为空,直接抛出异常(NullPointerException)能第一时间暴露“忘记挂载节点”的问题。
  • 添加空值判断会掩盖配置错误,导致功能静默失效,增加排查难度。

3. 多语言调用方法

需要使用多语言文案时,统一通过以下方式获取:

1
GlobalObjectMgr.instance.localizedTextHelper.getLocalString("");

约束

  • 统一使用 GlobalObjectMgr.instance.localizedTextHelper.getLocalString(key)

4. 动画实现方式

需要实现动画效果时,统一使用 cc.tween 方法。

1
cc.tween(node).to(0.3, { scale: 1.1 }).start();

如有异步串行动画需求,统一使用:

1
await asyncMgr.tween(cc.tween(node).to(0.3, { scale: 1.1 }));

约束

  • 普通动画统一使用 cc.tween(...).start()
  • 异步场景统一使用 asyncMgr.tween(cc.tween(...)),异步方法调用不需要调用 start()

5. 弹窗节点复用规范

继承 WindowBaseUIBase 的脚本属于弹窗界面。

生命周期说明

  • 节点一般情况下不会销毁。
  • 每次打开弹窗都会调用 refreshUI

开发约束

  • 节点尽量避免“销毁后再创建”的实现方式。
  • 如果存在动态创建节点并维护数组的场景,不要在每次 refreshUI 时清空并重建。
  • 首次创建后应缓存到数组中,后续 refreshUI 直接复用数组内节点,仅更新显示状态和内容。
  • 仅在确实需要时补充新增节点,已有节点优先复用。

代码审查规范

当用户输入 review 时,按以下步骤自动执行代码审查:

执行流程

  1. 查看 unstaged 文件

    • 执行 git status 查看当前所有 unstaged 文件列表
  2. 筛选代码文件

    • 只审查代码文件(.ts.js 等源代码文件)
    • 跳过以下类型的文件
      • .meta 文件(Cocos Creator 元数据文件)
      • .prefab 文件(预制体文件)
      • .scene 文件(场景文件)
      • 图片、音频等资源文件(.png.jpg.mp3 等)
      • 其他非代码文件
  3. 审查内容

    • 读取筛选后的每个代码文件
    • 检查以下问题:
      • 代码风格是否符合项目规范
      • 是否遵循 development-standards.instructions.md 中定义的开发习惯
      • 是否有明显的逻辑错误或 bug
      • 是否有未处理的 null/undefined 检查
      • 是否存在不适当的错误处理
      • 命名是否清晰规范
      • 是否有多余的代码或死代码
      • 是否有性能问题
  4. 输出审查结果

    • 逐个文件列出发现的问题
    • 对每个问题提供具体建议
    • 如果没有问题,输出确认信息

审查标准

必须检查

  • 遵循 TypeScript 编码规范
  • 遵循项目中 development-standards.instructions.md 的约定
  • Button 事件监听是否使用规范的 on("click", handler, this) 写法
  • @property 节点是否添加了不必要的空值判断
  • 是否正确使用多语言 API
  • 动画是否使用了 cc.tween 方法
  • 弹窗节点是否遵循复用规范

参考检查

  • 代码可读性和维护性
  • 潜在的边界情况处理
  • 异常处理的完整性
  • 资源泄漏风险

explicit

构造用explicit声明,防止非必要的隐式转换

reference &

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

int main()
{
int val = 1024;
int &refVal = ival;

int ii = val;

std::cout << val << " " << refVal << " " << ii << std::endl;

val = 1025;
std::cout << val << " " << refVal << " " << ii << std::endl;

refVal = 1026;
std::cout << val << " " << refVal << " " << ii << std::endl;

return 0;
}

// 1024 1024 1024
// 1025 1025 1024
// 1026 1026 1024

IOS

devtools://devtools/bundled/js_app.html?v8only=true&ws=127.0.0.1:6086/00010002-0003-4004-8005-000600070008

usb端口映射

brew install libusbmuxd
iproxy 6086 6086


Android

第一步:构建项目

第二步:AndroidStudio 打开项目 build/jsb-default/frameworks/runtime-src/proj.android-studio

第三步:使用外部 Android 设备运行项目;

第四步:

  • App 启动后控制台输入 adb logcat|grep “chrome-devtools”;

  • 输出 log 04-17 14:05:51.386 9858 10015 D jswrapper: Debugger listening…, visit [ chrome-devtools://devtools/bundled/inspector.html?v8only=true&ws=0.0.0.0:5086/00010002-0003-4004-8005-000600070008 ] in chrome browser to debug!

  • 控制台输入 adb forward tcp:5086 tcp:5086;

第五步: 浏览器打开:devtools://devtools/bundled/inspector.html?v8only=true&ws=0.0.0.0:5086/00010002-0003-4004-8005-000600070008 开始调试

远程调试接口快照

1
http://{设备ip}:6086/json

开发环境构建

插件安装

1
npm install -D @eslint/js@^10.0.1 eslint@^10.0.3 eslint-config-prettier@^10.1.8 eslint-plugin-only-warn@^1.2.1 eslint-plugin-prettier@^5.5.5 globals@^17.4.0 prettier@^3.8.1 typescript@^5.9.3 typescript-eslint@^8.57.0

环境配置

开发

事件


useCapture

node.on(event,callback,target,useCapture)

  • true: 捕获模式,从父节点开始依次捕获事件
  • false: 冒泡模式,从子节点开始依次向父节点冒泡

阻止冒泡

node.stopPropagation()

  • 阻止向父节点冒泡,当前节点其他事件监听依然有效
  • useCapture 为 true 时阻止子节点捕获

swallowTouches

  • node._touchListener.setSwallowTouches(args:bool)
    • true:穿透触摸事件

匀速贝塞尔曲线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
bezier(p0: cc.Vec2, p1: cc.Vec2, p2: cc.Vec2, t: number) {
const p = p0
.mul(Math.pow(1 - t, 2))
.add(p1.mul(2 * t * (1 - t)))
.add(p2.mul(Math.pow(t, 2)));
return p;
},

async bezierUniformMove(pos: cc.Vec2, mid: cc.Vec2, target: cc.Vec2, node: cc.Node, duration: number) {
let samples: { t: number, len: number }[] = [];
let totalLen = 0;
let prev = this.bezier(pos, mid, target, 0);
for (let i = 1; i <= 100; i++) {
let t = i / 100;
let pt = this.bezier(pos, mid, target, t);
let d = prev.sub(pt).mag();
totalLen += d;
samples.push({ t, len: totalLen });
prev = pt;
}

const getTByLen = (curLen: number) => {
for (let i = 0; i < samples.length; i++) {
if (samples[i].len >= curLen) {
// 线性插值
let prev = samples[i - 1] || { t: 0, len: 0 };
let ratio = (curLen - prev.len) / (samples[i].len - prev.len);
return prev.t + (samples[i].t - prev.t) * ratio;
}
}
return 1;
}

await asyncMgr.tween(cc.tween(node).to(duration, { position: target }, {
progress: (start, end, current, ratio) => {
const cl = totalLen * ratio;
const t = getTByLen(cl);
const result = this.bezier(pos, mid, target, t);
return result;
}
}))
}

2.4.x更新lib库

android firebase crashlytics 上传符号表

1
2
3
4
5
6
7
8
9
10
firebaseCrashlytics {
// Enable processing and uploading of native symbols to Crashlytics servers.
// By default, this is disabled to improve build speeds.
// This flag must be enabled to see properly-symbolicated native
// stack traces in the Crashlytics dashboard.
nativeSymbolUploadEnabled true
strippedNativeLibsDir 'build/intermediates/stripped_native_libs/'
unstrippedNativeLibsDir 'build/intermediates/merged_native_libs'
mappingFileUploadEnabled true
}

开发日记

项目管理

  • 确定需求时直接判断是否大量复用,拆分成公用组件。
  • App版本号规范,MAJOR.MINOR.PATCH。

    版本迭代更新MINOR,服务器也至少通过MINOR判断对应服务器。PATCH更新不需要对应服务器。ios app每次提审后都需要更新PATCH版本号,因此如果使用PATCH对应服务器版本则提审后再次更新都需要更新对应服务器。

目录结构

定义类型声明

  • 通用类型声明最好放一块, 便于查找, 避免定义过多重复类型。
  • 单独类型放入各自目录下, 避免于通用类型声明混淆。
1
2
3
4
5
6
7
scripts/
├── interfaces/ -->接口
│ ├── IUser.ts
│ └── IReward.ts
├── activities/activity/ -->某单独活动
│ └── interfaces/
│ └── IActivity.ts

代码大全

软件构建中的设计

代码设计基本特性

  • 最小复杂度
  • 易于维护
  • 松散耦合
  • 可扩展性
  • 可重用性
  • 高扇入 (大量的类使用给定的类, 例如工具类)
  • 低扇出 (一个类适中地使用其他类)
  • 可移植性
  • 精简性
  • 层次性
  • 标准技术
    Read more »

《DirectX12 3D 游戏开发实战》

Q&S


Common

  • “HANDLE CreateEventExW(LPSECURITY_ATTRIBUTES,LPCWSTR,DWORD,DWORD)”: 无法将参数 2 从“bool”转换为“LPCWSTR” DirectX12\Common\d3dApp.cpp 535

    • VS2019 报错,将 CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS); 第二个参数false改为nullptr;
  • LNK2019 无法解析的外部符号 _main,函数 “int __cdecl invoke_main(void)” (?invoke_main@@YAHXZ) 中引用了该符号 DirectX12\D3DCommon\MSVCRTD.lib(exe_main.obj)

    • 初始化时没有将项目设置为桌面应用程序,右键项目-> 属性 -> 配置属性 -> C/C++ -> 预处理器 -> 预处理器定义中将_CONSOLE 改为 _WINDOWS;../-> 链接器 -> 系统 -> 子系统改为”窗口 (/SUBSYSTEM:WINDOWS)”

Mac

Ruby

  • rvm 安装 ruby 报错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    brew install ruby
    // 查看自己安装的是哪个版本

    rvm mount /usr/local/Cellar/ruby/3.4.2
    // mount命令好像会让你自己起名,没起直接用默认的了。

    // rvm list查看名字
    rvm use ext-ruby-3.4.2-rd2930f8e7a --default

    // use后环境可能有问题,重登shell ruby依然使用的系统ruby,直接重装rvm
    rvm get stable --auto-dotfiles
  • rvm 安装警告,乱七八糟的问题。

    1
    2
    3
    4
    // 直接重装
    rvm implode
    \curl -sSL https://get.rvm.io | bash
    source ~/.rvm/scripts/rvm

Linear Algebra

向量绝对值

  • ||v|| = √(x^2+y^2+…)

Dot Product 点积、内积 P·Q

几何定义:

avatar

代数定义:

avatar

主要用于

  • 计算向量夹角余弦值

  • 计算a向量在b向量上的投影: a的单位向量乘以b向量长度再乘以两向量夹角余弦
    avatar

  • sqrt(dot(a,a)) = length(a)


Cross Product 外积 P x Q

||PxQ|| = ||P|| ||Q|| sinα

代数定义:

avatar

主要用于

  • 判断左右,右手螺旋定则,a x b,z为正则在a左侧,反之则右侧,0两个向量平行
  • 判断点是否包含于图形
    avatar
    AB x AP, BC x BP, CA x CP, 若z值同向则包含ABC包含P

Orthonormal bases 正交,内积为0则为正交向量

Coordinate frames


ShaderToy

1
2
3
4
5
6
7
8
9
10
11
12
// 平滑过渡
float smoothstep(float a, float b, float x){
// saturate 取0 ~ 1范围内的值
float t = saturate((x - a)/(b - a));
return t*t*(3.0 - (2.0*t));
}

// 混合函数
vec3 mix(vec3 x,vec3 y,float a){
return x * (1. - a) + y * a;
}

0%