在这里插入图片描述

📖 引言

在知识问答学习应用中,关卡服务(LevelService)是管理关卡数据和用户进度的核心组件。它负责处理关卡的解锁、完成状态、进度查询等关键操作,是连接用户学习路径和游戏化体验的桥梁。

本文将详细讲解 LevelService 的完整实现,包括关卡数据管理、解锁机制、进度跟踪等核心功能。通过本文,你将掌握:

  • 如何设计关卡数据管理机制
  • 如何实现关卡解锁和完成逻辑
  • 如何跟踪用户关卡进度
  • 如何在实际项目中管理关卡奖励

🎯 学习目标

完成本文后,你将能够:

  • ✅ 理解 LevelService 的核心职责和功能
  • ✅ 实现关卡数据的增删改查
  • ✅ 管理关卡解锁和完成状态
  • ✅ 计算关卡进度和奖励
  • ✅ 集成学科和关卡的关联关系

💡 需求分析

功能模块设计

模块 功能描述 技术要点
关卡数据管理 CRUD操作关卡数据 数据持久化、缓存
解锁机制 管理关卡解锁条件 前置关卡、用户等级
进度跟踪 跟踪用户关卡进度 完成状态、星级
奖励计算 计算关卡通关奖励 积分、经验、星级倍数
学科管理 管理学科和关卡的关联 一对多关系

🛠️ 核心实现

步骤1: LevelService 接口设计

功能说明

定义 LevelService 的核心接口,规范服务的方法签名和返回类型。

完整代码
// services/interfaces/LevelServiceInterface.ts

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

/**
 * 关卡服务接口
 */
export interface LevelServiceInterface {
  /**
   * 获取所有学科
   * @returns 学科列表
   */
  getAllSubjects(): ServiceResponse<Subject[]>;

  /**
   * 根据ID获取学科
   * @param subjectId - 学科ID
   * @returns 学科信息
   */
  getSubjectById(subjectId: string): ServiceResponse<Subject | undefined>;

  /**
   * 获取所有关卡
   * @returns 关卡列表
   */
  getAllLevels(): ServiceResponse<Level[]>;

  /**
   * 根据ID获取关卡
   * @param levelId - 关卡ID
   * @returns 关卡信息
   */
  getLevelById(levelId: string): ServiceResponse<Level | undefined>;

  /**
   * 获取学科下的所有关卡
   * @param subjectId - 学科ID
   * @returns 关卡列表
   */
  getLevelsBySubject(subjectId: string): ServiceResponse<Level[]>;

  /**
   * 获取用户可用的关卡
   * @param user - 用户对象
   * @returns 可用关卡列表
   */
  getAvailableLevels(user: User): ServiceResponse<Level[]>;

  /**
   * 判断关卡是否已解锁
   * @param levelId - 关卡ID
   * @param user - 用户对象
   * @returns 是否已解锁
   */
  isLevelUnlocked(levelId: string, user: User): ServiceResponse<boolean>;

  /**
   * 判断关卡是否已完成
   * @param levelId - 关卡ID
   * @param user - 用户对象
   * @returns 是否已完成
   */
  isLevelCompleted(levelId: string, user: User): ServiceResponse<boolean>;

  /**
   * 获取关卡星级
   * @param levelId - 关卡ID
   * @param user - 用户对象
   * @returns 星级(0-3)
   */
  getLevelStars(levelId: string, user: User): ServiceResponse<number>;

  /**
   * 获取学科进度
   * @param subjectId - 学科ID
   * @param user - 用户对象
   * @returns 进度信息
   */
  getSubjectProgress(subjectId: string, user: User): ServiceResponse<SubjectProgress>;

  /**
   * 获取用户关卡进度摘要
   * @param user - 用户对象
   * @returns 进度摘要
   */
  getUserLevelProgress(user: User): ServiceResponse<UserLevelProgress>;

  /**
   * 更新关卡完成状态
   * @param levelId - 关卡ID
   * @param user - 用户对象
   * @param stars - 星级
   * @returns 是否成功
   */
  updateLevelCompletion(levelId: string, user: User, stars: number): ServiceResponse<boolean>;

  /**
   * 计算关卡奖励
   * @param levelId - 关卡ID
   * @param stars - 星级
   * @returns 奖励信息
   */
  calculateLevelRewards(levelId: string, stars: number): ServiceResponse<LevelReward>;

  /**
   * 添加新关卡
   * @param level - 关卡信息
   * @returns 创建的关卡
   */
  addLevel(level: Level): ServiceResponse<Level>;

  /**
   * 更新关卡信息
   * @param levelId - 关卡ID
   * @param updates - 更新内容
   * @returns 更新后的关卡
   */
  updateLevel(levelId: string, updates: Partial<Level>): ServiceResponse<Level>;

  /**
   * 删除关卡
   * @param levelId - 关卡ID
   * @returns 是否成功
   */
  deleteLevel(levelId: string): ServiceResponse<boolean>;
}

/**
 * 学科进度接口
 */
export interface SubjectProgress {
  subjectId: string;        // 学科ID
  subjectName: string;      // 学科名称
  totalLevels: number;      // 总关卡数
  completedLevels: number;  // 已完成关卡数
  unlockedLevels: number;   // 已解锁关卡数
  progressPercent: number;  // 进度百分比
  stars: number;            // 总星级
  perfectLevels: number;    // 满分关卡数
}

/**
 * 用户关卡进度摘要
 */
export interface UserLevelProgress {
  totalLevels: number;      // 总关卡数
  completedLevels: number;  // 已完成关卡数
  unlockedLevels: number;   // 已解锁关卡数
  totalStars: number;       // 总星级
  perfectLevels: number;    // 满分关卡数
  subjectProgress: SubjectProgress[];  // 各学科进度
}

/**
 * 关卡奖励接口
 */
export interface LevelReward {
  score: number;         // 积分奖励
  experience: number;    // 经验奖励
  starMultiplier: number; // 星级倍数
}
代码解析

1. 接口方法分类

分类 方法 功能说明
学科管理 getAllSubjects(), getSubjectById() 获取学科信息
关卡查询 getAllLevels(), getLevelById(), getLevelsBySubject() 查询关卡数据
进度判断 isLevelUnlocked(), isLevelCompleted(), getLevelStars() 判断关卡状态
进度统计 getSubjectProgress(), getUserLevelProgress() 获取进度信息
状态更新 updateLevelCompletion() 更新关卡完成状态
奖励计算 calculateLevelRewards() 计算关卡奖励
数据管理 addLevel(), updateLevel(), deleteLevel() 关卡CRUD操作

步骤2: LevelService 实现

功能说明

实现 LevelService 的核心逻辑,包括关卡数据管理、解锁机制、进度跟踪等功能。

完整代码
// services/LevelService.ts

import { Singleton } from './base/BaseService';
import { BaseService } from './base/BaseService';
import { ServiceResponse, ServiceError, createSuccessResponse, createErrorResponse } from './interfaces/ServiceInterface';
import { LevelServiceInterface, SubjectProgress, UserLevelProgress, LevelReward } from './interfaces/LevelServiceInterface';
import { Level, Subject, Difficulty } from '../models/Level';
import { User } from '../models/User';

/**
 * 关卡服务实现
 */
@Singleton
export class LevelService extends BaseService implements LevelServiceInterface {
  private subjects: Subject[] = [];

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

  /**
   * 初始化学科和关卡数据
   */
  private initializeSubjects(): void {
    // 初始化学科数据
    this.subjects = this.loadSubjectData();
  }

  /**
   * 加载学科数据(模拟)
   */
  private loadSubjectData(): Subject[] {
    // 实际项目中从服务器或本地存储加载
    return [
      this.createMathSubject(),
      this.createGeographySubject(),
      this.createLifeSubject(),
      this.createLiteratureSubject(),
      this.createSportsSubject()
    ];
  }

  /**
   * 创建数学学科
   */
  private createMathSubject(): Subject {
    return {
      id: 'math',
      name: '数学',
      description: '培养数学思维,提升逻辑推理能力',
      icon: 'https://example.com/icons/math.png',
      color: '#4A90D9',
      levels: [
        this.createLevel('math', 1, Difficulty.EASY),
        this.createLevel('math', 2, Difficulty.EASY),
        this.createLevel('math', 3, Difficulty.MEDIUM),
        this.createLevel('math', 4, Difficulty.MEDIUM),
        this.createLevel('math', 5, Difficulty.HARD),
        this.createLevel('math', 6, Difficulty.HARD),
        this.createLevel('math', 7, Difficulty.EXPERT),
        this.createLevel('math', 8, Difficulty.EXPERT)
      ]
    };
  }

  /**
   * 创建地理学科
   */
  private createGeographySubject(): Subject {
    return {
      id: 'geography',
      name: '地理',
      description: '探索地球奥秘,了解世界文化',
      icon: 'https://example.com/icons/geography.png',
      color: '#4CAF50',
      levels: [
        this.createLevel('geography', 1, Difficulty.EASY),
        this.createLevel('geography', 2, Difficulty.EASY),
        this.createLevel('geography', 3, Difficulty.MEDIUM),
        this.createLevel('geography', 4, Difficulty.MEDIUM),
        this.createLevel('geography', 5, Difficulty.HARD),
        this.createLevel('geography', 6, Difficulty.HARD),
        this.createLevel('geography', 7, Difficulty.EXPERT),
        this.createLevel('geography', 8, Difficulty.EXPERT)
      ]
    };
  }

  /**
   * 创建生活学科
   */
  private createLifeSubject(): Subject {
    return {
      id: 'life',
      name: '生活',
      description: '学习生活常识,提升生活技能',
      icon: 'https://example.com/icons/life.png',
      color: '#FF9800',
      levels: [
        this.createLevel('life', 1, Difficulty.EASY),
        this.createLevel('life', 2, Difficulty.EASY),
        this.createLevel('life', 3, Difficulty.MEDIUM),
        this.createLevel('life', 4, Difficulty.MEDIUM),
        this.createLevel('life', 5, Difficulty.HARD),
        this.createLevel('life', 6, Difficulty.HARD),
        this.createLevel('life', 7, Difficulty.EXPERT),
        this.createLevel('life', 8, Difficulty.EXPERT)
      ]
    };
  }

  /**
   * 创建文学学科
   */
  private createLiteratureSubject(): Subject {
    return {
      id: 'literature',
      name: '文学',
      description: '品味文学经典,提升文化修养',
      icon: 'https://example.com/icons/literature.png',
      color: '#9C27B0',
      levels: [
        this.createLevel('literature', 1, Difficulty.EASY),
        this.createLevel('literature', 2, Difficulty.EASY),
        this.createLevel('literature', 3, Difficulty.MEDIUM),
        this.createLevel('literature', 4, Difficulty.MEDIUM),
        this.createLevel('literature', 5, Difficulty.HARD),
        this.createLevel('literature', 6, Difficulty.HARD),
        this.createLevel('literature', 7, Difficulty.EXPERT),
        this.createLevel('literature', 8, Difficulty.EXPERT)
      ]
    };
  }

  /**
   * 创建体育学科
   */
  private createSportsSubject(): Subject {
    return {
      id: 'sports',
      name: '体育',
      description: '了解体育知识,热爱运动生活',
      icon: 'https://example.com/icons/sports.png',
      color: '#F44336',
      levels: [
        this.createLevel('sports', 1, Difficulty.EASY),
        this.createLevel('sports', 2, Difficulty.EASY),
        this.createLevel('sports', 3, Difficulty.MEDIUM),
        this.createLevel('sports', 4, Difficulty.MEDIUM),
        this.createLevel('sports', 5, Difficulty.HARD),
        this.createLevel('sports', 6, Difficulty.HARD),
        this.createLevel('sports', 7, Difficulty.EXPERT),
        this.createLevel('sports', 8, Difficulty.EXPERT)
      ]
    };
  }

  /**
   * 创建关卡
   */
  private createLevel(subjectId: string, order: number, difficulty: Difficulty): Level {
    const difficultyLevel = this.getDifficultyLevel(difficulty);
    const subject = this.subjects.find(s => s.id === subjectId);
    
    return {
      id: `${subjectId}_level_${order}`,
      subjectId,
      subjectName: subject?.name || '',
      name: this.getLevelName(subjectId, order),
      description: this.getLevelDescription(difficulty),
      difficulty,
      difficultyLevel,
      order,
      unlockCondition: this.getUnlockCondition(subjectId, order),
      questionCount: 10,
      timeLimit: this.getTimeLimit(difficulty),
      rewards: {
        baseScore: this.getBaseScore(difficulty),
        baseExperience: this.getBaseExperience(difficulty),
        star1Multiplier: 1.0,
        star2Multiplier: 1.5,
        star3Multiplier: 2.0
      },
      icon: `https://example.com/levels/${subjectId}_${order}.png`,
      background: `https://example.com/bg/${subjectId}_${order}.jpg`
    };
  }

  /**
   * 获取难度等级数值
   */
  private getDifficultyLevel(difficulty: Difficulty): number {
    switch (difficulty) {
      case Difficulty.EASY: return 1;
      case Difficulty.MEDIUM: return 2;
      case Difficulty.HARD: return 3;
      case Difficulty.EXPERT: return 4;
      default: return 1;
    }
  }

  /**
   * 获取关卡名称
   */
  private getLevelName(subjectId: string, order: number): string {
    const names: Record<string, string[]> = {
      math: ['数学入门', '基础运算', '代数基础', '几何初步', '函数进阶', '概率统计', '微积分入门', '数学竞赛'],
      geography: ['地理入门', '地球概览', '气候环境', '人文地理', '区域分析', '地图技能', '地理综合', '地理挑战'],
      life: ['生活入门', '日常常识', '健康知识', '安全防护', '应急处理', '生活技能', '生活综合', '生活挑战'],
      literature: ['文学入门', '诗词鉴赏', '名著导读', '文学流派', '写作技巧', '文学理论', '文学综合', '文学挑战'],
      sports: ['体育入门', '球类运动', '田径运动', '水上运动', '冰雪运动', '武术搏击', '体育综合', '体育挑战']
    };
    return names[subjectId]?.[order - 1] || `${order}`;
  }

  /**
   * 获取关卡描述
   */
  private getLevelDescription(difficulty: Difficulty): string {
    const descriptions: Record<Difficulty, string> = {
      [Difficulty.EASY]: '基础关卡,适合新手入门',
      [Difficulty.MEDIUM]: '进阶关卡,需要一定知识储备',
      [Difficulty.HARD]: '困难关卡,考验你的能力',
      [Difficulty.EXPERT]: '专家关卡,挑战极限'
    };
    return descriptions[difficulty];
  }

  /**
   * 获取解锁条件
   */
  private getUnlockCondition(subjectId: string, order: number): string {
    if (order === 1) {
      return '无(初始关卡)';
    }
    return `通关"${this.getLevelName(subjectId, order - 1)}"`;
  }

  /**
   * 获取时间限制(秒)
   */
  private getTimeLimit(difficulty: Difficulty): number {
    const limits: Record<Difficulty, number> = {
      [Difficulty.EASY]: 300,    // 5分钟
      [Difficulty.MEDIUM]: 240,  // 4分钟
      [Difficulty.HARD]: 180,    // 3分钟
      [Difficulty.EXPERT]: 180   // 3分钟
    };
    return limits[difficulty];
  }

  /**
   * 获取基础积分
   */
  private getBaseScore(difficulty: Difficulty): number {
    const scores: Record<Difficulty, number> = {
      [Difficulty.EASY]: 100,
      [Difficulty.MEDIUM]: 150,
      [Difficulty.HARD]: 200,
      [Difficulty.EXPERT]: 300
    };
    return scores[difficulty];
  }

  /**
   * 获取基础经验
   */
  private getBaseExperience(difficulty: Difficulty): number {
    const exp: Record<Difficulty, number> = {
      [Difficulty.EASY]: 50,
      [Difficulty.MEDIUM]: 75,
      [Difficulty.HARD]: 100,
      [Difficulty.EXPERT]: 150
    };
    return exp[difficulty];
  }

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

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

  /**
   * 获取所有关卡
   */
  getAllLevels(): ServiceResponse<Level[]> {
    const allLevels = this.subjects.flatMap(s => s.levels);
    return createSuccessResponse<Level[]>(allLevels);
  }

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

  /**
   * 获取学科下的所有关卡
   */
  getLevelsBySubject(subjectId: string): ServiceResponse<Level[]> {
    const subject = this.subjects.find(s => s.id === subjectId);
    return createSuccessResponse<Level[]>(subject?.levels || []);
  }

  /**
   * 获取用户可用的关卡
   */
  getAvailableLevels(user: User): ServiceResponse<Level[]> {
    const available: Level[] = [];
    
    for (const subject of this.subjects) {
      for (const level of subject.levels) {
        if (this.isLevelUnlockedInternal(level.id, user)) {
          available.push(level);
        }
      }
    }
    
    return createSuccessResponse<Level[]>(available);
  }

  /**
   * 判断关卡是否已解锁(内部方法)
   */
  private isLevelUnlockedInternal(levelId: string, user: User): boolean {
    const level = this.getLevelByIdInternal(levelId);
    if (!level) {
      return false;
    }

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

    // 检查是否在已解锁列表中
    if (user.unlockedLevels.includes(levelId)) {
      return true;
    }

    // 检查前置关卡是否完成
    const subject = this.subjects.find(s => s.id === 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);
  }

  /**
   * 判断关卡是否已解锁(公开方法)
   */
  isLevelUnlocked(levelId: string, user: User): ServiceResponse<boolean> {
    const unlocked = this.isLevelUnlockedInternal(levelId, user);
    return createSuccessResponse<boolean>(unlocked);
  }

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

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

  /**
   * 获取学科进度
   */
  getSubjectProgress(subjectId: string, user: User): ServiceResponse<SubjectProgress> {
    const subject = this.subjects.find(s => s.id === subjectId);
    if (!subject) {
      return createErrorResponse<SubjectProgress>(ServiceError.NOT_FOUND, '学科不存在');
    }

    const totalLevels = subject.levels.length;
    const completedLevels = subject.levels.filter(l => user.completedLevels.includes(l.id)).length;
    const unlockedLevels = subject.levels.filter(l => this.isLevelUnlockedInternal(l.id, user)).length;
    const stars = subject.levels.reduce((sum, l) => sum + (user.levelStars.get(l.id) || 0), 0);
    const perfectLevels = subject.levels.filter(l => user.levelStars.get(l.id) === 3).length;

    return createSuccessResponse<SubjectProgress>({
      subjectId: subject.id,
      subjectName: subject.name,
      totalLevels,
      completedLevels,
      unlockedLevels,
      progressPercent: totalLevels > 0 ? (completedLevels / totalLevels) * 100 : 0,
      stars,
      perfectLevels
    });
  }

  /**
   * 获取用户关卡进度摘要
   */
  getUserLevelProgress(user: User): ServiceResponse<UserLevelProgress> {
    const allLevels = this.getAllLevels().data || [];
    const totalLevels = allLevels.length;
    const completedLevels = allLevels.filter(l => user.completedLevels.includes(l.id)).length;
    const unlockedLevels = allLevels.filter(l => this.isLevelUnlockedInternal(l.id, user)).length;
    const totalStars = allLevels.reduce((sum, l) => sum + (user.levelStars.get(l.id) || 0), 0);
    const perfectLevels = allLevels.filter(l => user.levelStars.get(l.id) === 3).length;

    const subjectProgress = this.subjects.map(s => {
      return this.getSubjectProgress(s.id, user).data!;
    });

    return createSuccessResponse<UserLevelProgress>({
      totalLevels,
      completedLevels,
      unlockedLevels,
      totalStars,
      perfectLevels,
      subjectProgress
    });
  }

  /**
   * 更新关卡完成状态
   */
  updateLevelCompletion(levelId: string, user: User, stars: number): ServiceResponse<boolean> {
    const level = this.getLevelByIdInternal(levelId);
    if (!level) {
      return createErrorResponse<boolean>(ServiceError.NOT_FOUND, '关卡不存在');
    }

    // 添加到已完成关卡
    if (!user.completedLevels.includes(levelId)) {
      user.completedLevels.push(levelId);
    }

    // 更新星级(保留最高星级)
    const currentStars = user.levelStars.get(levelId) || 0;
    if (stars > currentStars) {
      user.levelStars.set(levelId, stars);
    }

    // 解锁下一关
    this.unlockNextLevel(level, user);

    return createSuccessResponse<boolean>(true);
  }

  /**
   * 解锁下一关
   */
  private unlockNextLevel(level: Level, user: User): void {
    const subject = this.subjects.find(s => s.id === level.subjectId);
    if (!subject) {
      return;
    }

    const nextLevel = subject.levels.find(l => l.order === level.order + 1);
    if (nextLevel && !user.unlockedLevels.includes(nextLevel.id)) {
      user.unlockedLevels.push(nextLevel.id);
    }
  }

  /**
   * 计算关卡奖励
   */
  calculateLevelRewards(levelId: string, stars: number): ServiceResponse<LevelReward> {
    const level = this.getLevelByIdInternal(levelId);
    if (!level) {
      return createErrorResponse<LevelReward>(ServiceError.NOT_FOUND, '关卡不存在');
    }

    const rewards = level.rewards;
    const multiplier = stars === 3 ? rewards.star3Multiplier : 
                      stars === 2 ? rewards.star2Multiplier : rewards.star1Multiplier;

    return createSuccessResponse<LevelReward>({
      score: Math.round(rewards.baseScore * multiplier),
      experience: Math.round(rewards.baseExperience * multiplier),
      starMultiplier: multiplier
    });
  }

  /**
   * 添加新关卡
   */
  addLevel(level: Level): ServiceResponse<Level> {
    const subject = this.subjects.find(s => s.id === level.subjectId);
    if (!subject) {
      return createErrorResponse<Level>(ServiceError.NOT_FOUND, '学科不存在');
    }

    // 检查ID是否已存在
    if (subject.levels.find(l => l.id === level.id)) {
      return createErrorResponse<Level>(ServiceError.CONFLICT, '关卡ID已存在');
    }

    subject.levels.push(level);
    return createSuccessResponse<Level>(level);
  }

  /**
   * 更新关卡信息
   */
  updateLevel(levelId: string, updates: Partial<Level>): ServiceResponse<Level> {
    for (const subject of this.subjects) {
      const index = subject.levels.findIndex(l => l.id === levelId);
      if (index !== -1) {
        subject.levels[index] = { ...subject.levels[index], ...updates };
        return createSuccessResponse<Level>(subject.levels[index]);
      }
    }
    return createErrorResponse<Level>(ServiceError.NOT_FOUND, '关卡不存在');
  }

  /**
   * 删除关卡
   */
  deleteLevel(levelId: string): ServiceResponse<boolean> {
    for (const subject of this.subjects) {
      const index = subject.levels.findIndex(l => l.id === levelId);
      if (index !== -1) {
        subject.levels.splice(index, 1);
        return createSuccessResponse<boolean>(true);
      }
    }
    return createErrorResponse<boolean>(ServiceError.NOT_FOUND, '关卡不存在');
  }

  /**
   * 根据ID获取关卡(内部方法)
   */
  private getLevelByIdInternal(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;
  }
}
代码解析

1. 关卡解锁逻辑

private isLevelUnlockedInternal(levelId: string, user: User): boolean {
  const level = this.getLevelByIdInternal(levelId);
  
  // 第一个关卡默认解锁
  if (level.order === 1) {
    return true;
  }

  // 检查是否在已解锁列表中
  if (user.unlockedLevels.includes(levelId)) {
    return true;
  }

  // 检查前置关卡是否完成
  const previousLevel = subject.levels.find(l => l.order === level.order - 1);
  return user.completedLevels.includes(previousLevel.id);
}

2. 关卡奖励计算

calculateLevelRewards(levelId: string, stars: number): ServiceResponse<LevelReward> {
  const rewards = level.rewards;
  const multiplier = stars === 3 ? rewards.star3Multiplier : 
                    stars === 2 ? rewards.star2Multiplier : rewards.star1Multiplier;

  return {
    score: Math.round(rewards.baseScore * multiplier),
    experience: Math.round(rewards.baseExperience * multiplier),
    starMultiplier: multiplier
  };
}

3. 自动解锁下一关

private unlockNextLevel(level: Level, user: User): void {
  const nextLevel = subject.levels.find(l => l.order === level.order + 1);
  if (nextLevel && !user.unlockedLevels.includes(nextLevel.id)) {
    user.unlockedLevels.push(nextLevel.id);
  }
}

⚠️ 常见问题与解决方案

问题1: 关卡顺序不连续

现象:
关卡顺序跳号或重复,导致解锁逻辑错误。

错误代码:

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

正确代码:

// ✅ 正确:顺序连续且递增
levels: [
  { id: 'math_level_1', order: 1 },
  { id: 'math_level_2', order: 2 },
  { id: 'math_level_3', order: 3 }
]

规则/建议:

  • 关卡顺序必须从1开始连续递增
  • 使用一致的命名格式
  • 避免跳号和重复

问题2: 解锁条件检查错误

现象:
前置关卡完成后,下一关没有自动解锁。

错误代码:

// ❌ 错误:没有解锁下一关
updateLevelCompletion(levelId: string, user: User, stars: number): ServiceResponse<boolean> {
  user.completedLevels.push(levelId);  // 只标记完成,没有解锁下一关
}

正确代码:

// ✅ 正确:完成后解锁下一关
updateLevelCompletion(levelId: string, user: User, stars: number): ServiceResponse<boolean> {
  user.completedLevels.push(levelId);
  this.unlockNextLevel(level, user);  // 解锁下一关
}

规则/建议:

  • 完成关卡后自动解锁下一关
  • 使用 unlockNextLevel() 方法
  • 确保解锁逻辑正确执行

问题3: 星级计算错误

现象:
关卡星级计算不正确,导致奖励错误。

错误代码:

// ❌ 错误:星级判断逻辑错误
calculateLevelRewards(levelId: string, stars: number): ServiceResponse<LevelReward> {
  const multiplier = stars === 1 ? 2.0 : stars === 2 ? 1.5 : 1.0;  // 逻辑颠倒
}

正确代码:

// ✅ 正确:星级越高倍数越大
calculateLevelRewards(levelId: string, stars: number): ServiceResponse<LevelReward> {
  const multiplier = stars === 3 ? 2.0 : stars === 2 ? 1.5 : 1.0;
}

规则/建议:

  • 3星奖励最高(2.0倍)
  • 2星次之(1.5倍)
  • 1星最低(1.0倍)

问题4: 关卡数据不同步

现象:
关卡数据在内存中修改后没有持久化,导致重启后丢失。

错误代码:

// ❌ 错误:只修改内存数据
addLevel(level: Level): ServiceResponse<Level> {
  subject.levels.push(level);  // 没有保存到持久化存储
}

正确代码:

// ✅ 正确:同步持久化
addLevel(level: Level): ServiceResponse<Level> {
  subject.levels.push(level);
  await this.storageService.saveLevels(this.subjects);  // 保存到存储
}

规则/建议:

  • 修改关卡数据后立即保存
  • 使用 StorageService 持久化
  • 在初始化时恢复数据

问题5: 学科和关卡关联错误

现象:
关卡的 subjectId 与实际所属学科不匹配。

错误代码:

// ❌ 错误:subjectId 不匹配
const level: Level = {
  id: 'math_level_1',
  subjectId: 'geography',  // 错误:应该是 'math'
  subjectName: '数学'
};

正确代码:

// ✅ 正确:subjectId 匹配
const level: Level = {
  id: 'math_level_1',
  subjectId: 'math',
  subjectName: '数学'
};

规则/建议:

  • 确保 subjectId 与所属学科一致
  • 使用统一的命名规范
  • 在创建关卡时验证关联关系

📝 本章小结

核心知识点

本文详细讲解了 LevelService 关卡服务的实现,主要包括:

1. 学科和关卡管理

  • 学科数据结构设计
  • 关卡数据的增删改查

2. 解锁机制

  • 前置关卡完成解锁
  • 自动解锁下一关

3. 进度跟踪

  • 完成状态管理
  • 星级记录和更新

4. 奖励计算

  • 根据星级计算奖励
  • 积分和经验奖励

最佳实践总结

关卡命名规范

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

解锁逻辑

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

奖励计算

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

自动解锁

const nextLevel = subject.levels.find(l => l.order === level.order + 1);
if (nextLevel) user.unlockedLevels.push(nextLevel.id);

下一步预告

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

  • 🎨 讲解 AchievementService 成就服务实现详解
  • 📚 介绍成就解锁、进度跟踪和奖励发放
  • 🏷️ 探索成就数据的增删改查操作

🔗 相关链接


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

Logo

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

更多推荐