在这里插入图片描述

📖 引言

在知识问答学习应用中,关卡系统是引导用户按序学习、提升技能的核心机制。一个设计良好的关卡系统,不仅能够激励用户持续学习,还能通过合理的难度递进,帮助用户循序渐进地掌握知识。

本文将详细讲解 Level 模型和 Subject 接口的完整设计,包括关卡配置、难度分级、奖励机制、学科管理等核心内容。通过本文,你将掌握:

  • 如何设计完整的关卡数据模型
  • 如何实现关卡解锁和完成机制
  • 如何配置关卡奖励和难度递进
  • 如何在实际项目中管理学科和关卡数据

🎯 学习目标

完成本文后,你将能够:

  • ✅ 理解 Level 和 Subject 接口的完整字段定义
  • ✅ 掌握 Difficulty 枚举和关卡难度分级
  • ✅ 实现关卡解锁条件和完成判定
  • ✅ 设计关卡奖励和星级计算机制

💡 需求分析

功能模块设计

模块 功能描述 技术要点
关卡数据管理 存储关卡的完整配置信息 Level 接口、关卡属性
学科分类 管理不同学科的关卡集合 Subject 接口、levels 数组
难度分级 支持四种难度等级的关卡 Difficulty 枚举
解锁机制 控制用户解锁关卡的条件 unlockCondition
奖励系统 配置关卡通关奖励 LevelReward 接口

🛠️ 核心实现

步骤1: 定义 Level 接口

功能说明

Level 接口是关卡数据的核心模型,用于表示单个关卡的完整配置信息。每个关卡包含基本信息、难度等级、时间限制、奖励配置等数据。

完整代码
// models/Level.ts

/**
 * 难度等级枚举
 */
export enum Difficulty {
  EASY = '简单',
  MEDIUM = '中等',
  HARD = '困难',
  EXPERT = '专家'
}

/**
 * 关卡奖励接口
 */
export interface LevelReward {
  baseScore: number;           // 基础积分
  baseExperience: number;      // 基础经验
  star1Multiplier: number;     // 1星奖励倍数
  star2Multiplier: number;     // 2星奖励倍数
  star3Multiplier: number;     // 3星奖励倍数
}

/**
 * 关卡接口
 */
export interface Level {
  id: string;                    // 关卡唯一标识
  subjectId: string;             // 所属学科 ID
  subjectName: string;           // 所属学科名称
  name: string;                  // 关卡名称
  description: string;           // 关卡描述
  difficulty: Difficulty;         // 难度等级
  difficultyLevel: number;       // 难度数值(1-4)
  order: number;                 // 关卡顺序
  unlockCondition: string;       // 解锁条件描述
  questionCount: number;          // 题目数量
  timeLimit: number;             // 时间限制(秒)
  rewards: LevelReward;          // 奖励配置
  icon: string;                 // 关卡图标
  background: string;            // 关卡背景
}

/**
 * 学科接口
 */
export interface Subject {
  id: string;          // 学科 ID
  name: string;        // 学科名称
  description: string; // 学科描述
  icon: string;       // 学科图标
  color: string;      // 学科主题色
  levels: Level[];    // 该学科的关卡列表
}
代码解析

1. Level 接口字段说明

字段名 类型 必填 说明
id string 关卡唯一标识,格式为 {subject}_level_{difficultyLevel}
subjectId string 所属学科 ID
subjectName string 所属学科名称
name string 关卡显示名称
description string 关卡描述
difficulty Difficulty 难度等级枚举值
difficultyLevel number 难度数值(1-4),1=简单,4=专家
order number 关卡在学科内的顺序
unlockCondition string 解锁条件描述
questionCount number 题目数量
timeLimit number 时间限制(秒)
rewards LevelReward 奖励配置
icon string 关卡图标 URL
background string 关卡背景 URL

2. Difficulty 枚举设计

export enum Difficulty {
  EASY = '简单',       // 入门级,对应 difficultyLevel = 1
  MEDIUM = '中等',     // 中级,对应 difficultyLevel = 2
  HARD = '困难',       // 高级,对应 difficultyLevel = 3
  EXPERT = '专家'      // 专家级,对应 difficultyLevel = 4
}

原理:

  • 使用枚举确保难度等级的类型安全
  • 枚举值为中文显示名称,便于直接用于 UI 展示
  • difficultydifficultyLevel 的对应关系:
    • difficultyLevel = 1Difficulty.EASY
    • difficultyLevel = 2Difficulty.MEDIUM
    • difficultyLevel = 3Difficulty.HARD
    • difficultyLevel = 4Difficulty.EXPERT

注意事项:

  • 枚举值必须与 difficultyLevel 一致
  • 避免使用数字直接比较

3. LevelReward 接口设计

export interface LevelReward {
  baseScore: number;           // 基础积分
  baseExperience: number;      // 基础经验
  star1Multiplier: number;     // 1星奖励倍数
  star2Multiplier: number;     // 2星奖励倍数
  star3Multiplier: number;     // 3星奖励倍数
}

原理:

  • baseScore: 通关获得的基准积分
  • baseExperience: 通关获得的基准经验
  • starXMultiplier: 不同星级的奖励倍数

奖励计算公式:

实际奖励 = 基础奖励 × 星级倍数

示例:

rewards: {
  baseScore: 100,      // 基础积分
  baseExperience: 50,  // 基础经验
  star1Multiplier: 1.0,  // 1星:100分
  star2Multiplier: 1.5,  // 2星:150分
  star3Multiplier: 2.0   // 3星:200分
}

// 1星通关奖励计算
const score = 100 * 1.0;  // 100分
const experience = 50 * 1.0;  // 50经验

// 3星通关奖励计算
const score = 100 * 2.0;  // 200分
const experience = 50 * 2.0;  // 100经验

步骤2: 定义 Subject 接口

功能说明

Subject 接口用于管理不同学科的关卡集合,包含学科的基本信息和该学科下的所有关卡列表。

完整代码
// models/Level.ts

export interface Subject {
  id: string;          // 学科 ID
  name: string;        // 学科名称
  description: string; // 学科描述
  icon: string;       // 学科图标 URL
  color: string;      // 学科主题色
  levels: Level[];    // 该学科的关卡列表
}
代码解析

1. Subject 接口字段说明

字段名 类型 必填 说明
id string 学科唯一标识
name string 学科显示名称
description string 学科描述
icon string 学科图标 URL
color string 学科主题色(用于 UI 显示)
levels Level[] 该学科的关卡列表

2. levels 数组设计

levels: Level[]

原理:

  • levels 数组包含该学科的所有关卡
  • 关卡应该按照 order 字段排序
  • 可以为空数组(学科下暂无关卡)

注意事项:

  • 添加关卡时需要确保 id 唯一
  • 关卡顺序应该连续(1, 2, 3…)
  • 建议按难度递增排序

步骤3: 创建关卡实例

功能说明

创建数学学科的完整关卡实例,展示关卡数据的完整配置。

完整代码
// 创建数学学科关卡示例
const mathSubject: Subject = {
  id: 'math',
  name: '数学',
  description: '培养数学思维,提升逻辑推理能力',
  icon: 'https://example.com/icons/math.png',
  color: '#4A90D9',
  levels: [
    {
      id: 'math_level_1',
      subjectId: 'math',
      subjectName: '数学',
      name: '数学入门',
      description: '基础数学知识,开启学习之旅',
      difficulty: Difficulty.EASY,
      difficultyLevel: 1,
      order: 1,
      unlockCondition: '无(初始关卡)',
      questionCount: 10,
      timeLimit: 300,
      rewards: {
        baseScore: 100,
        baseExperience: 50,
        star1Multiplier: 1.0,
        star2Multiplier: 1.5,
        star3Multiplier: 2.0
      },
      icon: 'https://example.com/levels/math_1.png',
      background: 'https://example.com/bg/math_1.jpg'
    },
    {
      id: 'math_level_2',
      subjectId: 'math',
      subjectName: '数学',
      name: '数学进阶',
      description: '深入学习数学概念',
      difficulty: Difficulty.MEDIUM,
      difficultyLevel: 2,
      order: 2,
      unlockCondition: '通关"数学入门"',
      questionCount: 10,
      timeLimit: 240,
      rewards: {
        baseScore: 150,
        baseExperience: 75,
        star1Multiplier: 1.0,
        star2Multiplier: 1.5,
        star3Multiplier: 2.0
      },
      icon: 'https://example.com/levels/math_2.png',
      background: 'https://example.com/bg/math_2.jpg'
    }
  ]
};
代码解析

1. 关卡解锁条件设计

unlockCondition: '通关"数学入门"'

原理:

  • unlockCondition 是描述性字段,用于显示给用户
  • 实际的解锁判断逻辑在业务层实现
  • 格式:"通关'{上一关卡名称}'""完成 {N} 道题目"

示例:

// ✅ 正确:关卡解锁条件描述
unlockCondition: '通关"数学入门"'
unlockCondition: '累计完成 10 道题目'
unlockCondition: '用户等级达到 5 级'

// ❌ 错误:使用过于复杂的描述
unlockCondition: '通关"数学入门"且用户等级达到3级且累计积分超过1000'

2. 时间限制配置

timeLimit: 300  // 5分钟
timeLimit: 240  // 4分钟

原理:

  • timeLimit 单位为秒
  • 不同难度等级可以设置不同的时间限制
  • 建议:高难度关卡时间限制更长

注意事项:

  • 时间限制应该合理,确保用户能够完成
  • 可以根据题目数量估算时间

步骤4: 实现关卡服务

功能说明

关卡服务(LevelService)用于管理关卡的查询、解锁判断、完成判定等业务逻辑。

完整代码
// services/LevelService.ts

import { Level, Subject, Difficulty } from '../models/Level';
import { User } from '../models/User';

/**
 * 关卡服务类
 */
export class LevelService {
  private static instance: LevelService;
  private subjects: Subject[] = [];

  private constructor() {
    this.initializeSubjects();
  }

  /**
   * 获取单例实例
   */
  static getInstance(): LevelService {
    if (!LevelService.instance) {
      LevelService.instance = new LevelService();
    }
    return LevelService.instance;
  }

  /**
   * 初始化关卡数据
   */
  private initializeSubjects(): void {
    // 这里加载关卡数据
    this.subjects = this.loadLevelData();
  }

  /**
   * 加载关卡数据(模拟)
   */
  private loadLevelData(): Subject[] {
    // 实际项目中从服务器或本地存储加载
    return [];
  }

  /**
   * 获取所有学科
   */
  getAllSubjects(): Subject[] {
    return this.subjects;
  }

  /**
   * 根据学科 ID 获取学科
   */
  getSubjectById(subjectId: string): Subject | undefined {
    return this.subjects.find(s => s.id === subjectId);
  }

  /**
   * 根据关卡 ID 获取关卡
   */
  getLevelById(levelId: string): Level | undefined {
    for (const subject of this.subjects) {
      const level = subject.levels.find(l => l.id === levelId);
      if (level) {
        return level;
      }
    }
    return undefined;
  }

  /**
   * 获取学科下的所有关卡
   */
  getLevelsBySubject(subjectId: string): Level[] {
    const subject = this.getSubjectById(subjectId);
    return subject ? subject.levels : [];
  }

  /**
   * 判断关卡是否已解锁
   */
  isLevelUnlocked(levelId: string, user: User): boolean {
    const level = this.getLevelById(levelId);
    if (!level) {
      return false;
    }

    // 第一个关卡默认解锁
    if (level.order === 1) {
      return true;
    }

    // 检查前置关卡是否完成
    const subject = this.getSubjectById(level.subjectId);
    if (!subject) {
      return false;
    }

    // 查找上一个关卡
    const previousLevel = subject.levels.find(l => l.order === level.order - 1);
    if (!previousLevel) {
      return true;
    }

    // 检查上一个关卡是否完成
    return user.completedLevels.includes(previousLevel.id);
  }

  /**
   * 判断关卡是否已完成
   */
  isLevelCompleted(levelId: string, user: User): boolean {
    return user.completedLevels.includes(levelId);
  }

  /**
   * 获取关卡星级
   */
  getLevelStars(levelId: string, user: User): number {
    const stars = user.levelStars.get(levelId);
    return stars || 0;
  }

  /**
   * 获取可用的关卡列表
   */
  getAvailableLevels(user: User): Level[] {
    const availableLevels: Level[] = [];
    
    for (const subject of this.subjects) {
      for (const level of subject.levels) {
        if (this.isLevelUnlocked(level.id, user)) {
          availableLevels.push(level);
        }
      }
    }
    
    return availableLevels;
  }

  /**
   * 计算通关奖励
   */
  calculateRewards(levelId: string, stars: number): { score: number; experience: number } {
    const level = this.getLevelById(levelId);
    if (!level) {
      return { score: 0, experience: 0 };
    }

    const multiplier = this.getStarMultiplier(stars);
    return {
      score: level.rewards.baseScore * multiplier,
      experience: level.rewards.baseExperience * multiplier
    };
  }

  /**
   * 获取星级倍数
   */
  private getStarMultiplier(stars: number): number {
    switch (stars) {
      case 1:
        return 1.0;
      case 2:
        return 1.5;
      case 3:
        return 2.0;
      default:
        return 0;
    }
  }

  /**
   * 根据难度获取关卡
   */
  getLevelsByDifficulty(difficulty: Difficulty): Level[] {
    const levels: Level[] = [];
    
    for (const subject of this.subjects) {
      const filtered = subject.levels.filter(l => l.difficulty === difficulty);
      levels.push(...filtered);
    }
    
    return levels;
  }

  /**
   * 获取学科进度
   */
  getSubjectProgress(subjectId: string, user: User): { completed: number; total: number } {
    const levels = this.getLevelsBySubject(subjectId);
    const completed = levels.filter(l => user.completedLevels.includes(l.id)).length;
    return { completed, total: levels.length };
  }
}
代码解析

1. 关卡解锁判断逻辑

isLevelUnlocked(levelId: string, user: User): boolean {
  const level = this.getLevelById(levelId);
  if (!level) {
    return false;
  }

  // 第一个关卡默认解锁
  if (level.order === 1) {
    return true;
  }

  // 检查前置关卡是否完成
  const subject = this.getSubjectById(level.subjectId);
  if (!subject) {
    return false;
  }

  // 查找上一个关卡
  const previousLevel = subject.levels.find(l => l.order === level.order - 1);
  if (!previousLevel) {
    return true;
  }

  // 检查上一个关卡是否完成
  return user.completedLevels.includes(previousLevel.id);
}

原理:

  • 第一个关卡(order = 1)默认解锁
  • 其他关卡需要完成前置关卡才能解锁
  • 使用 completedLevels 数组判断是否完成

示例:

// ✅ 正确:判断关卡是否解锁
const isUnlocked = levelService.isLevelUnlocked('math_level_2', user);

// ✅ 正确:判断关卡是否完成
const isCompleted = levelService.isLevelCompleted('math_level_1', user);

2. 奖励计算逻辑

calculateRewards(levelId: string, stars: number): { score: number; experience: number } {
  const level = this.getLevelById(levelId);
  if (!level) {
    return { score: 0, experience: 0 };
  }

  const multiplier = this.getStarMultiplier(stars);
  return {
    score: level.rewards.baseScore * multiplier,
    experience: level.rewards.baseExperience * multiplier
  };
}

原理:

  • 根据星级计算奖励倍数
  • 3星:2.0倍,2星:1.5倍,1星:1.0倍
  • 奖励 = 基础奖励 × 星级倍数

示例:

// 3星通关奖励
const rewards = levelService.calculateRewards('math_level_1', 3);
// { score: 200, experience: 100 }

// 1星通关奖励
const rewards = levelService.calculateRewards('math_level_1', 1);
// { score: 100, experience: 50 }

⚠️ 常见问题与解决方案

问题1: 关卡 ID 格式不规范

现象:
关卡 ID 格式不统一,导致数据管理和查询困难。

错误代码:

// ❌ 错误:ID 格式不规范
const level: Level = {
  id: 'level_001',  // 不包含学科信息
  subjectId: 'math',
  // ...
};

正确代码:

// ✅ 正确:ID 格式为 {subject}_level_{difficultyLevel}
const level: Level = {
  id: 'math_level_1',  // 学科_关卡_难度等级
  subjectId: 'math',
  // ...
};

规则/建议:

  • ID 格式:{subjectId}_level_{difficultyLevel}
  • 确保全局唯一性
  • 便于按学科和难度查询

问题2: difficulty 和 difficultyLevel 不一致

现象:
枚举值与难度数值不匹配,导致逻辑错误。

错误代码:

// ❌ 错误:难度枚举值与 difficultyLevel 不一致
const level: Level = {
  difficulty: Difficulty.EASY,     // '简单'
  difficultyLevel: 3,              // 但数值是 3
  // ...
};

正确代码:

// ✅ 正确:难度枚举值与 difficultyLevel 一致
const level: Level = {
  difficulty: Difficulty.EASY,     // '简单'
  difficultyLevel: 1,              // 数值也是 1
  // ...
};

规则/建议:

  • difficultyLevel = 1Difficulty.EASY
  • difficultyLevel = 2Difficulty.MEDIUM
  • difficultyLevel = 3Difficulty.HARD
  • difficultyLevel = 4Difficulty.EXPERT

问题3: 时间限制设置不合理

现象:
关卡时间限制设置过短或过长,影响用户体验。

错误代码:

// ❌ 错误:10道题只给30秒
const level: Level = {
  questionCount: 10,
  timeLimit: 30,  // 错误:每题平均只有3秒
  // ...
};

// ❌ 错误:10道题给1小时
const level: Level = {
  questionCount: 10,
  timeLimit: 3600,  // 错误:时间过长
  // ...
};

正确代码:

// ✅ 正确:根据题目数量合理设置时间
const level: Level = {
  questionCount: 10,
  timeLimit: 300,  // 正确:每题平均30秒,共5分钟
  // ...
};

规则/建议:

  • 建议每题平均用时 30-60 秒
  • 高难度关卡可以适当延长
  • 时间限制应该让大多数用户能够完成

问题4: 奖励倍数设置错误

现象:
星级奖励倍数设置不合理,导致奖励计算错误。

错误代码:

// ❌ 错误:1星奖励比2星、3星高
const level: Level = {
  rewards: {
    baseScore: 100,
    star1Multiplier: 2.0,   // 错误:1星最高
    star2Multiplier: 1.5,
    star3Multiplier: 1.0
  }
};

正确代码:

// ✅ 正确:星级越高奖励越高
const level: Level = {
  rewards: {
    baseScore: 100,
    star1Multiplier: 1.0,   // 1星:100分
    star2Multiplier: 1.5,    // 2星:150分
    star3Multiplier: 2.0    // 3星:200分
  }
};

规则/建议:

  • 3星奖励最高,2星次之,1星最低
  • 建议倍数:1星=1.0,2星=1.5,3星=2.0
  • 保持奖励系统的公平性

问题5: 关卡顺序不连续

现象:
关卡顺序不连续,导致解锁逻辑错误。

错误代码:

// ❌ 错误:关卡顺序不连续
const subject: Subject = {
  levels: [
    { id: 'math_level_1', order: 1, ... },
    { id: 'math_level_2', order: 3, ... },  // 错误:跳过了 order=2
    { id: 'math_level_3', order: 2, ... }   // 错误:顺序混乱
  ]
};

正确代码:

// ✅ 正确:关卡顺序连续且递增
const subject: Subject = {
  levels: [
    { id: 'math_level_1', order: 1, ... },
    { id: 'math_level_2', order: 2, ... },
    { id: 'math_level_3', order: 3, ... }
  ]
};

规则/建议:

  • 关卡顺序应该从 1 开始,连续递增
  • 避免跳号和重复
  • 便于解锁逻辑的实现

📝 本章小结

核心知识点

本文详细讲解了 Level 模型和关卡系统的设计与实现,主要包括:

1. Level 接口设计

  • 包含 13 个字段,覆盖关卡的完整配置
  • 使用 Difficulty 枚举定义难度等级
  • 使用 LevelReward 接口配置奖励

2. Subject 接口设计

  • 管理学科信息和关卡集合
  • 包含 levels 数组存储关卡
  • 支持按学科查询关卡

3. 关卡服务实现

  • 关卡解锁判断逻辑
  • 关卡完成判定
  • 奖励计算机制

最佳实践总结

关卡 ID 格式

id: 'math_level_1'  // {subjectId}_level_{difficultyLevel}

奖励计算

const multiplier = stars === 3 ? 2.0 : stars === 2 ? 1.5 : 1.0;
const score = baseScore * multiplier;

解锁判断

if (level.order === 1) return true;
return user.completedLevels.includes(previousLevel.id);

下一步预告

在下一篇文章中,我们将:

  • 🎨 讲解 Achievement 模型设计与成就系统
  • 📚 介绍成就类型和稀有度设计
  • 🏷️ 探索成就解锁和奖励机制

🔗 相关链接


💡 提示: 建议结合项目源码阅读,动手实践效果更好!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐