Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 

Repository files navigation

魔女炼金工坊 · 跨平台小游戏

基于《AI 开发规范:魔女炼金工坊 V2.0》实现的跨平台小游戏(Water Sort 倒水玩法,魔法炼金主题)。支持微信小游戏、微信小程序、抖音小程序等多平台。

核心技术栈:PixiJS 8 + TypeScript + Vite + GSAP
平台适配层:PlatformAdapter(屏蔽平台差异,统一 Canvas 渲染 + 本地存储)
开发原则:AI First + 配置驱动 + 数据优先


🎮 项目定位

维度 说明
游戏类型 休闲益智 / Water Sort 倒水玩法
主题 女巫炼金实验室
目标用户 女性向,18-40 岁
单局时长 1-5 分钟
变现方式 广告变现 + 皮肤销售
目标平台 ✅ 微信小游戏 / ✅ 微信小程序 / ✅ 抖音小程序 / ✅ H5

🎯 AI 开发原则(必须遵守)

原则 1:优先修改,不优先新增

如果已有模块能够扩展,则禁止创建新模块。

// ✅ 正确:修改现有模块
LevelManager 新增方法 generateDynamicLevel()

// ❌ 错误:创建新模块
新建 AdvancedLevelManager

原则 2:任何功能必须可配置

禁止代码决定行为,必须 JSON 决定行为。

// ❌ 错误
if (level === 20) {
  giveReward(1000);
}

// ✅ 正确
const levelConfig = await loadConfig(`levels/${level}.json`);
giveReward(levelConfig.reward);

配置覆盖范围:

  • 关卡难度 / 配置
  • 女巫升级体系
  • 广告策略
  • 音效 / 动画参数
  • 颜色 / UI 尺寸

原则 3:所有逻辑必须数据驱动

逻辑不在代码中,在数据中。

// ❌ 错误:硬编码逻辑
if (steps <= 5) stars = 3;
else if (steps <= 8) stars = 2;
else if (steps <= 12) stars = 1;

// ✅ 正确:数据驱动
const criteria = levelConfig.starCriteria;
const stars = calculateStars(steps, criteria);

原则 4:任何动画必须可替换

以后美术替换:不用改代码。

// ✅ 正确做法
// 1. 所有动画参数存放在 config
// 2. 动画逻辑在 AnimationManager
// 3. Bottle 只调用 AnimationManager.play(name)

// 美术替换时:只改 config 和资源,不改代码

原则 5:任何资源不能依赖路径

统一通过 AssetsManager 加载。

// ❌ 错误
const image = await PIXI.Assets.load('./assets/ui/button.png');

// ✅ 正确
const image = assetsManager.get('button');
// AssetsManager 内部管理映射:'button' → 真实路径

原则 6:所有代码必须支持小游戏

禁止浏览器专属 API,必须通过 PlatformAdapter。

// ❌ 错误
const isMobile = /iPhone|iPad|Android/.test(navigator.userAgent);

// ✅ 正确
const isMobile = await platformAdapter.isMobile();

原则 7:保持低耦合

Manager 不直接引用 Manager,统一通过依赖注入或事件通信。

// ❌ 错误:循环引用
class LevelManager {
  saveManager.saveLevelProgress(); // 直接调用
}

// ✅ 正确:事件驱动
class LevelManager {
  events.emit('levelComplete', data);
}

class GameManager {
  levelManager.on('levelComplete', (data) => {
    saveManager.save(data);
    audioManager.playSound('levelup');
  });
}

⚠️ 微信开发者工具导入须知

小游戏模式(微信小游戏)

  • 用「导入项目」时,目录必须选 dist/(编译产物),不要选 witch-alchemy/ 源码根目录
  • 源码根目录只有 game.json/project.config.json,没有 game.js 入口,开发者工具会回退成「小程序」模式去找 app.json,报「未找到 app.json」
  • dist/ 是完整自包含的小游戏工程:game.js + game.json + project.config.jsoncompileType: "game"
  • AppID 必须是「小游戏」类型(或在导入框点「使用测试号」)。若用了「小程序」AppID,开发者工具会按 AppID 注册类型强制小程序模式,直接忽略 compileType: "game"
  • appid 千万别填「小程序」AppID。小游戏项目绑了小程序 AppID 时,微信会做 gameTransfer 校验,类型不匹配直接被后台拒绝。解决:清空 appid 后重导、选测试号
  • 项目类型在「导入/创建」那一刻锁定,之后不会重读 compileType 切换。若已被识别成小程序,必须:① 在项目列表右键「移除项目」;② 重启开发者工具;③ 重新「导入项目」选 dist/ + 测试号。仅改 project.config.json 无法切换
  • 若之前导入过导致缓存了错误项目类型:删除 dist/project.private.config.json 后重新导入即可

技术栈确认

组件 选型 说明
语言 TypeScript(严格模式) 禁止 any,全量类型检查
渲染引擎 PixiJS 8 原生 Canvas,性能优秀,跨平台
动画 GSAP 3 补间/时间轴,配合 Pixi Ticker
构建工具 Vite 开发模式原生 ESM,生产 CJS 包(dist/game.js
平台适配 PlatformAdapter 统一 Canvas/Storage API,支持微信/抖音/浏览器
存储 LocalStorage(可扩展为 CloudAPI) 本地优先,后续无缝迁移到云端
音频 Web Audio API(平台垫片) 延迟初始化,避免微信/抖音的音频黑名单

跨平台兼容性: ✅ 微信小游戏 / ✅ 微信小程序(WebView) / ✅ 抖音小程序 / ✅ 浏览器 / ✅ iOS Safari


开发约束落实

约束 检查项 状态
全量 TypeScript tsconfig.json strict: true
禁止 any ESLint rule: @typescript-eslint/no-explicit-any
不使用 React/Vue 渲染游戏 所有 UI 使用 Pixi Container
所有配置 JSON 化 src/core/config/
不直接耦合平台 API 统一封装于 PlatformAdapter
数据层与 UI 层分离 Manager 模式 + 事件驱动
所有动画使用 GSAP 禁止 setInterval / setTimeout / requestAnimationFrame
资源统一管理 AssetsManager

核心玩法设计

游戏循环

开始游戏
  ↓
进入关卡
  ↓
完成关卡 → 获得金币 + 魔法经验
  ↓
升级女巫 → 解锁新皮肤 / 实验室
  ↓
解锁下一关
  ↓
无限循环

关卡规划

阶段 关卡范围 说明 生成方式
教学 1-20 手动设计,难度递进 手动
普通 21-60 基础玩法,稳定难度 手动
特殊机制 61-120 引入冰冻/锁链等特殊瓶子 手动
组合机制 121-200 多种机制混合 手动
无限挑战 200+ 自动生成,难度无限递进 动态

三星规则

取消时间限制,仅看步数:

interface LevelStarCriteria {
  levelId: number;
  optimalSteps: number;      // 3 星:完美(最优步数)
  acceptableSteps: number;   // 2 星:优秀(可接受范围)
  maxSteps: number;          // 1 星:完成(通关最多步数)
}

function calculateStars(steps: number, criteria: LevelStarCriteria): number {
  if (steps <= criteria.optimalSteps) return 3;     // ⭐⭐⭐ 完美
  if (steps <= criteria.acceptableSteps) return 2;  // ⭐⭐ 优秀
  if (steps <= criteria.maxSteps) return 1;         // ⭐ 完成
  return 0; // 失败
}

目录结构

witch-alchemy/
├── src/
│   ├── core/
│   │   ├── managers/
│   │   │   ├── GameManager.ts
│   │   │   ├── SceneManager.ts
│   │   │   ├── LevelManager.ts
│   │   │   ├── SaveManager.ts
│   │   │   ├── AudioManager.ts
│   │   │   ├── AdManager.ts
│   │   │   ├── AssetsManager.ts
│   │   │   └── PlatformAdapter.ts
│   │   ├── scenes/
│   │   │   ├── LoadingScene.ts
│   │   │   ├── HomeScene.ts
│   │   │   ├── GameScene.ts
│   │   │   └── ResultScene.ts
│   │   ├── objects/
│   │   │   ├── Bottle.ts
│   │   │   ├── Liquid.ts
│   │   │   ├── Witch.ts
│   │   │   └── Cat.ts
│   │   ├── ui/
│   │   │   ├── Button.ts
│   │   │   ├── Dialog.ts
│   │   │   └── HUD.ts
│   │   ├── config/
│   │   │   ├── levels.json
│   │   │   ├── assets.json
│   │   │   ├── ads.json
│   │   │   └── constants.ts
│   │   ├── api/
│   │   │   ├── storage.ts
│   │   │   ├── cloud.ts
│   │   │   └── events.ts
│   │   ├── app/
│   │   │   ├── GameApp.ts
│   │   │   └── context.ts
│   │   └── types.ts
│   ├── assets/
│   │   ├── sounds/
│   │   ├── ui/
│   │   └── sprites/
│   ├── main.ts
│   └── index.html
├── dist/
├── package.json
├── tsconfig.json
├── vite.config.ts
├── eslint.config.js
└── README.md

全局上下文(GameContext)

// types.ts
interface GameContext {
  // ========== 配置 ==========
  levelConfig: LevelConfig[];
  assetsConfig: AssetsConfig;
  adsConfig: AdsConfig;
  saveStrategy: SaveStrategy;

  // ========== Managers ==========
  gameManager: GameManager;
  sceneManager: SceneManager;
  levelManager: LevelManager;
  saveManager: SaveManager;
  audioManager: AudioManager;
  adManager: AdManager;
  assetsManager: AssetsManager;
  platformAdapter: PlatformAdapter;
  events: EventEmitter;

  // ========== 运行时数据 ==========
  userData: UserData;
  currentLevel: number;
  currentScene: string;
  isLoading: boolean;
}

// 全局访问
declare global {
  var gameContext: GameContext;
}

Manager 职责清单

Manager 职责 禁止
GameManager 全局游戏状态机、Manager 调度 直接改数据
SceneManager 场景加载、切换、销毁 业务逻辑
LevelManager 关卡加载、难度控制、胜负判定 保存数据
SaveManager 本地/云端存储、数据同步 游戏逻辑
AudioManager 音效播放、音量管理、BGM 控制 音效合成
AdManager 广告加载、展示、回调处理 强制广告
AssetsManager 资源映射、加载、缓存管理 路径依赖
PlatformAdapter 平台 API 适配、容错处理 业务逻辑

Manager 依赖关系图

GameManager(全局调度中枢)
  │
  ├─→ SceneManager
  │    ├─→ HomeScene
  │    │    └─ 依赖:LevelManager, SaveManager, AssetsManager
  │    ├─→ GameScene
  │    │    └─ 依赖:LevelManager, AudioManager, AdManager
  │    ├─→ ResultScene
  │    │    └─ 依赖:SaveManager, AudioManager, AdManager
  │    └─→ LoadingScene
  │         └─ 依赖:AssetsManager
  │
  ├─→ LevelManager
  │    └─ 内部依赖:AssetsManager
  │
  ├─→ SaveManager
  │    └─ 内部依赖:PlatformAdapter, StorageAPI
  │
  ├─→ AudioManager
  │    └─ 内部依赖:PlatformAdapter
  │
  ├─→ AdManager
  │    └─ 内部依赖:PlatformAdapter
  │
  ├─→ AssetsManager
  │    └─ 内部依赖:PlatformAdapter, PIXI.Assets
  │
  └─→ PlatformAdapter(所有 Manager 最终依赖)
       ├─ Canvas / WebGL 接口
       ├─ Storage 接口
       ├─ 广告 API
       └─ 平台检测

Scene - Manager 映射

HomeScene

需要的 Manager:

class HomeScene {
  constructor(
    private levelManager: LevelManager,
    private saveManager: SaveManager,
    private assetsManager: AssetsManager,
    private audioManager: AudioManager,
    private events: EventEmitter
  ) {}

  async create() {
    // 加载女巫数据
    const userData = await saveManager.loadUserData();
    
    // 加载关卡进度
    const levelProgress = await saveManager.loadLevelProgress(userData.currentLevel);
    
    // 获取资源
    const backgroundImage = assetsManager.get('home_bg');
  }
}

GameScene

需要的 Manager:

class GameScene {
  constructor(
    private levelManager: LevelManager,
    private saveManager: SaveManager,
    private audioManager: AudioManager,
    private adManager: AdManager,
    private events: EventEmitter
  ) {}

  async create() {
    // 加载关卡
    const levelConfig = await levelManager.loadLevel(this.levelId);
    
    // 监听关卡完成事件
    this.events.on('levelComplete', (data) => {
      this.handleLevelComplete(data);
    });
  }

  private async handleLevelComplete(data: GameplayResult) {
    // 保存进度
    await this.saveManager.saveLevelProgress(data);
    
    // 播放音效
    this.audioManager.play('levelup');
    
    // 切换到结果页
    this.events.emit('sceneChange', 'result');
  }
}

事件系统完整清单

// api/events.ts
export enum GameEvents {
  // ========== 场景事件 ==========
  SCENE_CHANGE = 'scene:change',           // 切换场景
  SCENE_CREATED = 'scene:created',         // 场景创建
  SCENE_DESTROYED = 'scene:destroyed',     // 场景销毁

  // ========== 关卡事件 ==========
  LEVEL_START = 'level:start',             // 关卡开始
  LEVEL_COMPLETE = 'level:complete',       // 关卡完成
  LEVEL_FAILED = 'level:failed',           // 关卡失败
  LEVEL_RETRY = 'level:retry',             // 关卡重试
  LEVEL_SKIP = 'level:skip',               // 关卡跳过(仅测试)

  // ========== 瓶子事件 ==========
  BOTTLE_SELECTED = 'bottle:selected',     // 瓶子被选中
  BOTTLE_DESELECTED = 'bottle:deselected', // 瓶子被取消选中
  BOTTLE_POURED = 'bottle:poured',         // 倒水完成
  BOTTLE_INVALID = 'bottle:invalid',       // 无效操作

  // ========== 游戏状态事件 ==========
  GAME_OVER = 'game:over',                 // 游戏结束
  GAME_PAUSE = 'game:pause',               // 游戏暂停
  GAME_RESUME = 'game:resume',             // 游戏继续

  // ========== 存储事件 ==========
  SAVE_START = 'save:start',               // 保存开始
  SAVE_COMPLETE = 'save:complete',         // 保存完成
  SAVE_FAILED = 'save:failed',             // 保存失败

  // ========== UI 事件 ==========
  UI_SHOW_DIALOG = 'ui:show_dialog',       // 显示对话框
  UI_HIDE_DIALOG = 'ui:hide_dialog',       // 关闭对话框
  UI_UPDATE_HUD = 'ui:update_hud',         // 更新 HUD
}

Manager 调用顺序(关键!)

当玩家通关时,各 Manager 的执行顺序很重要,必须避免数据竞争:

// GameManager 中的处理逻辑
async function handleLevelComplete(result: GameplayResult): Promise<void> {
  try {
    // 1️⃣ 最高优先级:保存数据(防止数据丢失)
    console.log('1. 保存进度');
    await saveManager.saveLevelProgress(result);
    events.emit(GameEvents.SAVE_COMPLETE);

    // 2️⃣ 高优先级:更新游戏状态
    console.log('2. 判定胜负和奖励');
    const nextLevelUnlocked = await levelManager.onLevelComplete(result.levelId);
    
    // 3️⃣ 中优先级:播放音效(非阻塞)
    console.log('3. 播放音效');
    audioManager.play('levelup');
    
    // 4️⃣ 中优先级:展示广告(可能耗时)
    console.log('4. 展示广告');
    await adManager.showRewardedAd({
      onSuccess: () => console.log('广告观看成功'),
      onFailed: () => console.log('广告展示失败,直接发放奖励'),
    });

    // 5️⃣ 低优先级:切换场景
    console.log('5. 切换场景');
    await sceneManager.goto('result', { result, nextLevelUnlocked });

  } catch (error) {
    console.error('关卡完成处理失败:', error);
    // 降级处理:至少保证数据已保存
    await sceneManager.goto('result', { result, error });
  }
}

执行顺序规则:

  • ⭐⭐⭐ 必须先保存数据
  • ⭐⭐ 然后更新游戏状态
  • ⭐ 最后处理 UI/声音/场景

关卡配置格式(levels.json)

{
  "levels": [
    {
      "levelId": 1,
      "difficulty": "easy",
      "bottles": [
        {
          "bottleId": "bottle_0",
          "type": "normal",
          "liquids": [0, 1, 2]
        },
        {
          "bottleId": "bottle_1",
          "type": "normal",
          "liquids": [1, 2]
        },
        {
          "bottleId": "bottle_2",
          "type": "normal",
          "liquids": []
        }
      ],
      "starCriteria": {
        "optimalSteps": 4,
        "acceptableSteps": 6,
        "maxSteps": 8
      },
      "unlockedBy": {
        "type": "default"
      },
      "reward": {
        "coins": 10,
        "exp": 50
      },
      "tips": "Try to match colors"
    },
    {
      "levelId": 2,
      "difficulty": "easy",
      "bottles": [
        {
          "bottleId": "bottle_0",
          "type": "normal",
          "liquids": [0, 1, 2, 3]
        },
        {
          "bottleId": "bottle_1",
          "type": "normal",
          "liquids": [1, 2]
        },
        {
          "bottleId": "bottle_2",
          "type": "normal",
          "liquids": []
        }
      ],
      "starCriteria": {
        "optimalSteps": 5,
        "acceptableSteps": 7,
        "maxSteps": 10
      },
      "unlockedBy": {
        "type": "previousLevel",
        "levelId": 1,
        "requiredStars": 1
      },
      "reward": {
        "coins": 10,
        "exp": 50
      }
    }
  ]
}

BottleRule 接口(特殊机制)

// objects/BottleRule.ts
interface IBottleRule {
  /**
   * 检查是否可以从该瓶子倒出
   * @param bottle 源瓶子
   * @returns true 可以倒出,false 不可以
   */
  canPourFrom(bottle: Bottle): boolean;

  /**
   * 检查是否可以倒入目标瓶子
   * @param fromBottle 源瓶子
   * @param toBottle 目标瓶子
   * @returns true 可以倒入,false 不可以
   */
  canPourTo(fromBottle: Bottle, toBottle: Bottle): boolean;

  /**
   * 执行倒水效果(如特殊瓶子的特殊处理)
   * @param fromBottle 源瓶子
   * @param toBottle 目标瓶子
   */
  applyEffect(fromBottle: Bottle, toBottle: Bottle): void;

  /**
   * 获取瓶子的显示样式
   * @returns CSS 类名或样式对象
   */
  getStyle(): string | object;
}

// 不同瓶子类型的实现
class NormalBottleRule implements IBottleRule {
  canPourFrom(bottle: Bottle): boolean {
    return bottle.liquids.length > 0;
  }

  canPourTo(fromBottle: Bottle, toBottle: Bottle): boolean {
    return toBottle.liquids.length < 4 &&
           (toBottle.liquids.length === 0 || 
            toBottle.liquids[toBottle.liquids.length - 1] === fromBottle.liquids[fromBottle.liquids.length - 1]);
  }

  applyEffect(fromBottle: Bottle, toBottle: Bottle): void {
    // 普通瓶没有特殊效果
  }

  getStyle(): string {
    return 'bottle-normal';
  }
}

class FrozenBottleRule implements IBottleRule {
  canPourFrom(bottle: Bottle): boolean {
    return false; // 冰冻瓶不能倒出
  }

  canPourTo(fromBottle: Bottle, toBottle: Bottle): boolean {
    return true; // 但可以被倒入
  }

  applyEffect(fromBottle: Bottle, toBottle: Bottle): void {
    // 倒入后冰冻消融
    toBottle.setType('frozen');
  }

  getStyle(): string {
    return 'bottle-frozen';
  }
}

class ChainBottleRule implements IBottleRule {
  chainCount: number = 2;

  canPourFrom(bottle: Bottle): boolean {
    return this.chainCount === 0; // 链子都解开才能倒出
  }

  canPourTo(fromBottle: Bottle, toBottle: Bottle): boolean {
    return toBottle.liquids.length < 4;
  }

  applyEffect(fromBottle: Bottle, toBottle: Bottle): void {
    // 每次倒水链子减 1
    this.chainCount--;
  }

  getStyle(): string {
    return `bottle-chain chain-${this.chainCount}`;
  }
}

// 在 GameScene 中使用
class GameScene {
  private bottleRules: Map<string, IBottleRule> = new Map();

  checkBottlePour(fromId: string, toId: string): boolean {
    const fromRule = this.bottleRules.get(fromId);
    const toBottle = this.bottles.get(toId);
    
    return fromRule?.canPourTo(this.bottles.get(fromId)!, toBottle!) ?? false;
  }

  executePour(fromId: string, toId: string): void {
    const fromBottle = this.bottles.get(fromId)!;
    const toBottle = this.bottles.get(toId)!;
    const rule = this.bottleRules.get(fromId)!;

    // 执行倒水
    rule.applyEffect(fromBottle, toBottle);
  }
}

自动保存策略(SaveStrategy)

// types.ts
interface SaveStrategy {
  // ========== 何时保存 ==========
  triggers: {
    onLevelComplete: boolean;    // 关卡完成时
    onLevelFail: boolean;        // 失败时
    onLevelRetry: boolean;       // 重试时
    autoSaveInterval: number;    // 自动保存间隔(毫秒),0 = 不自动保存
  };

  // ========== 保存位置 ==========
  local: {
    enabled: boolean;            // 是否启用本地保存
    storageKey: string;          // LocalStorage 键名
  };

  cloud: {
    enabled: boolean;            // 是否启用云端同步
    syncInterval: number;        // 云端同步间隔(毫秒)
    provider: 'wechat' | 'douyin' | 'custom'; // 云端提供商
  };

  // ========== 数据版本 ==========
  version: number;               // 存档版本,用于迁移
  lastSyncTime: number;          // 最后同步时间戳
}

// 默认配置
const DEFAULT_SAVE_STRATEGY: SaveStrategy = {
  triggers: {
    onLevelComplete: true,
    onLevelFail: false,
    onLevelRetry: false,
    autoSaveInterval: 30000,     // 30 秒自动保存一次
  },
  local: {
    enabled: true,
    storageKey: 'witch_alchemy_save',
  },
  cloud: {
    enabled: false,              // V1.0 不启用
    syncInterval: 60000,         // 60 秒同步一次
    provider: 'wechat',
  },
  version: 1,
  lastSyncTime: 0,
};

关卡解锁规则(LevelUnlockRule)

// types.ts
type UnlockType = 'default' | 'previousLevel' | 'purchase' | 'event';

interface LevelUnlockRule {
  levelId: number;
  type: UnlockType;

  // 当 type === 'previousLevel' 时
  previousLevelId?: number;      // 需要通过的前置关卡
  requiredStars?: number;        // 需要的最少星数(默认 1)

  // 当 type === 'purchase' 时
  cost?: number;                 // 需要花费的金币

  // 当 type === 'event' 时
  eventName?: string;            // 事件名称

  // 当 type === 'default' 时:无需额外条件
}

// 使用示例
const LEVEL_UNLOCK_RULES: LevelUnlockRule[] = [
  {
    levelId: 1,
    type: 'default',             // 第 1 关默认解锁
  },
  {
    levelId: 2,
    type: 'previousLevel',
    previousLevelId: 1,
    requiredStars: 1,            // 通过第 1 关即可
  },
  {
    levelId: 20,
    type: 'previousLevel',
    previousLevelId: 19,
    requiredStars: 3,            // 需要 3 星才能解锁
  },
  {
    levelId: 100,
    type: 'purchase',
    cost: 500,                   // 需要花费 500 金币
  },
];

// LevelManager 中的检查逻辑
async function isLevelUnlocked(levelId: number, userProgress: UserData): Promise<boolean> {
  const rule = LEVEL_UNLOCK_RULES.find(r => r.levelId === levelId);
  if (!rule) return false;

  switch (rule.type) {
    case 'default':
      return true;

    case 'previousLevel':
      const prevProgress = await saveManager.loadLevelProgress(rule.previousLevelId!);
      return prevProgress?.bestStars! >= (rule.requiredStars ?? 1);

    case 'purchase':
      return userProgress.coins >= (rule.cost ?? 0);

    case 'event':
      return await events.has(rule.eventName!);

    default:
      return false;
  }
}

Scene 生命周期

每个 Scene 必须实现完整的生命周期:

interface IScene {
  create(): Promise<void>;       // 初始化(资源加载)
  enter(): Promise<void>;        // 进入场景(动画开始)
  update(deltaTime: number): void; // 每帧更新
  exit(): Promise<void>;         // 离开场景(清理)
  destroy(): void;               // 销毁(释放内存)
}

class HomeScene implements IScene {
  async create(): Promise<void> {
    // 加载资源、初始化 UI
  }

  async enter(): Promise<void> {
    // 播放进场动画
  }

  update(deltaTime: number): void {
    // 每帧更新逻辑
  }

  async exit(): Promise<void> {
    // 播放退出动画
  }

  destroy(): void {
    // 释放纹理、移除事件监听等
  }
}

本地调试

npm install
npm run dev      # 打开浏览器即可游玩

浏览器模式下:

  • PlatformAdapter 自动回退到 DOM canvas + localStorage
  • 广告/音频为安全空实现
  • 游戏逻辑完全一致

构建与发布

npm run build    # 产物在 dist/

微信小游戏:

  1. 用「微信开发者工具」导入 dist/ 目录
  2. 选「小游戏」编译类型
  3. 填入小游戏 AppID 或选「使用测试号」
  4. 上传审核

TypeScript 规范

禁止项

// ❌ 禁止 any
function process(data: any) { }

// ❌ 禁止 as any
const x = data as any;

// ❌ 禁止 unknown 而不检查
function handle(value: unknown) {
  value.prop(); // 错误:未检查
}

要求项

// ✅ 完整的类型定义
interface LevelConfig {
  levelId: number;
  bottles: BottleConfig[];
  starCriteria: LevelStarCriteria;
}

// ✅ 使用 unknown 时必须检查
function handle(value: unknown): void {
  if (typeof value === 'object' && value !== null) {
    // 现在可以安全使用
  }
}

Commit 规范

使用 Conventional Commits:

git commit -m "feat: 添加关卡生成算法"
git commit -m "fix: 修复倒水动画闪烁问题"
git commit -m "refactor: 优化 Bottle 类职责"
git commit -m "docs: 补充 Manager 接口说明"

AI 执行清单

任何新增功能,必须遵循:

1️⃣ 阅读 README(理解架构)
   ↓
2️⃣ 检查现有 Manager(是否可扩展)
   ↓
3️⃣ 确认目录位置(遵循目录规范)
   ↓
4️⃣ 设计接口(定义类型 + 方法)
   ↓
5️⃣ 配置驱动(参数 JSON 化)
   ↓
6️⃣ 事件驱动(低耦合设计)
   ↓
7️⃣ 开始编码(注释 + 类型 + 测试)

不得自行改变项目架构。


许可证

MIT

About

魔女炼金工坊 · 跨平台小游戏

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors