Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 339

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 392

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 394
Joyber 的博客


    /**
     * 生成 uuid v4
     * @return string
     */
    public static function uuidv4()
    {
        try {
            $data = random_bytes(16);
        } catch (\Exception $e) {
            AppHelper::systemLog($e);
            // 兜底:固定前缀+mt_rand,保持和uuid一样格式
            return sprintf('00000000-%04x-%04x-%04x-%08x%04x',
                mt_rand(0, 0xffff),
                mt_rand(0, 0xffff),
                mt_rand(0, 0xffff),
                mt_rand(0, 0xffffffff),
                mt_rand(0, 0xffff)
            );
        }
        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
    }

    /**
     * 生成随机的批次码
     * @param $prefix
     * @param $mode
     * @return string
     */
    public static function randomBatchCode($prefix = '', $mode = 1)
    {
        // UUID v4,标准唯一,无小数点
        $prefix = $prefix != '' ? $prefix . '-' : '';
        $str    = $prefix . self::uuidv4();
        if ($mode != 1) {
            return str_replace('-', '', $str);
        }
        return $str;
    }

    /**
     * 纯数字订单号: 雪花算法,时钟回拨时自动降级到时间随机方案兜底
     * @param string $prefix 订单号前缀
     * @param int $machineId 机器编号 0~31
     * @return string
     */
    public static function createOrderSn($prefix='', int $machineId = 1): string
    {
        static $lastTimestamp = 0;
        static $sequence = 0;
        $epoch = 1609459200000;

        $timestamp = (int)(microtime(true) * 1000);

        // 时钟回拨,直接降级简易方案,不报错
        if ($timestamp < $lastTimestamp) {
            return self::createOrderSnSimple($prefix);
        }

        if ($timestamp === $lastTimestamp) {
            $sequence = ($sequence + 1) & 4095;
            if ($sequence === 0) {
                usleep(1000);
                $timestamp = (int)(microtime(true) * 1000);
                // 再次判断回拨兜底
                if ($timestamp < $lastTimestamp) {
                    return self::createOrderSnSimple($prefix);
                }
            }
        } else {
            $sequence = 0;
        }
        $lastTimestamp = $timestamp;

        $timePart  = $timestamp - $epoch;
        $snowflake = ($timePart << 22) | ($machineId << 17) | $sequence;
        $snowflake = (string)$snowflake;
        return $prefix . $snowflake;
    }

    /**
     * 纯数字订单号生成函数(兜底方案):毫秒时间 + 6位随机,纯数字,永远不会抛异常
     * @param string $prefix 订单号前缀
     * @return string
     */
    public static function createOrderSnSimple($prefix = ''): string
    {
        $ms = (string)(microtime(true) * 1000);
        $ms = explode('.', $ms)[0];
        try {
            $rand_number = random_int(0, 999999);
        } catch (\Exception $e) {
            //AppHelper::systemLog($e);
            $rand_number = mt_rand(0, 999999);
        }
        $rand = str_pad($rand_number, 6, '0', STR_PAD_LEFT);
        return $prefix . $ms . $rand;
    }

Cocos Creator 3.x 引擎系统进阶学习路线(12周)

前提:已掌握 TS 基础、Node/Component 生命周期、能独立做简单小游戏。本路线聚焦工程化、性能、内存、框架封装,不覆盖入门操作。
版本:Cocos Creator 3.8.x,目标平台微信小游戏。

第1周:资源系统进阶(生命周期与分包)

学习目标

  • 吃透 assetManager 的加载、缓存、释放链路,能定位内存泄漏
  • 掌握引用计数 addRef / decRef 的手动管理时机
  • 掌握分包加载与远程资源热更前置能力

核心知识点

  • resources.load / assetManager.loadAny 的加载管线与版本配置
  • 自动释放 vs 手动释放:场景切换后哪些资源会被自动回收
  • Asset.addRef() / decRef() 与内存泄漏的关系
  • 图集 SpriteAtlas 的依赖资源(SpriteFrame)引用链
  • 分包配置:主包 / 分包 / 远程包的加载策略
  • loadRemote 远程图片与缓存清理

练习 Demo:资源加载器封装

  • 写一个 ResManager 单例,封装 load / loadDir / release / releaseAll
  • 支持按模块(UI / 音效 / 场景)分组释放
  • 加载一个预制体 → 实例化 → 延迟销毁 → 主动释放资源,在微信开发者工具 Memory 面板观察纹理是否被回收

验收标准

  • 能解释为什么直接 destroy(node) 后纹理还在内存中
  • 能写出正确释放一个 Prefab 及其依赖纹理的代码
  • 配置一个分包,主包启动时不加载分包资源,进入对应界面时才加载
  • Demo 连续进出场景 10 次,内存曲线不持续上涨

第2周:节点组件架构与对象池

学习目标

  • 摆脱"一个脚本写到底",建立可维护的组件化架构
  • 掌握对象池解决频繁实例化开销

核心知识点

  • 节点树查找的性能代价:find() vs 缓存引用
  • 组件间通信的三种方式:直接 getComponent / 节点事件 / 全局事件总线
  • NodePool 对象池:创建、获取、回收、预热
  • 预制体实例化 instantiate 的开销与适用场景
  • 节点 active 切换 vs destroy 的性能差异

练习 Demo:子弹/掉落物对象池

  • 实现一个 ObjPool<T> 通用对象池组件
  • 生成 200 个移动的子弹,用池化方式复用,禁止每帧 instantiate
  • 对比池化前后帧率(在微信开发者工具 Performance 面板查看)

验收标准

  • 1 秒内生成 100 个对象不卡顿,GC 次数明显下降
  • 对象回收时正确重置状态(位置、旋转、透明度、事件监听)
  • 能说出直接 instantiate 在小游戏低端机上的问题

第3周:UI 框架与列表复用

学习目标

  • 搭出可复用的 UI 框架:弹窗栈、界面基类、自动适配
  • 掌握 ScrollView 高效复用(这是小游戏列表性能关键)

核心知识点

  • UI 基类设计:onOpen / onClose / onShow / onHide 生命周期
  • 弹窗管理器:弹窗栈、遮罩、模态队列、关闭回调
  • Widget 锚点适配与安全区(刘海屏)
  • ScrollView + Layout 普通模式 vs 列表虚拟滚动
  • GridLayout 与列表项预制体复用
  • Label 文字排版、RichText 性能开销

练习 Demo:完整 UI 框架 + 长列表

  • 实现 UIManager:open(prefabPath) / close(uiName) / closeAll
  • 实现一个 1000 条数据的排行榜列表,滚动不卡顿(用复用而非全量创建)
  • 做一个多层弹窗叠加:主界面 → 弹窗A → 弹窗B → 关闭B回到A

验收标准

  • 列表滚动 60fps(中低端机),节点数量保持在屏幕可见项附近
  • 弹窗打开关闭有统一动画和回调,不出现层级错乱
  • 切换不同分辨率设计稿,UI 不错位、不被刘海遮挡

第4周:动画系统进阶

学习目标

  • 掌握 Tween 链式编排与动画状态机,不依赖手动写帧动画
  • 理解 Animation 组件的状态控制与事件

核心知识点

  • tween 链式 API:sequence / parallel / repeat / delay / call
  • Tween 的 stop / pause / resume 与 target 引用管理
  • Animation 组件:play / crossFade / stop,动画状态切换
  • 动画事件(Animation Event)回调到脚本
  • Spine 骨骼动画基础(如角色需要):动画混合、插槽控制
  • 动效性能:避免每帧修改 color / position 触发过度渲染

练习 Demo:游戏开场动效编排

  • 用 Tween 编排:Logo 缩放入场 → 文字淡入 → 按钮弹起 → 可跳过
  • 实现一个角色待机/移动/受伤三个 AnimationClip 的状态切换
  • 给按钮做统一的按压缩放动效(可复用组件)

验收标准

  • 动效时序精确,能在中途跳过且不残留状态
  • 动画切换无突兀跳变(用 crossFade)
  • 关闭界面时正确停止 Tween,防止回调操作已销毁节点

第5周:输入系统与手势

学习目标

  • 脱离 Button 组件,处理复杂触摸手势与多点触控
  • 掌握坐标转换,适配不同分辨率

核心知识点

  • 触摸事件:TOUCH_START / MOVE / END / CANCEL
  • 事件冒泡与捕获,swallowTouches 的作用
  • 屏幕坐标 → UI 坐标 → 世界坐标的转换
  • 手势识别:点击、长按、滑动方向、双指缩放
  • 多点触控:touch id 跟踪
  • 微信小游戏:触摸事件与鼠标事件的差异

练习 Demo:拖拽拼图 / 摇杆控制

  • 实现一个可拖拽的物体,跟随手指,松手有回弹
  • 实现一个虚拟摇杆:控制角色移动方向和速度
  • 实现双指缩放一个图片

验收标准

  • 拖拽跟手,无明显延迟,快速滑动不丢失 touch
  • 摇杆有死区和最大半径,输出方向向量正确
  • 在不同分辨率手机上,触摸坐标映射正确

第6周:事件总线与架构模式

学习目标

  • 建立模块间解耦通信,避免节点互相引用导致的强耦合
  • 掌握常用游戏架构模式在 Cocos 中的落地

核心知识点

  • EventTarget 全局事件总线:on / off / emit / targetOff
  • 事件监听的生命周期:onLoad 监听,onDestroy 必须移除
  • 命令模式 / 消息队列:UI 事件 → 命令 → 逻辑处理
  • 单例管理器设计:GameManager / DataManager / AudioManager
  • MVC 思想:数据、视图、逻辑分离
  • 防止事件泄漏:组件销毁后仍收到事件导致报错

练习 Demo:全局事件通信

  • 实现一个 EventBus 单例
  • 战斗逻辑层发出 SCORE_CHANGE 事件,UI 层监听并刷新分数,两者不直接引用
  • 实现一个简单的命令队列:点击按钮 → 执行命令 → 广播结果

验收标准

  • 关闭任意界面后,该界面注册的事件全部移除(无残留监听)
  • 模块之间不出现 getComponent 跨层硬引用
  • 能解释为什么节点销毁后不移除监听会报错

第7周:音频系统与微信适配

学习目标

  • 掌握音频池与动态加载,解决微信小游戏音频常见问题
  • BGM 与音效分离管理

核心知识点

  • AudioSource 组件 vs audioManager 接口
  • 音效池:多个相同音效同时播放(如连续射击)
  • BGM 循环、淡入淡出、切换
  • 微信小游戏音频限制:必须由用户手势触发才能播放
  • 音频格式:mp3 vs wav 的包体与性能权衡
  • 静音开关与音量持久化

练习 Demo:音频管理器

  • 实现 AudioManager:playMusic / playSound / stopAll / setVolume
  • 音效对象池:同时播放多个射击音效不互相打断
  • 处理微信:首次点击屏幕后才解锁音频

验收标准

  • 连续快速点击按钮,音效叠加播放不卡顿
  • 切后台 BGM 暂停,回前台恢复
  • 重启游戏后音量设置保留

第8周:数据驱动与存档系统

学习目标

  • 用配置表驱动游戏数值,不硬编码
  • 掌握存档读写、版本迁移、数据保护

核心知识点

  • JSON 配置表加载:resources.load 加载 JsonAsset
  • 数据管理器:全局单例管理玩家数据
  • sys.localStorage 读写与序列化
  • 存档版本号:版本升级时旧存档迁移
  • 数据加密(可选):防止简单篡改
  • 脏数据标记:哪些数据变化了需要写盘(避免频繁 IO)

练习 Demo:玩家数据系统

  • 设计一个 PlayerData:金币、最高分、关卡进度、设置项
  • 实现 DataManager:load / save / reset / 自动存档
  • 做一个关卡配置表(JSON),从配置读取关卡参数,改配置不改代码

验收标准

  • 杀进程重进游戏,数据完整恢复
  • 修改配置表后,不改动脚本即可改变关卡数值
  • 存档损坏时有兜底(读默认值),不白屏

第9周:渲染性能优化

学习目标

  • 能定位并解决 DrawCall、过度绘制、纹理内存问题
  • 这是微信小游戏包体和帧率达标的关键

核心知识点

  • DrawCall 概念:为什么合图能减少 DrawCall
  • SpriteAtlas 图集规划:按界面/模块打包,避免全量图集
  • 动态合图 vs 静态图集的适用场景
  • 过度绘制:半透明遮罩叠加层数
  • 纹理格式:ETC2 / ASTC 压缩,包体与内存
  • 节点数量优化:不可见节点及时 active = false
  • 微信开发者工具:DrawCall、FPS、内存面板使用

练习 Demo:性能对比优化

  • 做一个有 50 个 Sprite 的界面,先散图后合图,对比 DrawCall
  • 找出一个过度绘制严重的界面(多层半透明),优化层数
  • 用 ASTC 压缩纹理,对比包体大小

验收标准

  • 能在开发者工具中读出当前场景 DrawCall 数
  • 同屏 100 个同图集 Sprite,DrawCall 保持低位
  • 主包纹理内存控制在微信限制内(通常 ≤ 150MB 运行内存)

第10周:物理系统进阶(2D)

学习目标

  • 掌握 2D 物理的碰撞过滤、传感器、物理调试
  • 理解物理与游戏逻辑的解耦

核心知识点

  • RigidBody2D 三种类型:dynamic / static / kinematic
  • Collider2D:Box / Circle / Polygon,传感器(isSensor)
  • 碰撞回调:onBeginContact / onEndContact,碰撞对象信息
  • 碰撞分组与掩码:group / category,避免不必要碰撞
  • 物理材质:摩擦、弹性
  • 物理调试绘制:PhysicsSystem2D.instance.debugDrawFlags
  • 物理更新时机:fixedUpdate vs update

练习 Demo:碰撞检测游戏

  • 实现一个角色(kinematic)与多个障碍物(static)碰撞
  • 用传感器触发"进入区域"事件,而非物理阻挡
  • 配置碰撞分组:玩家只与敌人碰撞,不与背景碰撞

验收标准

  • 碰撞回调能正确拿到双方节点并执行逻辑
  • 传感器触发不产生物理反弹
  • 开启调试绘制,碰撞体形状与视觉一致

第11周:网络层与实时通信

学习目标

  • 封装统一请求层,处理超时、重试、错误码
  • 了解 WebSocket 长连接基础

核心知识点

  • fetch / wx.request 的 Promise 封装
  • 请求拦截:统一加 token、loading、错误提示
  • 错误码处理:登录失效、网络断开、超时
  • WebSocket:连接、重连、心跳、消息分发
  • 协议选择:JSON vs protobuf(小游戏包体敏感)
  • 微信小游戏:必须 https / wss,域名白名单配置

练习 Demo:网络管理器

  • 封装 HttpManager:get / post,统一错误处理
  • 实现一个简单的 WebSocket 连接:断线自动重连 + 心跳
  • 请求一个远程配置接口,失败时有重试

验收标准

  • 断网时请求有友好提示,不卡死
  • WebSocket 断开后能自动重连
  • 所有请求走统一入口,不散落 wx.request

第12周:微信小游戏专项与发布

学习目标

  • 完成从开发到上传的全流程,处理平台特有限制
  • 掌握分包、广告、性能监控

核心知识点

  • 构建发布:微信小游戏构建选项、资源服务器配置
  • 主包 4M 限制:分包策略、远程资源
  • 微信登录:wx.login → code → 后端换 session
  • 激励视频广告:加载、播放、关闭回调、失败兜底
  • 插屏广告、Banner 广告
  • 开放数据域:好友排行榜(独立子域)
  • 性能监控:wx.getPerformance、错误上报
  • 审核注意事项:隐私协议、类目、内容合规

练习 Demo:可上传的完整小游戏

  • 选一个前面做过的 demo,配置分包:主包只放启动和首页,其余进分包
  • 接入一个激励视频广告:看广告复活 / 得奖励
  • 配置微信登录,获取 openid(可模拟)
  • 上传体验版,在真机测试

验收标准

  • 主包体积 ≤ 4M,分包按需加载
  • 广告加载失败时有兜底逻辑(不卡死游戏)
  • 真机测试:启动时间、帧率、内存无明显异常
  • 能走通上传 → 提审流程(不要求真过审)

配套交付物清单(每周产出)

周次产出物
1ResManager 封装 + 内存对比截图
2通用对象池 + 帧率对比
3UIManager + 可复用列表组件
4动效编排脚本 + 状态切换
5拖拽/摇杆组件
6EventBus + 模块解耦 demo
7AudioManager
8DataManager + 配置表
9优化前后 DrawCall 对比
10物理碰撞 demo
11HttpManager + WebSocket 封装
12可上传的微信小游戏工程

学习建议

  1. 每周一个可运行 demo,不要只看文档;每周末用微信开发者工具真机自测一次。
  2. 第 1、3、9、12 周是微信小游戏上线的硬骨头,建议多花时间。
  3. 遇到问题优先查 Cocos 官方 API 文档和微信小游戏官方文档,第三方博客版本可能滞后。

=AVERAGEIF(C:C,"餐后",D:D)

作用

单条件求平均值:在C列中,找到单元格内容等于餐后的行,对应D列的数值,计算这些数字的算术平均值。

语法

AVERAGEIF(条件区域, 条件, 求平均区域)
  • C:C:条件区域,用来判断的列(这里是C整列)
  • "餐后":判断条件,单元格文字必须完全匹配“餐后”才参与计算,文本条件要加英文双引号
  • D:D:要计算平均值的数据列

示例

C列是类型(餐前/餐后),D列是血糖数值

CD
餐后12.3
餐前8.1
餐后14.2

公式会筛选两行餐后,计算 (12.3+14.2)/2 = 13.25

注意点

  1. 条件文字区分空格,"餐后 "(后面带空格)和"餐后"判定为不相同
  2. D列中非数字内容会自动忽略,不参与平均
  3. 找不到满足条件的单元格,公式返回#DIV/0!除零错误

✅ 博客最常见两种:

1. particles.js(老博客最经典),升级版本叫 tsParticles(推荐新项目)

就是你描述效果:页面背景漂浮小点,近的点自动连线,鼠标移动附近粒子会吸附(grab抓取模式,粒子被鼠标吸过去,还拉出连线),Canvas实现。

  • onhover.mode: "grab":鼠标靠近,粒子吸附到光标,带连线,就是你看到的效果
  • line_linked:粒子互相之间近距离自动生成细线>

    很多Hexo、VuePress、VitePress博客早年全用这个。

2. canvas-nest.js(国内博客大量用,极简!)

国内博客圈非常流行,只有几百行原生JS,不需要任何依赖。
特点:背景小点、点之间连线;鼠标靠近,粒子向鼠标聚拢吸附。
很多sakura、hexo博客模板自带这个,代码极轻。

区别:canvas-nest功能少,只能做背景粒子;particles/tsParticles自定义能力极强。

简单区分

  • 只想快速给博客加背景粒子吸附效果,不想折腾配置:canvas-nest.js,一行引入直接用。
  • 需要精细调颜色、粒子数量、多种鼠标模式(吸附/排斥/点击爆炸):tsParticles(新版particles)

补充:和刚才模块网络图的区别

⚠️ 注意区分:

  1. particles.js / canvas-nest:背景粒子特效,粒子自由漂浮,鼠标吸附,用于博客首页背景;
  2. force-graph / d3-force:力导向图,是表达数据之间依赖关系(你前面Vite模块图),不是单纯装饰背景。

tsParticles 关键配置片段(实现鼠标吸附抓取效果)

"interactivity": {
  "events": {
    "onhover": {
      "enable": true,
      "mode": "grab" // grab=吸附抓取,鼠标和粒子之间拉出连线
    }
  },
  "modes": {
    "grab": {
      "distance": 140,
      "links": {
        "opacity": 0.5
      }
    }
  }
}

canvas-nest 最简引入

<script src="[https://cdn.jsdelivr.net/npm/canvas-nest.js/dist/canvas-nest.min.js](https://cdn.jsdelivr.net/npm/canvas-nest.js/dist/canvas-nest.min.js)"></script>

引入页面直接生效,无需额外配置。

你是打算加到Vue博客页面做背景吗?我可以直接给对应的Vue版本代码。

方案1:concat 拼接(按顺序首尾相连,最常用)

  1. 先新建 list.txt

    file 'audio1.mp3'
    file 'audio2.mp3'
    file 'audio3.mp3'
  2. ffmpeg命令

    ffmpeg -f concat -safe 0 -i list.txt -c:a libmp3lame output.mp3
  3. -safe 0:允许文件使用绝对路径
  4. -c:a libmp3lame:mp3编码

方案2:直接在命令行写3个文件,不用txt

ffmpeg -i audio1.mp3 -i audio2.mp3 -i audio3.mp3 -filter_complex "[0:a][1:a][2:a]concat=n=3:v=0:a=1" -c:a libmp3lame output.mp3
注意:此方式要求三个音频采样率、声道、采样位完全一致,否则会失真/失败。推荐优先用方案1。

方案3:混音(三个音频同时播放,叠加在一起,不是首尾拼接)

ffmpeg -i audio1.mp3 -i audio2.mp3 -i audio3.mp3 -filter_complex "[0:a][1:a][2:a]amix=inputs=3:duration=longest" -c:a libmp3lame output.mp3

常用可选参数

  • 指定码率 -b:a 128k

    ffmpeg -f concat -safe 0 -i list.txt -c:a libmp3lame -b:a 128k output.mp3

区分:

  • concat:一段播完接着下一段(拼接)
  • amix:三个声音一起响(混音叠加)