HarmonyOS应用<趣答>开发第3篇:Level模型设计与关卡系统——学习路径完整攻略

📖 引言
在知识问答学习应用中,关卡系统是引导用户按序学习、提升技能的核心机制。一个设计良好的关卡系统,不仅能够激励用户持续学习,还能通过合理的难度递进,帮助用户循序渐进地掌握知识。
本文将详细讲解 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 展示
difficulty和difficultyLevel的对应关系:difficultyLevel = 1→Difficulty.EASYdifficultyLevel = 2→Difficulty.MEDIUMdifficultyLevel = 3→Difficulty.HARDdifficultyLevel = 4→Difficulty.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 = 1→Difficulty.EASYdifficultyLevel = 2→Difficulty.MEDIUMdifficultyLevel = 3→Difficulty.HARDdifficultyLevel = 4→Difficulty.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 模型设计与成就系统
- 📚 介绍成就类型和稀有度设计
- 🏷️ 探索成就解锁和奖励机制
🔗 相关链接
- 项目源码: Atomgit仓库
💡 提示: 建议结合项目源码阅读,动手实践效果更好!
更多推荐


所有评论(0)