《用MATLAB玩转游戏开发:从零开始打造你的数字乐园》基础篇(2D图形交互)-《打砖块:向量反射与实时物理模拟》MATLAB教程 🎮
文章目录
- [《用MATLAB玩转游戏开发:从零开始打造你的数字乐园》基础篇(2D图形交互)-《打砖块:向量反射与实时物理模拟》MATLAB教程 🎮](#《用MATLAB玩转游戏开发:从零开始打造你的数字乐园》基础篇(2D图形交互)-《打砖块:向量反射与实时物理模拟》MATLAB教程 🎮)
-
- 引言:从游戏到物理模拟
- [1. 设计思路与游戏概述 🧠](#1. 设计思路与游戏概述 🧠)
-
- [1.1 物理模型关键点 ⚛️](#1.1 物理模型关键点 ⚛️)
- [2. 核心原理详解 🔍](#2. 核心原理详解 🔍)
-
- [2.1 向量反射原理 📐](#2.1 向量反射原理 📐)
- [2.2 碰撞检测原理 🔄](#2.2 碰撞检测原理 🔄)
- [2.3 游戏循环结构 🔁](#2.3 游戏循环结构 🔁)
- [3. 完整流程图 📊](#3. 完整流程图 📊)
- [4. 分步实现教程 🛠️](#4. 分步实现教程 🛠️)
-
- [4.1 初始化设置](#4.1 初始化设置)
- [4.2 创建游戏对象](#4.2 创建游戏对象)
- [4.3 游戏主循环](#4.3 游戏主循环)
- [4.4 碰撞检测函数](#4.4 碰撞检测函数)
- [5. 完整可运行代码 🏆](#5. 完整可运行代码 🏆)
- [6. 游戏操作说明 🎮](#6. 游戏操作说明 🎮)
- [7. 扩展思路 💡](#7. 扩展思路 💡)

引言:从游戏到物理模拟
还记得小时候玩过的打砖块游戏吗?🎯 一个小球在屏幕上弹来弹去,击碎各种砖块,同时玩家控制一块挡板防止小球掉落。这看似简单的游戏背后,其实蕴含着丰富的物理和数学原理!今天,我们就用MATLAB来实现这个经典游戏,并深入探讨其中的向量反射和实时物理模拟技术。
通过本教程,你将学到:
- 向量运算在游戏物理中的应用
- 实时碰撞检测的实现方法
- MATLAB动画和交互式图形编程
- 游戏状态管理和逻辑控制
准备好你的MATLAB环境(2016b版本),让我们开始这场有趣的编程之旅吧!🚀
1. 设计思路与游戏概述 🧠
打砖块游戏(Breakout/Arkanoid)是经典的街机游戏,包含以下核心元素:
- 一个由玩家控制的挡板(paddle)
- 一个运动的球(ball)
- 由砖块组成的墙(bricks)
- 边界(walls)
游戏目标是用挡板反弹球,消除所有砖块。每次球碰到砖块,砖块消失,玩家得分。
1.1 物理模型关键点 ⚛️
- 碰撞检测:判断球与边界、挡板、砖块的接触
- 反射计算:球碰到物体后的运动方向变化
- 实时渲染:游戏画面的流畅更新
- 游戏逻辑:得分、生命、胜负判断
2. 核心原理详解 🔍
2.1 向量反射原理 📐
球碰到平面后的反射遵循"入射角=反射角"原则。数学上可以用向量运算表示:
给定:
- 入射向量 v = [vx, vy]
- 法向量 n = [nx, ny] (垂直于反射平面)
反射向量 r 的计算公式为:
r = v − 2 ( v ⋅ n ) n r = v - 2(v \cdot n)n r=v−2(v⋅n)n
在MATLAB中实现:
matlab
function reflectedVec = reflectVector(inVec, normalVec)
% 归一化法向量
normalVec = normalVec / norm(normalVec);
% 计算反射向量
reflectedVec = inVec - 2 * dot(inVec, normalVec) * normalVec;
end
2.2 碰撞检测原理 🔄
我们需要检测球与以下物体的碰撞:
- 边界:比较球心坐标与边界位置
- 挡板:判断球是否在挡板的矩形区域内
- 砖块:类似挡板,但需要考虑砖块是否已被消除
2.3 游戏循环结构 🔁
标准游戏循环包含以下步骤:
- 初始化:创建游戏对象和变量
- 输入处理:读取玩家操作
- 状态更新:计算物理和游戏逻辑
- 渲染:绘制游戏画面
- 循环控制:控制帧率和退出条件
3. 完整流程图 📊
是 否 开始 初始化游戏 绘制初始场景 游戏进行中? 处理玩家输入 更新球位置 检测碰撞 处理碰撞响应 更新游戏状态 绘制新帧 显示游戏结果 结束
4. 分步实现教程 🛠️
4.1 初始化设置
matlab
function breakoutGame()
% 清除工作区和图形窗口
clc; clear; close all;
% 游戏参数设置
gameParams = struct(...
'paddleWidth', 100, ... % 挡板宽度
'paddleHeight', 15, ... % 挡板高度
'ballRadius', 10, ... % 球半径
'brickRows', 5, ... % 砖块行数
'brickCols', 10, ... % 砖块列数
'brickWidth', 60, ... % 砖块宽度
'brickHeight', 20, ... % 砖块高度
'brickOffsetTop', 50, ... % 砖块顶部偏移
'brickPadding', 5, ... % 砖块间距
'ballSpeed', 8, ... % 球初始速度
'lives', 3, ... % 初始生命数
'score', 0 ... % 初始得分
);
% 创建图形窗口
fig = figure('Name', 'MATLAB打砖块', ...
'NumberTitle', 'off', ...
'Position', [100, 100, 800, 600], ...
'KeyPressFcn', @keyDown, ...
'KeyReleaseFcn', @keyUp, ...
'WindowButtonDownFcn', @mouseClick);
% 创建坐标轴
ax = axes('Parent', fig, ...
'Position', [0.05, 0.05, 0.9, 0.9], ...
'XLim', [0, 800], ...
'YLim', [0, 600], ...
'Color', [0.1, 0.1, 0.3], ...
'XTick', [], ...
'YTick', []);
hold(ax, 'on');
axis equal;
4.2 创建游戏对象
matlab
% 创建挡板
paddle = rectangle('Parent', ax, ...
'Position', [350, 30, gameParams.paddleWidth, gameParams.paddleHeight], ...
'FaceColor', [0.8, 0.2, 0.2], ...
'EdgeColor', 'none', ...
'Curvature', [0.2, 0.2]);
% 创建球
theta = rand * 2 * pi; % 随机初始角度
ballVel = [gameParams.ballSpeed * cos(theta), gameParams.ballSpeed * sin(theta)];
ball = rectangle('Parent', ax, ...
'Position', [400, 200, gameParams.ballRadius*2, gameParams.ballRadius*2], ...
'FaceColor', [0.9, 0.9, 0.1], ...
'EdgeColor', 'none', ...
'Curvature', [1, 1]);
% 创建砖块
bricks = gobjects(gameParams.brickRows, gameParams.brickCols);
brickColors = hsv(gameParams.brickRows); % 每行不同颜色
for r = 1:gameParams.brickRows
for c = 1:gameParams.brickCols
brickX = (c-1) * (gameParams.brickWidth + gameParams.brickPadding);
brickY = 550 - (r-1) * (gameParams.brickHeight + gameParams.brickPadding);
bricks(r,c) = rectangle('Parent', ax, ...
'Position', [brickX, brickY, gameParams.brickWidth, gameParams.brickHeight], ...
'FaceColor', brickColors(r,:), ...
'EdgeColor', 'w');
end
end
% 创建文本显示
scoreText = text(ax, 20, 580, sprintf('得分: %d', gameParams.score), ...
'Color', 'w', 'FontSize', 12);
livesText = text(ax, 700, 580, sprintf('生命: %d', gameParams.lives), ...
'Color', 'w', 'FontSize', 12);
startText = text(ax, 400, 300, '点击开始游戏', ...
'Color', 'w', 'FontSize', 24, ...
'HorizontalAlignment', 'center');
4.3 游戏主循环
matlab
% 游戏状态变量
gameState = struct(...
'isRunning', false, ... % 游戏是否进行中
'paddleDir', 0, ... % 挡板移动方向 (-1:左, 0:停止, 1:右)
'paddleSpeed', 15, ... % 挡板移动速度
'activeBricks', true(gameParams.brickRows, gameParams.brickCols) ... % 活跃砖块
);
% 键盘控制回调函数
function keyDown(~, event)
switch event.Key
case 'leftarrow'
gameState.paddleDir = -1;
case 'rightarrow'
gameState.paddleDir = 1;
case 'escape'
gameState.isRunning = false;
end
end
function keyUp(~, event)
switch event.Key
case {'leftarrow', 'rightarrow'}
gameState.paddleDir = 0;
end
end
% 鼠标点击开始游戏
function mouseClick(~, ~)
if ~gameState.isRunning
gameState.isRunning = true;
delete(startText);
startText = [];
end
end
% 主游戏循环
while ishandle(fig)
if gameState.isRunning
% 更新挡板位置
paddlePos = get(paddle, 'Position');
newX = paddlePos(1) + gameState.paddleSpeed * gameState.paddleDir;
% 限制挡板不超出边界
newX = max(0, min(newX, 800 - gameParams.paddleWidth));
set(paddle, 'Position', [newX, paddlePos(2), paddlePos(3), paddlePos(4)]);
% 更新球位置
ballPos = get(ball, 'Position');
newBallX = ballPos(1) + ballVel(1);
newBallY = ballPos(2) + ballVel(2);
% 检测碰撞
[ballVel, gameParams, gameState] = checkCollisions(...
[newBallX, newBallY], ballVel, gameParams, gameState, paddle, bricks);
% 更新球位置
set(ball, 'Position', [newBallX, newBallY, ballPos(3), ballPos(4)]);
% 更新文本显示
set(scoreText, 'String', sprintf('得分: %d', gameParams.score));
set(livesText, 'String', sprintf('生命: %d', gameParams.lives));
% 检查游戏结束条件
if newBallY < 0 % 球落到底部
gameParams.lives = gameParams.lives - 1;
if gameParams.lives <= 0
gameState.isRunning = false;
text(ax, 400, 300, '游戏结束!', ...
'Color', 'r', 'FontSize', 36, ...
'HorizontalAlignment', 'center');
else
% 重置球位置
set(ball, 'Position', [400, 200, gameParams.ballRadius*2, gameParams.ballRadius*2]);
theta = rand * 2 * pi;
ballVel = [gameParams.ballSpeed * cos(theta), gameParams.ballSpeed * sin(theta)];
pause(1);
end
end
% 检查胜利条件
if ~any(gameState.activeBricks(:))
gameState.isRunning = false;
text(ax, 400, 300, '恭喜通关!', ...
'Color', 'g', 'FontSize', 36, ...
'HorizontalAlignment', 'center');
end
end
% 控制帧率
pause(0.02);
end
4.4 碰撞检测函数
matlab
function [newVel, gameParams, gameState] = checkCollisions(ballPos, ballVel, gameParams, gameState, paddle, bricks)
% 获取球参数
ballX = ballPos(1) + gameParams.ballRadius;
ballY = ballPos(2) + gameParams.ballRadius;
% 边界碰撞检测
if ballX <= gameParams.ballRadius || ballX >= 800 - gameParams.ballRadius
ballVel(1) = -ballVel(1); % 水平反转
end
if ballY >= 600 - gameParams.ballRadius
ballVel(2) = -ballVel(2); % 垂直反转
end
% 挡板碰撞检测
paddlePos = get(paddle, 'Position');
if ballY <= paddlePos(2) + paddlePos(4) + gameParams.ballRadius && ...
ballY >= paddlePos(2) && ...
ballX >= paddlePos(1) - gameParams.ballRadius && ...
ballX <= paddlePos(1) + paddlePos(3) + gameParams.ballRadius
% 计算碰撞点相对于挡板中心的位置 (-1到1)
hitPos = (ballX - (paddlePos(1) + paddlePos(3)/2)) / (paddlePos(3)/2);
% 根据碰撞点调整反射角度
maxAngle = pi/3; % 最大反射角度 (60度)
angle = hitPos * maxAngle;
% 计算新速度向量
speed = norm(ballVel);
ballVel = [speed * sin(angle), speed * cos(angle)];
% 增加一点速度让游戏更有挑战性
ballVel = ballVel * 1.02;
end
% 砖块碰撞检测
for r = 1:gameParams.brickRows
for c = 1:gameParams.brickCols
if gameState.activeBricks(r,c)
brickPos = get(bricks(r,c), 'Position');
% 检查球是否与砖块相交
if ballX + gameParams.ballRadius > brickPos(1) && ...
ballX - gameParams.ballRadius < brickPos(1) + brickPos(3) && ...
ballY + gameParams.ballRadius > brickPos(2) && ...
ballY - gameParams.ballRadius < brickPos(2) + brickPos(4)
% 确定碰撞边 (简化版)
if ballY < brickPos(2) || ballY > brickPos(2) + brickPos(4)
ballVel(2) = -ballVel(2); % 上下碰撞
else
ballVel(1) = -ballVel(1); % 左右碰撞
end
% 标记砖块为不活跃并隐藏
gameState.activeBricks(r,c) = false;
set(bricks(r,c), 'Visible', 'off');
% 增加分数
gameParams.score = gameParams.score + 10;
% 只需要处理一次碰撞
break;
end
end
end
end
newVel = ballVel;
end
5. 完整可运行代码 🏆
将以下所有代码段按顺序组合成一个.m文件即可运行:
matlab
function breakoutGame()
% 清除工作区和图形窗口
clc; clear; close all;
% 游戏参数设置
gameParams = struct(...
'paddleWidth', 100, ... % 挡板宽度
'paddleHeight', 15, ... % 挡板高度
'ballRadius', 10, ... % 球半径
'brickRows', 5, ... % 砖块行数
'brickCols', 10, ... % 砖块列数
'brickWidth', 60, ... % 砖块宽度
'brickHeight', 20, ... % 砖块高度
'brickOffsetTop', 50, ... % 砖块顶部偏移
'brickPadding', 5, ... % 砖块间距
'ballSpeed', 8, ... % 球初始速度
'lives', 3, ... % 初始生命数
'score', 0 ... % 初始得分
);
% 创建图形窗口
fig = figure('Name', 'MATLAB打砖块', ...
'NumberTitle', 'off', ...
'Position', [100, 100, 800, 600], ...
'KeyPressFcn', @keyDown, ...
'KeyReleaseFcn', @keyUp, ...
'WindowButtonDownFcn', @mouseClick);
% 创建坐标轴
ax = axes('Parent', fig, ...
'Position', [0.05, 0.05, 0.9, 0.9], ...
'XLim', [0, 800], ...
'YLim', [0, 600], ...
'Color', [0.1, 0.1, 0.3], ...
'XTick', [], ...
'YTick', []);
hold(ax, 'on');
axis equal;
% 创建挡板
paddle = rectangle('Parent', ax, ...
'Position', [350, 30, gameParams.paddleWidth, gameParams.paddleHeight], ...
'FaceColor', [0.8, 0.2, 0.2], ...
'EdgeColor', 'none', ...
'Curvature', [0.2, 0.2]);
% 创建球
theta = rand * 2 * pi; % 随机初始角度
ballVel = [gameParams.ballSpeed * cos(theta), gameParams.ballSpeed * sin(theta)];
ball = rectangle('Parent', ax, ...
'Position', [400, 200, gameParams.ballRadius*2, gameParams.ballRadius*2], ...
'FaceColor', [0.9, 0.9, 0.1], ...
'EdgeColor', 'none', ...
'Curvature', [1, 1]);
% 创建砖块
bricks = gobjects(gameParams.brickRows, gameParams.brickCols);
brickColors = hsv(gameParams.brickRows); % 每行不同颜色
for r = 1:gameParams.brickRows
for c = 1:gameParams.brickCols
brickX = (c-1) * (gameParams.brickWidth + gameParams.brickPadding);
brickY = 550 - (r-1) * (gameParams.brickHeight + gameParams.brickPadding);
bricks(r,c) = rectangle('Parent', ax, ...
'Position', [brickX, brickY, gameParams.brickWidth, gameParams.brickHeight], ...
'FaceColor', brickColors(r,:), ...
'EdgeColor', 'w');
end
end
% 创建文本显示
scoreText = text(ax, 20, 580, sprintf('得分: %d', gameParams.score), ...
'Color', 'w', 'FontSize', 12);
livesText = text(ax, 700, 580, sprintf('生命: %d', gameParams.lives), ...
'Color', 'w', 'FontSize', 12);
startText = text(ax, 400, 300, '点击开始游戏', ...
'Color', 'w', 'FontSize', 24, ...
'HorizontalAlignment', 'center');
% 游戏状态变量
gameState = struct(...
'isRunning', false, ... % 游戏是否进行中
'paddleDir', 0, ... % 挡板移动方向 (-1:左, 0:停止, 1:右)
'paddleSpeed', 15, ... % 挡板移动速度
'activeBricks', true(gameParams.brickRows, gameParams.brickCols) ... % 活跃砖块
);
% 键盘控制回调函数
function keyDown(~, event)
switch event.Key
case 'leftarrow'
gameState.paddleDir = -1;
case 'rightarrow'
gameState.paddleDir = 1;
case 'escape'
gameState.isRunning = false;
end
end
function keyUp(~, event)
switch event.Key
case {'leftarrow', 'rightarrow'}
gameState.paddleDir = 0;
end
end
% 鼠标点击开始游戏
function mouseClick(~, ~)
if ~gameState.isRunning
gameState.isRunning = true;
delete(startText);
startText = [];
end
end
% 主游戏循环
while ishandle(fig)
if gameState.isRunning
% 更新挡板位置
paddlePos = get(paddle, 'Position');
newX = paddlePos(1) + gameState.paddleSpeed * gameState.paddleDir;
% 限制挡板不超出边界
newX = max(0, min(newX, 800 - gameParams.paddleWidth));
set(paddle, 'Position', [newX, paddlePos(2), paddlePos(3), paddlePos(4)]);
% 更新球位置
ballPos = get(ball, 'Position');
newBallX = ballPos(1) + ballVel(1);
newBallY = ballPos(2) + ballVel(2);
% 检测碰撞
[ballVel, gameParams, gameState] = checkCollisions(...
[newBallX, newBallY], ballVel, gameParams, gameState, paddle, bricks);
% 更新球位置
set(ball, 'Position', [newBallX, newBallY, ballPos(3), ballPos(4)]);
% 更新文本显示
set(scoreText, 'String', sprintf('得分: %d', gameParams.score));
set(livesText, 'String', sprintf('生命: %d', gameParams.lives));
% 检查游戏结束条件
if newBallY < 0 % 球落到底部
gameParams.lives = gameParams.lives - 1;
if gameParams.lives <= 0
gameState.isRunning = false;
text(ax, 400, 300, '游戏结束!', ...
'Color', 'r', 'FontSize', 36, ...
'HorizontalAlignment', 'center');
else
% 重置球位置
set(ball, 'Position', [400, 200, gameParams.ballRadius*2, gameParams.ballRadius*2]);
theta = rand * 2 * pi;
ballVel = [gameParams.ballSpeed * cos(theta), gameParams.ballSpeed * sin(theta)];
pause(1);
end
end
% 检查胜利条件
if ~any(gameState.activeBricks(:))
gameState.isRunning = false;
text(ax, 400, 300, '恭喜通关!', ...
'Color', 'g', 'FontSize', 36, ...
'HorizontalAlignment', 'center');
end
end
% 控制帧率
pause(0.02);
end
% 碰撞检测函数
function [newVel, gameParams, gameState] = checkCollisions(ballPos, ballVel, gameParams, gameState, paddle, bricks)
% 获取球参数
ballX = ballPos(1) + gameParams.ballRadius;
ballY = ballPos(2) + gameParams.ballRadius;
% 边界碰撞检测
if ballX <= gameParams.ballRadius || ballX >= 800 - gameParams.ballRadius
ballVel(1) = -ballVel(1); % 水平反转
end
if ballY >= 600 - gameParams.ballRadius
ballVel(2) = -ballVel(2); % 垂直反转
end
% 挡板碰撞检测
paddlePos = get(paddle, 'Position');
if ballY <= paddlePos(2) + paddlePos(4) + gameParams.ballRadius && ...
ballY >= paddlePos(2) && ...
ballX >= paddlePos(1) - gameParams.ballRadius && ...
ballX <= paddlePos(1) + paddlePos(3) + gameParams.ballRadius
% 计算碰撞点相对于挡板中心的位置 (-1到1)
hitPos = (ballX - (paddlePos(1) + paddlePos(3)/2)) / (paddlePos(3)/2);
% 根据碰撞点调整反射角度
maxAngle = pi/3; % 最大反射角度 (60度)
angle = hitPos * maxAngle;
% 计算新速度向量
speed = norm(ballVel);
ballVel = [speed * sin(angle), speed * cos(angle)];
% 增加一点速度让游戏更有挑战性
ballVel = ballVel * 1.02;
end
% 砖块碰撞检测
for r = 1:gameParams.brickRows
for c = 1:gameParams.brickCols
if gameState.activeBricks(r,c)
brickPos = get(bricks(r,c), 'Position');
% 检查球是否与砖块相交
if ballX + gameParams.ballRadius > brickPos(1) && ...
ballX - gameParams.ballRadius < brickPos(1) + brickPos(3) && ...
ballY + gameParams.ballRadius > brickPos(2) && ...
ballY - gameParams.ballRadius < brickPos(2) + brickPos(4)
% 确定碰撞边 (简化版)
if ballY < brickPos(2) || ballY > brickPos(2) + brickPos(4)
ballVel(2) = -ballVel(2); % 上下碰撞
else
ballVel(1) = -ballVel(1); % 左右碰撞
end
% 标记砖块为不活跃并隐藏
gameState.activeBricks(r,c) = false;
set(bricks(r,c), 'Visible', 'off');
% 增加分数
gameParams.score = gameParams.score + 10;
% 只需要处理一次碰撞
break;
end
end
end
end
newVel = ballVel;
end
end
来看看我这个菜鸡玩了一局的效果,屏幕前的你也可以试试看看能的多少分~
6. 游戏操作说明 🎮
- 左右箭头键:移动挡板
- ESC键:退出游戏
- 鼠标点击:开始游戏
7. 扩展思路 💡
如果你想进一步提升这个游戏,可以考虑:
- 增加音效 :使用
audioplayer
添加碰撞音效 - 多种砖块:不同颜色砖块需要多次击中才能消除
- 特殊道具:球碰到某些砖块会掉落道具,如加长挡板、额外生命等
- 关卡设计:设计不同布局的砖块排列
- 粒子效果:砖块消除时添加爆炸效果
希望你喜欢这个MATLAB打砖块游戏教程!通过这个项目,你不仅学会了向量反射的原理,还掌握了实时物理模拟和游戏开发的基本技巧。Happy coding! 🚀