<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Io 游戏 on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/tags/io-%E6%B8%B8%E6%88%8F/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Wed, 26 Feb 2020 14:00:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/io-%E6%B8%B8%E6%88%8F/index.xml" rel="self" type="application/rss+xml" /><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>
    
  </channel>
</rss>
