# sgame
**Repository Path**: hdwang123/sgame
## Basic Information
- **Project Name**: sgame
- **Description**: 网页小游戏,基于JavaScript+Phaser
- **Primary Language**: JavaScript
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-06-27
- **Last Updated**: 2026-07-02
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Phaser 网页小游戏
基于 **Vite + Phaser 3 + 原生 JavaScript** 的纯网页小游戏合集,包含俄罗斯方块、贪吃蛇、玛丽跳跃、坦克大战、保卫萝卜、连连看、猜字谜、找不同、扫雷和打地鼠。
## 运行
```bash
npm install
npm run dev
```
生产构建:
```bash
npm run build
```
游戏厅中使用方向键选择,按 `Enter` 进入,也可以按数字键 `1` 至 `9`(以及 `0`)快速进入。游戏中按 `Esc` 返回游戏厅。
在手机或窄屏设备上,游戏画布下方会自动显示触摸控制面板和当前游戏的操作提示。**手机版仅支持竖屏游玩,请将手机保持为竖屏方向。**
- 俄罗斯方块:左右移动、软降、旋转、直落、暂停
- 贪吃蛇:四方向转向、暂停,并可选择慢速、标准、快速三档速度
- 玛丽跳跃:方向键或摇杆移动,支持普通跳、大跳和暂停
- 坦克大战:方向键或摇杆驾驶,按住开火、暂停
- 保卫萝卜:点击空地建造炮塔,再次点击升级,抵御六波像素怪物
- 连连看:点击两个相同图块,通过不超过两次转弯的路径连接消除
- 猜字谜:从 100 道经典拆字、会意与文字双关谜题中随机抽题挑战
- 找不同:挑战 100 幅不同构图,涵盖风景、城市、家具房间与卡通人物等 20 类主题
- 扫雷:翻开安全格并用右键、`F` 键或触屏按钮插旗
- 打地鼠:在限时内连续命中地鼠,金色地鼠有额外分数,同时避开炸弹
每款游戏都提供触摸“重开”和“返回”按钮。俄罗斯方块在手机上使用紧凑双栏布局:棋盘在左,标题、计分和下一个方块在右,通过压缩空白区域放大整体画面。
## 分层结构
```text
src/
├── audio/
│ └── SoundFX.js # Web Audio 程序化音效
├── ui/
│ └── MobileControls.js # 手机触摸状态与场景控制绑定
├── game/ # 不依赖 Phaser 画面的游戏规则层
│ ├── tetris/
│ │ ├── TetrisGame.js # 棋盘、移动、旋转、消行、计分
│ │ └── config.js # 七种方块与棋盘参数
│ ├── snake/
│ │ ├── SnakeGame.js # 蛇移动、转向、食物、碰撞、成长
│ │ └── config.js # 棋盘、速度和计分参数
│ ├── mary-jump/
│ │ ├── MaryJumpGame.js # 得分、踩踏判断、胜负状态
│ │ └── config.js # 平台、金币、敌人与关卡参数
│ ├── tank/
│ │ ├── TankGame.js # 生命、计分、射击节流、敌人决策
│ │ └── config.js # 地图、出生点、方向与数值配置
│ ├── carrot-defense/
│ │ ├── CarrotDefenseGame.js # 金币、萝卜生命、炮塔和波次状态
│ │ └── config.js # 道路、建塔点、敌人波次与数值配置
│ └── link-match/
│ ├── LinkMatchGame.js # 成对棋盘、两拐点寻路、消除与洗牌
│ └── config.js # 棋盘、图块、时间和计分参数
├── scenes/ # Phaser 适配与表现层
│ ├── MenuScene.js # 游戏厅菜单
│ ├── TetrisScene.js # 俄罗斯方块输入与绘制
│ ├── SnakeScene.js # 贪吃蛇输入与绘制
│ ├── MaryJumpScene.js # 玛丽跳跃物理与绘制
│ ├── TankScene.js # 坦克物理、碰撞与绘制
│ ├── CarrotDefenseScene.js # 像素塔防、路径移动与炮塔射击
│ └── LinkMatchScene.js # 图块点击、连接线、倒计时与洗牌
├── main.js # Phaser 初始化与场景注册
└── styles.css # 网页外壳样式
```
分层原则:`game/` 决定“游戏怎么算”,`scenes/` 决定“怎么接收输入、使用 Phaser 物理并显示出来”。
平台游戏与坦克大战的原创像素素材位于 `public/assets/`,其余多款游戏的图形由 Phaser 程序化绘制。十款游戏共用 `SoundFX.js` 提供的移动、旋转、吞食、跳跃、金币、射击、爆炸和胜负音效。
## 俄罗斯方块流程图
```mermaid
%%{init: {"flowchart": {"nodeSpacing": 12, "rankSpacing": 18, "curve": "linear"}, "themeVariables": {"fontSize": "20px"}}}%%
flowchart TB
START["进入 TetrisScene"] --> INIT["创建 TetrisGame
初始化 10×20 棋盘与七袋随机方块"]
INIT --> SPAWN["生成当前方块并准备下一个方块
手机端默认方向按键,可切换摇杆"]
SPAWN --> COLLIDE_SPAWN{"出生位置发生碰撞?"}
COLLIDE_SPAWN -->|"是"| GAME_OVER["游戏结束
显示得分与重新开始提示"]
COLLIDE_SPAWN -->|"否"| DRAW["绘制棋盘、活动方块、幽灵落点
下一个方块、分数、行数和等级"]
DRAW --> LOOP["Phaser update 循环"]
LOOP --> STATE{"暂停或游戏结束?"}
STATE -->|"是"| WAIT["停止自动下落
等待继续、重开或返回"]
STATE -->|"否"| INPUT{"键盘 / 触屏按键 / 摇杆输入"}
INPUT -->|"按键或摇杆左右"| MOVE["尝试水平移动
按住时连续横移"]
INPUT -->|"上 / Z / 旋转"| ROTATE["旋转矩阵
尝试左右踢墙修正"]
INPUT -->|"下键或摇杆向下"| SOFT["缩短下落间隔
每格增加分数"]
INPUT -->|"Space / 直落"| HARD["移动到幽灵落点
按距离增加分数"]
INPUT -->|"无操作"| TIMER["累计自动下落时间"]
MOVE --> VALID_MOVE{"新位置碰撞?"}
ROTATE --> VALID_MOVE
VALID_MOVE -->|"否"| REDRAW["播放反馈并重绘"]
VALID_MOVE -->|"是"| LOOP
TIMER --> INTERVAL{"达到当前等级的下落间隔?"}
INTERVAL -->|"否"| LOOP
INTERVAL -->|"是"| DOWN["尝试向下移动一格"]
SOFT --> DOWN
DOWN --> BLOCKED{"下方发生碰撞?"}
BLOCKED -->|"否"| REDRAW
BLOCKED -->|"是"| LOCK["把活动方块写入棋盘"]
HARD --> LOCK
LOCK --> CLEAR["检查并删除填满的行
顶部补充空行"]
CLEAR --> SCORE["按消除行数和等级计分
每 10 行提升等级并加快下落"]
SCORE --> SPAWN
REDRAW --> LOOP
WAIT -->|"继续"| LOOP
WAIT -->|"重开"| START
WAIT -->|"ESC / 返回"| MENU["回到游戏厅"]
```
## 玛丽跳跃流程图
```mermaid
%%{init: {"flowchart": {"nodeSpacing": 12, "rankSpacing": 18, "curve": "linear"}, "themeVariables": {"fontSize": "20px"}}}%%
flowchart TB
START["进入 MaryJumpScene"] --> LEVEL["读取当前关卡配置
尺寸、重力、平台、金币、敌人和终点"]
LEVEL --> WORLD["创建 Arcade Physics 世界
生成角色、平台、金币、蘑菇、火焰花、敌人与终点"]
WORLD --> INTRO["显示关卡介绍
开始跟随角色的摄像机"]
INTRO --> LOOP["Phaser update 循环"]
LOOP --> PHASE{"当前状态是 playing?"}
PHASE -->|"否"| WAIT["等待继续、重试或返回"]
PHASE -->|"是"| INPUT["读取键盘 / 触屏按键 / 摇杆输入"]
INPUT --> HORIZONTAL{"左 / 右 / 无方向"}
HORIZONTAL -->|"左或右"| RUN["设置水平速度与角色朝向"]
HORIZONTAL -->|"无方向"| STOP["水平速度归零"]
INPUT --> JUMP{"收到跳跃输入?"}
INPUT -->|"F / 手机 B"| FIRE_READY{"当前为火焰状态
且发射冷却结束?"}
FIRE_READY -->|"是"| FIREBALL["朝角色面向方向生成火球"]
FIRE_READY -->|"否"| PHYSICS
FIREBALL --> PHYSICS
JUMP -->|"是"| BUFFER["记录 160ms 跳跃缓冲"]
BUFFER --> GROUNDED{"角色着地
或仍在 110ms 土狼时间内?"}
GROUNDED -->|"是"| TAKEOFF["施加向上的跳跃速度"]
GROUNDED -->|"否"| PHYSICS
JUMP -->|"否"| PHYSICS["物理引擎更新重力、位置与碰撞"]
RUN --> PHYSICS
STOP --> PHYSICS
TAKEOFF --> PHYSICS
PHYSICS --> EVENT{"本帧发生什么?"}
EVENT -->|"收集金币"| COIN["销毁金币
增加分数并更新 HUD"]
EVENT -->|"吃到蘑菇"| MUSHROOM["玛丽变大并增加分数
可抵挡一次普通碰撞"]
EVENT -->|"吃到火焰花"| FLOWER["进入火焰状态并增加分数
解锁火球发射"]
EVENT -->|"火球命中敌人"| FIRE_HIT["销毁火球与敌人
增加击敌分数"]
EVENT -->|"碰到敌人"| STOMP{"角色正在下落且位于敌人上方?"}
STOMP -->|"是"| DEFEAT["消灭敌人、反弹
增加分数"]
STOMP -->|"否"| POWERED{"处于变大或火焰状态?"}
POWERED -->|"是"| SHRINK["恢复小玛丽
获得 1.2 秒受击保护"]
POWERED -->|"否"| LOSE["挑战失败
暂停物理世界"]
EVENT -->|"掉出地图"| LOSE
EVENT -->|"碰到终点旗帜"| COMPLETE["关卡完成
增加通关奖励"]
EVENT -->|"无事件"| ENEMY["敌人在巡逻边界或撞墙时掉头"]
COIN --> ENEMY
MUSHROOM --> ENEMY
FLOWER --> ENEMY
FIRE_HIT --> ENEMY
SHRINK --> ENEMY
DEFEAT --> ENEMY
ENEMY --> LOOP
LOSE --> WAIT
COMPLETE --> FINAL{"是否为最后一关?"}
FINAL -->|"否"| NEXT["短暂显示过关提示
携带分数与能力状态进入下一关"]
FINAL -->|"是"| ALL_CLEAR["显示全部通关与总分"]
NEXT --> LEVEL
ALL_CLEAR --> WAIT
WAIT -->|"Enter / 重试"| RETRY["从本关起点恢复
本关初始分数与能力状态"]
WAIT -->|"R / 第1关"| FIRST["分数归零并从第 1 关开始"]
WAIT -->|"ESC / 返回"| MENU["回到游戏厅"]
RETRY --> LEVEL
FIRST --> LEVEL
```
## 保卫萝卜流程图
```mermaid
%%{init: {"flowchart": {"nodeSpacing": 12, "rankSpacing": 18, "curve": "linear"}, "themeVariables": {"fontSize": "20px"}}}%%
flowchart TB
START["进入 CarrotDefenseScene"] --> INIT["创建草地、道路、像素萝卜
建塔点、金币、生命和六个波次"]
INIT --> WAVE["开始当前波次
按时间间隔生成像素怪物"]
WAVE --> LOOP["Phaser update 循环"]
LOOP --> INPUT{"玩家点击建塔点?"}
INPUT -->|"空位且金币足够"| BUILD["扣除金币并建造一级炮塔"]
INPUT -->|"已有炮塔且未满级"| UPGRADE["扣除金币并提升伤害、射速和范围"]
INPUT -->|"无操作"| MOVE["怪物沿折线路径向萝卜移动"]
BUILD --> MOVE
UPGRADE --> MOVE
MOVE --> TARGET["炮塔搜索射程内最靠前的怪物"]
TARGET -->|"找到且冷却结束"| SHOOT["发射像素子弹并结算伤害"]
TARGET -->|"没有目标"| EVENT
SHOOT --> EVENT{"本帧发生什么?"}
EVENT -->|"怪物生命归零"| REWARD["播放像素爆炸
奖励金币并更新 HUD"]
EVENT -->|"怪物抵达终点"| DAMAGE["萝卜生命减一
移除该怪物"]
EVENT -->|"无事件"| CHECK
REWARD --> CHECK{"本波怪物全部清除?"}
DAMAGE --> HEALTH{"萝卜仍有生命?"}
HEALTH -->|"否"| LOSE["保卫失败
等待重开或返回"]
HEALTH -->|"是"| CHECK
CHECK -->|"否"| LOOP
CHECK -->|"是"| FINAL{"是否完成最后一波?"}
FINAL -->|"否"| NEXT["显示守波提示
延迟后进入下一波"]
FINAL -->|"是"| WIN["萝卜保卫成功
等待重新挑战"]
NEXT --> WAVE
```
## 连连看流程图
```mermaid
%%{init: {"flowchart": {"nodeSpacing": 12, "rankSpacing": 18, "curve": "linear"}, "themeVariables": {"fontSize": "20px"}}}%%
flowchart TB
START["进入 LinkMatchScene"] --> INIT["生成 8×8 成对图块
外围保留一圈空路径"]
INIT --> LOOP["开始 150 秒倒计时
等待点击图块"]
LOOP --> FIRST{"是否已选择第一个图块?"}
FIRST -->|"否"| SELECT["高亮当前图块"]
FIRST -->|"是"| SAME{"两个图块类型相同?"}
SAME -->|"否"| SELECT
SAME -->|"是"| PATH{"能否用直线、一拐或两拐连接?"}
PATH -->|"否"| SELECT
PATH -->|"是"| REMOVE["绘制连接线并移除图块
增加配对分数"]
REMOVE --> CLEAR{"棋盘已经清空?"}
CLEAR -->|"是"| WIN["全部消除
显示得分并等待重开"]
CLEAR -->|"否"| MOVE{"仍存在可消除组合?"}
MOVE -->|"是"| LOOP
MOVE -->|"否"| AUTO["自动洗牌剩余图块"]
AUTO --> LOOP
LOOP --> SHUFFLE{"玩家点击洗牌或按 H?"}
SHUFFLE -->|"有剩余次数"| MANUAL["消耗一次洗牌次数
重新排列剩余图块"]
SHUFFLE -->|"没有"| LOOP
MANUAL --> LOOP
LOOP --> TIME{"倒计时归零?"}
TIME -->|"否"| LOOP
TIME -->|"是"| LOSE["时间到
显示得分并等待重开"]
```
## 贪吃蛇流程图
```mermaid
%%{init: {"flowchart": {"nodeSpacing": 12, "rankSpacing": 18, "curve": "linear"}, "themeVariables": {"fontSize": "20px"}}}%%
flowchart TB
START["进入 SnakeScene"] --> INIT["创建 SnakeGame
在棋盘中央生成蛇身、方向和食物"]
INIT --> SPEED["设置标准速度并默认使用方向按键
绘制棋盘、蛇、食物和分数"]
SPEED --> LOOP["Phaser update 循环"]
LOOP --> STATE{"游戏结束或暂停?"}
STATE -->|"是"| WAIT["停止步进
等待继续、重开或返回"]
STATE -->|"否"| INPUT{"键盘 / 触屏按键 / 摇杆输入"}
INPUT -->|"方向键 / WASD / 摇杆主方向"| TURN["记录下一移动方向
拒绝直接反向"]
INPUT -->|"慢速 / 标准 / 快速"| SET_SPEED["切换基础步进间隔
重置本轮计时"]
INPUT -->|"P / 暂停"| PAUSE["切换暂停状态并显示提示"]
INPUT -->|"无操作"| TIMER["累计步进时间"]
TURN --> TIMER
SET_SPEED --> TIMER
PAUSE --> LOOP
TIMER --> READY{"达到当前步进间隔?"}
READY -->|"否"| LOOP
READY -->|"是"| HEAD["按下一方向计算新蛇头位置"]
HEAD --> COLLISION{"撞到边界或自身?"}
COLLISION -->|"是"| GAME_OVER["标记游戏结束
播放失败音效并显示得分"]
COLLISION -->|"否"| ADVANCE["把新蛇头加入蛇身"]
ADVANCE --> EAT{"新蛇头碰到食物?"}
EAT -->|"否"| TAIL["移除蛇尾
保持蛇身长度"]
EAT -->|"是"| GROW["保留蛇尾使蛇身增长
增加分数和进食次数"]
GROW --> ACCELERATE["根据进食次数逐步加速
随机生成不与蛇身重叠的新食物"]
TAIL --> REDRAW["重绘蛇与食物"]
ACCELERATE --> REDRAW
REDRAW --> LOOP
GAME_OVER --> WAIT
WAIT -->|"P / 继续"| LOOP
WAIT -->|"Enter / 重开"| START
WAIT -->|"ESC / 返回"| MENU["回到游戏厅"]
```
## 坦克大战流程图
```mermaid
%%{init: {"flowchart": {"nodeSpacing": 12, "rankSpacing": 18, "curve": "linear"}, "themeVariables": {"fontSize": "20px"}}}%%
flowchart TB
START["进入 TankScene"] --> LEVEL["读取当前战区配置
地图、墙体、基地、出生点和敌军参数"]
LEVEL --> INIT["创建 TankGame
恢复本关起始分数与生命"]
INIT --> WORLD["创建 Arcade Physics 战场
生成基地、玩家、普通/红色敌军、子弹与道具组"]
WORLD --> SHIELD["玩家出生并开启短暂无敌护盾"]
SHIELD --> LOOP["Phaser update 循环"]
LOOP --> PHASE{"当前状态是 playing?"}
PHASE -->|"否"| WAIT["等待重试、从第1关开始或返回"]
PHASE -->|"是"| PLAYER{"玩家是否正在等待重生?"}
PLAYER -->|"是"| ENEMY_AI["跳过玩家输入
更新敌军行为"]
PLAYER -->|"否"| INPUT["读取键盘 / 触屏按键 / 摇杆输入"]
INPUT -->|"方向键 / WASD / 摇杆主方向"| MOVE["设置单方向速度与坦克朝向"]
INPUT -->|"Space / 开火"| COOLDOWN{"射击冷却结束?"}
COOLDOWN -->|"是"| SHOOT["从炮口生成玩家子弹"]
COOLDOWN -->|"否"| ENEMY_AI
MOVE --> ENEMY_AI
SHOOT --> ENEMY_AI
ENEMY_AI --> DECIDE["敌军按随机延迟选择方向
遇到障碍时掉头"]
DECIDE --> ENEMY_SHOOT["达到射击时间时生成敌军子弹"]
ENEMY_SHOOT --> PHYSICS["物理引擎更新坦克、子弹与碰撞"]
PHYSICS --> EVENT{"发生哪类碰撞?"}
EVENT -->|"我方与敌方坦克接触"| TANK_BLOCK["刚性碰撞并阻挡移动
敌方坦克不可被我方推动"]
TANK_BLOCK --> LOOP
EVENT -->|"玩家子弹命中敌军"| HIT_ENEMY["销毁敌军与子弹
播放爆炸并增加分数"]
HIT_ENEMY --> BONUS_TANK{"是否为红色奖励坦克?"}
BONUS_TANK -->|"是"| DROP["随机掉落道具
冻结、全歼、无敌、生命或基地钢墙"]
BONUS_TANK -->|"否"| REMAIN{"本关仍有敌军?"}
DROP --> COLLECT["玩家拾取并立即应用道具效果"]
COLLECT --> REMAIN
REMAIN -->|"有"| LOOP
REMAIN -->|"无"| BONUS["增加战区通关奖励"]
BONUS --> FINAL{"是否为最后一关?"}
FINAL -->|"否"| NEXT["携带分数和剩余生命
进入下一战区"]
FINAL -->|"是"| CLEAR["显示全部战区解放与总分"]
NEXT --> LEVEL
CLEAR --> WAIT
EVENT -->|"敌军子弹命中玩家"| IMMUNE{"正在恢复或处于无敌期?"}
IMMUNE -->|"是"| LOOP
IMMUNE -->|"否"| HIT_PLAYER["播放爆炸并扣除一条生命"]
HIT_PLAYER --> ALIVE{"仍有生命?"}
ALIVE -->|"否"| LOST["任务失败并暂停物理世界"]
ALIVE -->|"是"| RESPAWN["隐藏并禁用玩家 1 秒
清除敌军子弹"]
RESPAWN --> REAPPEAR["在出生点重新出现
恢复操作并开启护盾"]
REAPPEAR --> LOOP
EVENT -->|"任意子弹命中基地"| BASE["基地切换为燃烧废墟
播放爆炸与震屏"]
BASE --> LOST
EVENT -->|"子弹撞墙或双方子弹相撞"| REMOVE["销毁发生碰撞的子弹"]
EVENT -->|"无关键碰撞"| LOOP
REMOVE --> LOOP
LOST --> WAIT
WAIT -->|"Enter / 重试"| RETRY["恢复本关起始分数和生命"]
WAIT -->|"R / 第1关"| FIRST["分数归零、生命重置
从第 1 战区开始"]
WAIT -->|"ESC / 返回"| MENU["回到游戏厅"]
RETRY --> LEVEL
FIRST --> LEVEL
```