<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>游戏开发 on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Sat, 05 Sep 2020 21:00:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/categories/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>《princes》：用 Cocos Creator 写一款餐厅经营放置小游戏</title>
      <link>https://ftmao.com/blog/posts/princes-restaurant-game-intro/</link>
      <pubDate>Sat, 05 Sep 2020 21:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/princes-restaurant-game-intro/</guid>
      <description>&lt;h1 id=&#34;princes用-cocos-creator-写一款餐厅经营放置小游戏&#34;&gt;《princes》：用 Cocos Creator 写一款餐厅经营放置小游戏&lt;/h1&gt;
&lt;p&gt;继《去赶海吧》之后，把更早做的一款餐厅经营小游戏也整理开源了，照例写篇文章介绍玩法和实现上的技术点。仓库名叫 princes（历史遗留命名，内容其实是餐厅），代码里还住着不少从其他项目移民过来的残留，这次一并&amp;quot;考古&amp;quot;出来。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开源地址：&lt;a href=&#34;https://gitee.com/soulnew/princes&#34;&gt;https://gitee.com/soulnew/princes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;技术栈：Cocos Creator 2.3.4 + JavaScript&lt;/li&gt;
&lt;li&gt;平台：微信小游戏（竖屏 720×1280）&lt;/li&gt;
&lt;li&gt;规模：39 个脚本，约 4200 行代码，单主场景&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;游戏核心玩法一句话概括：&lt;strong&gt;开一家餐厅，顾客自动上门排队等位、走到餐桌点餐、用餐完留下金币，你点击收金币，再用金币解锁更多食物、餐桌和顾客皮肤&lt;/strong&gt;——放置 + 点击收菜的经典经营循环。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一玩法介绍&#34;&gt;一、玩法介绍&lt;/h2&gt;
&lt;h3 id=&#34;11-核心循环&#34;&gt;1.1 核心循环&lt;/h3&gt;
&lt;p&gt;玩家几乎不直接操作角色，核心是&amp;quot;看店 + 点击收钱 + 花钱解锁&amp;quot;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[顾客自动上门&amp;lt;br/&amp;gt;每15秒补1个] --&amp;gt; B{有空餐桌?}
    B --&amp;gt;|有| C[A*寻路走到餐桌]
    B --&amp;gt;|没有| D[门口排队等位]
    D --&amp;gt;|有空桌释放| C
    C --&amp;gt; E[头顶弹气泡点单]
    E --&amp;gt; F[用餐5秒&amp;lt;br/&amp;gt;点击气泡可加速]
    F --&amp;gt; G[桌边产出一堆金币]
    G --&amp;gt; H[玩家点击拾取]
    H --&amp;gt; I[解锁新食物/餐桌/顾客]
    I --&amp;gt; A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;几个关键设计：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;餐桌是稀缺资源&lt;/strong&gt;：场上最多 10 个顾客（&lt;code&gt;maxPlayer = 10&lt;/code&gt;），没桌子的顾客会在门口按队列序号排开站位，有空桌释放时队首补位、全队前移；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;点单匹配&lt;/strong&gt;：每个顾客皮肤有自己的&amp;quot;可点菜单&amp;quot;（&lt;code&gt;foodList&lt;/code&gt;），随机点一道——这道菜已解锁就正常产金币，没解锁就显示&amp;quot;没有这道菜&amp;quot;的图标，等于告诉你&amp;quot;该去商店解锁了&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;巡游消费&lt;/strong&gt;：顾客吃完饭不直接走，而是从酒柜、古董花瓶、海滩桌等 6 类装饰家具里洗牌出一份&amp;quot;参观清单&amp;quot;，逐件走过去参观，每件停留几秒再留下一笔金币——买装饰家具不只是好看，是真的能多赚钱；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;邀请按钮&lt;/strong&gt;：等不及可以点&amp;quot;邀请&amp;quot;立刻来一位顾客，这个入口同时服务每日任务和成就。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;12-四屏地图与翻页&#34;&gt;1.2 四屏地图与翻页&lt;/h3&gt;
&lt;p&gt;餐厅不是一张单屏地图，而是四个房间：&lt;strong&gt;海滩（left1）、大厅（mid）、剧院（right1）、花园（down1）&lt;/strong&gt;。顾客在房间之间穿行时走&amp;quot;传送门&amp;quot;格子——比如大厅到海滩，先寻路到 &lt;code&gt;(0,11)&lt;/code&gt; 这个门，切换父节点到海滩场景节点，再从海滩的入口格 &lt;code&gt;(12,11)&lt;/code&gt; 继续寻路。&lt;/p&gt;
&lt;p&gt;相机不是跟随式的，而是&lt;strong&gt;手动拖拽翻页&lt;/strong&gt;：单轴锁定滑动，滑过屏宽 1/10 左右翻页、屏高 1/14 上下翻页。门口还有两个服务员（&lt;code&gt;waiterPlayer&lt;/code&gt;），有顾客进门就弹出欢迎气泡 5 秒——不做实际传菜逻辑，纯氛围组。&lt;/p&gt;
&lt;h3 id=&#34;13-成长线解锁三件套&#34;&gt;1.3 成长线：解锁三件套&lt;/h3&gt;
&lt;p&gt;金币花在三个商店面板上：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;商店&lt;/th&gt;
					&lt;th&gt;内容&lt;/th&gt;
					&lt;th&gt;解锁的意义&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;食物&lt;/td&gt;
					&lt;td&gt;54 道菜，价格 100~153 递增&lt;/td&gt;
					&lt;td&gt;顾客点单能匹配上，用餐才产金币&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;家具&lt;/td&gt;
					&lt;td&gt;大厅 6 组餐桌（每组 3 级升级）+ 各房间散桌 + 装饰家具&lt;/td&gt;
					&lt;td&gt;新餐桌 = 多一个接待座位；装饰家具 = 巡游产金币点&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;顾客&lt;/td&gt;
					&lt;td&gt;50 种皮肤&lt;/td&gt;
					&lt;td&gt;新皮肤进入上门顾客的随机池&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;顾客皮肤的名字很有梗：学士、水电工、警长、老板、小青龙、法师、熊猫、海贼、管道工、侦探、伯爵、外星人、厨神、牙医、流浪歌手、开膛手、屠夫、王少爷、腹肌男、特工K、机器人……基本是把能想到的梗都塞进去了。&lt;/p&gt;
&lt;h3 id=&#34;14-成就与每日任务&#34;&gt;1.4 成就与每日任务&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;15 条成就&lt;/strong&gt;分 5 种类型：解锁指定物品、解锁 N 种某类物品、服务 N 个顾客、累计金币（500/5000/10000/30000）、邀请 N 次，奖励统一 5000 金币；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每日任务&lt;/strong&gt;从 7 条里随机洗牌取 6 条（服务顾客 2 次、累计 800 金币、邀请 2 次、招待指定顾客 2 次等），按本地日期键 &lt;code&gt;年_月_日&lt;/code&gt; 翻日重置，还会随机指定&amp;quot;今天要招待某位顾客&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两个系统都有红点提醒，领取后自动检查链上下一档成就。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二技术亮点&#34;&gt;二、技术亮点&lt;/h2&gt;
&lt;p&gt;项目不大（4000 行出头），但有几个点是自己撸出来的轮子，值得展开。&lt;/p&gt;
&lt;h3 id=&#34;21-手写-a--传送门式跨屏寻路&#34;&gt;2.1 手写 A* + &amp;ldquo;传送门&amp;quot;式跨屏寻路&lt;/h3&gt;
&lt;p&gt;寻路没有用任何库，&lt;code&gt;AStar.js&lt;/code&gt;（217 行）是手写的标准实现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;地图是 &lt;code&gt;position.json&lt;/code&gt; 里一个 &lt;strong&gt;21×13 的一维数组&lt;/strong&gt;（273 个元素，&lt;code&gt;0&lt;/code&gt; 可走，其他障碍），&lt;code&gt;screenManager&lt;/code&gt; 按屏幕尺寸算出格子边长，做格坐标到像素坐标的换算；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AStarStep&lt;/code&gt; 封装 &lt;code&gt;g/h/position/last&lt;/code&gt;，&lt;code&gt;f&lt;/code&gt; 是 &lt;code&gt;g+h&lt;/code&gt; 的 getter；启发函数用曼哈顿距离，八方向扩展，斜走 14、直走 10（经典的 √2×10 近似）；&lt;/li&gt;
&lt;li&gt;拿到格子路径后，把每一格转成 &lt;code&gt;cc.moveTo(1, 像素坐标)&lt;/code&gt; 拼成 &lt;code&gt;cc.sequence&lt;/code&gt; 播放——&lt;strong&gt;每格固定走 1 秒&lt;/strong&gt;，移动节奏完全由动画序列驱动，不需要 &lt;code&gt;update&lt;/code&gt; 里手动插值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;跨屏是这套寻路最有意思的部分——四个房间不是一张大地图，而是各自独立的节点树，靠 6 对&amp;quot;门坐标&amp;quot;缝合：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[当前房间] --&amp;gt;|寻路到门格 mid_left1| B[门 0,11]
    B --&amp;gt;|切换父节点| C[目标房间]
    C --&amp;gt;|从入口格 left1_mid 继续寻路| D[最终目标]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;大地图拆成小地图 + 门对，每个房间的寻路空间始终只有 21×13，复杂度和内存都可控，是小工程里很实用的拆法。&lt;/p&gt;
&lt;h3 id=&#34;22-自研虚拟列表-listview&#34;&gt;2.2 自研虚拟列表 ListView&lt;/h3&gt;
&lt;p&gt;Cocos 2.x 官方没有现成的虚拟列表，商店、成就、每日任务的长列表就手写了一个（&lt;code&gt;ListView.js&lt;/code&gt;，380 行，项目内最大文件）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NodePool 复用&lt;/strong&gt;：划出视野的 item 回池，进入视野的从池里取或 instantiate；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;增量填充&lt;/strong&gt;：&lt;code&gt;_filledIds&lt;/code&gt; 记录当前树上已填充的索引，&lt;code&gt;update()&lt;/code&gt; 每帧用 content 的滚动偏移算起始 item 索引，变了才触发 &lt;code&gt;updateView&lt;/code&gt;，只回收越界的、只补缺失的；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adapter 模式&lt;/strong&gt;：&lt;code&gt;ListAdapter&lt;/code&gt; 持有数据和 item 脚本名，&lt;code&gt;updateView&lt;/code&gt; 里 &lt;code&gt;item.getComponent(componentName).setData(data)&lt;/code&gt;——Android ListView 的味道很浓，View 和数据彻底分离；&lt;/li&gt;
&lt;li&gt;支持多列网格（&lt;code&gt;oneLineItemNumber&lt;/code&gt;）、上拉/下拉回调、监听 &lt;code&gt;refreshScollView&lt;/code&gt; 事件做局部刷新。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;家具面板没用这个 ListView，而是手写了一个更简的裁剪：视野内 &lt;code&gt;opacity=255&lt;/code&gt;、视野外 &lt;code&gt;opacity=0&lt;/code&gt;。同一项目里两种方案并存，算是&amp;quot;够用就好&amp;quot;和&amp;quot;造个轮子&amp;quot;的对照组。&lt;/p&gt;
&lt;h3 id=&#34;23-餐桌分配一个微缩排队系统&#34;&gt;2.3 餐桌分配：一个微缩排队系统&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;realTimeData.js&lt;/code&gt; 里藏着一个小小的服务台排队模型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;activeFurnitureData&lt;/code&gt; 是&amp;quot;空闲桌池&amp;rdquo;，顾客来了随机抽一张（&lt;code&gt;splice&lt;/code&gt; 取出），用完 &lt;code&gt;push&lt;/code&gt; 回去；&lt;/li&gt;
&lt;li&gt;没桌时顾客进 &lt;code&gt;waitTableQuenue&lt;/code&gt;，按 &lt;code&gt;index/maxPlayer&lt;/code&gt; 比例在门口插值站位——队伍有多长，人就排多密；&lt;/li&gt;
&lt;li&gt;空桌释放时遍历等待队列，队首补位、全队回调前移。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;没有定时器轮询，全靠事件驱动，几十行代码把&amp;quot;等位&amp;quot;这个经营游戏的核心张力做出来了。&lt;/p&gt;
&lt;h3 id=&#34;24-运行时程序化动画两帧图集走天下&#34;&gt;2.4 运行时程序化动画：两帧图集走天下&lt;/h3&gt;
&lt;p&gt;所有角色（50 个顾客皮肤 + 2 个服务员）的走路动画都是&lt;strong&gt;运行时代码创建&lt;/strong&gt;的。&lt;code&gt;AnimationBase.js&lt;/code&gt; 只有 23 行：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;clip&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;AnimationClip&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;createWithSpriteFrames&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;frames&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;clip&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;wrapMode&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WrapMode&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Loop&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;美术只需要给每个皮肤画两帧（&lt;code&gt;icon&lt;/code&gt;/&lt;code&gt;icon2&lt;/code&gt;），代码动态建 clip 循环播放，&amp;ldquo;两帧鬼畜踏步&amp;quot;反而有一种魔性的喜感。50 套皮肤 = 100 帧图 + 一段 23 行的代码，动画资源维护成本为零。这个思路和《去赶海吧》的皮肤动画一脉相承。&lt;/p&gt;
&lt;h3 id=&#34;25-金币堆对象池--聚合拾取&#34;&gt;2.5 金币堆：对象池 + 聚合拾取&lt;/h3&gt;
&lt;p&gt;金币是这个游戏最高频的创建/销毁对象，处理上花了最多心思（git 历史里&amp;quot;金币堆积优化&amp;quot;&amp;ldquo;金币拾取动画效果修改&amp;quot;好几个 commit 都在打磨这块）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;金币走 &lt;code&gt;cc.NodePool&lt;/code&gt;（上限 50），生成时随机旋转、1 秒散落动画到桌边随机位置；&lt;/li&gt;
&lt;li&gt;同一格子的金币记入 &lt;code&gt;nearGroup&lt;/code&gt; &lt;strong&gt;按堆聚合&lt;/strong&gt;，面额累加进 &lt;code&gt;valueMap&lt;/code&gt;——十份饭钱堆成一坨，而不是十个独立节点；&lt;/li&gt;
&lt;li&gt;池满时 &lt;code&gt;getGoldByMoveNearby&lt;/code&gt; 从邻居堆&amp;quot;借&amp;quot;一个实体过来复用；&lt;/li&gt;
&lt;li&gt;点击一堆 → 整堆回收，合成一个 &amp;ldquo;+总额&amp;rdquo; 数字标签飞向屏幕左上角的 HUD，飞行时长按距离等比计算，落地才到账。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;性能和手感都照顾到了：节点数有上限，拾取有&amp;quot;整堆收进腰包&amp;quot;的爽感。&lt;/p&gt;
&lt;h3 id=&#34;26-事件驱动与成就三态流转&#34;&gt;2.6 事件驱动与成就三态流转&lt;/h3&gt;
&lt;p&gt;全局只有一个 50 行的事件中心（&lt;code&gt;eventuality.js&lt;/code&gt;，出自 Crockford《JavaScript 语言精粹》里的 &lt;code&gt;eventuality&lt;/code&gt; 函数），只有 &lt;code&gt;fire/on/removeListener&lt;/code&gt; 三个方法，所有跨模块通信——&lt;code&gt;loadover&lt;/code&gt;、&lt;code&gt;addGold&lt;/code&gt;、&lt;code&gt;unLock&lt;/code&gt;、&lt;code&gt;serverPlayer&lt;/code&gt;、&lt;code&gt;invite&lt;/code&gt;、&lt;code&gt;arriveAtGate&lt;/code&gt;、&lt;code&gt;showDialog&lt;/code&gt;——都走它。&lt;/p&gt;
&lt;p&gt;成就/每日任务系统（两个 Manager 合计 660 行）建立在这个事件中心上，数据按三态流转：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[unachieveMap&amp;lt;br/&amp;gt;未达成 按类型分桶] --&amp;gt;|事件计数达标| B[waitPayMap&amp;lt;br/&amp;gt;达成待领取]
    B --&amp;gt;|点击领取| C[已领取&amp;lt;br/&amp;gt;写入存档]
    C --&amp;gt;|自动检查| D[链上下一档成就]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;事件一来，只检查对应桶里的成就，不用全表扫描；领取后自动把下一档（服务 10 人 → 服务 50 人 → ……）挪进未达成桶继续监听。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三整体架构一览&#34;&gt;三、整体架构一览&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    subgraph 场景层
        L[loading.fire&amp;lt;br/&amp;gt;微信分包加载] --&amp;gt; G[game.fire&amp;lt;br/&amp;gt;主场景 103节点]
    end
    subgraph 入口与全局
        G --&amp;gt; GAME[game.js&amp;lt;br/&amp;gt;顾客生成/对象池]
        GL[global.js&amp;lt;br/&amp;gt;playerData/event/realTimeData]
        GAME --&amp;gt; GL
    end
    subgraph 玩法层
        GAME --&amp;gt; P[player.js&amp;lt;br/&amp;gt;顾客AI: 排队/寻路/点单/巡游]
        GAME --&amp;gt; W[waiterPlayer.js&amp;lt;br/&amp;gt;迎宾氛围组]
        GAME --&amp;gt; FM[FurnitureManager&amp;lt;br/&amp;gt;家具摆放/餐桌注册]
        GAME --&amp;gt; GM[GoldManager&amp;lt;br/&amp;gt;金币堆/拾取]
        P --&amp;gt; AS[AStar.js&amp;lt;br/&amp;gt;21×13格子寻路]
        P --&amp;gt; SM[screenManager&amp;lt;br/&amp;gt;格子坐标系/跨屏门]
    end
    subgraph 系统层
        AM[AchievementManager&amp;lt;br/&amp;gt;15条成就]
        DM[DailyTaskManager&amp;lt;br/&amp;gt;每日任务随机6条]
        LV[ListView&amp;lt;br/&amp;gt;自研虚拟列表]
        GUI[guide.js&amp;lt;br/&amp;gt;HUD与五个面板]
    end
    subgraph 数据层
        PD[player-data.js&amp;lt;br/&amp;gt;6份json配置+localStorage存档]
        RT[realTimeData.js&amp;lt;br/&amp;gt;餐桌队列/对象池/运行时状态]
    end
    GL --&amp;gt; AM
    GL --&amp;gt; DM
    GL --&amp;gt; PD
    GL --&amp;gt; RT
    GUI --&amp;gt; LV&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;数据分三层：&lt;code&gt;defines.js&lt;/code&gt; 常量表（配置路径 / 存档键 / 事件名）、&lt;code&gt;player-data.js&lt;/code&gt; 持久层（6 份 json 配置 + localStorage 存档，配置是 Excel 手填导出的）、&lt;code&gt;realTimeData.js&lt;/code&gt; 运行时状态（对象池、餐桌队列、zIndex 发号器）。首次进游戏送 400 金币，默认解锁每类第一个条目。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四找茬彩蛋&#34;&gt;四、找茬彩蛋&lt;/h2&gt;
&lt;p&gt;和《去赶海吧》一样，代码里的瑕疵原样保留，欢迎找茬：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;&lt;em&gt;A&lt;/em&gt; 代价函数传错参&lt;/em&gt;*（&lt;code&gt;AStar.js:87&lt;/code&gt;）：算邻居格移动代价时，&lt;code&gt;_costToMove(borderPosition, finish)&lt;/code&gt; 把终点当成上一个节点传了进去，导致大量格子被按斜走 14 计费、g 值普遍虚高。好在曼哈顿 h 也偏小，错误互相稀释，路还是能找对，只是已经不是标准 A* 了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全项目调了一个不存在的方法&lt;/strong&gt;：事件中心只实现了 &lt;code&gt;removeListener&lt;/code&gt;，但十几处 &lt;code&gt;onDestroy&lt;/code&gt; 里都在调 &lt;code&gt;global.event.off(...)&lt;/code&gt;——调用即 TypeError，只是主场景从不销毁所以没炸过。就算有 &lt;code&gt;off&lt;/code&gt;，传进去的也是新建箭头函数，永远匹配不上原监听器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;死代码雷区&lt;/strong&gt;（&lt;code&gt;utils/pathSearch.js&lt;/code&gt;）：另一套从未被实例化的 A*，里面有 &lt;code&gt;this.year = &#39;2002&#39;&lt;/code&gt; 调试残留、原型方法引用闭包变量（调用即 ReferenceError）、严格模式下裸赋值，是从网上教程抄来没跑通的草稿。&lt;code&gt;paopao.js&lt;/code&gt; 和 &lt;code&gt;food.js&lt;/code&gt; 则是从泡泡龙/三消项目移民来的——&lt;code&gt;food.js&lt;/code&gt; 的 &lt;code&gt;onLoad&lt;/code&gt; 会调一个不存在的方法，挂上节点必崩，所以它谁也没挂。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拼写宇宙&lt;/strong&gt;：&lt;code&gt;createPalyer&lt;/code&gt;、&lt;code&gt;walktoTabel&lt;/code&gt;、&lt;code&gt;distence&lt;/code&gt;、&lt;code&gt;memuPanel&lt;/code&gt; 全文通行；最惨的是 &lt;code&gt;awarddiamand&lt;/code&gt;（成就表）和 &lt;code&gt;awarddiamond&lt;/code&gt;（家具表）两种拼法并存，&lt;code&gt;AchievementItem.getAward&lt;/code&gt; 只认前者，家具表里的钻石奖励永远读不到。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每日任务污染成就存档&lt;/strong&gt;（&lt;code&gt;DailyTaskManager.js:220&lt;/code&gt;）：每日服务计数误写进了成就用的总计数键；&lt;code&gt;payAward&lt;/code&gt; 的 switch 里还有两个永远进不去的 &lt;code&gt;undefined&lt;/code&gt; case。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;另外 &lt;code&gt;defines.js&lt;/code&gt; 里挂着 9 个根本不存在的配置路径（&lt;code&gt;engineerNameConfig&lt;/code&gt;……&amp;ldquo;工程师&amp;quot;暴露了这是从另一个项目复制来的），food.json 里 54 道菜的名字是&amp;quot;食物1&amp;quot;到&amp;quot;食物54&amp;rdquo;——明显的占位数值没来得及填。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五写在最后&#34;&gt;五、写在最后&lt;/h2&gt;
&lt;p&gt;从 git 历史能还原出开发顺序：跨屏寻路 → 桌子排队 → 成就系统 → 每日任务 → 钻石 → 金币特效与堆积优化 → 层级修正——先把核心玩法立住，再一层层包外围系统，最后打磨手感。&lt;/p&gt;
&lt;p&gt;这是一个典型的个人开发者&amp;quot;麻雀型&amp;quot;项目：4000 行代码里同时住着写得相当讲究的虚拟列表、排队模型、跨屏寻路，和成堆的复制粘贴残留、拼写错误与失效代码。亮点和找茬各占一半，反而更适合做&amp;quot;真实项目代码考古&amp;quot;的样本——毕竟真实项目从来不是教科书。&lt;/p&gt;
&lt;p&gt;源码在 &lt;a href=&#34;https://gitee.com/soulnew/princes&#34;&gt;https://gitee.com/soulnew/princes&lt;/a&gt;，欢迎 Star，也欢迎提 Issue 交流。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>教你打麻将</title>
      <link>https://ftmao.com/blog/posts/beimi-chess-card-game-summary/</link>
      <pubDate>Wed, 24 Jun 2020 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/beimi-chess-card-game-summary/</guid>
      <description>&lt;h1 id=&#34;教你打麻将&#34;&gt;教你打麻将&lt;/h1&gt;
&lt;p&gt;文章面向&lt;strong&gt;从未接触过联网游戏开发&lt;/strong&gt;的读者，所以前半部分会把&amp;quot;联网棋牌游戏到底是怎么跑起来的&amp;quot;讲清楚，中间穿插真实代码层面的例子；后半部分专门开一章讲四川麻将的新手实战策略——毕竟做麻将游戏的人，自己先得会打麻将。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务端：Java + Spring Boot + Netty（netty-socketio）+ Hazelcast + MySQL&lt;/li&gt;
&lt;li&gt;客户端：Cocos Creator / JavaScript&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一先搞明白联网棋牌游戏到底是怎么回事&#34;&gt;一、先搞明白：联网棋牌游戏到底是怎么回事&lt;/h2&gt;
&lt;h3 id=&#34;11-单机游戏和联网游戏的本质区别&#34;&gt;1.1 单机游戏和联网游戏的本质区别&lt;/h3&gt;
&lt;p&gt;单机游戏里，所有逻辑都在你自己的设备上运行，规则由你的设备说了算。联网游戏不行——四个人打麻将，&lt;strong&gt;谁摸了什么牌、谁点了炮，必须有一个&amp;quot;裁判&amp;quot;说了算&lt;/strong&gt;，否则每个人的手机都各算各的，立刻就乱套了。&lt;/p&gt;
&lt;p&gt;这个&amp;quot;裁判&amp;quot;就是&lt;strong&gt;服务器&lt;/strong&gt;。联网棋牌游戏的基本模型是：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    subgraph 玩家设备
        C1[玩家A的手机&amp;lt;br/&amp;gt;Cocos Creator 客户端]
        C2[玩家B的手机]
        C3[玩家C的手机]
        C4[玩家D的手机]
    end
    S[游戏服务器&amp;lt;br/&amp;gt;贝密：Spring Boot + Netty]
    DB[(MySQL&amp;lt;br/&amp;gt;账号/战绩/房卡)]
    CACHE[(内存缓存 Hazelcast&amp;lt;br/&amp;gt;房间/牌局实时状态)]

    C1 &amp;lt;--&amp;gt;|WebSocket 长连接| S
    C2 &amp;lt;--&amp;gt; S
    C3 &amp;lt;--&amp;gt; S
    C4 &amp;lt;--&amp;gt; S
    S --&amp;gt; CACHE
    S -.异步落盘.-&amp;gt; DB&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;几个关键点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;牌在服务器手里&lt;/strong&gt;。洗牌、发牌都在服务器上做，客户端只负责&amp;quot;显示&amp;quot;和&amp;quot;发指令&amp;quot;。你在手机上点&amp;quot;打出三万&amp;quot;，本质是给服务器发了一条消息：&lt;code&gt;doplaycards: &amp;quot;12&amp;quot;&lt;/code&gt;，服务器校验&amp;quot;现在确实轮到你、你手里确实有三万&amp;quot;之后，才广播给全桌人。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每个人看到的信息不同&lt;/strong&gt;。服务器把同一局牌的状态，按&amp;quot;你能看到什么&amp;quot;过滤后分别发给四个人——你只能看到自己的手牌和别人打出来的牌。这叫&lt;strong&gt;信息不对称&lt;/strong&gt;，也是棋牌游戏和射击游戏最大的不同：状态同步必须分人定制。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;防作弊的第一道防线就是&amp;quot;服务器权威&amp;quot;&lt;/strong&gt;。客户端想赖牌（打出一张自己没有的牌），服务器直接拒绝。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;12-为什么用-websocket-而不是-http&#34;&gt;1.2 为什么用 WebSocket 而不是 HTTP&lt;/h3&gt;
&lt;p&gt;没接触过联网游戏的人容易问：发消息不就是调接口吗，用 HTTP 不行吗？&lt;/p&gt;
&lt;p&gt;不行，因为 HTTP 是&amp;quot;问一句答一句&amp;quot;，服务器没法&lt;strong&gt;主动&lt;/strong&gt;找你。而棋牌游戏里大量事件是服务器主动推的：轮到你出牌了、有人碰了你的牌、房间人齐了……这些都需要服务器&lt;strong&gt;随时&lt;/strong&gt;推消息给客户端。WebSocket 是一条建立后一直保持的双向通道，双方随时可以说话，延迟低、开销小。贝密用的 &lt;code&gt;netty-socketio&lt;/code&gt; 就是在 Netty 上实现了 Socket.IO 协议（跑在 WebSocket 上），客户端 Cocos Creator 里直接用 socket.io 的 JS 库连。&lt;/p&gt;
&lt;h3 id=&#34;13-房卡模式一张虚拟门票&#34;&gt;1.3 房卡模式：一张&amp;quot;虚拟门票&amp;quot;&lt;/h3&gt;
&lt;p&gt;贝密 0.8.0 主打&lt;strong&gt;房卡模式&lt;/strong&gt;，这也是 2016-2018 年棋牌游戏爆发的典型商业模式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;你在商城购买&amp;quot;房卡&amp;quot;（虚拟道具，存在 MySQL 里）；&lt;/li&gt;
&lt;li&gt;你想和朋友打牌，就&amp;quot;开房&amp;quot;——消耗房卡，服务器生成一个 &lt;strong&gt;6 位数字房号&lt;/strong&gt;（贝密里由 &lt;code&gt;GameEngine.creatGameRoom&lt;/code&gt; 生成）；&lt;/li&gt;
&lt;li&gt;朋友在大厅输入房号进房（客户端发 &lt;code&gt;searchroom&lt;/code&gt; 事件），人齐开局；&lt;/li&gt;
&lt;li&gt;打完约定的局数，服务器出结算单，房间销毁。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;和陌生人随机撮合的&amp;quot;大厅模式&amp;quot;相比，房卡模式本质上卖的是&amp;quot;熟人社交的场地&amp;quot;，技术上则多了一个&amp;quot;按房号精确找房&amp;quot;的入口。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二贝密的整体架构拆解&#34;&gt;二、贝密的整体架构拆解&lt;/h2&gt;
&lt;h3 id=&#34;21-架构总览&#34;&gt;2.1 架构总览&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    subgraph 客户端
        A[Cocos Creator&amp;lt;br/&amp;gt;dizhu / majiang 模块]
    end
    subgraph 服务端 com.beimi
        B[GameEventHandler&amp;lt;br/&amp;gt;Socket 事件入口]
        C[GameEngine&amp;lt;br/&amp;gt;游戏总调度]
        D[自研状态机&amp;lt;br/&amp;gt;BeiMiStateMachine]
        E[Board 牌局内存模型&amp;lt;br/&amp;gt;MaJiangBoard / DuZhuBoard]
        F[Action/Task&amp;lt;br/&amp;gt;出牌·结算·计时任务]
    end
    subgraph 存储层
        G[(Hazelcast&amp;lt;br/&amp;gt;房间/牌局/玩家/撮合队列)]
        H[(cache2k&amp;lt;br/&amp;gt;过期回调当计时器)]
        I[Disruptor RingBuffer&amp;lt;br/&amp;gt;异步事件]
        J[(MySQL JPA)]
        K[(Elasticsearch)]
    end

    A &amp;lt;--&amp;gt;|WebSocket /game namespace| B
    B --&amp;gt; C --&amp;gt; D --&amp;gt; E
    D --&amp;gt; F
    E --&amp;gt; G
    F --&amp;gt; H
    C --&amp;gt; I
    I --&amp;gt; J
    I --&amp;gt; K&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;各层职责一句话概括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GameEventHandler&lt;/strong&gt;：所有 &lt;code&gt;@OnEvent&lt;/code&gt; 端点都在这里，负责把客户端消息翻译成人话（比如把 &lt;code&gt;&amp;quot;12,13&amp;quot;&lt;/code&gt; 解析成字节数组），然后交给 GameEngine；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GameEngine&lt;/strong&gt;：总调度，加房、开局、出牌、杠碰胡、离房、解散房间都从这儿过；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自研状态机&lt;/strong&gt;：驱动&amp;quot;房间&amp;quot;这个对象的生命周期（后面细讲）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Board 内存模型&lt;/strong&gt;：一局牌的完整状态（四家的手牌、桌面牌、轮到谁、倍率），只活在 Hazelcast 内存缓存里；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存储分层&lt;/strong&gt;：实时状态全在 Hazelcast，MySQL 只存账号/战绩/房卡流水，而且是通过 Disruptor &lt;strong&gt;异步&lt;/strong&gt;写入——牌局进行中的一秒钟都不等数据库。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;22-一次出牌的完整旅程以麻将为例&#34;&gt;2.2 一次出牌的完整旅程（以麻将为例）&lt;/h3&gt;
&lt;p&gt;这是最核心的一条链路，用实际代码里的事件名走一遍：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant P1 as 玩家A客户端
    participant H as GameEventHandler
    participant E as GameEngine
    participant B as MaJiangBoard
    participant T as 计时器(cache2k)
    participant P2 as 其他玩家客户端

    P1-&amp;gt;&amp;gt;H: emit(&amp;#34;doplaycards&amp;#34;, &amp;#34;12&amp;#34;)
    H-&amp;gt;&amp;gt;E: takeCardsRequest(token, card)
    E-&amp;gt;&amp;gt;B: 出牌校验 + 从手牌移除
    E-&amp;gt;&amp;gt;T: remove(roomid) 取消A的出牌倒计时
    E--&amp;gt;&amp;gt;P2: 广播 &amp;#34;command&amp;#34;: takecards
    E-&amp;gt;&amp;gt;E: checkMJCard 遍历其他家&amp;lt;br/&amp;gt;查 杠/碰/吃/胡
    alt 有人能杠碰胡
        E-&amp;gt;&amp;gt;T: 挂5秒等待任务(DEAL事件)
        P2-&amp;gt;&amp;gt;H: emit(&amp;#34;selectaction&amp;#34;, &amp;#34;peng&amp;#34;)
        H-&amp;gt;&amp;gt;E: actionEventRequest 执行碰
    else 无人响应
        E-&amp;gt;&amp;gt;B: dealRequest 下家抓牌
        E--&amp;gt;&amp;gt;P2: 广播 &amp;#34;command&amp;#34;: dealcard
        E-&amp;gt;&amp;gt;T: 挂8秒出牌任务&amp;lt;br/&amp;gt;(超时自动打)
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;举例说明&lt;/strong&gt;：假设你打出一张&amp;quot;五筒&amp;quot;，服务器干的事依次是——&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;校验：轮到你、你有五筒、这张牌合法；&lt;/li&gt;
&lt;li&gt;把你 8 秒的出牌倒计时从计时器缓存里删掉（你不主动出，超时会由 &lt;code&gt;CreateMJPlayCardsTask&lt;/code&gt; 强制替你出）；&lt;/li&gt;
&lt;li&gt;广播给全桌：&amp;ldquo;A 打了五筒&amp;rdquo;；&lt;/li&gt;
&lt;li&gt;挨个问另外三家：你能碰吗？能杠吗？能胡吗？（定缺了筒子的那家直接跳过——这就是四川麻将的规则在代码里的体现，&lt;code&gt;GameUtils.processMJCard&lt;/code&gt; 里定缺花色不参与判断）；&lt;/li&gt;
&lt;li&gt;如果 B 能碰，给 B 弹&amp;quot;碰/过&amp;quot;按钮，牌局挂起 5 秒等他决定；B 点了&amp;quot;碰&amp;quot;，就执行碰、轮到 B 出牌；&lt;/li&gt;
&lt;li&gt;如果三家都&amp;quot;过&amp;quot;，下家自动摸一张牌，服务器先替他检查一次&amp;quot;摸上来的牌能不能暗杠/自摸&amp;quot;，然后通知他出牌，重新挂上 8 秒倒计时。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;整个过程中，客户端只做两件事：把服务器的消息渲染成动画，把玩家的点击发给服务器。&lt;/p&gt;
&lt;h3 id=&#34;23-房间的状态机流转&#34;&gt;2.3 房间的状态机流转&lt;/h3&gt;
&lt;p&gt;房间是有生命的：创建、等人、人齐、开局、结算、销毁。贝密没有用 Spring StateMachine，而是&lt;strong&gt;自研了一个极简状态机&lt;/strong&gt;（&lt;code&gt;core/statemachine&lt;/code&gt; 包），每种游戏一个 Bean：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;stateDiagram-v2
    [*] --&amp;gt; NONE
    NONE --&amp;gt; CRERATED: ENTER 创建房间
    CRERATED --&amp;gt; WAITTING: JOIN 玩家陆续进房
    WAITTING --&amp;gt; READY: ENOUGH 人齐
    WAITTING --&amp;gt; WAITTING: AUTO 超时补AI机器人
    READY --&amp;gt; BEGIN: RAISEHANDS 全员准备
    BEGIN --&amp;gt; LASTHANDS: 发牌完成
    LASTHANDS --&amp;gt; PLAY: 庄家打出首张
    PLAY --&amp;gt; PLAY: DEAL/PLAYCARDS&amp;lt;br/&amp;gt;摸牌出牌循环
    PLAY --&amp;gt; END: ALLCARDS 有人胡牌/牌摸完
    END --&amp;gt; [*]: 结算发战报&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;状态机里每个&amp;quot;事件&amp;quot;对应一串 Action（比如麻将的链路是 &lt;code&gt;EnterAction → JoinAction → EnoughAction → SelectAction(定缺) → MJRaiseHandsAction → DealMJCardAction → PlayMJCardsAction → AllCardsAction&lt;/code&gt;），业务代码只需要一句 &lt;code&gt;game.change(room, event, intervalSeconds)&lt;/code&gt;，状态机负责找到对应 Action 执行，并由 Action 顺手创建下一个延时任务，形成闭环。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三断线重连联网游戏逃不掉的工程问题&#34;&gt;三、断线重连：联网游戏逃不掉的工程问题&lt;/h2&gt;
&lt;p&gt;新手最容易低估的就是这块。手机上打牌，来个电话、进个电梯，连接说断就断。贝密的处理方式是&amp;quot;托管 + 快照恢复&amp;quot;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[玩家网络断开] --&amp;gt; B{服务器收到&amp;lt;br/&amp;gt;OnDisconnect}
    B --&amp;gt;|玩家正在对局中| C[标记 OFFLINE&amp;lt;br/&amp;gt;进入托管状态]
    C --&amp;gt; D[计时器超时后&amp;lt;br/&amp;gt;auto=true 自动出牌]
    D --&amp;gt; E{玩家重连?}
    E --&amp;gt;|emit recovery| F[GameEngine 查缓存&amp;lt;br/&amp;gt;发现该房间有进行中的 Board]
    F --&amp;gt; G[下发 RecoveryData 全量快照:&amp;lt;br/&amp;gt;自己的手牌/桌面牌/轮到谁/剩余倒计时25s]
    G --&amp;gt; H[客户端按快照重建牌桌画面]
    E --&amp;gt;|一直没回来| I[AI托管打到本局结束]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;举例&lt;/strong&gt;：你出牌出了半局断网了，服务器不会暂停牌局等你（其他三个人不答应），而是把你标记为 OFFLINE，由超时任务替你&amp;quot;摸什么打什么&amp;quot;。30 秒后你连回来了，客户端发 &lt;code&gt;recovery&lt;/code&gt;，服务器把当前牌局的完整快照发给你，你的手机根据快照把桌面重新画出来，无缝接上。注意重连期间你是被托管的——所以掉线久了回来，往往牌已经很难看了，这是规则的一部分。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四四川麻将新手致胜策略一个结构化的决策框架&#34;&gt;四、四川麻将新手致胜策略：一个结构化的决策框架&lt;/h2&gt;
&lt;p&gt;做了这么久的麻将游戏，经常被问&amp;quot;新手怎么提高胜率&amp;quot;。零散的口诀（&amp;ldquo;记牌&amp;quot;&amp;ldquo;防点炮&amp;rdquo;）新手记不住也用不上，所以这一章我把四川麻将的策略整理成一个&lt;strong&gt;结构化框架&lt;/strong&gt;：先确立唯一的目标函数，再按&amp;quot;战略层 → 信息层 → 战术层 → 概率层&amp;quot;四个层级组织所有决策，最后给出血战到底的特有推论和新手错误清单。逻辑主线只有一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;麻将的每一步都是在信息不完备下做期望收益最大化的决策。四个层级分别回答：往哪走（战略）、知道什么（信息）、这巡打哪张（战术）、什么时候切换路线（概率）。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;40-规则即策略先立规则再推结论&#34;&gt;4.0 规则即策略：先立规则，再推结论&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;定缺&lt;/strong&gt;：开局后每人必须选一门花色（万/筒/条）作为&amp;quot;缺门&amp;rdquo;，胡牌时手里不能有这门牌，摸到缺门的牌必须打掉。牌墙只有 108 张（三门花色，无风牌）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;血战到底&lt;/strong&gt;：一家胡牌不结束牌局，剩下的人继续打，直到牌摸完或只剩一家没胡。点炮只有点炮者付分，最后没胡的人还要给已听牌的人&amp;quot;查叫&amp;quot;赔分。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从这两条规则可以直接推出三条战略推论，整章都是它们的展开：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;信息压缩&lt;/strong&gt;：定缺把每家的&amp;quot;有效牌域&amp;quot;从三门砍到两门，而且是&lt;strong&gt;公开信息&lt;/strong&gt;。读牌成本比别的麻将低一个量级——新手也能读。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时间价值&lt;/strong&gt;：牌局不因一人胡牌而结束，每张牌的价值随牌墙减少而衰减。&lt;strong&gt;&amp;ldquo;还剩几巡&amp;quot;是所有决策的隐含变量&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;止损优先&lt;/strong&gt;：点炮只由点炮者一人付分、没胡要赔查叫——&lt;strong&gt;落后的代价是确定的，追上的收益是概率性的&lt;/strong&gt;。这决定了&amp;quot;不跟随到最后&amp;quot;在数学上往往是对的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;三条推论的推导关系：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    R1[规则一: 定缺&amp;lt;br/&amp;gt;缺门公开] --&amp;gt; C1[推论一: 信息压缩&amp;lt;br/&amp;gt;读牌成本低]
    R2[规则二: 血战到底&amp;lt;br/&amp;gt;胡牌不终局] --&amp;gt; C2[推论二: 时间价值&amp;lt;br/&amp;gt;剩几巡是隐含变量]
    R2 --&amp;gt; C3[推论三: 止损优先&amp;lt;br/&amp;gt;点炮独赔+查叫保底]
    C1 --&amp;gt; S[决定 4.2 信息层怎么读]
    C2 --&amp;gt; S2[决定 4.4 何时切换路线]
    C3 --&amp;gt; S3[决定 4.5 防守与保底策略]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;41-战略层起手评估与路线选择&#34;&gt;4.1 战略层：起手评估与路线选择&lt;/h3&gt;
&lt;p&gt;拿到起手牌、定缺之前，先给牌打分。不需要精确，三个数就够：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;向听数&lt;/strong&gt;：还差几张牌能听牌。数一下你手里有几个成型搭子/刻子/对子，13 张牌需要 4 副 + 1 将，差几副就是几向听。新手只要会区分&amp;quot;两向听以内（好牌）&amp;ldquo;和&amp;quot;三向听以上（差牌）&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对子数&lt;/strong&gt;：4 对以上 → 七对/大对子路线候选；0~1 对 → 别惦记对子路线。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同花色集中度&lt;/strong&gt;：9 张以上同花色 → 清一色路线候选。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;定缺选择&lt;/strong&gt;是第一个战略决策，原则按优先级排：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;缺张数最少的那门&lt;/strong&gt;——缺门每张都是必打的废牌，张数越少浪费的巡数越少；&lt;/li&gt;
&lt;li&gt;张数相近时，&lt;strong&gt;缺结构最差的那门&lt;/strong&gt;（孤张多、没搭子的）；&lt;/li&gt;
&lt;li&gt;还在纠结时，&lt;strong&gt;缺端张多的那门&lt;/strong&gt;（1、9 多），因为端张成搭能力弱。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;举例&lt;/strong&gt;：起手万子 2 张（三万、八万，无关联）、筒子 5 张（两个搭子）、条子 6 张（一个刻子一个边搭）。毫无疑问题缺万——但如果万子是 2 张而筒子是 3 张但全是散牌，就该缺筒子：定缺看的是&amp;quot;废牌数量&amp;rdquo;，不是&amp;quot;张数&amp;rdquo;。&lt;/p&gt;
&lt;p&gt;定缺公布后立即做第二步战略判断：&lt;strong&gt;看三家的缺门，决定做大还是快跑&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;三家中有两家缺你这门 → 你要做的花色没人抢，牌墙里全剩给你，&lt;strong&gt;做大的绿灯&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;三家都不缺你这门 → 四个人抢一门牌，你的关键张会被飞速消耗，&lt;strong&gt;果断转快跑&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;至此，三条路线已经清晰：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;路线&lt;/th&gt;
					&lt;th&gt;触发条件&lt;/th&gt;
					&lt;th&gt;目标&lt;/th&gt;
					&lt;th&gt;核心动作&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;快跑&lt;/td&gt;
					&lt;td&gt;牌型一般、向听小、花色被抢&lt;/td&gt;
					&lt;td&gt;最速听牌、先胡锁定收益&lt;/td&gt;
					&lt;td&gt;牌效优先，不留废搭&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;做大&lt;/td&gt;
					&lt;td&gt;起手 9 张+同花色/4 对+，且无竞争&lt;/td&gt;
					&lt;td&gt;清一色/大对子/七对&lt;/td&gt;
					&lt;td&gt;忍受慢听牌，屯关键张&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;防守&lt;/td&gt;
					&lt;td&gt;有人已听牌且自己手牌差&lt;/td&gt;
					&lt;td&gt;不点炮、保查叫底线&lt;/td&gt;
					&lt;td&gt;只打熟张，听牌优先于胡牌&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;把起手到定路线的全过程画成一张图：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[拿到13张起手牌] --&amp;gt; B[三个数打分:&amp;lt;br/&amp;gt;向听数 / 对子数 / 同花色集中度]
    B --&amp;gt; C{定缺: 缺哪门?}
    C --&amp;gt;|第一优先| C1[缺张数最少的门]
    C --&amp;gt;|张数相近| C2[缺结构最差的门&amp;lt;br/&amp;gt;孤张多没搭子]
    C --&amp;gt;|还纠结| C3[缺端张多的门&amp;lt;br/&amp;gt;1和9成搭弱]
    C1 &amp;amp; C2 &amp;amp; C3 --&amp;gt; D[公布定缺&amp;lt;br/&amp;gt;观察三家缺门]
    D --&amp;gt; E{有人抢我要做的花色?}
    E --&amp;gt;|两家以上缺我这门&amp;lt;br/&amp;gt;绿灯| F{起手支持做大?&amp;lt;br/&amp;gt;9张+同花色 或 4对+}
    F --&amp;gt;|是| G[做大路线&amp;lt;br/&amp;gt;清一色/大对子/七对]
    F --&amp;gt;|否| H[快跑路线&amp;lt;br/&amp;gt;最速听牌]
    E --&amp;gt;|三家都抢我这门| H
    E --&amp;gt;|牌型差 三向听以上| I[保守推进&amp;lt;br/&amp;gt;随时准备转防守]
    style G fill:#ffe6cc
    style H fill:#d4edda
    style I fill:#f8d7da&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;路线不是选定就不变——&lt;strong&gt;每一巡都可以用新信息重估&lt;/strong&gt;，4.4 会给出切换的量化标准。&lt;/p&gt;
&lt;h3 id=&#34;42-信息层记牌与读牌&#34;&gt;4.2 信息层：记牌与读牌&lt;/h3&gt;
&lt;p&gt;信息层回答&amp;quot;我知道什么&amp;quot;。它分两半：&lt;strong&gt;记牌&lt;/strong&gt;是对已发生事实的压缩存储，&lt;strong&gt;读牌&lt;/strong&gt;是从事实反推未知信息。&lt;/p&gt;
&lt;h4 id=&#34;421-记牌分层记忆法&#34;&gt;4.2.1 记牌：分层记忆法&lt;/h4&gt;
&lt;p&gt;让新手背下三家打过的每张牌不现实。按重要性只记三类：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一层：记&amp;quot;张数&amp;quot;不记&amp;quot;顺序&amp;quot;。&lt;/strong&gt; 每门花色每张牌只有 4 张。你不需要记住&amp;quot;上家第 3 巡打了七万&amp;quot;，只需在脑子里维护计数：&amp;ldquo;七万已见 3 张&amp;rdquo;。重点盯的是&lt;strong&gt;你自己将牌和搭子两端的关键牌&lt;/strong&gt;——你手里 6、8 万等卡 7 万，&amp;ldquo;7 万见了几张&amp;quot;直接决定这个搭子的死活：见 3 张以上，搭子基本死了，趁早拆。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二层：记&amp;quot;缺门&amp;quot;和&amp;quot;花色倾向&amp;rdquo;。&lt;/strong&gt; 定缺后，对手打出的&lt;strong&gt;非缺门&lt;/strong&gt;花色才是有效信息。下家缺条，从第 2 巡起一直打筒子、摸到万子全留——一句话记住&amp;quot;下家只留万&amp;quot;即可，不用记他打的每一张。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三层：记&amp;quot;序盘的反常中张&amp;quot;。&lt;/strong&gt; 序盘（前 4~5 巡）正常人先打孤张边张（1、9）。有人序盘就打 4、5、6 中张，说明这门要么极好（搭子齐了中张多余）要么整门不要，结合缺门大概率能分辨。&lt;/p&gt;
&lt;p&gt;口诀：&lt;strong&gt;数自己的关键张，记别人的花色倾向，盯序盘的反常中张。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;三层记忆的工作量递减、收益递增，像一座金字塔——新手从塔底练起：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart BT
    L1[第三层: 序盘反常中张&amp;lt;br/&amp;gt;一两个反常信号即可&amp;lt;br/&amp;gt;精力占比 ~10%] --&amp;gt; L2[第二层: 缺门与花色倾向&amp;lt;br/&amp;gt;每家一句话: 他只留X&amp;lt;br/&amp;gt;精力占比 ~30%]
    L2 --&amp;gt; L3[第一层: 关键张计数&amp;lt;br/&amp;gt;自己搭子两端的牌各见几张&amp;lt;br/&amp;gt;精力占比 ~60%]
    L3 --&amp;gt; R[收益: 搭子死活判断&amp;lt;br/&amp;gt;直接决定拆不拆]
    L2 --&amp;gt; R2[收益: 知道谁在收哪门&amp;lt;br/&amp;gt;直接决定防什么]
    L1 --&amp;gt; R3[收益: 序盘读出牌型方向]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h4 id=&#34;422-读牌他要什么不要什么打什么危险&#34;&gt;4.2.2 读牌：他要什么、不要什么、打什么危险&lt;/h4&gt;
&lt;p&gt;读牌的本质是&lt;strong&gt;用别人的&amp;quot;打出&amp;quot;反推他的&amp;quot;留下&amp;quot;&lt;/strong&gt;——人留下什么牌，才会打出什么牌。按牌局阶段读不同的东西：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;序盘读&amp;quot;结构&amp;quot;&lt;/strong&gt;：缺门 + 反常中张 + 第一碰，拼出每家的大致牌型方向。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;中盘读&amp;quot;进度&amp;quot;&lt;/strong&gt;：看连续动作。上家碰了&amp;quot;二筒&amp;quot;后摸打筒子以外的牌，说明他筒子上还有货；碰完后打的那张牌的&lt;strong&gt;邻张&lt;/strong&gt;往往是他不需要的（碰二筒后打五万，他大概率没有 3-4 万、4-6 万的搭子）；碰了二筒后打的还是筒子，几乎可断定他在做筒子清一色或已听牌。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;尾盘读&amp;quot;节奏&amp;quot;&lt;/strong&gt;：一个人&lt;strong&gt;连续摸牌就打（摸打）&lt;/strong&gt;，说明手牌已成型，打出的就是刚摸的；反过来&lt;strong&gt;想了很久才打出一张&lt;/strong&gt;，说明这张是从手里&amp;quot;拆&amp;quot;出来的，他在这张牌附近有搭子变动，其邻张不再安全。&lt;/p&gt;
&lt;p&gt;由此得到防点炮 checklist，中后盘（牌墙剩 1/3 以内）每打一张前过一遍：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;这张牌见过几张？&lt;/strong&gt; 见 2~3 张的熟张相对安全；一张没见的生张风险最高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有没有人明显在收这门？&lt;/strong&gt; 对家在做万子，你手里的生张五万就是高危，宁可拆搭子打熟张。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;他是不是单钓/卡张？&lt;/strong&gt; 碰了三副、手牌只剩一两张的人，听的牌范围很窄，往往就在他碰的那几副的邻张上——那一片中张全是雷区。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;完整例子&lt;/strong&gt;：对家定缺条子，碰了&amp;quot;三筒&amp;quot;，摸打两巡后思考良久切出&amp;quot;八万&amp;quot;。推断：他在做筒子（碰三筒+留筒），八万是万子里的多余搭子；&lt;strong&gt;你手里五、六、七万从此相对安全&lt;/strong&gt;（他不要万子），但&lt;strong&gt;任何生张筒子都是高危&lt;/strong&gt;，尤其一、二、四筒（三筒邻张）。&lt;/p&gt;
&lt;p&gt;读牌的推理链条固定是&amp;quot;观察 → 反推 → 行动&amp;quot;三段，常见信号对照如下：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 观察[他打出/做了什么]
        O1[缺门公布后&amp;lt;br/&amp;gt;一直打筒留万]
        O2[碰二筒后&amp;lt;br/&amp;gt;又打出筒子]
        O3[碰二筒后&amp;lt;br/&amp;gt;打出五万]
        O4[连续摸打]
        O5[长考后拆出八万]
    end
    subgraph 反推[他留下/在想什么]
        I1[在做万子清一色]
        I2[筒子听牌或做筒]
        I3[万子3-4/4-6搭子不存在&amp;lt;br/&amp;gt;→ 万字邻张变安全]
        I4[手牌已成型&amp;lt;br/&amp;gt;打的就是刚摸的]
        I5[八万附近有搭子变动&amp;lt;br/&amp;gt;→ 邻张不安全]
    end
    subgraph 行动[我该怎么办]
        A1[生张万子列为高危]
        A2[生张筒子绝不打]
        A3[手里4万6万可放心打]
        A4[按他已听牌对待&amp;lt;br/&amp;gt;进入防守]
        A5[七万九万扣住观察]
    end
    O1 --&amp;gt; I1 --&amp;gt; A1
    O2 --&amp;gt; I2 --&amp;gt; A2
    O3 --&amp;gt; I3 --&amp;gt; A3
    O4 --&amp;gt; I4 --&amp;gt; A4
    O5 --&amp;gt; I5 --&amp;gt; A5&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;43-战术层牌效这一巡到底打哪张&#34;&gt;4.3 战术层：牌效——这一巡到底打哪张&lt;/h3&gt;
&lt;p&gt;战术层是执行：在路线已定、信息已有的前提下，每张出牌都按同一套优先级评估。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;出牌优先级（无安全压力时）&lt;/strong&gt;：缺门牌（必打）→ 孤立端张（无邻张的 1、9）→ 孤立中张 → 多余搭子中进张最少的那张 → 拆搭子。&lt;strong&gt;核心原则是&amp;quot;每一巡都最大化有效进张总数&amp;quot;&lt;/strong&gt;：假设搭子 A（4-5 万）有 8 张潜在进张、搭子 B（1-2 筒）只有 3 张潜在进张，要拆就先拆 B——哪怕 B 看起来更&amp;quot;连得顺&amp;quot;。注意&amp;quot;潜在进张&amp;quot;还要减去已见张数，见 3 张的两面搭子不如没见过的卡张。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;碰/杠决策&lt;/strong&gt;是新手最容易犯错的地方，默认答案是&amp;quot;&lt;strong&gt;不碰&lt;/strong&gt;&amp;quot;，除非满足以下至少一条：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;碰了立刻听牌，或碰的是你的将牌方向（对子路线）；&lt;/li&gt;
&lt;li&gt;你在做清一色/大对子，碰能加速成型；&lt;/li&gt;
&lt;li&gt;这张牌不碰就被下家吃碰走、你再也等不到。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为碰有隐性成本：&lt;strong&gt;暴露信息&lt;/strong&gt;（全桌知道你在收这门）、&lt;strong&gt;破坏门清弹性&lt;/strong&gt;（碰完之后手牌变窄，改路线更难）、&lt;strong&gt;改变摸牌顺序&lt;/strong&gt;（可能把本该你摸的关键张让给下家）。杠同理：明杠多摸一张且血战里&amp;quot;杠上花&amp;quot;有额外收益，但同样暴露信息，尾盘还可能给别人开&amp;quot;杠上炮&amp;quot;。&lt;strong&gt;起手做大时积极碰杠，快跑路线时能不碰就不碰。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;听口选择&lt;/strong&gt;遵循两个数字：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;听口宽度&lt;/strong&gt;：两面听（8 张潜在）&amp;gt; 卡张/边张（4 张）&amp;gt; 对倒/单钓（2~4 张）。能选宽不选窄；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;剩余张数&lt;/strong&gt;：减去已见的。听见过 2 张的两面（剩 4~6 张），通常不如听没见过的卡张（剩 4 张但还有别人扣牌的空间）。尾盘再加一条修正：&lt;strong&gt;听熟张&lt;/strong&gt;——别人敢打的牌，你才能吃到点炮；听生张只能指望自摸。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把出牌优先级和碰杠决策各画成一张图，实战时按图索骥即可：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[这巡要出牌] --&amp;gt; B{手里有缺门牌?}
    B --&amp;gt;|有| C[打缺门牌]
    B --&amp;gt;|没有| D{有孤立端张?&amp;lt;br/&amp;gt;无邻张的1/9}
    D --&amp;gt;|有| E[打孤立端张]
    D --&amp;gt;|没有| F{有孤立中张?}
    F --&amp;gt;|有| G[打孤立中张]
    F --&amp;gt;|没有| H{搭子进张数比较}
    H --&amp;gt;|拆进张最少的搭子| I[打出该搭子中&amp;lt;br/&amp;gt;价值更低的一张]
    C &amp;amp; E &amp;amp; G &amp;amp; I --&amp;gt; J[打完前最后过一遍&amp;lt;br/&amp;gt;4.2.2 防点炮checklist]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    P[别人打出一张牌&amp;lt;br/&amp;gt;我可以碰/杠] --&amp;gt; Q{碰了立刻听牌&amp;lt;br/&amp;gt;或是对子路线?}
    Q --&amp;gt;|是| R[碰]
    Q --&amp;gt;|否| S{我在做大&amp;lt;br/&amp;gt;且碰能加速成型?}
    S --&amp;gt;|是| R
    S --&amp;gt;|否| T{不碰就永远&amp;lt;br/&amp;gt;等不到这张?}
    T --&amp;gt;|是| R
    T --&amp;gt;|否| U[不碰, 默认答案]
    U -.成本.-&amp;gt; V1[暴露信息: 全桌知道你收这门]
    U -.成本.-&amp;gt; V2[破坏门清弹性: 手牌变窄]
    U -.成本.-&amp;gt; V3[改变摸牌顺序: 可能放走关键张]
    style U fill:#d4edda
    style R fill:#fff3cd&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;44-概率层期望值决策做大快跑防守的切换点&#34;&gt;4.4 概率层：期望值决策——做大、快跑、防守的切换点&lt;/h3&gt;
&lt;p&gt;这是整个框架的&amp;quot;调度器&amp;quot;，回答&amp;quot;什么时候换路线&amp;quot;。先把 4.3 的直觉量化成一个可心算的模型。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基本概率感觉&lt;/strong&gt;：任一时刻，你某方向的&amp;quot;期望进展&amp;quot; ≈ 剩余进张数 × 剩余可摸次数。你手里 4-5 万等 3/6 万，若都少见，剩余进张约 6-8 张；牌墙剩 40 张、四家轮流摸，你大约还能摸 8-10 次，摸中概率相当可观。但如果 3/6 万已见 3 张，进张只剩 1~2 张，期望直接砍到四分之一以下——&lt;strong&gt;搭子的价值不看牌面，看剩余进张数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;切换到&amp;quot;做大&amp;quot;的条件&lt;/strong&gt;（需同时满足）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;起手结构支持：9 张以上同花色，或 4 对以上；&lt;/li&gt;
&lt;li&gt;进张供给充足：三家缺门不与你抢这门（4.1 的绿灯）；&lt;/li&gt;
&lt;li&gt;时间充足：牌墙过半之前。做牌是&amp;quot;用听牌速度换番数&amp;quot;，起手离目标越近、剩余巡数越多，换得越划算；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;血战规则加成&lt;/strong&gt;：一家胡牌不结束牌局，早胡者只是先锁定小收益，晚胡的大牌仍能兑现——大牌的&amp;quot;被截胡损失&amp;quot;比推倒胡小得多，所以四川麻将天然鼓励做牌。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;切换到&amp;quot;快跑/止损&amp;quot;的条件&lt;/strong&gt;（满足任一即执行）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;进张枯竭&lt;/strong&gt;：关键牌期望进张 &amp;lt; 2，每一巡等待都是负期望——马上换听、拆死搭子；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有人已听牌且你生张密集&lt;/strong&gt;：此时你的期望收益为负（点炮赔分 + 被查叫），最优解不是&amp;quot;我也赶紧听&amp;quot;，而是&lt;strong&gt;只打熟张把点炮概率压到最低&lt;/strong&gt;，保住查叫底线。这就是&amp;quot;不跟随到最后&amp;quot;——追不上就不再投入，及时止损；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;牌墙见底（剩 15 张以内）还没听牌&lt;/strong&gt;：做牌的时间价值归零，唯一目标是&lt;strong&gt;听牌防查叫&lt;/strong&gt;——往剩余张数最多的熟张方向听，只听一张也比&amp;quot;做了一半的大牌&amp;quot;值钱。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;一个简化的期望算账例子&lt;/strong&gt;（假设平胡 1 番 = 2 分，清一色 4 番 = 16 分）：你听牌了，可以选听口 A——平胡两面听，剩余进张 6 张；或改听口 B——清一色单钓，剩余进张 2 张。若剩余可摸次数约 6 次、点炮胡与自摸混合，A 的期望收益约是正 6~8 分；B 做成的概率约一半不到，做成了 16 分、做不成还要承担被查叫赔 2 分——期望约 6 分，且方差巨大。&lt;strong&gt;结论：番数翻倍抵不过进张减半，除非时间足够早或你已经落后必须搏。&lt;/strong&gt; 新手默认选宽听口，永远是长期正期望。&lt;/p&gt;
&lt;p&gt;把&amp;quot;什么时候切换路线&amp;quot;画成一张象限图——横轴是你的剩余进张，纵轴是牌局剩余时间，每巡定位一次自己落在哪个象限：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    UP[纵轴 ↑ 剩余时间多] --- TOP
    subgraph TOP[ ]
        direction LR
        LEFT2[进张少] ~~~ A[换听调整&amp;lt;br/&amp;gt;时间够但方向死了&amp;lt;br/&amp;gt;例: 卡七万已见三张]
        A ~~~ B[做大&amp;lt;br/&amp;gt;时间够且进张足&amp;lt;br/&amp;gt;例: 起手清一色九张]
        B ~~~ RIGHT2[进张多]
    end
    TOP --- BOT
    subgraph BOT[ ]
        direction LR
        LEFT[进张少] ~~~ C[防守保底&amp;lt;br/&amp;gt;先听牌防查叫&amp;lt;br/&amp;gt;例: 剩十二张没听牌]
        C ~~~ D[快跑&amp;lt;br/&amp;gt;进张还在抢时间&amp;lt;br/&amp;gt;例: 两面听六张剩半局]
        D ~~~ RIGHT[进张多]
    end
    BOT --- DOWN[纵轴 ↓ 剩余时间少]
    style A fill:#fff3cd
    style B fill:#ffe6cc
    style C fill:#f8d7da
    style D fill:#d4edda&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;两条边界记死：&lt;strong&gt;进张 &amp;lt; 2 立刻换方向（左半区行动），剩 15 张只保听牌（下半区行动）&lt;/strong&gt;。右下象限是血战到底最常见的好局面——进张还在但时间不多，别犹豫，最快胡法锁收益。&lt;/p&gt;
&lt;h3 id=&#34;45-血战到底的三条特有推论&#34;&gt;4.5 血战到底的三条特有推论&lt;/h3&gt;
&lt;p&gt;规则特殊，策略就有别处用不上的推论：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先胡是&amp;quot;锁仓&amp;quot;，不是&amp;quot;胜利&amp;quot;&lt;/strong&gt;。你第一个平胡跑了，只是锁定 2 分；剩下三人继续打，大牌仍在你走后兑现。所以领先时&lt;strong&gt;快跑锁收益&lt;/strong&gt;，落后时反而要&lt;strong&gt;做大搏翻盘&lt;/strong&gt;——这和推倒胡&amp;quot;先胡为王&amp;quot;的直觉相反。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查叫把尾盘变成了保底竞赛&lt;/strong&gt;。没胡的人要给听牌者赔分，所以尾盘的及格线是&amp;quot;听牌&amp;quot;而不是&amp;quot;胡牌&amp;quot;。最后 15 张，一切决策让位于&amp;quot;听上&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;点炮一人付，防守成本低&lt;/strong&gt;。别人自摸三家分摊、你点炮你独赔——你打熟张&amp;quot;放生&amp;quot;别人自摸，损失远小于你点炮。所以防守期打熟张不仅是避险，还是&lt;strong&gt;把损失转移给全桌分摊&lt;/strong&gt;的理性选择。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;三种结算方式的赔付流向对比，一看就懂为什么&amp;quot;防守打熟张&amp;quot;是理性选择：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 点炮[你点炮: 你独赔]
        Y1[你 -8分] --&amp;gt; H1[胡牌者 +8分]
        O1[另两家 0分]
    end
    subgraph 自摸[对手自摸: 三家分摊]
        Y2[你 -3分] --&amp;gt; H2[胡牌者 +9分]
        O2[另两家各 -3分] --&amp;gt; H2
    end
    subgraph 查叫[流局查叫: 没听赔听牌]
        Y3[你没听 -2分] --&amp;gt; H3[听牌者 +2分]
    end
    style Y1 fill:#f8d7da
    style Y2 fill:#fff3cd
    style Y3 fill:#d4edda&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;同样是对手胡牌，你点炮亏 8 分、放生他自摸只亏 3 分——&lt;strong&gt;防守期每打一张熟张，赚的就是这个差价。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;46-新手常见错误清单&#34;&gt;4.6 新手常见错误清单&lt;/h3&gt;
&lt;p&gt;对照检查，改掉一条涨一截胜率：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;只记自己牌，不看池子&lt;/strong&gt;——搭子死活取决于关键张已见几张，不看池子等于闭眼打牌；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;见碰就碰&lt;/strong&gt;——暴露信息、锁死手牌结构，快跑路线尤其要忍住；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;死抱死搭子&lt;/strong&gt;——7 万已见 3 张还抱着 6、8 万不拆，每巡都在付租金；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;做大不看缺门&lt;/strong&gt;——三家都抢你这门还硬做清一色，供给断了；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;尾盘还想着翻盘&lt;/strong&gt;——剩 15 张没听牌，唯一 KPI 是听牌防查叫；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不跟随到最后反着用&lt;/strong&gt;——该止损时硬追生张点炮，该搏的时候（落后、时间早）却提前保守。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;47-一页纸决策框架&#34;&gt;4.7 一页纸决策框架&lt;/h3&gt;
&lt;p&gt;把整章压缩成一张每巡都走的决策图：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[每一巡] --&amp;gt; B{信息更新:&amp;lt;br/&amp;gt;关键张已见数/&amp;lt;br/&amp;gt;三家缺门/谁听牌了}
    B --&amp;gt; C{阶段判断}
    C --&amp;gt;|序盘: 定缺+定路线| D{起手支持做大?&amp;lt;br/&amp;gt;9张+同花色/4对+&amp;lt;br/&amp;gt;且三家不抢这门}
    D --&amp;gt;|是| E[做大路线:&amp;lt;br/&amp;gt;积极碰杠, 屯关键张]
    D --&amp;gt;|否| F[快跑路线:&amp;lt;br/&amp;gt;牌效优先, 能不碰不碰]
    C --&amp;gt;|中盘| G{有人已听牌&amp;lt;br/&amp;gt;且我生张密集?}
    G --&amp;gt;|是| H[防守: 只打熟张&amp;lt;br/&amp;gt;放生自摸, 不点炮]
    G --&amp;gt;|否| I{我的关键进张&amp;lt;br/&amp;gt;剩余&amp;gt;=4?}
    I --&amp;gt;|是| J[继续推进&amp;lt;br/&amp;gt;选进张最多的打]
    I --&amp;gt;|否| K[拆死搭子&amp;lt;br/&amp;gt;换听熟张方向]
    C --&amp;gt;|尾盘: 剩15张内| L{已听牌?}
    L --&amp;gt;|否| M[唯一目标: 听牌防查叫&amp;lt;br/&amp;gt;听剩张最多的熟张]
    L --&amp;gt;|是| N[宽听口优先&amp;lt;br/&amp;gt;不为一番改窄]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;四个层级的关系：&lt;strong&gt;战略层定方向，信息层供弹药，战术层管执行，概率层负责喊停。&lt;/strong&gt; 新手先把战术层的&amp;quot;进张最大化&amp;quot;练成肌肉记忆，再逐步叠加记牌和读牌，最后学会用期望值喊停——这条学习路径本身也是按依赖关系排的。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五贝密的技术亮点&#34;&gt;五、贝密的技术亮点&lt;/h2&gt;
&lt;p&gt;最后回到工程，挑几个我认为值得讲的点。&lt;/p&gt;
&lt;h3 id=&#34;51-用-byte-编码整副牌一副麻将只要几十个字节&#34;&gt;5.1 用 byte 编码整副牌：一副麻将只要几十个字节&lt;/h3&gt;
&lt;p&gt;贝密把每张麻将牌编码成一个 &lt;code&gt;byte&lt;/code&gt;：万/筒/条各占 0~107（每门 36 个编号 = 9 个面值 × 4 张），牌面值 = &lt;code&gt;(card % 36) / 4&lt;/code&gt;，花色 = &lt;code&gt;card / 36&lt;/code&gt;；风牌用负数。这样 108 张牌的牌墙就是一个 &lt;code&gt;byte[108]&lt;/code&gt;，序列化后不到 80 字节。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;举例&lt;/strong&gt;：编号 12 这张牌，&lt;code&gt;12 / 36 = 0&lt;/code&gt;（万子），&lt;code&gt;(12 % 36) / 4 = 3&lt;/code&gt;（第 4 个面值）——就是&amp;quot;四万&amp;quot;。一个除法一个取模就解出了花色和面值，不用查表、不用对象，广播出牌消息时网络上跑的就是 &lt;code&gt;&amp;quot;12,13&amp;quot;&lt;/code&gt; 这样的字符串。&lt;/p&gt;
&lt;h3 id=&#34;52-胡牌判断不用-3n2-递归用分堆--贪心摘顺子&#34;&gt;5.2 胡牌判断：不用 3N+2 递归，用&amp;quot;分堆 + 贪心摘顺子&amp;quot;&lt;/h3&gt;
&lt;p&gt;经典麻将胡牌算法是 3N+2 递归枚举。贝密的 &lt;code&gt;GameUtils.processMJCard&lt;/code&gt; 走了一条不同的路：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把 14 张手牌按 &lt;code&gt;key = 面值 + 9 × 花色&lt;/code&gt; 放进字典计数，&lt;strong&gt;先查定缺&lt;/strong&gt;——缺门花色还在手里，直接不能胡（四川规则前置拦截）；&lt;/li&gt;
&lt;li&gt;按计数把牌分成三堆：&lt;code&gt;others&lt;/code&gt;（单张）、&lt;code&gt;pairs&lt;/code&gt;（对子）、&lt;code&gt;kezi&lt;/code&gt;（刻子）；&lt;/li&gt;
&lt;li&gt;对单张堆排序后&lt;strong&gt;贪心摘顺子&lt;/strong&gt;：同花色且 key 连续的 k/k+1/k+2 三张直接移除；&lt;/li&gt;
&lt;li&gt;终止条件：单张摘空且恰好剩 1 对将（或 7 对——七对牌型天然被&amp;quot;pairs 堆 = 14 张&amp;quot;覆盖）；&lt;/li&gt;
&lt;li&gt;对子多于 1 对时，把多余对子拆开当顺子候选再摘；全无刻子时枚举刻子拆开试。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;源码 &lt;code&gt;main()&lt;/code&gt; 里作者还放了性能测试：&lt;strong&gt;500 万次胡牌判断&lt;/strong&gt;直接计时。要说明的是，这是个简化实现——作者自己在注释里承认某些&amp;quot;三连对&amp;quot;式的特殊牌型没完整处理。对在线棋牌的实际负载来说，这种&amp;quot;足够快、覆盖主流牌型&amp;quot;的取舍是合理的。&lt;/p&gt;
&lt;h3 id=&#34;53-斗地主牌型识别频次直方图一把梭&#34;&gt;5.3 斗地主牌型识别：频次直方图一把梭&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ActionTaskUtils.identification(byte[])&lt;/code&gt; 的做法很干净：先统计同点数张数的分布（几个单张、几个对子、几个三张、几个炸弹），再用 &lt;code&gt;switch(种类数, 最大点数)&lt;/code&gt; 一把识别全部牌型——单张、对子、三带一/二、顺子、连对、飞机带翅、四带二、炸弹、王炸。比较两手牌大小的 &lt;code&gt;allow()&lt;/code&gt; 则只有三条规则：王炸最大、炸弹管一切非炸弹、同牌型比最大点数。AI 托管出牌也复用这套识别，找&amp;quot;能压住上家的最小牌&amp;quot;。&lt;/p&gt;
&lt;h3 id=&#34;54-自研极简状态机统一驱动两种游戏&#34;&gt;5.4 自研极简状态机统一驱动两种游戏&lt;/h3&gt;
&lt;p&gt;斗地主和麻将的流程差异巨大（抢地主 vs 定缺、出牌规则完全不同），但贝密用一个抽象统一了：&lt;code&gt;BeiMiStateMachine&lt;/code&gt; + 按事件名查 transition 表 + Action 链。新增一种游戏 = 写一组 Action + 一份状态配置，调度、计时、广播的基础设施全部复用。&lt;/p&gt;
&lt;h3 id=&#34;55-把缓存过期回调当游戏计时器用&#34;&gt;5.5 把缓存过期回调当游戏计时器用&lt;/h3&gt;
&lt;p&gt;这是我觉得最&amp;quot;巧&amp;quot;的设计。棋牌游戏里到处是倒计时：出牌 8 秒、杠碰胡决策 5 秒、抢地主超时自动放弃、房间超时补机器人。贝密没用 Quartz、没开线程池，而是用一个 &lt;code&gt;sharpExpiry&lt;/code&gt; 的 &lt;strong&gt;cache2k 缓存&lt;/strong&gt;当计时器：key 是 roomid，value 是任务对象，&lt;strong&gt;到期自动触发过期监听器执行任务&lt;/strong&gt;。玩家主动操作了？&lt;code&gt;remove(roomid)&lt;/code&gt; 就把倒计时取消了；要重置？put 一个新任务进去。零额外线程，语义严丝合缝。&lt;/p&gt;
&lt;h3 id=&#34;56-实时状态与持久化彻底解耦&#34;&gt;5.6 实时状态与持久化彻底解耦&lt;/h3&gt;
&lt;p&gt;牌局进行中的所有状态（房间、Board、玩家手牌、撮合队列）全在 &lt;strong&gt;Hazelcast&lt;/strong&gt; 分布式内存里，MySQL 只在结算时被&lt;strong&gt;异步&lt;/strong&gt;写入——通过 &lt;strong&gt;Disruptor&lt;/strong&gt; 的 RingBuffer 把变更事件丢给消费者，JPA 和 Elasticsearch 双写（ES 用于运营后台的战绩检索）。注释里还有一条重要的工程纪律：&amp;ldquo;只有一个地方对 GameRoom 二次写入，避免分布式锁&amp;rdquo;——用设计约束换掉了锁的开销。另外，房卡模式建房时的局数等规则赋值用了 &lt;strong&gt;Drools&lt;/strong&gt; 规则引擎，规则写在 &lt;code&gt;.drl&lt;/code&gt; 文件里，运营改规则不用改代码。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六总结与反思&#34;&gt;六、总结与反思&lt;/h2&gt;
&lt;p&gt;回顾这个项目，对新手最有价值的三个认知是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;联网游戏 = 服务器权威 + 按人过滤的状态分发&lt;/strong&gt;。理解了&amp;quot;客户端只是显示器和遥控器&amp;quot;，就看懂了所有联网游戏的骨架。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;棋牌游戏的复杂度不在算法，在工程&lt;/strong&gt;。胡牌判断几百行就写完了，真正难的是断线重连、超时托管、并发房间这些&amp;quot;无聊&amp;quot;的部分——它们决定了玩家半夜打牌掉线时骂不骂人。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;做游戏的人必须会玩游戏&lt;/strong&gt;。第四章的四川麻将策略——分层记牌、由&amp;quot;打出&amp;quot;反推&amp;quot;留下&amp;quot;、用剩余进张 × 剩余巡数做期望决策——不只是给玩家的攻略，也是当初我们校验 AI 托管和出牌提示是否合理的标尺。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当然也有粗糙之处，如实记录：README 宣称的德州扑克在 0.8.0 里只有枚举占位并未实现；胡牌算法是简化版；源码里散落的 &lt;code&gt;System.out.println&lt;/code&gt; 和作者自注的 bug 注释（比如多人同时可碰时先碰者胜出的竞态问题）都还在。但作为一个学习联网游戏开发的开源样本，它把一个房卡棋牌系统该有的零件——长连接、房间状态机、计时器、托管、异步落盘——都真实地摆在了那里，这比完美更有价值。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《去赶海吧》：用 Cocos Creator 写一款「大鱼吃小鱼」io 微信小游戏</title>
      <link>https://ftmao.com/blog/posts/fishpick-cocos-io-game-intro/</link>
      <pubDate>Wed, 26 Feb 2020 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/fishpick-cocos-io-game-intro/</guid>
      <description>&lt;h1 id=&#34;去赶海吧用-cocos-creator-写一款大鱼吃小鱼io-微信小游戏&#34;&gt;《去赶海吧》：用 Cocos Creator 写一款「大鱼吃小鱼」io 微信小游戏&lt;/h1&gt;
&lt;p&gt;最近把自己做的一款微信小游戏整理开源了，写篇文章介绍一下玩法和实现上的一些技术点，希望能给同样在做休闲小游戏的朋友一些参考。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开源地址：&lt;a href=&#34;https://gitee.com/soulnew/fishPick&#34;&gt;https://gitee.com/soulnew/fishPick&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;技术栈：Cocos Creator 2.3.4 + TypeScript&lt;/li&gt;
&lt;li&gt;平台：微信小游戏&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;游戏叫《去赶海吧》，核心玩法一句话概括：&lt;strong&gt;摇杆控制赶海人在海里吃海鲜攒贝壳，分数即体型，大鱼吃小鱼，活到最后吃鸡&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一玩法介绍&#34;&gt;一、玩法介绍&lt;/h2&gt;
&lt;h3 id=&#34;11-核心循环&#34;&gt;1.1 核心循环&lt;/h3&gt;
&lt;p&gt;玩家通过全屏虚拟摇杆控制角色游动，摄像机始终跟随主角。地图上持续刷新各种海洋生物，吃到就得分（贝壳），分数直接决定体型和战斗力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;两个玩家相遇，分高者吃掉分低者&lt;/strong&gt;——这是 io 类游戏的经典规则；&lt;/li&gt;
&lt;li&gt;被吃的玩家死亡后会&lt;strong&gt;爆出金币&lt;/strong&gt;（一半的分数拆成最多 10 条金币鱼散布在周围），形成&amp;quot;击杀后抢遗产&amp;quot;的爽点；&lt;/li&gt;
&lt;li&gt;分数变成负数会直接死亡，所以海里的&amp;quot;毒蛇&amp;quot;（-2 分）和&amp;quot;海胆&amp;quot;（-1 分）是要躲着走的。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[摇杆控制游动] --&amp;gt; B[吃海鲜攒贝壳]
    B --&amp;gt; C{遇到其他玩家?}
    C --&amp;gt;|我分高| D[吃掉对方]
    C --&amp;gt;|我分低| E[触发逃跑 AI / 被吃]
    D --&amp;gt; F[对方爆金币]
    F --&amp;gt; B
    B --&amp;gt; G[实时排行榜 Top5]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;12-鱼的种类与概率设计&#34;&gt;1.2 鱼的种类与概率设计&lt;/h3&gt;
&lt;p&gt;海里的鱼不只是&amp;quot;分&amp;quot;，还混入了道具和陷阱，让取舍更有策略性：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;类型&lt;/th&gt;
					&lt;th&gt;效果&lt;/th&gt;
					&lt;th&gt;概率（实现值）&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;加速道具&lt;/td&gt;
					&lt;td&gt;速度 ×1.5，持续 10 秒&lt;/td&gt;
					&lt;td&gt;5%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;护盾道具&lt;/td&gt;
					&lt;td&gt;免疫被吃 / 免疫负分鱼，10 秒&lt;/td&gt;
					&lt;td&gt;5%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;鱼、虾、蟹&lt;/td&gt;
					&lt;td&gt;+1 分&lt;/td&gt;
					&lt;td&gt;约 35%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;水母、海星&lt;/td&gt;
					&lt;td&gt;+2 分&lt;/td&gt;
					&lt;td&gt;20%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;乌龟&lt;/td&gt;
					&lt;td&gt;+3 分&lt;/td&gt;
					&lt;td&gt;10%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;海豚&lt;/td&gt;
					&lt;td&gt;+4 分&lt;/td&gt;
					&lt;td&gt;5%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;毒蛇&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;-2 分&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;10%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;海胆&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;-1 分&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;10%&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;负分鱼 10 秒后会自动消失，道具的剩余时间通过节点透明度渐变来提示，玩家一眼就能看懂状态。&lt;/p&gt;
&lt;h3 id=&#34;13-四种游戏模式&#34;&gt;1.3 四种游戏模式&lt;/h3&gt;
&lt;p&gt;主菜单提供四个入口，共用同一套核心机制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;无尽模式&lt;/strong&gt;：死了可以用激励视频复活，AI 每 5 秒重生补齐，刷分为主；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;挑战模式&lt;/strong&gt;：&amp;ldquo;吃鸡&amp;quot;玩法——剩余玩家数低于阈值后进入 10 秒倒计时，活到最后获胜；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;贪吃蛇模式&lt;/strong&gt;：吃到的鱼变成尾巴跟随身后，撞到别人的尾巴即死；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;人挤人模式&lt;/strong&gt;：群体 Steering Behaviors 的实验玩法，20 个跟随者 + 1 个领队挤作一团。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;挑战模式和贪吃蛇模式开局前有一个&lt;strong&gt;伪联机匹配面板&lt;/strong&gt;：&amp;ldquo;搜索附近玩家…&amp;ldquo;的省略号动画，12 个头像从 288 张头像图集中不重复随机抽取、按 0~6 秒随机延迟逐个亮起——营造真人匹配的感觉，实际对手全是 AI。这是小团队没有联机服务器时的一个低成本体验方案。&lt;/p&gt;
&lt;h3 id=&#34;14-ai-设计&#34;&gt;1.4 AI 设计&lt;/h3&gt;
&lt;p&gt;AI 不是无头苍蝇，每个 AI 出生时会随机分到三种性格之一：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;进攻型&lt;/strong&gt;：主动出击追杀人；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;吃鱼型&lt;/strong&gt;：闷头吃海鲜发育；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;闲逛型&lt;/strong&gt;：随机游走为主。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;配合两个细节参数避免行为抖动：被追时有 2 秒&amp;quot;逃跑冷却&amp;rdquo;（不会在追/逃之间反复横跳），追击最长持续 7 秒（避免死追不放）。AI 找鱼也不搜全图，而是在自己屏幕范围内找&lt;strong&gt;鱼最多的格子&lt;/strong&gt;再过去吃——这个格子划分和碰撞检测用的是同一套空间网格，下面会讲。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二技术亮点&#34;&gt;二、技术亮点&lt;/h2&gt;
&lt;p&gt;整个项目是单场景架构（主场景约 400 个节点），28 个 TypeScript 脚本，代码量不大，但性能优化和 AI 上的几个点值得展开说说。&lt;/p&gt;
&lt;h3 id=&#34;21-空间哈希网格把碰撞检测从-on-降为局部查询&#34;&gt;2.1 空间哈希网格：把碰撞检测从 O(n) 降为局部查询&lt;/h3&gt;
&lt;p&gt;地图上同时存在几百条鱼，如果每帧让每条鱼和所有玩家做碰撞检测，微信小游戏的低端机上肯定扛不住。做法是把地图划分为 &lt;strong&gt;20×20 的网格&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[鱼出生/移动] --&amp;gt; B[按坐标计算所在格子&amp;lt;br/&amp;gt;key = x_y]
    B --&amp;gt; C[挂到对应桶 BlockManager]
    D[碰撞检测] --&amp;gt; E[只查玩家 AABB 覆盖的格子&amp;lt;br/&amp;gt;getCoveredBlock]
    E --&amp;gt; F[与桶内鱼做精确检测]
    C -.鱼移动跨格.-&amp;gt; G[updatePosofFish 迁移桶]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;碰撞检测时只查询玩家包围盒覆盖的几个格子里的鱼，而不是全图扫描；&lt;/li&gt;
&lt;li&gt;鱼移动跨格时从一个桶迁移到另一个桶；&lt;/li&gt;
&lt;li&gt;这套网格还顺手解决了另外两个需求：&lt;strong&gt;视野裁剪&lt;/strong&gt;（视野外格子里的鱼直接隐藏）和上面提到的 &lt;strong&gt;AI 找鱼最密集区域&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;再配合&lt;strong&gt;降频采样&lt;/strong&gt;——每 4 帧才做一次全量碰撞检测——CPU 占用就控制得很好了。&lt;/p&gt;
&lt;h3 id=&#34;22-对象池向-gc-抖动说不&#34;&gt;2.2 对象池：向 GC 抖动说不&lt;/h3&gt;
&lt;p&gt;鱼、金币、飘分数字这些高频创建销毁的节点全部走对象池：&lt;code&gt;Map&amp;lt;string, Node[]&amp;gt;&lt;/code&gt; 按预制名分桶，全局上限 500。小游戏平台上 GC 卡顿是卡顿的主要来源之一，对象池是性价比最高的优化。&lt;/p&gt;
&lt;h3 id=&#34;23-steering-behaviors教科书式的群体行为实现&#34;&gt;2.3 Steering Behaviors：教科书式的群体行为实现&lt;/h3&gt;
&lt;p&gt;&amp;ldquo;人挤人&amp;quot;模式的背后是把 Reynolds 那套经典的 Steering Behaviors 完整实现了一遍（&lt;code&gt;Gamer.ts&lt;/code&gt;，约 400 行）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    subgraph Steering Behaviors
        A[Seek 追踪] --&amp;gt; H[AccumulateForce&amp;lt;br/&amp;gt;力累加 + 截断]
        B[Flee 逃跑] --&amp;gt; H
        C[Arrive 减速到达] --&amp;gt; H
        D[Wander 随机游走] --&amp;gt; H
        E[Alignment / Cohesion / Separation&amp;lt;br/&amp;gt;群体三规则] --&amp;gt; H
        F[Pursuit 带预测提前量的追击] --&amp;gt; H
        H --&amp;gt; I[EnforceNonPenetrationConstraint&amp;lt;br/&amp;gt;防穿透约束]
        I --&amp;gt; J[最终速度/朝向]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;其中 Wander 用的是&amp;quot;jitter + 圆半径 + 投影距离&amp;quot;的经典做法，Pursuit 会根据目标速度算提前量。所有行为输出的力先累加、再按 max_force 截断，最后加一个防穿透的分离约束——这套结构在后面的项目里基本可以原样搬走。&lt;/p&gt;
&lt;h3 id=&#34;24-屏外敌人指示器io-游戏的标配-ux&#34;&gt;2.4 屏外敌人指示器：io 游戏的标配 UX&lt;/h3&gt;
&lt;p&gt;强敌在屏幕外时，玩家需要感知威胁方向。实现方式是给每个屏外 AI 分配一个&lt;strong&gt;钉在屏幕边缘的箭头 + 分数标签&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用 &lt;code&gt;atan2&lt;/code&gt; 计算敌人相对主角的方向角；&lt;/li&gt;
&lt;li&gt;按屏幕对角线角度判断箭头该落在上下左右哪条边上；&lt;/li&gt;
&lt;li&gt;求射线与屏幕边界的交点作为箭头位置；&lt;/li&gt;
&lt;li&gt;8 种随机颜色区分不同敌人。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;代码不长，但体验提升非常明显——被高分玩家接近时能提前看到箭头逼近，紧张感就出来了。&lt;/p&gt;
&lt;h3 id=&#34;25-运行时程序化动画&#34;&gt;2.5 运行时程序化动画&lt;/h3&gt;
&lt;p&gt;美术资源只有每张皮肤 2 帧的游动图，动画是运行时用 &lt;code&gt;cc.AnimationClip.createWithSpriteFrames&lt;/code&gt; &lt;strong&gt;动态创建&lt;/strong&gt;的 2 帧循环。换皮肤只需要换图集里的起始帧索引（100/102/104…），不用为每套皮肤手工做 Animation 资源。6 套皮肤 = 12 帧图 + 一段代码，维护成本几乎为零。&lt;/p&gt;
&lt;h3 id=&#34;26-事件总线与模块解耦&#34;&gt;2.6 事件总线与模块解耦&lt;/h3&gt;
&lt;p&gt;全局只有一个 50 行的事件总线（on/off/fire）加一个聚合单例，音乐、广告、面板切换、碰撞结果全部走事件通信。单场景多面板的结构下，这种轻量解耦比引入框架更实用。&lt;/p&gt;
&lt;h3 id=&#34;27-微信小游戏生态全套集成&#34;&gt;2.7 微信小游戏生态全套集成&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分包加载&lt;/strong&gt;：字体、头像、音频、图集拆成 4 个分包，loading 场景按包显示进度；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;广告变现&lt;/strong&gt;：Banner + 激励视频复活；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;社交&lt;/strong&gt;：&lt;code&gt;wx.showShareMenu&lt;/code&gt; 分享、好友排行榜（开放数据域 + &lt;code&gt;wx.postMessage&lt;/code&gt; 通信）、&lt;code&gt;wx.login&lt;/code&gt; 登录；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;轻量后端&lt;/strong&gt;：服务器只负责登录、存分数、拉排行三个接口，玩法完全单机，部署成本极低。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三整体架构一览&#34;&gt;三、整体架构一览&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    subgraph 场景层
        L[loading.fire&amp;lt;br/&amp;gt;分包加载 + 剧情动画] --&amp;gt; F[FirstPannel.fire&amp;lt;br/&amp;gt;单场景多面板]
    end
    subgraph 玩法层
        F --&amp;gt; GM[gameManager&amp;lt;br/&amp;gt;刷鱼/模式管理/降频碰撞]
        GM --&amp;gt; P[Player / BasePlayer&amp;lt;br/&amp;gt;得分/体型/道具/死亡爆币]
        GM --&amp;gt; Fish[Fish&amp;lt;br/&amp;gt;概率产出]
        GM --&amp;gt; Snake[贪吃蛇尾巴 lerp 跟随]
    end
    subgraph 数据与系统层
        GM --&amp;gt; BM[BlockManager&amp;lt;br/&amp;gt;20×20 空间网格]
        GM --&amp;gt; CM[CollideManager]
        GM --&amp;gt; PM[PoolManager&amp;lt;br/&amp;gt;对象池上限500]
        GM --&amp;gt; SM[ScoreManager&amp;lt;br/&amp;gt;实时排行]
        GM --&amp;gt; EV[CustomEventListener&amp;lt;br/&amp;gt;事件总线]
    end
    subgraph 平台层
        EV --&amp;gt; WX[分包/广告/分享/好友排行/登录]
        EV --&amp;gt; SRV[轻量后端&amp;lt;br/&amp;gt;login / saveScore / getRank]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;四写在最后&#34;&gt;四、写在最后&lt;/h2&gt;
&lt;p&gt;这个项目验证了一个想法：&lt;strong&gt;没有联机服务器，用&amp;quot;伪匹配 + 性格化 AI&amp;quot;也能撑起 io 游戏的核心体验&lt;/strong&gt;。代码里当然也留下了一些遗憾，比如数值全部硬编码没有配置表、&lt;code&gt;BasePlayer.scoreChange()&lt;/code&gt; 里有一条永远执行不到的 else-if 分支（&lt;code&gt;score&amp;gt;=10&lt;/code&gt; 先命中，后面的大体型缩放被挡住了）——这些都在代码里原样保留着，权当给读者的&amp;quot;找茬彩蛋&amp;rdquo;。&lt;/p&gt;
&lt;p&gt;源码在 &lt;a href=&#34;https://gitee.com/soulnew/fishPick&#34;&gt;https://gitee.com/soulnew/fishPick&lt;/a&gt;，欢迎 Star，也欢迎提 Issue 交流。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《通关！游戏设计之道》读书笔记：游戏设计入门</title>
      <link>https://ftmao.com/blog/posts/level-up-game-design-notes/</link>
      <pubDate>Thu, 09 Jan 2020 20:56:56 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/level-up-game-design-notes/</guid>
      <description>&lt;h1 id=&#34;通关游戏设计之道读书笔记游戏设计入门&#34;&gt;《通关！游戏设计之道》读书笔记：游戏设计入门&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;参考书籍：Scott Rogers《通关！游戏设计之道》（&lt;em&gt;Level Up! The Guide to Great Video Game Design&lt;/em&gt;）。&lt;br&gt;
目标读者：对游戏设计感兴趣、但还没系统学过的同学，尤其是刚接触游戏或互联网产品的新人。&lt;br&gt;
阅读目标：把书里关于“做游戏”的核心方法，翻译成产品经理也能听懂、能复用的语言。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;游戏产品和其他互联网产品一样，都是在&lt;strong&gt;有限资源&lt;/strong&gt;下，想办法让用户获得持续的价值感。不同的是，游戏把“价值感”藏进了玩法、叙事、画面、手感里。作为产品经理，理解游戏设计的基本语言，能帮助我们更好地和策划、美术、程序沟通，也能让我们在做任何“让人上瘾、让人感动”的产品时，多一套思考框架。&lt;/p&gt;
&lt;p&gt;这篇文章不按书逐字翻译，而是把前 8 章内容重新组织成新人能直接上手的 6 个模块：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;游戏分类与团队角色&lt;/li&gt;
&lt;li&gt;创意的产生与筛选&lt;/li&gt;
&lt;li&gt;讲故事的“剧情三角论”&lt;/li&gt;
&lt;li&gt;用“游戏流程表”管理节奏&lt;/li&gt;
&lt;li&gt;3C：角色、镜头、操控&lt;/li&gt;
&lt;li&gt;HUD 与交互效率&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一游戏分类与开发团队角色先搞清楚你在做什么和谁一起做&#34;&gt;一、游戏分类与开发团队角色：先搞清楚你在做什么、和谁一起做&lt;/h2&gt;
&lt;h3 id=&#34;11-常见游戏分类&#34;&gt;1.1 常见游戏分类&lt;/h3&gt;
&lt;p&gt;游戏分类不只是标签，它直接决定了&lt;strong&gt;核心玩法、用户画像、商业模式、技术重点&lt;/strong&gt;。新人不需要背下所有类型，但要能快速判断一个游戏“本质上在卖什么”。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;类型&lt;/th&gt;
					&lt;th&gt;核心体验&lt;/th&gt;
					&lt;th&gt;产品关注点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;动作（Action）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;反应、操作快感&lt;/td&gt;
					&lt;td&gt;手感、帧率、打击反馈&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;冒险（Adventure）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;探索、解谜、剧情&lt;/td&gt;
					&lt;td&gt;关卡节奏、叙事引导&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;角色扮演（RPG）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;成长、代入、养成&lt;/td&gt;
					&lt;td&gt;数值体系、角色塑造、长留存&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;射击（FPS/TPS）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;瞄准、对抗、竞技&lt;/td&gt;
					&lt;td&gt;枪械手感、网络同步、反作弊&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;策略（Strategy）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;决策、资源、布局&lt;/td&gt;
					&lt;td&gt;策略深度、平衡性、AI&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;休闲/益智（Casual/Puzzle）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;短平快、放松&lt;/td&gt;
					&lt;td&gt;上手门槛、关卡难度曲线、广告/内购节奏&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;PM 视角：分类是“需求场景”的简化表达。做产品前先回答——用户打开这款游戏，是想爽、想放松、想社交，还是想被挑战？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;12-开发团队里的常见角色&#34;&gt;1.2 开发团队里的常见角色&lt;/h3&gt;
&lt;p&gt;游戏开发是高度协作的创意工程。产品经理（在很多团队里对应 &lt;strong&gt;Producer / 制作人&lt;/strong&gt;）其实是把各方串起来的人。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    制作人/PM --&amp;gt; 策划
    制作人/PM --&amp;gt; 程序
    制作人/PM --&amp;gt; 美术
    制作人/PM --&amp;gt; 音效
    制作人/PM --&amp;gt; QA
    策划 --&amp;gt; 玩法设计
    程序 --&amp;gt; 技术实现
    美术 --&amp;gt; 视觉风格
    音效 --&amp;gt; 沉浸感
    QA --&amp;gt; 质量保证&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;角色&lt;/th&gt;
					&lt;th&gt;主要负责&lt;/th&gt;
					&lt;th&gt;PM 和他们的协作要点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;策划（Game Designer）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;玩法、数值、关卡、系统&lt;/td&gt;
					&lt;td&gt;明确目标用户和核心体验，别让他们“自嗨”&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;程序（Programmer）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;技术实现、性能、工具&lt;/td&gt;
					&lt;td&gt;评估可行性，留足迭代时间&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;美术（Artist）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;角色、场景、UI、动画&lt;/td&gt;
					&lt;td&gt;统一美术风格，避免和玩法打架&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;音效（Audio）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;音乐、音效、配音&lt;/td&gt;
					&lt;td&gt;很容易被忽略，但对沉浸感影响极大&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;QA&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;测试、找 Bug、验收&lt;/td&gt;
					&lt;td&gt;越早介入，后期返工越少&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;制作人/PM&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;方向、资源、进度、风险&lt;/td&gt;
					&lt;td&gt;做团队的“信息枢纽”和“用户代言人”&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;关键 takeaway&lt;/strong&gt;：PM 不一定懂代码或美术，但必须懂“每个决策对用户体验的影响”。你的价值不是拍板设计，而是让团队始终朝同一个用户价值前进。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二创意不是等灵感而是管理灵感&#34;&gt;二、创意：不是等灵感，而是管理灵感&lt;/h2&gt;
&lt;p&gt;书里把创意过程拆得很细。对 PM 来说，创意不是“灵光一闪”，而是&lt;strong&gt;产生假设 → 快速验证 → 剔除坏点子 → 保留好点子&lt;/strong&gt;的循环。&lt;/p&gt;
&lt;h3 id=&#34;21-获取灵感的-6-个方法&#34;&gt;2.1 获取灵感的 6 个方法&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;方法&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
					&lt;th&gt;PM 复用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;读平时不会读的东西&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;跨界阅读能打破思维定势&lt;/td&gt;
					&lt;td&gt;多看其他行业的产品案例&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;开车兜风、散步、冲澡&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;放空大脑时潜意识会连接信息&lt;/td&gt;
					&lt;td&gt;想不通问题时，先离开屏幕&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;听演讲、看访谈&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;吸收别人的经验和视角&lt;/td&gt;
					&lt;td&gt;多听用户访谈和行业分享&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;玩一个烂游戏&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;分析它为什么不好玩，比看好游戏更有启发&lt;/td&gt;
					&lt;td&gt;多研究失败产品&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;听从心的声音&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;记录让你兴奋、愤怒、感动的瞬间&lt;/td&gt;
					&lt;td&gt;关注自己真实的用户感受&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;限制条件倒逼创意&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;时间、预算、平台限制往往催生好想法&lt;/td&gt;
					&lt;td&gt;别等政策完美才开始做&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;22-做玩家想玩的游戏而不是你想做的游戏&#34;&gt;2.2 做“玩家想玩的游戏”，而不是“你想做的游戏”&lt;/h3&gt;
&lt;p&gt;这是很多新人策划和 PM 都会踩的坑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自己喜欢的点子 ≠ 用户愿意付费的点子&lt;/li&gt;
&lt;li&gt;酷炫的技术 ≠ 好玩的体验&lt;/li&gt;
&lt;li&gt;自嗨的剧情 ≠ 玩家会记住的故事&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;书里强调：&lt;strong&gt;创意必须服务于玩家体验&lt;/strong&gt;。放到产品语言里，就是做任何功能前，先回答：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;目标用户是谁？&lt;/li&gt;
&lt;li&gt;他们当前的核心痛点/爽点是什么？&lt;/li&gt;
&lt;li&gt;这个功能解决后，玩家会有什么不同？&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;23-去除无趣的点子学会砍功能&#34;&gt;2.3 去除无趣的点子：学会“砍功能”&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;“好设计是删出来的。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;判断一个点子是否该留下来，书里给了几个简单标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它能让核心玩法更有趣吗？&lt;/li&gt;
&lt;li&gt;玩家能立刻理解它的价值吗？&lt;/li&gt;
&lt;li&gt;实现成本和它带来的体验成正比吗？&lt;/li&gt;
&lt;li&gt;如果删掉它，游戏会塌掉吗？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果答案都是“否”或“一般”，那就果断砍掉。PM 要有勇气说“这个不做”，比说“这个要做”更重要。&lt;/p&gt;
&lt;h3 id=&#34;24-头脑风暴的正确姿势&#34;&gt;2.4 头脑风暴的正确姿势&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先追求数量，再追求质量&lt;/strong&gt;：10 个烂点子里可能藏着 1 个好点子。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;暂不评判&lt;/strong&gt;：头脑风暴时先不要否定，避免过早杀死可能性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;鼓励跨界组合&lt;/strong&gt;：把两个看似无关的点子拼在一起，可能产生新体验。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记录所有想法&lt;/strong&gt;：很多好点子不是当下用，而是以后用。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;25-突破创意障碍&#34;&gt;2.5 突破创意障碍&lt;/h3&gt;
&lt;p&gt;创意卡住时，书里建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;换个环境或媒介（画画、写卡片、做原型）&lt;/li&gt;
&lt;li&gt;给自己设限制（“如果只能做 3 个功能会怎样？”）&lt;/li&gt;
&lt;li&gt;先做一个最烂的初版，再迭代&lt;/li&gt;
&lt;li&gt;和别人聊，旁观者往往一眼看到你的盲点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;PM 视角&lt;/strong&gt;：创意流程 = 需求探索流程。不要指望一次会议就得出完美方案，而是通过低成本原型和用户反馈，逐步收敛。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三给游戏编故事剧情三角论&#34;&gt;三、给游戏编故事：剧情三角论&lt;/h2&gt;
&lt;p&gt;游戏叙事和产品叙事有很多相似之处：你都需要让用户快速理解“我是谁、我在哪、我要做什么”。&lt;/p&gt;
&lt;h3 id=&#34;31-剧情三角论角色事件世界只能突出一个&#34;&gt;3.1 剧情三角论：角色、事件、世界，只能突出一个&lt;/h3&gt;
&lt;p&gt;书里提出的“剧情三角论”可以用下面这个图表示：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[角色 Character] --- B[事件 Event]
    B --- C[世界 World]
    C --- A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个角分别是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;角色（Character）&lt;/strong&gt;：玩家扮演谁？他有什么动机和性格？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;事件（Event）&lt;/strong&gt;：发生了什么？冲突是什么？任务是什么？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;世界（World）&lt;/strong&gt;：故事发生在什么样的环境里？规则和文化是什么？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心原则：这三个角只能重点突出一个，其他两个作为支撑。&lt;/strong&gt; 如果三者都想抢戏，玩家会觉得信息过载、重点模糊、体验不爽。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;突出角色&lt;/th&gt;
					&lt;th&gt;突出事件&lt;/th&gt;
					&lt;th&gt;突出世界&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;《最后生还者》&lt;/td&gt;
					&lt;td&gt;《生化危机》&lt;/td&gt;
					&lt;td&gt;《塞尔达传说》&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;玩家关注人物关系、情感成长&lt;/td&gt;
					&lt;td&gt;玩家关注剧情推进、危机解决&lt;/td&gt;
					&lt;td&gt;玩家关注探索、 lore、世界观&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;PM 视角：做产品宣讲时也一样。不要同时讲“用户痛点很痛”“技术很牛”“市场很大”，一次只打透一个核心卖点，其他作为支撑。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;32-角色和游戏名字要简短精炼&#34;&gt;3.2 角色和游戏名字要简短精炼&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;角色名字要好记、有辨识度、能体现性格。&lt;/li&gt;
&lt;li&gt;游戏名字更重要，它决定了用户的第一印象和传播成本。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;好名字的特征：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;短（2-4 个字或 1-2 个英文单词最佳）&lt;/li&gt;
&lt;li&gt;有画面感或情绪感&lt;/li&gt;
&lt;li&gt;不容易读错、拼错&lt;/li&gt;
&lt;li&gt;能注册商标更好&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;33-不要小看小孩子&#34;&gt;3.3 不要小看小孩子&lt;/h3&gt;
&lt;p&gt;儿童玩家的理解力和感受力往往被低估。给他们做设计时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不要用“幼稚”代替“简单”&lt;/li&gt;
&lt;li&gt;视觉和反馈可以夸张，但逻辑要清晰&lt;/li&gt;
&lt;li&gt;尊重他们的审美和情感&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;PM 视角：任何垂直用户群体都不要被刻板印象主导。真正去访谈、去观察，比拍脑袋有效 100 倍。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;34-角色的死亡要有价值&#34;&gt;3.4 角色的死亡要有价值&lt;/h3&gt;
&lt;p&gt;如果角色可以随便死、死了没后果，玩家不会珍惜；但如果死亡太惩罚，又会挫败。&lt;/p&gt;
&lt;p&gt;书里建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;死亡要让玩家学到东西（这次我为什么会死？）&lt;/li&gt;
&lt;li&gt;死亡要推动剧情或改变策略&lt;/li&gt;
&lt;li&gt;不要让玩家觉得“死得冤枉”&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;PM 视角：产品里的“失败”也要有价值。比如新手引导失败，要告诉用户“下一步该怎么做”，而不是只弹一个错误提示。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四文档用游戏流程表管理节奏&#34;&gt;四、文档：用“游戏流程表”管理节奏&lt;/h2&gt;
&lt;p&gt;游戏策划文档不是写得越厚越好。对新人来说，最重要的是学会画&lt;strong&gt;游戏流程表（也叫游戏节拍表 / Beat Chart）&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;41-什么是游戏流程表&#34;&gt;4.1 什么是游戏流程表&lt;/h3&gt;
&lt;p&gt;它把游戏从头到尾拆成一系列“节拍”，每个节拍标注：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发生在哪个阶段/关卡&lt;/li&gt;
&lt;li&gt;玩家要做什么&lt;/li&gt;
&lt;li&gt;情绪目标（紧张、放松、惊喜、悲伤……）&lt;/li&gt;
&lt;li&gt;难度曲线&lt;/li&gt;
&lt;li&gt;新机制/新信息是否引入&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;阶段      | 玩家行为          | 情绪目标 | 难度 | 备注
----------|------------------|----------|------|------------------
开场      | 学习移动和跳跃    | 好奇     | 低   | 无文字教学
第1关中段 | 遇到第一个敌人    | 紧张     | 中   | 教学战斗节奏
第1关Boss | 击败 Boss         | 成就感   | 高   | 奖励新能力&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;42-对-pm-的启示&#34;&gt;4.2 对 PM 的启示&lt;/h3&gt;
&lt;p&gt;游戏流程表本质上就是&lt;strong&gt;用户体验地图 + 产品路线图&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把用户旅程拆成关键节点&lt;/li&gt;
&lt;li&gt;每个节点明确用户行为和情绪目标&lt;/li&gt;
&lt;li&gt;用节奏控制用户留存和付费&lt;/li&gt;
&lt;li&gt;提前发现“这里会不会太无聊/太难”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：写文档时先画流程表，再补细节。团队对齐一张图，比读 50 页文字更高效。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五3c-之一角色character&#34;&gt;五、3C 之一：角色（Character）&lt;/h2&gt;
&lt;p&gt;3C 是游戏设计里非常经典的框架，分别指 &lt;strong&gt;Character（角色）、Camera（镜头）、Control（操控）&lt;/strong&gt;。它们共同决定了玩家“能不能快速进入游戏、愿不愿意一直玩”。&lt;/p&gt;
&lt;h3 id=&#34;51-角色要有独特剪影&#34;&gt;5.1 角色要有独特剪影&lt;/h3&gt;
&lt;p&gt;玩家在一堆角色中，应该一眼认出谁是谁。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;轮廓要清晰，即使去掉颜色也能辨认&lt;/li&gt;
&lt;li&gt;体型、武器、服装要有差异化&lt;/li&gt;
&lt;li&gt;NPC 和敌人也要遵守这个原则&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;52-尽量快速移动&#34;&gt;5.2 尽量快速移动&lt;/h3&gt;
&lt;p&gt;慢吞吞的角色会让玩家焦虑。除非“慢”本身就是玩法（如潜行、恐怖游戏），否则移动速度要偏快。&lt;/p&gt;
&lt;h3 id=&#34;53-站立时也要有小动作&#34;&gt;5.3 站立时也要有小动作&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;角色 idle（待机）时呼吸、张望、跺脚，会让世界更有生气&lt;/li&gt;
&lt;li&gt;也能帮助玩家确认“游戏没有卡死”&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;54-给玩家更多自定义空间&#34;&gt;5.4 给玩家更多自定义空间&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;名称、外貌、服装、技能路线&lt;/li&gt;
&lt;li&gt;自定义越多，玩家对角色的归属感越强&lt;/li&gt;
&lt;li&gt;但也不要一次给太多，避免选择瘫痪&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;PM 视角：角色设计 = 用户身份设计。让用户在产品里“成为谁”，是留存和付费的重要驱动力。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六3c-之二镜头camera&#34;&gt;六、3C 之二：镜头（Camera）&lt;/h2&gt;
&lt;p&gt;镜头决定了玩家“看到什么、怎么看”。&lt;/p&gt;
&lt;h3 id=&#34;61-第一人称更有代入感但可能眩晕&#34;&gt;6.1 第一人称更有代入感，但可能眩晕&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;视角&lt;/th&gt;
					&lt;th&gt;优点&lt;/th&gt;
					&lt;th&gt;缺点&lt;/th&gt;
					&lt;th&gt;典型场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;第一人称&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;沉浸感强、瞄准直观&lt;/td&gt;
					&lt;td&gt;易眩晕、看不到自己身体&lt;/td&gt;
					&lt;td&gt;FPS、恐怖游戏&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;第三人称&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;能看到角色动作、空间感好&lt;/td&gt;
					&lt;td&gt;代入感稍弱、镜头易被遮挡&lt;/td&gt;
					&lt;td&gt;动作冒险、RPG&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;俯视/固定&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;信息完整、操作清晰&lt;/td&gt;
					&lt;td&gt;沉浸感较弱&lt;/td&gt;
					&lt;td&gt;策略、MOBA、休闲&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;选择视角时要考虑：目标体验、玩家耐受度、平台限制（VR 几乎只能用第一人称）。&lt;/p&gt;
&lt;h3 id=&#34;62-镜头不能离开主角&#34;&gt;6.2 镜头不能离开主角&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;玩家迷路或找不到自己时，体验会瞬间崩塌&lt;/li&gt;
&lt;li&gt;镜头要始终让玩家知道“我在哪、我要去哪”&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;63-多人模式对镜头的要求更高&#34;&gt;6.3 多人模式对镜头的要求更高&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;分屏、跟随、自由视角都要保证公平性&lt;/li&gt;
&lt;li&gt;不能让某个玩家因为镜头吃亏&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;PM 视角：镜头 = 信息架构。你的产品页面、功能入口、通知提示，本质上也是在控制用户“看到什么”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七3c-之三操控control&#34;&gt;七、3C 之三：操控（Control）&lt;/h2&gt;
&lt;p&gt;操控是玩家和游戏的“对话方式”。一旦操控拉胯，再好玩的玩法也救不回来。&lt;/p&gt;
&lt;h3 id=&#34;71-按键都要有响应&#34;&gt;7.1 按键都要有响应&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;按下按钮后，画面、音效、震动要立刻给反馈&lt;/li&gt;
&lt;li&gt;延迟超过 100ms，玩家就会明显感觉“不跟手”&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;72-操作方式要自然不违反人性&#34;&gt;7.2 操作方式要自然，不违反人性&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;跳跃用 A/空格，攻击用右肩键/鼠标左键——玩家已经形成肌肉记忆&lt;/li&gt;
&lt;li&gt;不要刻意标新立异，除非你的创新明显更好&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;73-不能长时间高强度操作&#34;&gt;7.3 不能长时间高强度操作&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;让玩家有节奏地紧张和放松&lt;/li&gt;
&lt;li&gt;避免连续高难度的操作段，否则容易疲劳和弃坑&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;74-动作感应要参考真实幅度放大&#34;&gt;7.4 动作感应要“参考真实，幅度放大”&lt;/h3&gt;
&lt;p&gt;如果游戏用到摄像头、陀螺仪、体感：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;动作设计要参考真实人体动作，玩家才好理解&lt;/li&gt;
&lt;li&gt;但识别幅度要适当放大，因为设备不一定能捕捉细微动作&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;PM 视角：操控 = 交互设计。每一个按钮、每一个手势、每一个流程，都要让玩家/用户觉得“顺手、可预期、有反馈”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八hud游戏辅助界面&#34;&gt;八、HUD：游戏辅助界面&lt;/h2&gt;
&lt;p&gt;HUD（Head-Up Display）是屏幕上显示的血条、技能图标、小地图、任务提示等。它直接影响玩家的信息获取效率。&lt;/p&gt;
&lt;h3 id=&#34;81-血条最重要通常放左上角&#34;&gt;8.1 血条最重要，通常放左上角&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;血条是玩家最关心的信息之一&lt;/li&gt;
&lt;li&gt;位置固定、样式醒目，能让玩家余光一扫就看到&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;82-允许用户自己调整界面&#34;&gt;8.2 允许用户自己调整界面&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;不同玩家对信息密度的需求不同&lt;/li&gt;
&lt;li&gt;提供 UI 缩放、图标大小、透明度、自定义布局，能大幅提升满意度&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;83-最短操作路径任何操作别超过-3-次点击&#34;&gt;8.3 最短操作路径：任何操作别超过 3 次点击&lt;/h3&gt;
&lt;p&gt;书里强调“三次点击原则”：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;常用功能要在 1-2 次点击内到达&lt;/li&gt;
&lt;li&gt;超过 3 次，玩家会觉得繁琐&lt;/li&gt;
&lt;li&gt;复杂功能可以通过“长按”“滑动”“快捷栏”优化&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;84-其他-hud-设计原则&#34;&gt;8.4 其他 HUD 设计原则&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;原则&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;信息分层&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;最重要的放中心/左上角，次要的放边缘&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;避免遮挡&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;HUD 不要挡住关键画面和敌人&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;时机出现&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;不需要的信息可以隐藏，需要时再出现&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;风格统一&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;HUD 视觉风格要和游戏世界观一致&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;PM 视角：HUD 就是产品的界面设计。信息优先级、操作路径、自定义能力，是评估一款产品“好不好用”的核心指标。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九总结给新人-pm-的一张速查卡&#34;&gt;九、总结：给新人 PM 的一张速查卡&lt;/h2&gt;
&lt;p&gt;把这 8 章内容浓缩成 10 句话：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先分类、再定位&lt;/strong&gt;：搞清楚你的游戏卖给谁、卖什么体验。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;团队是协作网络&lt;/strong&gt;：PM 是信息枢纽，不是个人英雄。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;创意要管理&lt;/strong&gt;：灵感来自跨界和观察，但好点子靠验证和筛选。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;做玩家想玩的&lt;/strong&gt;：时刻把用户需求放在个人喜好前面。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;叙事要聚焦&lt;/strong&gt;：角色、事件、世界，一次只打透一个。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用流程表控节奏&lt;/strong&gt;：把玩家旅程拆成节拍，管理情绪和难度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;角色要有辨识度&lt;/strong&gt;：剪影、速度、小动作、自定义，缺一不可。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;镜头别丢主角&lt;/strong&gt;：视角选择服务于体验，多人模式要公平。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;操控必须跟手&lt;/strong&gt;：自然、反馈、节奏，是手感三要素。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HUD 要省点击&lt;/strong&gt;：核心信息一眼看到，常用操作三步以内。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;游戏设计的很多方法，本质上和互联网产品设计是相通的：&lt;strong&gt;找到用户真正想要的东西，用有限资源做出让人愿意反复使用的体验&lt;/strong&gt;。希望这篇笔记能帮你把书中的概念落地到日常工作中。&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你也在读《通关！游戏设计之道》，欢迎在评论区交流：哪一章对你启发最大？&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    <item>
      <title>Cocos Creator 2.x 实战笔记：动画、事件、向量、资源与避坑指南</title>
      <link>https://ftmao.com/blog/posts/cocos-creator-guide/</link>
      <pubDate>Sat, 28 Dec 2019 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/cocos-creator-guide/</guid>
      <description>&lt;h1 id=&#34;cocos-creator-2x-实战笔记动画事件向量资源与避坑指南&#34;&gt;Cocos Creator 2.x 实战笔记：动画、事件、向量、资源与避坑指南&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;本文基于 Cocos Creator 2.x（&lt;code&gt;cc.*&lt;/code&gt; API）整理，覆盖从动作动画、输入事件、位置向量、场景管理、音效、资源加载到摄像机跟随等高频知识点。虽然 3.x 已全面转向 TypeScript 与新的组件系统，但 2.x 的核心思想——&lt;strong&gt;节点树 + 组件 + 生命周期 + Action 系统&lt;/strong&gt;——仍然是理解引擎设计的最佳入口。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一高层次思想cocos-creator-在组织什么&#34;&gt;一、高层次思想：Cocos Creator 在组织什么？&lt;/h2&gt;
&lt;p&gt;Cocos Creator 的核心理念可以概括为一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“节点（Node）负责层级与变换，组件（Component）负责行为与表现。”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[Scene 场景] --&amp;gt; B[Canvas]
    B --&amp;gt; C[Node 节点]
    C --&amp;gt; D[Transform 变换]
    C --&amp;gt; E[Sprite 渲染]
    C --&amp;gt; F[Script 脚本]
    C --&amp;gt; G[AudioSource 音效]
    C --&amp;gt; H[PhysicsCollider 物理]
    D --&amp;gt; I[position / rotation / scale]
    F --&amp;gt; J[onLoad / start / update]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;11-节点树--变换层级&#34;&gt;1.1 节点树 = 变换层级&lt;/h3&gt;
&lt;p&gt;每个 &lt;code&gt;cc.Node&lt;/code&gt; 都有父节点，子节点的变换会继承父节点。理解这一点才能避免“为什么我改了坐标没效果？”这类问题。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;World Space（世界坐标系）
   │
   ├── Canvas (Design Resolution: 720x1280)
   │      ├── Player (position: 0, -400)
   │      │      ├── Head (position: 0, 80)    ← 子节点相对父节点
   │      │      └── Gun  (position: 60, 0)
   │      └── Enemy&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;12-组件生命周期&#34;&gt;1.2 组件生命周期&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;stateDiagram-v2
    [*] --&amp;gt; onLoad: 场景初始化
    onLoad --&amp;gt; onEnable: 节点激活
    onEnable --&amp;gt; start: 第一次 update 前
    start --&amp;gt; update: 每帧调用
    update --&amp;gt; lateUpdate: update 之后
    lateUpdate --&amp;gt; update: 下一帧
    update --&amp;gt; onDisable: 节点禁用
    onDisable --&amp;gt; onEnable: 重新激活
    onDisable --&amp;gt; onDestroy: 销毁节点&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #1&lt;/strong&gt;：&lt;code&gt;onLoad&lt;/code&gt; 中适合获取节点引用和初始化数据；&lt;code&gt;start&lt;/code&gt; 中适合访问其他组件（此时场景中所有节点的 &lt;code&gt;onLoad&lt;/code&gt; 都已执行完毕）。不要把耗时的初始化放在 &lt;code&gt;update&lt;/code&gt; 里。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二动作系统action让节点动起来&#34;&gt;二、动作系统（Action）：让节点“动起来”&lt;/h2&gt;
&lt;p&gt;Action 是 Cocos Creator 2.x 中非常优雅的动画封装。它基于&lt;strong&gt;组合模式&lt;/strong&gt;：简单动作可以拼接成复杂序列。&lt;/p&gt;
&lt;h3 id=&#34;21-跳跃动画位移--缓动曲线&#34;&gt;2.1 跳跃动画：位移 + 缓动曲线&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 跳跃上升
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;jumpUp&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;moveBy&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;jumpDuration&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;jumpHeight&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;easing&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;easeCubicActionOut&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 下落
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;jumpDown&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;moveBy&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;jumpDuration&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;jumpHeight&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;easing&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;easeCubicActionIn&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 不断重复
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;repeatForever&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sequence&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;jumpUp&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;jumpDown&lt;/span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;xychart-beta
    title &amp;#34;跳跃高度随时间变化&amp;#34;
    x-axis [0, 0.25, 0.5, 0.75, 1.0]
    y-axis &amp;#34;高度&amp;#34; 0 --&amp;gt; 100
    line [0, 80, 100, 80, 0]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #2&lt;/strong&gt;：&lt;code&gt;moveBy&lt;/code&gt; 是&lt;strong&gt;相对移动&lt;/strong&gt;，&lt;code&gt;moveTo&lt;/code&gt; 是&lt;strong&gt;绝对移动&lt;/strong&gt;。跳跃一般用 &lt;code&gt;moveBy&lt;/code&gt;，因为落地位置取决于起跳点。如果误用 &lt;code&gt;moveTo&lt;/code&gt;，角色会跳到固定世界坐标。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;22-缩放与回调序列动作&#34;&gt;2.2 缩放与回调：序列动作&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sequence&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;scaleTo&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;dua&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.8&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.8&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;scaleTo&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;dua&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;callFunc&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;paopaoNode&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;destroy&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;23-旋转&#34;&gt;2.3 旋转&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rotationTo&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rotateTo&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;180&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;paiNode&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;runAction&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rotationTo&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;24-左右晃动以锚点为原点&#34;&gt;2.4 左右晃动（以锚点为原点）&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;seq&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sequence&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rotateTo&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rotateTo&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.4&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rotateTo&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rotateTo&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.4&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rotateTo&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rotateTo&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;delayTime&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;node&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;runAction&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;repeatForever&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;seq&lt;/span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[初始 0°] --&amp;gt; B[右偏 +15°]
    B --&amp;gt; C[左偏 -15°]
    C --&amp;gt; D[右偏 +15°]
    D --&amp;gt; E[左偏 -15°]
    E --&amp;gt; F[回正 0°]
    F --&amp;gt; G[停顿 2s]
    G --&amp;gt; A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #3&lt;/strong&gt;：旋转方向遵循引擎约定，正数为&lt;strong&gt;顺时针&lt;/strong&gt;，负数为&lt;strong&gt;逆时针&lt;/strong&gt;。&lt;code&gt;rotateTo&lt;/code&gt; 会尽量选择最短路径到达目标角度；如果需要连续多圈旋转，请用 &lt;code&gt;rotateBy&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;25-点击时按钮放大&#34;&gt;2.5 点击时按钮放大&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 在按钮的触摸开始/结束时分别放大与恢复
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;buttonNode&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;on&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Node&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;EventType&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;TOUCH_START&lt;/span&gt;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;buttonNode&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;runAction&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;scaleTo&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1.1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1.1&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;buttonNode&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;on&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Node&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;EventType&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;TOUCH_END&lt;/span&gt;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;buttonNode&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;runAction&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;scaleTo&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;三输入事件键盘触摸与鼠标&#34;&gt;三、输入事件：键盘、触摸与鼠标&lt;/h2&gt;
&lt;h3 id=&#34;31-键盘事件&#34;&gt;3.1 键盘事件&lt;/h3&gt;
&lt;p&gt;注册：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;systemEvent&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;on&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;SystemEvent&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;EventType&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;KEY_DOWN&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;onKeyDown&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;实现监听函数：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;onKeyDown&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;event&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;event&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;keyCode&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;macro&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;KEY&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;accLeft&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;macro&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;KEY&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;d&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;accRight&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #4&lt;/strong&gt;：第三个参数 &lt;code&gt;this&lt;/code&gt; 不要漏！否则回调里的 &lt;code&gt;this&lt;/code&gt; 会指向 &lt;code&gt;cc.systemEvent&lt;/code&gt;，导致 &lt;code&gt;this.accLeft&lt;/code&gt; 为 &lt;code&gt;undefined&lt;/code&gt;。建议在 &lt;code&gt;onDestroy&lt;/code&gt; 中移除监听，避免场景切换后事件泄漏。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;32-触摸--鼠标事件&#34;&gt;3.2 触摸 / 鼠标事件&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;node&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;on&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;touchstart&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;touchStart&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;node&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;on&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;touchend&amp;#39;&lt;/span&gt;,   &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;touchEnd&lt;/span&gt;,   &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;枚举与事件名对照：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;枚举&lt;/th&gt;
					&lt;th&gt;字符串事件名&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;cc.Node.EventType.TOUCH_START&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;touchstart&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;手指按下&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;cc.Node.EventType.TOUCH_MOVE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;touchmove&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;手指移动&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;cc.Node.EventType.TOUCH_END&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;touchend&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;手指抬起（在节点内）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;cc.Node.EventType.TOUCH_CANCEL&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;touchcancel&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;系统中断（如来电）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;cc.Node.EventType.MOUSE_DOWN&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;mousedown&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;鼠标按下&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;cc.Node.EventType.MOUSE_ENTER&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;mouseenter&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;鼠标进入节点&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;cc.Node.EventType.MOUSE_MOVE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;mousemove&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;鼠标移动&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;cc.Node.EventType.MOUSE_LEAVE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;mouseleave&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;鼠标离开节点&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;cc.Node.EventType.MOUSE_UP&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;mouseup&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;鼠标抬起&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;cc.Node.EventType.MOUSE_WHEEL&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;mousewheel&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;滚轮滚动&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四位置向量-vec22d-游戏的数学基础&#34;&gt;四、位置向量 Vec2：2D 游戏的数学基础&lt;/h2&gt;
&lt;h3 id=&#34;41-生成与设置位置&#34;&gt;4.1 生成与设置位置&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;randX&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;randY&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;newStar&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;setPosition&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;42-距离长度与运算&#34;&gt;4.2 距离、长度与运算&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 计算当前节点与玩家之间的距离
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dist&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;node&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sub&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;playerPos&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;mag&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 返回向量长度
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;len&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dir&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;mag&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 自加（修改原向量）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;addSelf&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 返回新向量（不修改原向量）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vnew&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 缩放
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;mul&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;);   &lt;span style=&#34;color:#75715e&#34;&gt;// 返回 Vec2 {x: 50, y: 50}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 仍打印 Vec2 {x: 10, y: 10}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 判断相等
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;res&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;equals&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;)); &lt;span style=&#34;color:#75715e&#34;&gt;// true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #5&lt;/strong&gt;：&lt;code&gt;addSelf&lt;/code&gt;、&lt;code&gt;mulSelf&lt;/code&gt;、&lt;code&gt;subSelf&lt;/code&gt; 等方法会&lt;strong&gt;修改原向量&lt;/strong&gt;；不带 &lt;code&gt;Self&lt;/code&gt; 的版本会返回新向量。在 &lt;code&gt;update&lt;/code&gt; 中计算方向时，若不想意外修改 &lt;code&gt;this.node.position&lt;/code&gt;，请使用非 &lt;code&gt;Self&lt;/code&gt; 版本。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;43-线性插值lerp&#34;&gt;4.3 线性插值（Lerp）&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v3&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 存储结果
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lerp&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;v3&lt;/span&gt;);  &lt;span style=&#34;color:#75715e&#34;&gt;// 在 v 与 v1 之间取 50%
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v3&lt;/span&gt;);      &lt;span style=&#34;color:#75715e&#34;&gt;// Vec2 {x: 7.5, y: 7.5}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;44-坐标转换&#34;&gt;4.4 坐标转换&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;方法&lt;/th&gt;
					&lt;th&gt;原点&lt;/th&gt;
					&lt;th&gt;用途&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;convertToWorldSpaceAR(point)&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;节点锚点&lt;/td&gt;
					&lt;td&gt;局部坐标 → 世界坐标&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;convertToNodeSpaceAR(point)&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;节点锚点&lt;/td&gt;
					&lt;td&gt;世界坐标 → 局部坐标&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;convertToWorldSpace(point)&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;节点左下角&lt;/td&gt;
					&lt;td&gt;不建议，除非 UI 特殊需求&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;convertToNodeSpace(point)&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;节点左下角&lt;/td&gt;
					&lt;td&gt;不建议&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    subgraph 世界坐标系
        direction TB
        A[World Origin] --&amp;gt; B[Player]
    end
    subgraph 局部坐标系
        direction TB
        C[Anchor Point] --&amp;gt; D[(100, 100)]
    end
    B --&amp;gt;|convertToWorldSpaceAR| D
    D --&amp;gt;|convertToNodeSpaceAR| B&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #6&lt;/strong&gt;：Cocos 编辑器默认以&lt;strong&gt;锚点（Anchor）&lt;strong&gt;为原点，因此&lt;/strong&gt;优先使用带 AR 的方法&lt;/strong&gt;。左下角为原点的方法主要用于兼容旧项目或特殊 UI 对齐。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五场景与生命周期&#34;&gt;五、场景与生命周期&lt;/h2&gt;
&lt;h3 id=&#34;51-重新加载场景&#34;&gt;5.1 重新加载场景&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;director&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;loadScene&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;game&amp;#39;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #7&lt;/strong&gt;：&lt;code&gt;loadScene&lt;/code&gt; 会销毁当前场景所有节点。如果需要在切换前保存数据（如分数、关卡进度），请先写入本地存储，再调用加载。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;52-暂停与恢复&#34;&gt;5.2 暂停与恢复&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 暂停游戏逻辑，但渲染和 UI 响应仍在
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;director&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;pause&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 恢复
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;director&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;resume&lt;/span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;常用于弹出结算面板或暂停菜单。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六透明度音效与组件&#34;&gt;六、透明度、音效与组件&lt;/h2&gt;
&lt;h3 id=&#34;61-透明度渐变实现缓慢消失&#34;&gt;6.1 透明度渐变（实现缓慢消失）&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;update&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;dt&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;node&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;opacity&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dt&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;node&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;opacity&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;node&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;destroy&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;62-音效&#34;&gt;6.2 音效&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;properties&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;scoreAudio&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;AudioClip&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 播放音效（false 表示不循环）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;audioEngine&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;playEffect&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;scoreAudio&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #8&lt;/strong&gt;：背景音乐请用 &lt;code&gt;playMusic&lt;/code&gt;，音效用 &lt;code&gt;playEffect&lt;/code&gt;。微信小游戏对同时播放的音频数量有限制，过量的 &lt;code&gt;playEffect&lt;/code&gt; 可能导致后续音效被丢弃。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;63-getcomponent获取组件&#34;&gt;6.3 getComponent：获取组件&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;newStar&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getComponent&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Star&amp;#39;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;game&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #9&lt;/strong&gt;：&lt;code&gt;getComponent&lt;/code&gt; 是 &lt;code&gt;Node&lt;/code&gt; 的方法，JS 中的方法和属性都挂在组件上。不要把脚本逻辑直接写在场景或节点上，而要写在组件脚本里。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七屏幕包围盒与资源加载&#34;&gt;七、屏幕、包围盒与资源加载&lt;/h2&gt;
&lt;h3 id=&#34;71-获取视图大小&#34;&gt;7.1 获取视图大小&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;winSize&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;director&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getWinSize&lt;/span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;72-包围盒&#34;&gt;7.2 包围盒&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;first_xMax&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bgList&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;].&lt;span style=&#34;color:#a6e22e&#34;&gt;getBoundingBox&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;xMax&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;code&gt;getBoundingBox()&lt;/code&gt; 返回&lt;strong&gt;父节坐标系&lt;/strong&gt;下的轴向对齐包围盒（AABB），包含 &lt;code&gt;xMin&lt;/code&gt;、&lt;code&gt;xMax&lt;/code&gt;、&lt;code&gt;yMin&lt;/code&gt;、&lt;code&gt;yMax&lt;/code&gt;、&lt;code&gt;width&lt;/code&gt;、&lt;code&gt;height&lt;/code&gt; 等属性。&lt;/p&gt;
&lt;h3 id=&#34;73-加载图片&#34;&gt;7.3 加载图片&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;loader&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;load&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { &lt;span style=&#34;color:#a6e22e&#34;&gt;url&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;res/raw-assets/resource/texture/gold.png&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;png&amp;#39;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;texture&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fra&lt;/span&gt;   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;spr2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getComponent&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sprite&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sframe&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;SpriteFrame&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;texture&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rect&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;120&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;fra&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;spriteFrame&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sframe&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;code&gt;cc.rect(20, 20, 100, 120)&lt;/code&gt; 表示从图片的 &lt;code&gt;(20, 20)&lt;/code&gt; 开始，取宽 100、高 120 的矩形区域，即图片中 &lt;code&gt;(20, 20)&lt;/code&gt; 到 &lt;code&gt;(120, 140)&lt;/code&gt; 的内容。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #10&lt;/strong&gt;：微信小游戏对图片尺寸有限制，&lt;strong&gt;长宽都不能超过 2048&lt;/strong&gt;。超出此限制会导致加载失败或显示异常，导入资源前请先检查尺寸。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;74-加载-prefab&#34;&gt;7.4 加载 Prefab&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;loader&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;loadRes&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;`prefab/game08/&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;randIdx&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Prefab&lt;/span&gt;, (&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;drawing&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;instantiate&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;res&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;drawing&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;group&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;drawing&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;八本地存储&#34;&gt;八、本地存储&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 写入
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sys&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;localStorage&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;setItem&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;finishLevel&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;curLevel&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 读取（注意转换类型，localStorage 只存字符串）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;level&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; parseInt(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sys&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;localStorage&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getItem&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;finishLevel&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #11&lt;/strong&gt;：&lt;code&gt;localStorage&lt;/code&gt; 只能存储字符串。存储对象时请先 &lt;code&gt;JSON.stringify&lt;/code&gt;，读取后再 &lt;code&gt;JSON.parse&lt;/code&gt;。微信小游戏有本地存储上限，重要数据建议结合云端存档。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九摄像机跟随&#34;&gt;九、摄像机跟随&lt;/h2&gt;
&lt;h3 id=&#34;91-主角简单跟随&#34;&gt;9.1 主角简单跟随&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// this.node 为 Camera 节点
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;update&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;dt&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;targetPos&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;player&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;node&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;targetPos&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;92-主角自然跟随平滑插值&#34;&gt;9.2 主角自然跟随（平滑插值）&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;update&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;dt&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;targetPos&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;player&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;currentPos&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;node&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;currentPos&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lerp&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;targetPos&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.1&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;currentPos&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;node&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;currentPos&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[Camera 当前位置] --&amp;gt;|lerp 0.1| B[Player 当前位置]
    B --&amp;gt; C[下一帧继续插值]
    C --&amp;gt; D[最终重合]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;十调试绘制与更新策略&#34;&gt;十、调试、绘制与更新策略&lt;/h2&gt;
&lt;h3 id=&#34;101-cclog&#34;&gt;10.1 cc.log&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;只在发布时勾选调试模式才起效&amp;#34;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;102-graphics-画图&#34;&gt;10.2 Graphics 画图&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;g&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;node&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getComponent&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Graphics&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;g&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;moveTo&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;g&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lineTo&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;g&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stroke&lt;/span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;103-打包更新&#34;&gt;10.3 打包更新&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;发布时勾选 &lt;strong&gt;MD5 Cache&lt;/strong&gt;，避免浏览器/微信缓存旧资源。&lt;/li&gt;
&lt;li&gt;热更新需配合 &lt;code&gt;cc.assetManager.loadBundle&lt;/code&gt; 或官方热更新方案。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;assetManager&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;loadBundle&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;level2&amp;#39;&lt;/span&gt;, (&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;bundle&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;bundle&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;loadDir&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;prefabs&amp;#39;&lt;/span&gt;, (&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;assets&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;十一物理关节旋转关节-revolutejoint&#34;&gt;十一、物理关节：旋转关节 RevoluteJoint&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// cc.RevoluteJoint 可约束两个刚体围绕一个点旋转
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;joint&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;nodeA&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;addComponent&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;RevoluteJoint&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;joint&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;connectedBody&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;nodeB&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getComponent&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;RigidBody&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;joint&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;anchor&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;joint&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;connectedAnchor&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;常用于门铰链、钟摆、车轮等效果。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十二帧率与屏幕适配&#34;&gt;十二、帧率与屏幕适配&lt;/h2&gt;
&lt;h3 id=&#34;121-锁定帧率&#34;&gt;12.1 锁定帧率&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fps&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sys&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;os&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sys&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;OS_IOS&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;game&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;setFrameRate&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;fps&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sys&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;platform&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sys&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WECHAT_GAME&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;wx&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;setPreferredFramesPerSecond&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;fps&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;122-分辨率适配&#34;&gt;12.2 分辨率适配&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;frameSize&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getFrameSize&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;scrall&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;frameSize&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;frameSize&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;setResolutionPolicy&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ResolutionPolicy&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;EXACT_FIT&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;setOrientation&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;macro&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ORIENTATION_LANDSCAPE&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Canvas&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;instance&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fitWidth&lt;/span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Canvas&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;instance&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fitHeight&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isFitWidth&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;scrall&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.77&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Canvas&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;instance&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fitWidth&lt;/span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isFitWidth&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Canvas&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;instance&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fitHeight&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;isFitWidth&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;123-常用设计分辨率&#34;&gt;12.3 常用设计分辨率&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;分辨率&lt;/th&gt;
					&lt;th&gt;比例&lt;/th&gt;
					&lt;th&gt;适用场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;720 × 1280&lt;/td&gt;
					&lt;td&gt;9:16&lt;/td&gt;
					&lt;td&gt;竖屏手游最常见&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;640 × 960&lt;/td&gt;
					&lt;td&gt;2:3&lt;/td&gt;
					&lt;td&gt;早期 iPhone 经典&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;1080 × 1920&lt;/td&gt;
					&lt;td&gt;9:16&lt;/td&gt;
					&lt;td&gt;高清竖屏&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;1280 × 720&lt;/td&gt;
					&lt;td&gt;16:9&lt;/td&gt;
					&lt;td&gt;横屏手游&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[设计分辨率 720x1280] --&amp;gt; B[实际屏幕分辨率]
    B --&amp;gt; C{选择适配策略}
    C --&amp;gt;|FIT_WIDTH| D[宽度铺满，高度可能裁剪/留黑]
    C --&amp;gt;|FIT_HEIGHT| E[高度铺满，宽度可能裁剪/留黑]
    C --&amp;gt;|EXACT_FIT| F[拉伸铺满，可能变形]
    C --&amp;gt;|SHOW_ALL| G[等比缩放，全部可见，可能留黑]
    C --&amp;gt;|NO_BORDER| H[等比缩放，铺满屏幕，可能裁剪]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;十三网络同步colyseus&#34;&gt;十三、网络同步：Colyseus&lt;/h2&gt;
&lt;p&gt;对于多人实时游戏，Colyseus 是 Cocos Creator 常用的 Node.js 状态同步框架。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;同步方式&lt;/th&gt;
					&lt;th&gt;核心思想&lt;/th&gt;
					&lt;th&gt;适用场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;状态同步&lt;/td&gt;
					&lt;td&gt;服务端权威，客户端插值预测&lt;/td&gt;
					&lt;td&gt;MMO、卡牌、休闲对战&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;帧同步&lt;/td&gt;
					&lt;td&gt;只同步输入，客户端各自演算&lt;/td&gt;
					&lt;td&gt;RTS、MOBA、格斗&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #12&lt;/strong&gt;：帧同步对浮点数一致性和随机种子要求极高。Cocos 2.x 默认使用 JavaScript 浮点运算，跨平台（iOS/Android/微信）可能出现不一致，需统一数学库与随机算法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十四避坑清单速查表&#34;&gt;十四、避坑清单速查表&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;编号&lt;/th&gt;
					&lt;th&gt;避坑点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;初始化放 &lt;code&gt;onLoad&lt;/code&gt;，跨组件访问放 &lt;code&gt;start&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;moveBy&lt;/code&gt; 相对移动，&lt;code&gt;moveTo&lt;/code&gt; 绝对移动，别混用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;旋转方向：正顺负逆；多圈旋转用 &lt;code&gt;rotateBy&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4&lt;/td&gt;
					&lt;td&gt;注册事件必须传 &lt;code&gt;this&lt;/code&gt;，切场景时记得 &lt;code&gt;off&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;5&lt;/td&gt;
					&lt;td&gt;向量运算区分 &lt;code&gt;Self&lt;/code&gt; 与非 &lt;code&gt;Self&lt;/code&gt; 版本&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;6&lt;/td&gt;
					&lt;td&gt;坐标转换优先用带 &lt;code&gt;AR&lt;/code&gt; 的方法&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;7&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;loadScene&lt;/code&gt; 前保存必要数据&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;8&lt;/td&gt;
					&lt;td&gt;音乐用 &lt;code&gt;playMusic&lt;/code&gt;，音效用 &lt;code&gt;playEffect&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;9&lt;/td&gt;
					&lt;td&gt;逻辑写在组件脚本里，不要直接挂在节点上&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;微信小游戏图片尺寸不能超过 2048×2048&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;11&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;localStorage&lt;/code&gt; 只存字符串，对象需 JSON 序列化&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;12&lt;/td&gt;
					&lt;td&gt;帧同步注意浮点一致性与随机种子&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十五总结&#34;&gt;十五、总结&lt;/h2&gt;
&lt;p&gt;Cocos Creator 2.x 的开发可以归纳为以下流程：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[搭建场景节点树] --&amp;gt; B[挂载脚本组件]
    B --&amp;gt; C[处理输入事件]
    C --&amp;gt; D[在 update 中更新逻辑]
    D --&amp;gt; E[使用 Action 或逐帧修改属性实现动画]
    E --&amp;gt; F[加载资源 / 切换场景 / 保存数据]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;掌握 &lt;strong&gt;节点-组件模型、生命周期、Action 组合、Vec2 运算、坐标转换、屏幕适配&lt;/strong&gt; 这六大核心后，2D 游戏开发中大部分问题都能迎刃而解。希望这份笔记能成为你项目中的速查手册。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.cocos.com/creator/manual/zh/getting-started/quick-start.html&#34;&gt;Cocos Creator 官方文档 - 快速上手&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://forum.cocos.org/t/topic/74316&#34;&gt;Cocos Creator 官方论坛 - Colyseus 状态同步&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.csdn.net/qq_31967569/article/details/102624728&#34;&gt;CSDN - Cocos Creator 帧同步 + TypeScript 定义&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
