在移动应用开发中,游戏应用是一类特殊的应用类型,需要考虑游戏逻辑、状态管理、用户交互等多个方面。本文将深入分析一个功能完备的 React Native 推箱子游戏实现,探讨其架构设计、状态管理、游戏逻辑以及跨端兼容性策略。

组件化

该实现采用了清晰的组件化架构,主要包含以下部分:

  • 主应用组件 (PushBoxGame) - 负责整体布局和状态管理
  • 游戏网格渲染 - 负责渲染游戏地图、玩家、箱子和目标点
  • 游戏控制 - 提供移动、暂停、重启等控制功能
  • 关卡选择 - 允许玩家选择不同的游戏关卡

这种架构设计使得代码结构清晰,易于维护和扩展。主应用组件负责管理全局状态和业务逻辑,而各个功能部分负责具体的 UI 渲染,实现了关注点分离。

状态管理

PushBoxGame 组件使用 useState 钩子管理多个关键状态:

const [currentLevel, setCurrentLevel] = useState(0);
const [grid, setGrid] = useState<CellType[][]>(levels[0].grid);
const [playerPosition, setPlayerPosition] = useState({ row: levels[0].playerStart.row, col: levels[0].playerStart.col });
const [moves, setMoves] = useState(0);
const [gameStatus, setGameStatus] = useState<'playing' | 'won' | 'paused'>('playing');
const [levelSelectionOpen, setLevelSelectionOpen] = useState(false);

这种状态管理方式简洁高效,通过状态更新触发组件重新渲染,实现了游戏的各种功能。使用 TypeScript 联合类型确保了 gameStatus 的取值只能是预定义的几个选项之一,提高了代码的类型安全性。


关卡初始化

应用实现了关卡初始化逻辑,当用户选择不同关卡时,会重新初始化游戏状态:

const initializeLevel = (levelIndex: number) => {
  const level = levels[levelIndex];
  setGrid(level.grid.map(row => [...row]));
  setPlayerPosition({ row: level.playerStart.row, col: level.playerStart.col });
  setMoves(0);
  setGameStatus('playing');
};

useEffect(() => {
  initializeLevel(currentLevel);
}, [currentLevel]);

这种实现方式使用了 useEffect 钩子,当 currentLevel 状态变化时,自动重新初始化游戏,确保了游戏状态与当前关卡的一致性。

玩家移动与箱子推动

应用实现了核心的玩家移动和箱子推动逻辑:

const movePlayer = (direction: 'up' | 'down' | 'left' | 'right') => {
  if (gameStatus !== 'playing') return;

  const { row, col } = playerPosition;
  let newRow = row;
  let newCol = col;

  // 根据方向计算新位置
  switch (direction) {
    case 'up': newRow = row - 1; break;
    case 'down': newRow = row + 1; break;
    case 'left': newCol = col - 1; break;
    case 'right': newCol = col + 1; break;
  }

  // 检查新位置是否有效
  if (newRow < 0 || newRow >= grid.length || newCol < 0 || newCol >= grid[0].length) return;
  if (grid[newRow][newCol] === 'wall') return;

  // 检查是否可以推动箱子
  if (grid[newRow][newCol] === 'box' || grid[newRow][newCol] === 'boxOnTarget') {
    // 计算箱子的新位置
    let boxNewRow = newRow + (newRow - row);
    let boxNewCol = newCol + (newCol - col);

    // 检查箱子移动位置是否有效
    if (
      boxNewRow < 0 ||
      boxNewRow >= grid.length ||
      boxNewCol < 0 ||
      boxNewCol >= grid[0].length ||
      grid[boxNewRow][boxNewCol] === 'wall' ||
      grid[boxNewRow][boxNewCol] === 'box' ||
      grid[boxNewRow][boxNewCol] === 'boxOnTarget'
    ) {
      return; // 无法推动箱子
    }

    // 更新网格:移动箱子
    const newGrid = [...grid];
    // 清除原箱子位置
    if (grid[newRow][newCol] === 'box') {
      newGrid[newRow][newCol] = 'empty';
    } else {
      newGrid[newRow][newCol] = 'target';
    }

    // 放置箱子到新位置
    if (grid[boxNewRow][boxNewCol] === 'target') {
      newGrid[boxNewRow][boxNewCol] = 'boxOnTarget';
    } else {
      newGrid[boxNewRow][boxNewCol] = 'box';
    }

    setGrid(newGrid);
  }

  // 更新玩家位置
  setPlayerPosition({ row: newRow, col: newCol });
  setMoves(moves + 1);

  // 检查是否获胜
  checkWinCondition();
};

这种实现方式详细处理了玩家移动的各种情况,包括:

  • 边界检查 - 确保玩家不会移动出游戏网格
  • 碰撞检测 - 确保玩家不会穿过墙壁
  • 箱子推动 - 当玩家移动到箱子位置时,尝试推动箱子
  • 箱子碰撞检测 - 确保箱子不会被推到墙壁或其他箱子上
  • 状态更新 - 移动后更新游戏状态并检查是否获胜

胜负判断

应用实现了胜负判断逻辑,当所有箱子都被推到目标位置时,玩家获胜。这个逻辑虽然在代码片段中没有完整展示,但从 checkWinCondition 函数的调用可以推断出来。


类型定义

该实现使用 TypeScript 定义了两个核心数据类型:

  1. CellType - 游戏单元格类型,包括空、墙壁、箱子、目标点、玩家和箱子在目标点上
  2. Level - 游戏关卡类型,包含关卡 ID、名称、网格布局、玩家起始位置和箱子目标位置

这些类型定义使得数据结构更加清晰,提高了代码的可读性和可维护性,同时也提供了类型安全保障。

关卡数据

应用定义了三个游戏关卡,每个关卡包含不同的网格布局和挑战难度:

const levels: Level[] = [
  {
    id: 1,
    name: '简单入门',
    grid: [
      ['wall', 'wall', 'wall', 'wall', 'wall'],
      ['wall', 'empty', 'empty', 'empty', 'wall'],
      ['wall', 'empty', 'box', 'empty', 'wall'],
      ['wall', 'player', 'empty', 'target', 'wall'],
      ['wall', 'empty', 'empty', 'empty', 'wall'],
      ['wall', 'wall', 'wall', 'wall', 'wall'],
    ],
    playerStart: { row: 3, col: 1 },
    boxTargets: [{ row: 3, col: 3 }],
  },
  // 其他关卡...
];

这种数据组织方式使得关卡数据管理更加清晰,易于扩展和维护。通过修改或添加关卡数据,可以轻松创建新的游戏关卡。


游戏状态

应用实现了三种游戏状态:

  • playing - 游戏中,玩家可以移动
  • won - 游戏胜利,显示胜利信息
  • paused - 游戏暂停,玩家无法移动

这种状态管理使得用户界面根据游戏进度动态变化,提供了良好的用户体验。

交互设计

应用实现了直观的交互设计:

  • 方向控制 - 允许玩家通过按钮控制角色移动方向
  • 游戏控制 - 提供暂停、重启、撤销等控制功能
  • 关卡选择 - 允许玩家选择不同的游戏关卡
  • 移动计数 - 记录玩家的移动步数,鼓励玩家使用最少的步数完成关卡

这些交互设计元素共同构成了良好的用户体验,使得游戏操作简单直观。

  1. 使用 React Native 核心组件 - 优先使用 React Native 内置的组件,如 View、Text、TouchableOpacity、ScrollView、FlatList 等
  2. 统一的样式定义 - 使用 StyleSheet.create 定义样式,确保样式在不同平台上的一致性
  3. Base64 图标 - 使用 Base64 编码的图标,确保图标在不同平台上的一致性
  4. 简化的交互逻辑 - 使用简单直接的交互逻辑,减少平台差异带来的问题
  5. 平台无关的游戏逻辑 - 使用纯 JavaScript 实现游戏逻辑,确保在不同平台上的一致性

关卡系统

该实现采用了高度可扩展的关卡系统,通过修改或添加关卡数据,可以轻松创建新的游戏关卡:

const levels: Level[] = [
  // 现有关卡...
  {
    id: 4,
    name: '新关卡',
    grid: [
      // 新网格布局...
    ],
    playerStart: { row: 1, col: 1 },
    boxTargets: [{ row: 2, col: 2 }, { row: 3, col: 3 }],
  },
];

这种设计使得游戏能够轻松扩展到更多关卡,提供更丰富的游戏内容。

游戏控制

应用的游戏控制逻辑设计为可扩展的,通过添加新的控制按钮和相应的处理函数,可以轻松添加新的游戏功能:

  • 撤销移动 - 允许玩家撤销上一步移动
  • 提示功能 - 为玩家提供游戏提示
  • 计时器 - 记录完成关卡的时间
  • 难度选择 - 允许玩家选择不同的游戏难度

存储

应用可以集成本地存储系统,保存游戏进度和玩家成绩:

  • 关卡进度 - 记录玩家已完成的关卡
  • 最佳成绩 - 记录每个关卡的最佳移动步数和完成时间
  • 游戏设置 - 保存游戏设置,如音效、难度等

当前实现使用二维数组存储游戏网格,可以考虑使用更高效的数据结构:

// 优化前
const [grid, setGrid] = useState<CellType[][]>(levels[0].grid);

// 优化后
const [grid, setGrid] = useState<CellType[]>(flattenGrid(levels[0].grid));

// 将二维网格转换为一维数组
const flattenGrid = (grid: CellType[][]): CellType[] => {
  return grid.flat();
};

// 获取网格单元格
const getCell = (row: number, col: number, grid: CellType[], gridWidth: number): CellType => {
  return grid[row * gridWidth + col];
};

// 设置网格单元格
const setCell = (row: number, col: number, value: CellType, grid: CellType[], gridWidth: number): CellType[] => {
  const newGrid = [...grid];
  newGrid[row * gridWidth + col] = value;
  return newGrid;
};

这种实现方式使用一维数组存储网格数据,减少了数组嵌套带来的性能开销,特别是在网格较大的情况下。

2. 状态管理

当前实现使用多个 useState 钩子管理状态,可以考虑使用 useReducer 或状态管理库来管理复杂状态:

// 优化前
const [currentLevel, setCurrentLevel] = useState(0);
const [grid, setGrid] = useState<CellType[][]>(levels[0].grid);
const [playerPosition, setPlayerPosition] = useState({ row: levels[0].playerStart.row, col: levels[0].playerStart.col });
const [moves, setMoves] = useState(0);
const [gameStatus, setGameStatus] = useState<'playing' | 'won' | 'paused'>('playing');

// 优化后
type GameState = {
  currentLevel: number;
  grid: CellType[][];
  playerPosition: { row: number; col: number };
  moves: number;
  gameStatus: 'playing' | 'won' | 'paused';
};

type GameAction =
  | { type: 'INITIALIZE_LEVEL'; payload: number }
  | { type: 'MOVE_PLAYER'; payload: 'up' | 'down' | 'left' | 'right' }
  | { type: 'PAUSE_GAME' }
  | { type: 'RESUME_GAME' }
  | { type: 'RESTART_LEVEL' };

const initialState: GameState = {
  currentLevel: 0,
  grid: levels[0].grid,
  playerPosition: { row: levels[0].playerStart.row, col: levels[0].playerStart.col },
  moves: 0,
  gameStatus: 'playing'
};

const gameReducer = (state: GameState, action: GameAction): GameState => {
  switch (action.type) {
    case 'INITIALIZE_LEVEL':
      const level = levels[action.payload];
      return {
        ...state,
        currentLevel: action.payload,
        grid: level.grid.map(row => [...row]),
        playerPosition: { row: level.playerStart.row, col: level.playerStart.col },
        moves: 0,
        gameStatus: 'playing'
      };
    case 'MOVE_PLAYER':
      // 移动玩家的逻辑
      return state;
    case 'PAUSE_GAME':
      return { ...state, gameStatus: 'paused' };
    case 'RESUME_GAME':
      return { ...state, gameStatus: 'playing' };
    case 'RESTART_LEVEL':
      const currentLevel = levels[state.currentLevel];
      return {
        ...state,
        grid: currentLevel.grid.map(row => [...row]),
        playerPosition: { row: currentLevel.playerStart.row, col: currentLevel.playerStart.col },
        moves: 0,
        gameStatus: 'playing'
      };
    default:
      return state;
  }
};

const [state, dispatch] = useReducer(gameReducer, initialState);

这种实现方式使用 useReducer 管理复杂的游戏状态,使得状态更新逻辑更加清晰和可维护。

3. 动画

可以为游戏添加动画效果,提升用户体验:

import { Animated } from 'react-native';

const PushBoxGame = () => {
  const [playerAnim] = useState(new Animated.ValueXY());
  const [boxAnim] = useState(new Animated.ValueXY());
  
  const movePlayer = (direction: 'up' | 'down' | 'left' | 'right') => {
    // 计算新位置
    // ...
    
    // 动画效果
    Animated.timing(playerAnim, {
      toValue: { x: newCol * cellSize, y: newRow * cellSize },
      duration: 200,
      useNativeDriver: true
    }).start();
    
    // 移动箱子时的动画
    if (/* 箱子被移动 */) {
      Animated.timing(boxAnim, {
        toValue: { x: boxNewCol * cellSize, y: boxNewRow * cellSize },
        duration: 200,
        useNativeDriver: true
      }).start();
    }
    
    // 其他移动逻辑
    // ...
  };
  
  return (
    <View style={styles.gameContainer}>
      {/* 游戏网格 */}
      <View style={styles.gridContainer}>
        {/* 网格渲染 */}
        {/* ... */}
        
        {/* 玩家 */}
        <Animated.View 
          style={[
            styles.player,
            {
              transform: [
                { translateX: playerAnim.x },
                { translateY: playerAnim.y }
              ]
            }
          ]}
        />
        
        {/* 箱子 */}
        {/* ... */}
      </View>
      
      {/* 游戏控制 */}
      {/* ... */}
    </View>
  );
};

这种实现方式为玩家和箱子的移动添加了动画效果,使得游戏更加生动有趣。

4. 触摸控制

可以优化触摸控制,支持手势操作:

import { PanResponder } from 'react-native';

const PushBoxGame = () => {
  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onPanResponderMove: (evt, gestureState) => {
        // 根据手势方向移动玩家
        const { dx, dy } = gestureState;
        
        if (Math.abs(dx) > Math.abs(dy)) {
          // 水平移动
          if (dx > 20) {
            movePlayer('right');
          } else if (dx < -20) {
            movePlayer('left');
          }
        } else {
          // 垂直移动
          if (dy > 20) {
            movePlayer('down');
          } else if (dy < -20) {
            movePlayer('up');
          }
        }
      },
      onPanResponderRelease: () => {
        // 手势结束时的处理
      },
    })
  ).current;
  
  return (
    <View style={styles.gameContainer} {...panResponder.panHandlers}>
      {/* 游戏内容 */}
      {/* ... */}
    </View>
  );
};

这种实现方式支持通过手势操作控制玩家移动,提供了更加直观的游戏操作方式。

本文深入分析了一个功能完备的 React Native 推箱子游戏实现,从架构设计、状态管理、游戏逻辑到跨端兼容性都进行了详细探讨。该实现不仅功能完整,而且代码结构清晰,具有良好的可扩展性和可维护性。


理解这个功能完整的 React Native 推箱子游戏的技术实现细节,同时掌握其向鸿蒙(HarmonyOS)平台跨端适配的核心思路与实践方案。该游戏具备完整的关卡系统、游戏逻辑、状态管理和用户交互,是典型的休闲游戏类应用架构,涵盖了移动端游戏开发的核心场景与技术要点。

1. 游戏核心

该推箱子游戏采用状态驱动+网格渲染的现代化 React Native 架构,核心类型系统设计体现了格子类游戏的典型特征:

// 单元格类型定义 - 覆盖推箱子游戏所有元素类型
type CellType = 'empty' | 'wall' | 'box' | 'target' | 'player' | 'boxOnTarget';

// 关卡数据模型 - 包含游戏关卡的完整定义
type Level = {
  id: number;                          // 关卡ID
  name: string;                        // 关卡名称
  grid: CellType[][];                  // 游戏网格数据
  playerStart: { row: number; col: number }; // 玩家初始位置
  boxTargets: { row: number; col: number }[]; // 箱子目标位置数组
};

类型系统设计亮点

  • 语义化枚举CellType 枚举覆盖游戏所有视觉元素,便于渲染逻辑判断;
  • 结构化关卡数据Level 类型完整包含关卡所需的所有配置,支持多关卡扩展;
  • 坐标系统统一:使用行列坐标系统,符合网格类游戏的通用设计规范;
  • 扩展性良好:可轻松添加新的单元格类型(如特殊道具)或关卡属性(如步数限制)。

(1)状态管理

游戏通过 React Hooks 实现精细化的状态管理,多状态协同完成游戏全生命周期控制:

// 核心游戏状态
const [currentLevel, setCurrentLevel] = useState(0);          // 当前关卡索引
const [grid, setGrid] = useState<CellType[][]>(levels[0].grid); // 游戏网格状态
const [playerPosition, setPlayerPosition] = useState({ row: levels[0].playerStart.row, col: levels[0].playerStart.col }); // 玩家位置
const [moves, setMoves] = useState(0);                        // 移动步数
const [gameStatus, setGameStatus] = useState<'playing' | 'won' | 'paused'>('playing'); // 游戏状态
const [levelSelectionOpen, setLevelSelectionOpen] = useState(false); // 关卡选择弹窗状态
(2)关卡初始化
// 关卡切换时自动初始化
useEffect(() => {
  initializeLevel(currentLevel);
}, [currentLevel]);

// 初始化指定关卡
const initializeLevel = (levelIndex: number) => {
  const level = levels[levelIndex];
  setGrid(level.grid.map(row => [...row])); // 深拷贝网格数据,避免引用共享
  setPlayerPosition({ row: level.playerStart.row, col: level.playerStart.col });
  setMoves(0);
  setGameStatus('playing');
};
(3)核心移动

这是游戏的核心算法,实现了玩家移动和箱子推动的完整逻辑:

const movePlayer = (direction: 'up' | 'down' | 'left' | 'right') => {
  if (gameStatus !== 'playing') return; // 非游戏中状态不响应移动

  const { row, col } = playerPosition;
  let newRow = row;
  let newCol = col;

  // 计算新位置
  switch (direction) {
    case 'up': newRow = row - 1; break;
    case 'down': newRow = row + 1; break;
    case 'left': newCol = col - 1; break;
    case 'right': newCol = col + 1; break;
  }

  // 边界和墙壁检查
  if (newRow < 0 || newRow >= grid.length || newCol < 0 || newCol >= grid[0].length) return;
  if (grid[newRow][newCol] === 'wall') return;

  // 箱子推动逻辑
  if (grid[newRow][newCol] === 'box' || grid[newRow][newCol] === 'boxOnTarget') {
    // 计算箱子的新位置
    let boxNewRow = newRow + (newRow - row);
    let boxNewCol = newCol + (newCol - col);

    // 检查箱子移动的有效性
    if (
      boxNewRow < 0 || 
      boxNewRow >= grid.length || 
      boxNewCol < 0 || 
      boxNewCol >= grid[0].length || 
      grid[boxNewRow][boxNewCol] === 'wall' ||
      grid[boxNewRow][boxNewCol] === 'box' ||
      grid[boxNewRow][boxNewCol] === 'boxOnTarget'
    ) {
      return; // 箱子无法移动,玩家也不能移动
    }

    // 更新网格:移动箱子
    const newGrid = [...grid];
    // 清除原箱子位置
    if (grid[newRow][newCol] === 'box') {
      newGrid[newRow][newCol] = 'empty';
    } else {
      newGrid[newRow][newCol] = 'target'; // 箱子从目标点移开
    }

    // 放置箱子到新位置
    if (grid[boxNewRow][boxNewCol] === 'target') {
      newGrid[boxNewRow][boxNewCol] = 'boxOnTarget'; // 箱子到达目标点
    } else {
      newGrid[boxNewRow][boxNewCol] = 'box';
    }

    setGrid(newGrid);
  }

  // 更新玩家位置
  setPlayerPosition({ row: newRow, col: newCol });
  setMoves(moves + 1);

  // 检查胜利条件
  checkWinCondition();
};

移动算法核心要点

  • 状态前置检查:非游戏中状态直接返回,避免无效操作;
  • 边界安全检查:防止玩家移出网格范围;
  • 墙壁碰撞检测:玩家无法穿过墙壁;
  • 箱子推动规则
    • 计算箱子新位置(玩家移动方向的延伸);
    • 检查箱子新位置是否合法(非墙壁/非其他箱子);
    • 原子性更新网格状态(先清除原位置,再设置新位置);
  • 目标点状态管理:区分箱子在目标点/不在目标点的状态。
(4)胜利条件检测
const checkWinCondition = () => {
  for (let i = 0; i < grid.length; i++) {
    for (let j = 0; j < grid[i].length; j++) {
      // 如果还有未被箱子覆盖的目标点,或还有不在目标点的箱子,则未获胜
      if (grid[i][j] === 'target' || grid[i][j] === 'box') {
        return;
      }
    }
  }
  setGameStatus('won');
  Alert.alert('恭喜!', `您完成了第 ${currentLevel + 1} 关!\n用了 ${moves}`, [
    { text: '继续下一关', onPress: nextLevel },
    { text: '重新开始', onPress: resetLevel }
  ]);
};

(1)网格渲染
const renderCell = (cell: CellType, rowIndex: number, colIndex: number) => {
  let cellStyle = [styles.cell];
  let cellContent = '';

  // 玩家位置优先渲染
  if (rowIndex === playerPosition.row && colIndex === playerPosition.col) {
    cellStyle.push(styles.player);
    cellContent = '😊';
  } else {
    // 根据单元格类型应用样式和内容
    switch (cell) {
      case 'wall':
        cellStyle.push(styles.wall);
        cellContent = '🧱';
        break;
      case 'box':
        cellStyle.push(styles.box);
        cellContent = '📦';
        break;
      case 'target':
        cellStyle.push(styles.target);
        cellContent = '🎯';
        break;
      case 'boxOnTarget':
        cellStyle.push(styles.boxOnTarget);
        cellContent = '🎯📦';
        break;
      case 'empty':
        cellStyle.push(styles.empty);
        break;
    }
  }

  return (
    <View key={`${rowIndex}-${colIndex}`} style={cellStyle}>
      <Text style={styles.cellText}>{cellContent}</Text>
    </View>
  );
};

渲染逻辑设计亮点

  • 玩家优先渲染:玩家位置覆盖单元格原有内容,符合游戏视觉逻辑;
  • 视觉语义化:使用emoji符号直观展示游戏元素,无需额外图片资源;
  • 样式分层设计:基础单元格样式 + 类型特有样式,便于维护;
  • 唯一键标识:使用行列组合作为key,保证React渲染性能。
(2)网格容器
<View style={styles.gridContainer}>
  {grid.map((row, rowIndex) => (
    <View key={rowIndex} style={styles.gridRow}>
      {row.map((cell, colIndex) => renderCell(cell, rowIndex, colIndex))}
    </View>
  ))}
</View>

(1)方向控制
<View style={styles.controls}>
  <View style={styles.controlRow}>
    <TouchableOpacity style={styles.controlButton} onPress={() => movePlayer('up')}>
      <Text style={styles.controlButtonText}>⬆️</Text>
    </TouchableOpacity>
  </View>
  <View style={styles.controlRow}>
    <TouchableOpacity style={styles.controlButton} onPress={() => movePlayer('left')}>
      <Text style={styles.controlButtonText}>⬅️</Text>
    </TouchableOpacity>
    <TouchableOpacity style={styles.controlButton} onPress={() => movePlayer('down')}>
      <Text style={styles.controlButtonText}>⬇️</Text>
    </TouchableOpacity>
    <TouchableOpacity style={styles.controlButton} onPress={() => movePlayer('right')}>
      <Text style={styles.controlButtonText}>➡️</Text>
    </TouchableOpacity>
  </View>
</View>
(2)功能按钮与弹窗系统

实现了撤销、重置、暂停/继续、关卡选择等完整的游戏辅助功能:

{/* 功能按钮 */}
<View style={styles.functionButtons}>
  <TouchableOpacity style={styles.functionButton} onPress={undoMove}>
    <Text style={styles.functionButtonText}>🔄 撤销</Text>
  </TouchableOpacity>
  {/* 其他功能按钮 */}
</View>

{/* 关卡选择弹窗 */}
{levelSelectionOpen && (
  <View style={styles.modalOverlay}>
    <View style={styles.modalContent}>
      <Text style={styles.modalTitle}>选择关卡</Text>
      <FlatList
        data={levels}
        keyExtractor={item => item.id.toString()}
        renderItem={({ item, index }) => (
          <TouchableOpacity 
            style={styles.levelItem}
            onPress={() => selectLevel(index)}
          >
            <Text style={styles.levelItemText}>第 {item.id} 关: {item.name}</Text>
          </TouchableOpacity>
        )}
      />
      {/* 关闭按钮 */}
    </View>
  </View>
)}

将该 React Native 推箱子游戏适配到鸿蒙平台,核心是将 React 的状态管理、组件体系、渲染逻辑映射到鸿蒙 ArkTS + ArkUI 生态,以下是完整的适配方案。

1. 核心技术栈映射

React Native 核心能力 鸿蒙 ArkTS 对应实现 适配关键说明
useState 状态管理 @State/@Link/@Prop 状态声明语法替换,逻辑一致
useEffect 生命周期 aboutToAppear/onPageShow 生命周期方法替换
条件渲染(三元运算符) if/else 条件渲染 视图控制语法适配
不可变状态更新 直接状态赋值 + 强制更新 状态更新机制调整
FlatList 列表渲染 List + LazyForEach 虚拟列表渲染,性能更优
TouchableOpacity Button + stateEffect(false) 可点击组件替换
StyleSheet.create @Styles/@Extend + 内联样式 样式体系重构
Alert.alert AlertDialog 组件 弹窗交互替换
嵌套 View 渲染 嵌套容器组件(Column/Row/Stack) 布局容器语法替换

2. 鸿蒙端

// index.ets - 鸿蒙端推箱子游戏完整实现
import router from '@ohos.router';

@Entry
@Component
struct PushBoxGame {
  // 核心游戏状态(对应 React Native 的 useState)
  @State currentLevel: number = 0;
  @State grid: CellType[][] = [];
  @State playerPosition: { row: number; col: number } = { row: 0, col: 0 };
  @State moves: number = 0;
  @State gameStatus: 'playing' | 'won' | 'paused' = 'playing';
  @State levelSelectionOpen: boolean = false;

  // 类型定义(完全复用 React Native 的类型)
  type CellType = 'empty' | 'wall' | 'box' | 'target' | 'player' | 'boxOnTarget';
  
  type Level = {
    id: number;
    name: string;
    grid: CellType[][];
    playerStart: { row: number; col: number };
    boxTargets: { row: number; col: number }[];
  };

  // 游戏关卡数据(完全复用)
  private levels: Level[] = [
    {
      id: 1,
      name: '简单入门',
      grid: [
        ['wall', 'wall', 'wall', 'wall', 'wall'],
        ['wall', 'empty', 'empty', 'empty', 'wall'],
        ['wall', 'empty', 'box', 'empty', 'wall'],
        ['wall', 'player', 'empty', 'target', 'wall'],
        ['wall', 'empty', 'empty', 'empty', 'wall'],
        ['wall', 'wall', 'wall', 'wall', 'wall'],
      ],
      playerStart: { row: 3, col: 1 },
      boxTargets: [{ row: 3, col: 3 }],
    },
    {
      id: 2,
      name: '十字路口',
      grid: [
        ['wall', 'wall', 'wall', 'wall', 'wall', 'wall', 'wall'],
        ['wall', 'empty', 'empty', 'wall', 'empty', 'empty', 'wall'],
        ['wall', 'empty', 'box', 'wall', 'box', 'empty', 'wall'],
        ['wall', 'empty', 'empty', 'player', 'empty', 'empty', 'wall'],
        ['wall', 'empty', 'box', 'wall', 'target', 'empty', 'wall'],
        ['wall', 'empty', 'empty', 'wall', 'empty', 'empty', 'wall'],
        ['wall', 'wall', 'wall', 'wall', 'wall', 'wall', 'wall'],
      ],
      playerStart: { row: 3, col: 3 },
      boxTargets: [{ row: 4, col: 4 }],
    },
    {
      id: 3,
      name: '角落难题',
      grid: [
        ['wall', 'wall', 'wall', 'wall', 'wall', 'wall'],
        ['wall', 'empty', 'empty', 'empty', 'empty', 'wall'],
        ['wall', 'empty', 'box', 'wall', 'empty', 'wall'],
        ['wall', 'empty', 'empty', 'empty', 'target', 'wall'],
        ['wall', 'player', 'empty', 'empty', 'empty', 'wall'],
        ['wall', 'wall', 'wall', 'wall', 'wall', 'wall'],
      ],
      playerStart: { row: 4, col: 1 },
      boxTargets: [{ row: 3, col: 4 }],
    },
  ];

  // 通用样式封装 - 替代 React Native 的 StyleSheet
  @Styles
  cardShadow() {
    .shadow({ radius: 2, color: '#000', opacity: 0.1, offsetX: 0, offsetY: 1 });
  }

  // 生命周期方法(对应 React 的 useEffect)
  aboutToAppear() {
    this.initializeLevel(this.currentLevel);
  }

  // 初始化指定关卡(核心逻辑复用)
  private initializeLevel(levelIndex: number) {
    const level = this.levels[levelIndex];
    // 深拷贝网格数据
    this.grid = level.grid.map(row => [...row]);
    this.playerPosition = { row: level.playerStart.row, col: level.playerStart.col };
    this.moves = 0;
    this.gameStatus = 'playing';
  }

  // 移动玩家(核心算法完全复用,仅状态更新语法调整)
  private movePlayer(direction: 'up' | 'down' | 'left' | 'right') {
    if (this.gameStatus !== 'playing') return;

    const { row, col } = this.playerPosition;
    let newRow = row;
    let newCol = col;

    // 计算新位置
    switch (direction) {
      case 'up': newRow = row - 1; break;
      case 'down': newRow = row + 1; break;
      case 'left': newCol = col - 1; break;
      case 'right': newCol = col + 1; break;
    }

    // 边界和墙壁检查
    if (newRow < 0 || newRow >= this.grid.length || newCol < 0 || newCol >= this.grid[0].length) return;
    if (this.grid[newRow][newCol] === 'wall') return;

    // 箱子推动逻辑
    if (this.grid[newRow][newCol] === 'box' || this.grid[newRow][newCol] === 'boxOnTarget') {
      // 计算箱子的新位置
      let boxNewRow = newRow + (newRow - row);
      let boxNewCol = newCol + (newCol - col);

      // 检查箱子移动的有效性
      if (
        boxNewRow < 0 || 
        boxNewRow >= this.grid.length || 
        boxNewCol < 0 || 
        boxNewCol >= this.grid[0].length || 
        this.grid[boxNewRow][boxNewCol] === 'wall' ||
        this.grid[boxNewRow][boxNewCol] === 'box' ||
        this.grid[boxNewRow][boxNewCol] === 'boxOnTarget'
      ) {
        return; // 箱子无法移动
      }

      // 更新网格:移动箱子(鸿蒙直接更新状态)
      // 清除原箱子位置
      if (this.grid[newRow][newCol] === 'box') {
        this.grid[newRow][newCol] = 'empty';
      } else {
        this.grid[newRow][newCol] = 'target';
      }

      // 放置箱子到新位置
      if (this.grid[boxNewRow][boxNewCol] === 'target') {
        this.grid[boxNewRow][boxNewCol] = 'boxOnTarget';
      } else {
        this.grid[boxNewRow][boxNewCol] = 'box';
      }
    }

    // 更新玩家位置
    this.playerPosition = { row: newRow, col: newCol };
    this.moves++;

    // 检查胜利条件
    this.checkWinCondition();
  }

  // 检查胜利条件(核心逻辑复用)
  private checkWinCondition() {
    for (let i = 0; i < this.grid.length; i++) {
      for (let j = 0; j < this.grid[i].length; j++) {
        if (this.grid[i][j] === 'target' || this.grid[i][j] === 'box') {
          return;
        }
      }
    }
    this.gameStatus = 'won';
    // 鸿蒙弹窗替代 Alert.alert
    AlertDialog.show({
      title: '恭喜!',
      message: `您完成了第 ${this.currentLevel + 1} 关!\n用了 ${this.moves} 步`,
      confirm: { 
        value: '继续下一关',
        action: () => this.nextLevel()
      },
      cancel: {
        value: '重新开始',
        action: () => this.resetLevel()
      }
    });
  }

  // 下一关(逻辑复用)
  private nextLevel() {
    if (this.currentLevel < this.levels.length - 1) {
      this.currentLevel++;
      this.initializeLevel(this.currentLevel);
    } else {
      AlertDialog.show({
        title: '游戏完成',
        message: '您已经完成所有关卡!',
        confirm: { value: '确定' }
      });
    }
  }

  // 重置当前关卡(逻辑复用)
  private resetLevel() {
    this.initializeLevel(this.currentLevel);
  }

  // 撤销移动(简化版本)
  private undoMove() {
    this.resetLevel();
  }

  // 选择关卡
  private selectLevel(index: number) {
    this.currentLevel = index;
    this.initializeLevel(index);
    this.levelSelectionOpen = false;
  }

  // 渲染单元格(核心渲染逻辑复用)
  @Builder
  renderCell(cell: CellType, rowIndex: number, colIndex: number) {
    let cellContent = '';
    let cellBgColor = '#ffffff'; // 默认背景色
    
    // 玩家位置优先渲染
    if (rowIndex === this.playerPosition.row && colIndex === this.playerPosition.col) {
      cellBgColor = '#dbeafe';
      cellContent = '😊';
    } else {
      // 根据单元格类型设置样式和内容
      switch (cell) {
        case 'wall':
          cellBgColor = '#9ca3af';
          cellContent = '🧱';
          break;
        case 'box':
          cellBgColor = '#fbbf24';
          cellContent = '📦';
          break;
        case 'target':
          cellBgColor = '#c7d2fe';
          cellContent = '🎯';
          break;
        case 'boxOnTarget':
          cellBgColor = '#86efac';
          cellContent = '🎯📦';
          break;
        case 'empty':
          cellBgColor = '#ffffff';
          break;
      }
    }

    // 鸿蒙单元格渲染
    Stack() {
      Text(cellContent)
        .fontSize(24)
        .textAlign(TextAlign.Center);
    }
    .width('12.5%') // 对应 React Native 的 width / 8
    .aspectRatio(1)
    .backgroundColor(cellBgColor)
    .border({ width: 1, color: '#e2e8f0' })
    .justifyContent(FlexAlign.Center);
  }

  build() {
    Column({ space: 0 }) {
      // 头部组件
      this.Header();
      
      // 内容区域
      Scroll() {
        Column({ space: 16 }) {
          // 游戏信息卡片
          this.GameInfo();

          // 游戏网格
          this.GameGrid();

          // 控制按钮
          this.Controls();

          // 功能按钮
          this.FunctionButtons();

          // 游戏说明
          this.InfoCard();
        }
        .padding(16)
        .width('100%');
      }
      .flex(1)
      .width('100%');

      // 底部导航
      this.BottomNav();

      // 关卡选择弹窗(鸿蒙弹窗实现)
      if (this.levelSelectionOpen) {
        this.LevelSelectionModal();
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f8fafc')
    .safeArea(true);
  }

  // 头部组件
  @Builder
  Header() {
    Row({ space: 0 }) {
      Text('推箱子游戏')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1e293b');
      
      Row({ space: 10 }) {
        // 关卡按钮
        Button('关卡')
          .fontSize(14)
          .fontColor('#3b82f6')
          .fontWeight(FontWeight.Medium)
          .backgroundColor('#f1f5f9')
          .padding({ left: 12, right: 12, top: 6, bottom: 6 })
          .borderRadius(6)
          .stateEffect(true)
          .onClick(() => this.levelSelectionOpen = true);
        
        // 帮助按钮
        Button('帮助')
          .fontSize(14)
          .fontColor('#3b82f6')
          .fontWeight(FontWeight.Medium)
          .backgroundColor('#f1f5f9')
          .padding({ left: 12, right: 12, top: 6, bottom: 6 })
          .borderRadius(6)
          .stateEffect(true)
          .onClick(() => {
            AlertDialog.show({
              title: '帮助',
              message: '推箱子游戏规则:\n1. 将箱子推到目标点\n2. 不能拉动箱子\n3. 不能穿过墙壁',
              confirm: { value: '确定' }
            });
          });
      }
      .marginLeft('auto');
    }
    .padding(20)
    .backgroundColor('#ffffff')
    .borderBottom({ width: 1, color: '#e2e8f0' })
    .width('100%');
  }

  // 游戏信息卡片
  @Builder
  GameInfo() {
    Column({ space: 8 }) {
      Text(`第 ${this.currentLevel + 1} 关: ${this.levels[this.currentLevel].name}`)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1e293b')
        .textAlign(TextAlign.Center);
      
      Text(`步数: ${this.moves}`)
        .fontSize(16)
        .fontColor('#64748b')
        .textAlign(TextAlign.Center);
      
      Text(this.gameStatus === 'playing' ? '进行中' : this.gameStatus === 'won' ? '已获胜!' : '暂停')
        .fontSize(16)
        .fontColor('#10b981')
        .fontWeight(FontWeight.Medium)
        .textAlign(TextAlign.Center);
    }
    .backgroundColor('#ffffff')
    .borderRadius(12)
    .padding(16)
    .cardShadow()
    .width('100%');
  }

  // 游戏网格
  @Builder
  GameGrid() {
    Column({ space: 0 }) {
      ForEach(this.grid, (row: CellType[], rowIndex: number) => {
        Row({ space: 0 }) {
          ForEach(row, (cell: CellType, colIndex: number) => {
            this.renderCell(cell, rowIndex, colIndex);
          });
        }
      });
    }
    .backgroundColor('#ffffff')
    .borderRadius(12)
    .padding(16)
    .cardShadow()
    .width('100%')
    .justifyContent(FlexAlign.Center);
  }

  // 控制按钮
  @Builder
  Controls() {
    Column({ space: 8 }) {
      // 向上按钮
      Row({ space: 0 }) {
        Button('⬆️')
          .width(60)
          .height(60)
          .borderRadius(30)
          .backgroundColor('#3b82f6')
          .fontSize(24)
          .stateEffect(true)
          .onClick(() => this.movePlayer('up'));
      }
      .justifyContent(FlexAlign.Center);
      
      // 左/下/右按钮
      Row({ space: 8 }) {
        Button('⬅️')
          .width(60)
          .height(60)
          .borderRadius(30)
          .backgroundColor('#3b82f6')
          .fontSize(24)
          .stateEffect(true)
          .onClick(() => this.movePlayer('left'));
        
        Button('⬇️')
          .width(60)
          .height(60)
          .borderRadius(30)
          .backgroundColor('#3b82f6')
          .fontSize(24)
          .stateEffect(true)
          .onClick(() => this.movePlayer('down'));
        
        Button('➡️')
          .width(60)
          .height(60)
          .borderRadius(30)
          .backgroundColor('#3b82f6')
          .fontSize(24)
          .stateEffect(true)
          .onClick(() => this.movePlayer('right'));
      }
      .justifyContent(FlexAlign.Center);
    }
    .width('100%');
  }

  // 功能按钮
  @Builder
  FunctionButtons() {
    Row({ space: 4 }) {
      Button('🔄 撤销')
        .flex(1)
        .backgroundColor('#f1f5f9')
        .padding(12)
        .borderRadius(8)
        .fontSize(14)
        .fontColor('#3b82f6')
        .fontWeight(FontWeight.Medium)
        .stateEffect(true)
        .onClick(() => this.undoMove());
      
      Button('🔄 重置')
        .flex(1)
        .backgroundColor('#f1f5f9')
        .padding(12)
        .borderRadius(8)
        .fontSize(14)
        .fontColor('#3b82f6')
        .fontWeight(FontWeight.Medium)
        .stateEffect(true)
        .onClick(() => this.resetLevel());
      
      Button(this.gameStatus === 'playing' ? '⏸️ 暂停' : '▶️ 继续')
        .flex(1)
        .backgroundColor('#f1f5f9')
        .padding(12)
        .borderRadius(8)
        .fontSize(14)
        .fontColor('#3b82f6')
        .fontWeight(FontWeight.Medium)
        .stateEffect(true)
        .onClick(() => {
          this.gameStatus = this.gameStatus === 'playing' ? 'paused' : 'playing';
        });
    }
    .width('100%');
  }

  // 游戏说明卡片
  @Builder
  InfoCard() {
    Column({ space: 8 }) {
      Text('游戏说明')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1e293b');
      
      Text('• 🧱 墙壁:不可穿越')
        .fontSize(14)
        .fontColor('#64748b')
        .lineHeight(22);
      
      Text('• 📦 箱子:可以推动')
        .fontSize(14)
        .fontColor('#64748b')
        .lineHeight(22);
      
      Text('• 🎯 目标:将箱子推到这里')
        .fontSize(14)
        .fontColor('#64748b')
        .lineHeight(22);
      
      Text('• 😊 玩家:使用方向键移动')
        .fontSize(14)
        .fontColor('#64748b')
        .lineHeight(22);
      
      Text('• 🎯📦 已到位:箱子在目标点')
        .fontSize(14)
        .fontColor('#64748b')
        .lineHeight(22);
    }
    .backgroundColor('#ffffff')
    .borderRadius(12)
    .padding(16)
    .cardShadow()
    .width('100%');
  }

  // 底部导航
  @Builder
  BottomNav() {
    Row({ space: 0 }) {
      // 首页
      Button() {
        Column({ space: 4 }) {
          Text('🏠')
            .fontSize(20)
            .fontColor('#94a3b8');
          
          Text('首页')
            .fontSize(12)
            .fontColor('#94a3b8');
        }
      }
      .flex(1)
      .backgroundColor(Color.Transparent)
      .stateEffect(false);
      
      // 游戏
      Button() {
        Column({ space: 4 }) {
          Text('🎮')
            .fontSize(20)
            .fontColor('#94a3b8');
          
          Text('游戏')
            .fontSize(12)
            .fontColor('#94a3b8');
        }
      }
      .flex(1)
      .backgroundColor(Color.Transparent)
      .stateEffect(false);
      
      // 排行
      Button() {
        Column({ space: 4 }) {
          Text('🏆')
            .fontSize(20)
            .fontColor('#94a3b8');
          
          Text('排行')
            .fontSize(12)
            .fontColor('#94a3b8');
        }
      }
      .flex(1)
      .backgroundColor(Color.Transparent)
      .stateEffect(false);
      
      // 我的
      Button() {
        Column({ space: 4 }) {
          Text('👤')
            .fontSize(20)
            .fontColor('#94a3b8');
          
          Text('我的')
            .fontSize(12)
            .fontColor('#94a3b8');
        }
      }
      .flex(1)
      .backgroundColor(Color.Transparent)
      .stateEffect(false);
    }
    .backgroundColor('#ffffff')
    .borderTop({ width: 1, color: '#e2e8f0' })
    .paddingVertical(12)
    .width('100%');
  }

  // 关卡选择弹窗
  @Builder
  LevelSelectionModal() {
    Stack() {
      // 遮罩层
      Column()
        .width('100%')
        .height('100%')
        .backgroundColor('rgba(0,0,0,0.5)');
      
      // 弹窗内容
      Column({ space: 16 }) {
        Text('选择关卡')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1e293b')
          .textAlign(TextAlign.Center);
        
        // 关卡列表(鸿蒙 List 替代 FlatList)
        List() {
          LazyForEach(new LevelDataSource(this.levels), (item: Level) => {
            ListItem() {
              Button(`第 ${item.id} 关: ${item.name}`)
                .width('100%')
                .padding(12)
                .backgroundColor(Color.Transparent)
                .borderBottom({ width: 1, color: '#e2e8f0' })
                .fontSize(16)
                .fontColor('#1e293b')
                .stateEffect(true)
                .onClick(() => {
                  // 查找关卡索引
                  const index = this.levels.findIndex(level => level.id === item.id);
                  if (index !== -1) {
                    this.selectLevel(index);
                  }
                });
            }
          });
        }
        .height(200)
        .width('100%');
        
        // 关闭按钮
        Button('关闭')
          .width('100%')
          .backgroundColor('#3b82f6')
          .padding(12)
          .borderRadius(8)
          .fontColor('#ffffff')
          .fontWeight(FontWeight.Bold)
          .stateEffect(true)
          .onClick(() => {
            this.levelSelectionOpen = false;
          });
      }
      .backgroundColor('#ffffff')
      .borderRadius(12)
      .padding(20)
      .width('80%')
      .alignSelf(ItemAlign.Center)
      .marginTop('20%');
    }
    .width('100%')
    .height('100%')
    .position({ top: 0, left: 0 });
  }
}

// 鸿蒙 List 数据源(LazyForEach 必需)
class LevelDataSource implements IDataSource {
  private levels: Level[];
  private listener: DataChangeListener | null = null;

  constructor(levels: Level[]) {
    this.levels = levels;
  }

  totalCount(): number {
    return this.levels.length;
  }

  getData(index: number): Level {
    return this.levels[index];
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    this.listener = listener;
  }

  unregisterDataChangeListener(): void {
    this.listener = null;
  }
}

// 补充 Level 类型定义(全局)
type Level = {
  id: number;
  name: string;
  grid: CellType[][];
  playerStart: { row: number; col: number };
  boxTargets: { row: number; col: number }[];
};

type CellType = 'empty' | 'wall' | 'box' | 'target' | 'player' | 'boxOnTarget';

(1)游戏核心

推箱子的核心移动算法100% 复用,仅状态更新语法有细微调整:

// React Native(不可变更新)
const newGrid = [...grid];
newGrid[newRow][newCol] = 'empty';
setGrid(newGrid);

// 鸿蒙(直接更新)
this.grid[newRow][newCol] = 'empty'; // 直接修改数组,框架自动触发重渲染
(2)网格渲染

React Native 的嵌套 View 渲染替换为鸿蒙的 Column/Row 嵌套:

// React Native
<View style={styles.gridRow}>
  {row.map((cell, colIndex) => renderCell(cell, rowIndex, colIndex))}
</View>

// 鸿蒙
Row({ space: 0 }) {
  ForEach(row, (cell: CellType, colIndex: number) => {
    this.renderCell(cell, rowIndex, colIndex);
  });
}

该推箱子游戏的跨端适配实践验证了休闲游戏类应用从 React Native 向鸿蒙迁移的高效性,核心游戏逻辑可实现完全复用,仅需适配UI渲染和交互层,这种适配模式特别适合逻辑密集型的游戏类应用开发。


真实演示案例代码:





// app.tsx
import React, { useState, useEffect } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, FlatList } from 'react-native';

// Base64 图标库
const ICONS_BASE64 = {
  home: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  play: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  pause: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  restart: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  undo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  settings: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  help: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  level: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

const { width, height } = Dimensions.get('window');

// 推箱子游戏类型定义
type CellType = 'empty' | 'wall' | 'box' | 'target' | 'player' | 'boxOnTarget';

type Level = {
  id: number;
  name: string;
  grid: CellType[][];
  playerStart: { row: number; col: number };
  boxTargets: { row: number; col: number }[];
};

// 游戏关卡数据
const levels: Level[] = [
  {
    id: 1,
    name: '简单入门',
    grid: [
      ['wall', 'wall', 'wall', 'wall', 'wall'],
      ['wall', 'empty', 'empty', 'empty', 'wall'],
      ['wall', 'empty', 'box', 'empty', 'wall'],
      ['wall', 'player', 'empty', 'target', 'wall'],
      ['wall', 'empty', 'empty', 'empty', 'wall'],
      ['wall', 'wall', 'wall', 'wall', 'wall'],
    ],
    playerStart: { row: 3, col: 1 },
    boxTargets: [{ row: 3, col: 3 }],
  },
  {
    id: 2,
    name: '十字路口',
    grid: [
      ['wall', 'wall', 'wall', 'wall', 'wall', 'wall', 'wall'],
      ['wall', 'empty', 'empty', 'wall', 'empty', 'empty', 'wall'],
      ['wall', 'empty', 'box', 'wall', 'box', 'empty', 'wall'],
      ['wall', 'empty', 'empty', 'player', 'empty', 'empty', 'wall'],
      ['wall', 'empty', 'box', 'wall', 'target', 'empty', 'wall'],
      ['wall', 'empty', 'empty', 'wall', 'empty', 'empty', 'wall'],
      ['wall', 'wall', 'wall', 'wall', 'wall', 'wall', 'wall'],
    ],
    playerStart: { row: 3, col: 3 },
    boxTargets: [{ row: 4, col: 4 }],
  },
  {
    id: 3,
    name: '角落难题',
    grid: [
      ['wall', 'wall', 'wall', 'wall', 'wall', 'wall'],
      ['wall', 'empty', 'empty', 'empty', 'empty', 'wall'],
      ['wall', 'empty', 'box', 'wall', 'empty', 'wall'],
      ['wall', 'empty', 'empty', 'empty', 'target', 'wall'],
      ['wall', 'player', 'empty', 'empty', 'empty', 'wall'],
      ['wall', 'wall', 'wall', 'wall', 'wall', 'wall'],
    ],
    playerStart: { row: 4, col: 1 },
    boxTargets: [{ row: 3, col: 4 }],
  },
];

const PushBoxGame: React.FC = () => {
  const [currentLevel, setCurrentLevel] = useState(0);
  const [grid, setGrid] = useState<CellType[][]>(levels[0].grid);
  const [playerPosition, setPlayerPosition] = useState({ row: levels[0].playerStart.row, col: levels[0].playerStart.col });
  const [moves, setMoves] = useState(0);
  const [gameStatus, setGameStatus] = useState<'playing' | 'won' | 'paused'>('playing');
  const [levelSelectionOpen, setLevelSelectionOpen] = useState(false);

  // 初始化游戏
  useEffect(() => {
    initializeLevel(currentLevel);
  }, [currentLevel]);

  const initializeLevel = (levelIndex: number) => {
    const level = levels[levelIndex];
    setGrid(level.grid.map(row => [...row]));
    setPlayerPosition({ row: level.playerStart.row, col: level.playerStart.col });
    setMoves(0);
    setGameStatus('playing');
  };

  // 移动玩家
  const movePlayer = (direction: 'up' | 'down' | 'left' | 'right') => {
    if (gameStatus !== 'playing') return;

    const { row, col } = playerPosition;
    let newRow = row;
    let newCol = col;

    // 根据方向计算新位置
    switch (direction) {
      case 'up': newRow = row - 1; break;
      case 'down': newRow = row + 1; break;
      case 'left': newCol = col - 1; break;
      case 'right': newCol = col + 1; break;
    }

    // 检查新位置是否有效
    if (newRow < 0 || newRow >= grid.length || newCol < 0 || newCol >= grid[0].length) return;
    if (grid[newRow][newCol] === 'wall') return;

    // 检查是否可以推动箱子
    if (grid[newRow][newCol] === 'box' || grid[newRow][newCol] === 'boxOnTarget') {
      // 计算箱子的新位置
      let boxNewRow = newRow + (newRow - row);
      let boxNewCol = newCol + (newCol - col);

      // 检查箱子移动位置是否有效
      if (
        boxNewRow < 0 || 
        boxNewRow >= grid.length || 
        boxNewCol < 0 || 
        boxNewCol >= grid[0].length || 
        grid[boxNewRow][boxNewCol] === 'wall' ||
        grid[boxNewRow][boxNewCol] === 'box' ||
        grid[boxNewRow][boxNewCol] === 'boxOnTarget'
      ) {
        return; // 无法推动箱子
      }

      // 更新网格:移动箱子
      const newGrid = [...grid];
      // 清除原箱子位置
      if (grid[newRow][newCol] === 'box') {
        newGrid[newRow][newCol] = 'empty';
      } else {
        newGrid[newRow][newCol] = 'target';
      }

      // 放置箱子到新位置
      if (grid[boxNewRow][boxNewCol] === 'target') {
        newGrid[boxNewRow][boxNewCol] = 'boxOnTarget';
      } else {
        newGrid[boxNewRow][boxNewCol] = 'box';
      }

      setGrid(newGrid);
    }

    // 更新玩家位置
    setPlayerPosition({ row: newRow, col: newCol });
    setMoves(moves + 1);

    // 检查是否获胜
    checkWinCondition();
  };

  // 检查胜利条件
  const checkWinCondition = () => {
    for (let i = 0; i < grid.length; i++) {
      for (let j = 0; j < grid[i].length; j++) {
        if (grid[i][j] === 'target' || grid[i][j] === 'box') {
          return; // 如果还有目标点没有箱子,则未获胜
        }
      }
    }
    setGameStatus('won');
    Alert.alert('恭喜!', `您完成了第 ${currentLevel + 1} 关!\n用了 ${moves}`, [
      { text: '继续下一关', onPress: nextLevel },
      { text: '重新开始', onPress: resetLevel }
    ]);
  };

  // 下一关
  const nextLevel = () => {
    if (currentLevel < levels.length - 1) {
      setCurrentLevel(currentLevel + 1);
    } else {
      Alert.alert('游戏完成', '您已经完成所有关卡!');
    }
  };

  // 重置当前关卡
  const resetLevel = () => {
    initializeLevel(currentLevel);
  };

  // 上一步(简化版本,实际上只是重置)
  const undoMove = () => {
    resetLevel();
  };

  // 选择关卡
  const selectLevel = (index: number) => {
    setCurrentLevel(index);
    setLevelSelectionOpen(false);
  };

  // 渲染游戏网格
  const renderCell = (cell: CellType, rowIndex: number, colIndex: number) => {
    let cellStyle = [styles.cell];
    let cellContent = '';

    // 检查是否是玩家位置
    if (rowIndex === playerPosition.row && colIndex === playerPosition.col) {
      cellStyle.push(styles.player);
      cellContent = '😊';
    } else {
      switch (cell) {
        case 'wall':
          cellStyle.push(styles.wall);
          cellContent = '🧱';
          break;
        case 'box':
          cellStyle.push(styles.box);
          cellContent = '📦';
          break;
        case 'target':
          cellStyle.push(styles.target);
          cellContent = '🎯';
          break;
        case 'boxOnTarget':
          cellStyle.push(styles.boxOnTarget);
          cellContent = '🎯📦';
          break;
        case 'empty':
          cellStyle.push(styles.empty);
          break;
      }
    }

    return (
      <View key={`${rowIndex}-${colIndex}`} style={cellStyle}>
        <Text style={styles.cellText}>{cellContent}</Text>
      </View>
    );
  };

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>推箱子游戏</Text>
        <View style={styles.headerActions}>
          <TouchableOpacity style={styles.headerButton} onPress={() => setLevelSelectionOpen(true)}>
            <Text style={styles.headerButtonText}>关卡</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.headerButton} onPress={() => Alert.alert('帮助', '推箱子游戏规则:\n1. 将箱子推到目标点\n2. 不能拉动箱子\n3. 不能穿过墙壁')}>
            <Text style={styles.headerButtonText}>帮助</Text>
          </TouchableOpacity>
        </View>
      </View>

      <ScrollView style={styles.content}>
        {/* 游戏信息 */}
        <View style={styles.gameInfo}>
          <Text style={styles.levelText}>{currentLevel + 1}: {levels[currentLevel].name}</Text>
          <Text style={styles.movesText}>步数: {moves}</Text>
          <Text style={styles.statusText}>
            {gameStatus === 'playing' ? '进行中' : gameStatus === 'won' ? '已获胜!' : '暂停'}
          </Text>
        </View>

        {/* 游戏网格 */}
        <View style={styles.gridContainer}>
          {grid.map((row, rowIndex) => (
            <View key={rowIndex} style={styles.gridRow}>
              {row.map((cell, colIndex) => renderCell(cell, rowIndex, colIndex))}
            </View>
          ))}
        </View>

        {/* 控制按钮 */}
        <View style={styles.controls}>
          <View style={styles.controlRow}>
            <TouchableOpacity style={styles.controlButton} onPress={() => movePlayer('up')}>
              <Text style={styles.controlButtonText}>⬆️</Text>
            </TouchableOpacity>
          </View>
          <View style={styles.controlRow}>
            <TouchableOpacity style={styles.controlButton} onPress={() => movePlayer('left')}>
              <Text style={styles.controlButtonText}>⬅️</Text>
            </TouchableOpacity>
            <TouchableOpacity style={styles.controlButton} onPress={() => movePlayer('down')}>
              <Text style={styles.controlButtonText}>⬇️</Text>
            </TouchableOpacity>
            <TouchableOpacity style={styles.controlButton} onPress={() => movePlayer('right')}>
              <Text style={styles.controlButtonText}>➡️</Text>
            </TouchableOpacity>
          </View>
        </View>

        {/* 功能按钮 */}
        <View style={styles.functionButtons}>
          <TouchableOpacity style={styles.functionButton} onPress={undoMove}>
            <Text style={styles.functionButtonText}>🔄 撤销</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.functionButton} onPress={resetLevel}>
            <Text style={styles.functionButtonText}>🔄 重置</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.functionButton} onPress={() => setGameStatus(gameStatus === 'playing' ? 'paused' : 'playing')}>
            <Text style={styles.functionButtonText}>{gameStatus === 'playing' ? '⏸️ 暂停' : '▶️ 继续'}</Text>
          </TouchableOpacity>
        </View>

        {/* 关卡选择弹窗 */}
        {levelSelectionOpen && (
          <View style={styles.modalOverlay}>
            <View style={styles.modalContent}>
              <Text style={styles.modalTitle}>选择关卡</Text>
              <FlatList
                data={levels}
                keyExtractor={item => item.id.toString()}
                renderItem={({ item, index }) => (
                  <TouchableOpacity 
                    style={styles.levelItem}
                    onPress={() => selectLevel(index)}
                  >
                    <Text style={styles.levelItemText}>{item.id}: {item.name}</Text>
                  </TouchableOpacity>
                )}
              />
              <TouchableOpacity 
                style={styles.closeModalButton}
                onPress={() => setLevelSelectionOpen(false)}
              >
                <Text style={styles.closeModalButtonText}>关闭</Text>
              </TouchableOpacity>
            </View>
          </View>
        )}

        {/* 游戏说明 */}
        <View style={styles.infoCard}>
          <Text style={styles.infoTitle}>游戏说明</Text>
          <Text style={styles.infoText}>• 🧱 墙壁:不可穿越</Text>
          <Text style={styles.infoText}>• 📦 箱子:可以推动</Text>
          <Text style={styles.infoText}>• 🎯 目标:将箱子推到这里</Text>
          <Text style={styles.infoText}>• 😊 玩家:使用方向键移动</Text>
          <Text style={styles.infoText}>• 🎯📦 已到位:箱子在目标点</Text>
        </View>
      </ScrollView>

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>🏠</Text>
          <Text style={styles.navText}>首页</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>🎮</Text>
          <Text style={styles.navText}>游戏</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>🏆</Text>
          <Text style={styles.navText}>排行</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>👤</Text>
          <Text style={styles.navText}>我的</Text>
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8fafc',
  },
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    padding: 20,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  headerActions: {
    flexDirection: 'row',
  },
  headerButton: {
    marginLeft: 10,
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 6,
  },
  headerButtonText: {
    fontSize: 14,
    color: '#3b82f6',
    fontWeight: '500',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  gameInfo: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  levelText: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 8,
    textAlign: 'center',
  },
  movesText: {
    fontSize: 16,
    color: '#64748b',
    textAlign: 'center',
    marginBottom: 8,
  },
  statusText: {
    fontSize: 16,
    color: '#10b981',
    fontWeight: '500',
    textAlign: 'center',
  },
  gridContainer: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  gridRow: {
    flexDirection: 'row',
  },
  cell: {
    width: width / 8,
    height: width / 8,
    borderWidth: 1,
    borderColor: '#e2e8f0',
    justifyContent: 'center',
    alignItems: 'center',
  },
  cellText: {
    fontSize: 24,
  },
  wall: {
    backgroundColor: '#9ca3af',
  },
  box: {
    backgroundColor: '#fbbf24',
  },
  target: {
    backgroundColor: '#c7d2fe',
  },
  boxOnTarget: {
    backgroundColor: '#86efac',
  },
  player: {
    backgroundColor: '#dbeafe',
  },
  empty: {
    backgroundColor: '#ffffff',
  },
  controls: {
    marginBottom: 16,
  },
  controlRow: {
    flexDirection: 'row',
    justifyContent: 'center',
    marginBottom: 8,
  },
  controlButton: {
    backgroundColor: '#3b82f6',
    width: 60,
    height: 60,
    borderRadius: 30,
    justifyContent: 'center',
    alignItems: 'center',
    marginHorizontal: 8,
  },
  controlButtonText: {
    fontSize: 24,
  },
  functionButtons: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 16,
  },
  functionButton: {
    flex: 1,
    backgroundColor: '#f1f5f9',
    padding: 12,
    borderRadius: 8,
    alignItems: 'center',
    marginHorizontal: 4,
  },
  functionButtonText: {
    fontSize: 14,
    color: '#3b82f6',
    fontWeight: '500',
  },
  modalOverlay: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: 'rgba(0,0,0,0.5)',
    justifyContent: 'center',
    alignItems: 'center',
    zIndex: 10,
  },
  modalContent: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 20,
    width: width * 0.8,
  },
  modalTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 16,
    textAlign: 'center',
  },
  levelItem: {
    padding: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  levelItemText: {
    fontSize: 16,
    color: '#1e293b',
  },
  closeModalButton: {
    marginTop: 16,
    backgroundColor: '#3b82f6',
    padding: 12,
    borderRadius: 8,
    alignItems: 'center',
  },
  closeModalButtonText: {
    color: '#ffffff',
    fontWeight: 'bold',
  },
  infoCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  infoTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 12,
  },
  infoText: {
    fontSize: 14,
    color: '#64748b',
    lineHeight: 22,
    marginBottom: 8,
  },
  bottomNav: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#ffffff',
    borderTopWidth: 1,
    borderTopColor: '#e2e8f0',
    paddingVertical: 12,
  },
  navItem: {
    alignItems: 'center',
    flex: 1,
  },
  navIcon: {
    fontSize: 20,
    color: '#94a3b8',
    marginBottom: 4,
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
});

export default PushBoxGame;

请添加图片描述


打包

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

在这里插入图片描述

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

在这里插入图片描述

最后运行效果图如下显示:

请添加图片描述
本文深入分析了一个React Native推箱子游戏的实现方案,重点介绍了其组件化架构设计、状态管理和游戏逻辑。游戏采用清晰的组件划分,包括主应用组件、游戏网格渲染、控制功能和关卡选择。通过useState管理游戏状态,实现了玩家移动、箱子推动和胜负判断等核心逻辑。文章还探讨了关卡系统设计、跨平台兼容策略以及交互体验优化,展示了如何利用React Native和TypeScript构建功能完备的移动游戏应用。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐