React Native鸿蒙跨平台实现推箱子游戏,完成玩家移动与箱子推动,当所有箱子都被推到目标位置时,玩家获胜
本文深入分析了一个React Native推箱子游戏的实现方案,重点介绍了其组件化架构设计、状态管理和游戏逻辑。游戏采用清晰的组件划分,包括主应用组件、游戏网格渲染、控制功能和关卡选择。通过useState管理游戏状态,实现了玩家移动、箱子推动和胜负判断等核心逻辑。文章还探讨了关卡系统设计、跨平台兼容策略以及交互体验优化,展示了如何利用React Native和TypeScript构建功能完备的移
在移动应用开发中,游戏应用是一类特殊的应用类型,需要考虑游戏逻辑、状态管理、用户交互等多个方面。本文将深入分析一个功能完备的 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 定义了两个核心数据类型:
- CellType - 游戏单元格类型,包括空、墙壁、箱子、目标点、玩家和箱子在目标点上
- 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 - 游戏暂停,玩家无法移动
这种状态管理使得用户界面根据游戏进度动态变化,提供了良好的用户体验。
交互设计
应用实现了直观的交互设计:
- 方向控制 - 允许玩家通过按钮控制角色移动方向
- 游戏控制 - 提供暂停、重启、撤销等控制功能
- 关卡选择 - 允许玩家选择不同的游戏关卡
- 移动计数 - 记录玩家的移动步数,鼓励玩家使用最少的步数完成关卡
这些交互设计元素共同构成了良好的用户体验,使得游戏操作简单直观。
- 使用 React Native 核心组件 - 优先使用 React Native 内置的组件,如 View、Text、TouchableOpacity、ScrollView、FlatList 等
- 统一的样式定义 - 使用 StyleSheet.create 定义样式,确保样式在不同平台上的一致性
- Base64 图标 - 使用 Base64 编码的图标,确保图标在不同平台上的一致性
- 简化的交互逻辑 - 使用简单直接的交互逻辑,减少平台差异带来的问题
- 平台无关的游戏逻辑 - 使用纯 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构建功能完备的移动游戏应用。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐



所有评论(0)