《用MATLAB玩转游戏开发:从零开始打造你的数字乐园》第二阶段:进阶篇(动画与算法)-推箱子游戏的MATLAB趣味实现 🎮
文章目录
- [《用MATLAB玩转游戏开发:从零开始打造你的数字乐园》第二阶段:进阶篇(动画与算法)-推箱子游戏的MATLAB趣味实现 🎮](#《用MATLAB玩转游戏开发:从零开始打造你的数字乐园》第二阶段:进阶篇(动画与算法)-推箱子游戏的MATLAB趣味实现 🎮)
-
- [一、游戏介绍与设计思路 🧠](#一、游戏介绍与设计思路 🧠)
- [二、实现原理与技术细节 ⚙️](#二、实现原理与技术细节 ⚙️)
-
- [1. 游戏数据结构](#1. 游戏数据结构)
- [2. 图形渲染](#2. 图形渲染)
- [3. 游戏逻辑](#3. 游戏逻辑)
- [三、完整脑图与流程图 🗺️](#三、完整脑图与流程图 🗺️)
- [四、分步实现教程 🛠️](#四、分步实现教程 🛠️)
-
- [1. 初始化游戏环境](#1. 初始化游戏环境)
- [2. 关卡设计](#2. 关卡设计)
- [3. 游戏主循环](#3. 游戏主循环)
- [4. 渲染游戏地图](#4. 渲染游戏地图)
- [5. 键盘控制处理](#5. 键盘控制处理)
- [6. 胜利条件检查](#6. 胜利条件检查)
- [7. 保存游戏GIF](#7. 保存游戏GIF)
- [五、完整可运行代码 🏗️](#五、完整可运行代码 🏗️)
- [六、游戏运行与扩展建议 🚀](#六、游戏运行与扩展建议 🚀)
- [七、总结与收获 📚](#七、总结与收获 📚)

大家好!今天我要带大家用MATLAB实现一个经典游戏------推箱子!通过这个项目,你不仅能学习MATLAB的图形界面编程,还能掌握游戏开发的基本原理。让我们开始这段有趣的编程之旅吧!🚀
一、游戏介绍与设计思路 🧠
推箱子(Sokoban)是一款经典的益智游戏,玩家需要将箱子推到指定位置。我们的MATLAB版本将包含以下特点:
- 🎨 图形化界面显示
- 🎵 背景音乐播放
- 📸 游戏截图保存为GIF
- 🕹️ 键盘控制角色移动
- 🏆 关卡设计功能
游戏元素设计
- 角色:玩家控制的推箱子小人
- 墙壁:不可穿越的障碍物
- 箱子:可以被推动的物体
- 目标点:箱子需要被推到的位置
- 地面:可通行的区域
二、实现原理与技术细节 ⚙️
1. 游戏数据结构
我们将使用矩阵来表示游戏地图:
- 0: 空地
- 1: 墙壁
- 2: 箱子
- 3: 目标点
- 4: 角色
- 5: 箱子在目标点上
- 6: 角色在目标点上
2. 图形渲染
使用MATLAB的image
和imshow
函数来显示游戏地图,通过颜色映射来区分不同元素。
3. 游戏逻辑
- 移动检测:检查目标位置是否可移动
- 推动检测:检查箱子能否被推动
- 胜利条件:所有箱子都位于目标点上
三、完整脑图与流程图 🗺️
脑图 (游戏架构)

流程图
方向键 是 否 退出键 开始 初始化游戏 显示游戏界面 等待玩家输入 处理移动 更新游戏状态 渲染画面 是否胜利? 显示胜利信息 进入下一关 结束游戏
四、分步实现教程 🛠️
1. 初始化游戏环境
首先,我们需要设置游戏窗口和基本参数:
matlab
function sokoban()
% 清除工作区
clc; clear; close all;
% 创建图形窗口
fig = figure('Name', 'MATLAB推箱子游戏', ...
'NumberTitle', 'off', ...
'MenuBar', 'none', ...
'Color', [0.9 0.9 0.9], ...
'Position', [100, 100, 800, 600], ...
'KeyPressFcn', @keyPressHandler);
% 设置键盘回调函数
set(fig, 'WindowKeyPressFcn', @keyPressHandler);
% 初始化游戏参数
game.currentLevel = 1;
game.moves = 0;
game.isPlaying = true;
game.musicOn = true;
game.gifFrames = {};
% 加载背景音乐
[y, Fs] = audioread('background.mp3'); % 请准备一个MP3文件
game.audioPlayer = audioplayer(y, Fs);
% 定义关卡
game.levels = initializeLevels();
% 开始游戏
startGame(game, fig);
end
2. 关卡设计
我们需要设计几个关卡来增加游戏趣味性:
matlab
function levels = initializeLevels()
% 关卡1
levels{1}.map = [
1 1 1 1 1 1 1 1;
1 0 0 0 0 0 0 1;
1 0 0 2 3 0 0 1;
1 0 4 0 0 0 0 1;
1 0 0 0 0 0 0 1;
1 1 1 1 1 1 1 1;
];
% 关卡2
levels{2}.map = [
1 1 1 1 1 1 1 1;
1 0 0 0 0 0 0 1;
1 0 2 0 3 0 0 1;
1 0 4 0 2 0 0 1;
1 0 0 0 3 0 0 1;
1 1 1 1 1 1 1 1;
];
% 关卡3 - 更具挑战性
levels{3}.map = [
1 1 1 1 1 1 1 1 1;
1 0 0 0 0 0 0 0 1;
1 0 2 2 3 3 0 0 1;
1 0 2 0 3 0 0 0 1;
1 0 4 0 0 0 0 0 1;
1 0 0 0 0 0 0 0 1;
1 1 1 1 1 1 1 1 1;
];
end
3. 游戏主循环
这是游戏的核心部分,负责处理输入和更新游戏状态:
matlab
function startGame(game, fig)
% 播放背景音乐
if game.musicOn
play(game.audioPlayer);
end
% 加载当前关卡
currentMap = game.levels{game.currentLevel}.map;
% 创建游戏界面
ax = axes('Parent', fig, ...
'Position', [0.1 0.1 0.8 0.8], ...
'XTick', [], 'YTick', [], ...
'XLim', [0 size(currentMap, 2)+1], ...
'YLim', [0 size(currentMap, 1)+1], ...
'YDir', 'reverse');
axis equal;
hold on;
% 游戏主循环
while game.isPlaying
% 渲染游戏地图
renderMap(currentMap, ax);
% 捕获当前帧用于GIF
frame = getframe(fig);
game.gifFrames{end+1} = frame2im(frame);
% 检查胜利条件
if checkWinCondition(currentMap)
msg = sprintf('恭喜!你通过了第%d关!', game.currentLevel);
uiwait(msgbox(msg, '胜利', 'modal'));
% 进入下一关或结束游戏
if game.currentLevel < length(game.levels)
game.currentLevel = game.currentLevel + 1;
currentMap = game.levels{game.currentLevel}.map;
else
game.isPlaying = false;
msgbox('太棒了!你完成了所有关卡!', '游戏结束');
end
end
% 暂停一下避免CPU占用过高
pause(0.05);
end
% 保存GIF动画
saveGameGIF(game.gifFrames);
% 停止背景音乐
stop(game.audioPlayer);
end
4. 渲染游戏地图
这部分代码负责将数字矩阵转换为可视化的游戏界面:
matlab
function renderMap(map, ax)
% 定义颜色映射
colors = [
0.7 0.7 0.7; % 空地 - 灰色
0.3 0.3 0.3; % 墙壁 - 深灰色
0.8 0.5 0.2; % 箱子 - 橙色
0.2 0.8 0.2; % 目标点 - 绿色
0.2 0.2 0.8; % 角色 - 蓝色
0.8 0.8 0.2; % 箱子在目标点上 - 黄色
0.2 0.8 0.8; % 角色在目标点上 - 青色
];
% 创建RGB图像
[rows, cols] = size(map);
rgbImage = zeros(rows, cols, 3);
for r = 1:rows
for c = 1:cols
rgbImage(r, c, :) = colors(map(r, c)+1, :);
end
end
% 显示图像
imshow(rgbImage, 'Parent', ax);
% 添加网格线
hold on;
for x = 0.5:cols+0.5
plot([x x], [0.5 rows+0.5], 'k', 'LineWidth', 1);
end
for y = 0.5:rows+0.5
plot([0.5 cols+0.5], [y y], 'k', 'LineWidth', 1);
end
hold off;
% 添加标题和移动计数
title(ax, sprintf('推箱子游戏 - 第%d关', currentLevel), 'FontSize', 14);
xlabel(ax, sprintf('移动次数: %d', game.moves), 'FontSize', 12);
end
5. 键盘控制处理
处理玩家的键盘输入:
matlab
function keyPressHandler(src, event)
global game currentMap;
% 获取角色位置
[playerRow, playerCol] = find(currentMap == 4 | currentMap == 6);
% 根据按键处理移动
switch event.Key
case 'uparrow'
movePlayer(-1, 0);
case 'downarrow'
movePlayer(1, 0);
case 'leftarrow'
movePlayer(0, -1);
case 'rightarrow'
movePlayer(0, 1);
case 'r' % 重置关卡
currentMap = game.levels{game.currentLevel}.map;
case 'm' % 切换音乐
game.musicOn = ~game.musicOn;
if game.musicOn
play(game.audioPlayer);
else
stop(game.audioPlayer);
end
case 'escape' % 退出游戏
game.isPlaying = false;
end
% 嵌套函数处理实际移动逻辑
function movePlayer(dRow, dCol)
newRow = playerRow + dRow;
newCol = playerCol + dCol;
% 检查边界
if newRow < 1 || newRow > size(currentMap, 1) || ...
newCol < 1 || newCol > size(currentMap, 2)
return;
end
% 检查目标位置
target = currentMap(newRow, newCol);
% 空地或目标点
if target == 0 || target == 3
% 移动角色
if currentMap(playerRow, playerCol) == 4 % 从空地移动
currentMap(playerRow, playerCol) = 0;
else % 从目标点移动
currentMap(playerRow, playerCol) = 3;
end
if target == 0 % 移动到空地
currentMap(newRow, newCol) = 4;
else % 移动到目标点
currentMap(newRow, newCol) = 6;
end
game.moves = game.moves + 1;
% 箱子或箱子在目标点上
elseif target == 2 || target == 5
% 检查箱子后面是否有空间
boxNewRow = newRow + dRow;
boxNewCol = newCol + dCol;
if boxNewRow < 1 || boxNewRow > size(currentMap, 1) || ...
boxNewCol < 1 || boxNewCol > size(currentMap, 2)
return;
end
boxTarget = currentMap(boxNewRow, boxNewCol);
% 箱子后面是空地或目标点
if boxTarget == 0 || boxTarget == 3
% 移动箱子
if target == 2 % 普通箱子
if boxTarget == 0 % 推到空地
currentMap(boxNewRow, boxNewCol) = 2;
else % 推到目标点
currentMap(boxNewRow, boxNewCol) = 5;
end
else % 箱子在目标点上
if boxTarget == 0 % 推到空地
currentMap(boxNewRow, boxNewCol) = 2;
else % 推到目标点
currentMap(boxNewRow, boxNewCol) = 5;
end
end
% 移动角色
if currentMap(playerRow, playerCol) == 4 % 从空地移动
currentMap(playerRow, playerCol) = 0;
else % 从目标点移动
currentMap(playerRow, playerCol) = 3;
end
if target == 2 % 推普通箱子
currentMap(newRow, newCol) = 4;
else % 推在目标点上的箱子
currentMap(newRow, newCol) = 6;
end
game.moves = game.moves + 1;
end
end
end
end
6. 胜利条件检查
检查是否所有箱子都位于目标点上:
matlab
function win = checkWinCondition(map)
% 检查是否还有普通箱子(2)存在
win = isempty(find(map == 2, 1));
end
7. 保存游戏GIF
将游戏过程保存为GIF动画:
matlab
function saveGameGIF(frames)
filename = 'sokoban_gameplay.gif';
for idx = 1:length(frames)
[A, map] = rgb2ind(frames{idx}, 256);
if idx == 1
imwrite(A, map, filename, 'gif', 'LoopCount', Inf, 'DelayTime', 0.1);
else
imwrite(A, map, filename, 'gif', 'WriteMode', 'append', 'DelayTime', 0.1);
end
end
fprintf('游戏动画已保存为 %s\n', filename);
end
五、完整可运行代码 🏗️
将所有部分组合起来,以下是完整的推箱子游戏MATLAB实现:
matlab
function sokoban()
% 清除工作区
clc; clear; close all;
% 创建全局变量
global game currentMap;
% 创建图形窗口
fig = figure('Name', 'MATLAB推箱子游戏', ...
'NumberTitle', 'off', ...
'MenuBar', 'none', ...
'Color', [0.9 0.9 0.9], ...
'Position', [100, 100, 800, 600]);
% 初始化游戏参数
game.currentLevel = 1;
game.moves = 0;
game.isPlaying = true;
game.musicOn = true;
game.gifFrames = {};
% 尝试加载背景音乐
try
[y, Fs] = audioread('background.mp3');
game.audioPlayer = audioplayer(y, Fs);
catch
warning('无法加载背景音乐文件,游戏将继续但没有声音');
game.musicOn = false;
end
% 定义关卡
game.levels = initializeLevels();
% 设置键盘回调函数
set(fig, 'WindowKeyPressFcn', @keyPressHandler);
% 开始游戏
startGame(game, fig);
end
function levels = initializeLevels()
% 关卡1
levels{1}.map = [
1 1 1 1 1 1 1 1;
1 0 0 0 0 0 0 1;
1 0 0 2 3 0 0 1;
1 0 4 0 0 0 0 1;
1 0 0 0 0 0 0 1;
1 1 1 1 1 1 1 1;
];
% 关卡2
levels{2}.map = [
1 1 1 1 1 1 1 1;
1 0 0 0 0 0 0 1;
1 0 2 0 3 0 0 1;
1 0 4 0 2 0 0 1;
1 0 0 0 3 0 0 1;
1 1 1 1 1 1 1 1;
];
% 关卡3
levels{3}.map = [
1 1 1 1 1 1 1 1 1;
1 0 0 0 0 0 0 0 1;
1 0 2 2 3 3 0 0 1;
1 0 2 0 3 0 0 0 1;
1 0 4 0 0 0 0 0 1;
1 0 0 0 0 0 0 0 1;
1 1 1 1 1 1 1 1 1;
];
end
function startGame(game, fig)
global currentMap;
% 播放背景音乐
if game.musicOn && isfield(game, 'audioPlayer')
play(game.audioPlayer);
end
% 加载当前关卡
currentMap = game.levels{game.currentLevel}.map;
% 创建游戏界面
ax = axes('Parent', fig, ...
'Position', [0.1 0.1 0.8 0.8], ...
'XTick', [], 'YTick', [], ...
'XLim', [0 size(currentMap, 2)+1], ...
'YLim', [0 size(currentMap, 1)+1], ...
'YDir', 'reverse');
axis equal;
hold on;
% 游戏主循环
while game.isPlaying
% 渲染游戏地图
renderMap(currentMap, ax);
% 捕获当前帧用于GIF
frame = getframe(fig);
game.gifFrames{end+1} = frame2im(frame);
% 检查胜利条件
if checkWinCondition(currentMap)
msg = sprintf('恭喜!你通过了第%d关!', game.currentLevel);
uiwait(msgbox(msg, '胜利', 'modal'));
% 进入下一关或结束游戏
if game.currentLevel < length(game.levels)
game.currentLevel = game.currentLevel + 1;
currentMap = game.levels{game.currentLevel}.map;
game.moves = 0;
else
game.isPlaying = false;
msgbox('太棒了!你完成了所有关卡!', '游戏结束');
end
end
% 暂停一下避免CPU占用过高
pause(0.05);
end
% 保存GIF动画
if ~isempty(game.gifFrames)
saveGameGIF(game.gifFrames);
end
% 停止背景音乐
if game.musicOn && isfield(game, 'audioPlayer')
stop(game.audioPlayer);
end
end
function renderMap(map, ax)
global game;
% 定义颜色映射
colors = [
0.7 0.7 0.7; % 空地 - 灰色
0.3 0.3 0.3; % 墙壁 - 深灰色
0.8 0.5 0.2; % 箱子 - 橙色
0.2 0.8 0.2; % 目标点 - 绿色
0.2 0.2 0.8; % 角色 - 蓝色
0.8 0.8 0.2; % 箱子在目标点上 - 黄色
0.2 0.8 0.8; % 角色在目标点上 - 青色
];
% 创建RGB图像
[rows, cols] = size(map);
rgbImage = zeros(rows, cols, 3);
for r = 1:rows
for c = 1:cols
rgbImage(r, c, :) = colors(map(r, c)+1, :);
end
end
% 显示图像
imshow(rgbImage, 'Parent', ax);
% 添加网格线
hold on;
for x = 0.5:cols+0.5
plot([x x], [0.5 rows+0.5], 'k', 'LineWidth', 1);
end
for y = 0.5:rows+0.5
plot([0.5 cols+0.5], [y y], 'k', 'LineWidth', 1);
end
hold off;
% 添加标题和移动计数
title(ax, sprintf('推箱子游戏 - 第%d关', game.currentLevel), 'FontSize', 14);
xlabel(ax, sprintf('移动次数: %d', game.moves), 'FontSize', 12);
end
function keyPressHandler(src, event)
global game currentMap;
% 获取角色位置
[playerRow, playerCol] = find(currentMap == 4 | currentMap == 6);
if isempty(playerRow) || isempty(playerCol)
return;
end
% 根据按键处理移动
switch event.Key
case 'uparrow'
movePlayer(-1, 0);
case 'downarrow'
movePlayer(1, 0);
case 'leftarrow'
movePlayer(0, -1);
case 'rightarrow'
movePlayer(0, 1);
case 'r' % 重置关卡
currentMap = game.levels{game.currentLevel}.map;
game.moves = 0;
case 'm' % 切换音乐
if isfield(game, 'audioPlayer')
game.musicOn = ~game.musicOn;
if game.musicOn
play(game.audioPlayer);
else
stop(game.audioPlayer);
end
end
case 'escape' % 退出游戏
game.isPlaying = false;
end
% 嵌套函数处理实际移动逻辑
function movePlayer(dRow, dCol)
newRow = playerRow + dRow;
newCol = playerCol + dCol;
% 检查边界
if newRow < 1 || newRow > size(currentMap, 1) || ...
newCol < 1 || newCol > size(currentMap, 2)
return;
end
% 检查目标位置
target = currentMap(newRow, newCol);
% 空地或目标点
if target == 0 || target == 3
% 移动角色
if currentMap(playerRow, playerCol) == 4 % 从空地移动
currentMap(playerRow, playerCol) = 0;
else % 从目标点移动
currentMap(playerRow, playerCol) = 3;
end
if target == 0 % 移动到空地
currentMap(newRow, newCol) = 4;
else % 移动到目标点
currentMap(newRow, newCol) = 6;
end
game.moves = game.moves + 1;
% 箱子或箱子在目标点上
elseif target == 2 || target == 5
% 检查箱子后面是否有空间
boxNewRow = newRow + dRow;
boxNewCol = newCol + dCol;
if boxNewRow < 1 || boxNewRow > size(currentMap, 1) || ...
boxNewCol < 1 || boxNewCol > size(currentMap, 2)
return;
end
boxTarget = currentMap(boxNewRow, boxNewCol);
% 箱子后面是空地或目标点
if boxTarget == 0 || boxTarget == 3
% 移动箱子
if target == 2 % 普通箱子
if boxTarget == 0 % 推到空地
currentMap(boxNewRow, boxNewCol) = 2;
else % 推到目标点
currentMap(boxNewRow, boxNewCol) = 5;
end
else % 箱子在目标点上
if boxTarget == 0 % 推到空地
currentMap(boxNewRow, boxNewCol) = 2;
else % 推到目标点
currentMap(boxNewRow, boxNewCol) = 5;
end
end
% 移动角色
if currentMap(playerRow, playerCol) == 4 % 从空地移动
currentMap(playerRow, playerCol) = 0;
else % 从目标点移动
currentMap(playerRow, playerCol) = 3;
end
if target == 2 % 推普通箱子
currentMap(newRow, newCol) = 4;
else % 推在目标点上的箱子
currentMap(newRow, newCol) = 6;
end
game.moves = game.moves + 1;
end
end
end
end
function win = checkWinCondition(map)
% 检查是否还有普通箱子(2)存在
win = isempty(find(map == 2, 1));
end
function saveGameGIF(frames)
filename = 'sokoban_gameplay.gif';
for idx = 1:length(frames)
[A, map] = rgb2ind(frames{idx}, 256);
if idx == 1
imwrite(A, map, filename, 'gif', 'LoopCount', Inf, 'DelayTime', 0.1);
else
imwrite(A, map, filename, 'gif', 'WriteMode', 'append', 'DelayTime', 0.1);
end
end
fprintf('游戏动画已保存为 %s\n', filename);
end
六、游戏运行与扩展建议 🚀
如何运行游戏
- 将上述完整代码保存为
sokoban.m
文件 - 准备一个名为
background.mp3
的背景音乐文件放在同一目录下 - 在MATLAB命令窗口输入
sokoban
运行游戏
游戏操作说明
- 方向键:控制角色移动
- R键:重置当前关卡
- M键:切换背景音乐开关
- ESC键:退出游戏
以下是我玩了一局的效果,献丑了,哈哈~
- 红色块:箱子
- 蓝色块:人
- 绿色块 :目标位置
扩展建议
想要进一步提升这个游戏?可以考虑:
- 添加更多关卡:设计更具挑战性的地图
- 增加音效:为推箱子、胜利等动作添加音效
- 添加计时功能:记录完成关卡所用的时间
- 实现撤销功能:允许玩家撤销上一步操作
- 添加关卡编辑器:让玩家可以自己设计关卡
七、总结与收获 📚
通过这个项目,我们学习了:
- MATLAB图形界面编程
- 游戏状态管理和更新
- 键盘事件处理
- GIF动画生成
- 音频播放控制
希望这个推箱子游戏项目能带给你乐趣和知识!尝试修改代码,添加你自己的创意吧!🎉
Happy coding! 👨💻👩💻