《用MATLAB玩转游戏开发》Flappy Bird:小鸟飞行大战MATLAB趣味实现

发布于:2025-05-12 ⋅ 阅读:(14) ⋅ 点赞:(0)

《用MATLAB玩转游戏开发:从零开始打造你的数字乐园》基础篇(2D图形交互)-Flappy Bird:小鸟飞行大战MATLAB趣味实现

在这里插入图片描述

大家好!今天我将带大家用MATLAB实现经典游戏Flappy Bird!这个项目不仅能巩固你的MATLAB编程技能,还能让你了解游戏开发的基本原理。让我们开始这段有趣的编程之旅吧!🚀

1.设计思路与原理⚙️

Flappy Bird的核心机制其实非常简单,主要包括以下几个部分:

  1. 小鸟物理系统:模拟重力和跳跃
  2. 障碍物生成:随机生成上下管道
  3. 碰撞检测:判断小鸟是否撞到管道或地面
  4. 分数计算:记录玩家通过的管道数量
  5. 游戏循环:控制游戏节奏和画面更新

我们将使用MATLAB的图形处理功能来实现这些机制,主要依赖figurepatchtimer等对象。

2.完整流程图 📊

碰撞
未碰撞
开始游戏
初始化游戏参数
创建游戏界面
生成初始障碍物
启动游戏循环
玩家按空格?
小鸟跳跃
小鸟下落
更新小鸟位置
移动障碍物
检测碰撞
游戏结束
更新分数
需要新障碍物?
生成新障碍物
绘制游戏画面
游戏继续?
显示最终分数
结束游戏

3.分步实现教程 🛠️

3.1. 初始化游戏环境

首先我们需要设置游戏窗口和基本参数:

function flappy_bird()
    % 清空工作区
    clc; clear; close all;
    
    % 游戏参数设置
    game.screenWidth = 400;       % 屏幕宽度
    game.screenHeight = 600;      % 屏幕高度
    game.gravity = 0.25;          % 重力加速度
    game.jumpStrength = -5;       % 跳跃力度
    game.pipeSpeed = 2;           % 管道移动速度
    game.pipeGap = 150;           % 管道间隙
    game.pipeWidth = 50;          % 管道宽度
    game.birdSize = 20;           % 小鸟大小
    game.groundHeight = 50;       % 地面高度
    game.score = 0;               % 初始分数
    game.gameOver = false;        % 游戏状态标志

3.2. 创建游戏界面

接下来创建游戏图形界面和各种图形对象:

    % 创建图形窗口
    fig = figure('Name', 'Flappy Bird - MATLAB', ...
                'NumberTitle', 'off', ...
                'Position', [100, 100, game.screenWidth, game.screenHeight], ...
                'Color', [0.7 0.9 1], ...
                'KeyPressFcn', @keyPress, ...
                'MenuBar', 'none', ...
                'ToolBar', 'none');
    
    % 创建坐标轴
    ax = axes('Parent', fig, ...
              'Position', [0 0 1 1], ...
              'XLim', [0 game.screenWidth], ...
              'YLim', [0 game.screenHeight], ...
              'Visible', 'off');
    hold(ax, 'on');
    
    % 创建小鸟
    bird.x = game.screenWidth / 4;
    bird.y = game.screenHeight / 2;
    bird.velocity = 0;
    bird.handle = rectangle('Position', [bird.x, bird.y, game.birdSize, game.birdSize], ...
                           'FaceColor', [1 1 0], ...
                           'EdgeColor', [0 0 0], ...
                           'Curvature', [1 1]);
    
    % 创建地面
    ground.handle = patch([0, game.screenWidth, game.screenWidth, 0], ...
                          [0, 0, game.groundHeight, game.groundHeight], ...
                          [0.6 0.4 0.2], 'EdgeColor', 'none');
    
    % 创建分数显示
    scoreText = text(game.screenWidth/2, game.screenHeight-50, num2str(game.score), ...
                    'FontSize', 30, 'Color', [1 1 1], ...
                    'HorizontalAlignment', 'center', 'FontWeight', 'bold');

3.3. 障碍物生成系统

管道是游戏的主要障碍物,我们需要随机生成它们:

    % 初始化管道
    pipes = [];
    
    function generatePipe()
        % 随机生成管道间隙的位置
        gapY = randi([game.groundHeight+50, game.screenHeight-50-game.pipeGap]);
        
        % 下管道
        bottomPipe = struct();
        bottomPipe.x = game.screenWidth;
        bottomPipe.height = gapY - game.pipeGap/2;
        bottomPipe.handle = patch([0, game.pipeWidth, game.pipeWidth, 0]+bottomPipe.x, ...
                                  [0, 0, bottomPipe.height, bottomPipe.height], ...
                                  [0 0.8 0], 'EdgeColor', 'none');
        
        % 上管道
        topPipe = struct();
        topPipe.x = game.screenWidth;
        topPipe.height = game.screenHeight - gapY - game.pipeGap/2;
        topPipe.y = gapY + game.pipeGap/2;
        topPipe.handle = patch([0, game.pipeWidth, game.pipeWidth, 0]+topPipe.x, ...
                               [0, 0, topPipe.height, topPipe.height]+topPipe.y, ...
                               [0 0.8 0], 'EdgeColor', 'none');
        
        % 添加到管道列表
        pipes = [pipes; struct('bottom', bottomPipe, 'top', topPipe, 'passed', false)];
    end

3.4. 游戏主循环

使用MATLAB的timer对象创建游戏循环:

    % 创建游戏计时器
    gameTimer = timer('ExecutionMode', 'fixedRate', ...
                      'Period', 0.02, ...
                      'TimerFcn', @gameLoop);
    
    % 生成初始管道
    generatePipe();
    
    % 启动背景音乐
    try
        [y, Fs] = audioread('background_music.mp3');
        player = audioplayer(y, Fs);
        play(player);
    catch
        warning('背景音乐文件未找到,游戏将继续但没有背景音乐');
    end
    
    % 开始游戏
    start(gameTimer);

3.5. 游戏逻辑实现

实现游戏的核心逻辑:

    function gameLoop(~, ~)
        if game.gameOver
            return;
        end
        
        % 更新小鸟位置
        bird.velocity = bird.velocity + game.gravity;
        bird.y = bird.y + bird.velocity;
        set(bird.handle, 'Position', [bird.x, bird.y, game.birdSize, game.birdSize]);
        
        % 旋转小鸟(根据速度)
        rotationAngle = min(max(bird.velocity * 5, -30), 30);
        rotate(bird.handle, [0 0 1], rotationAngle, [bird.x, bird.y, 0]);
        
        % 移动管道
        for i = 1:length(pipes)
            pipes(i).bottom.x = pipes(i).bottom.x - game.pipeSpeed;
            pipes(i).top.x = pipes(i).top.x - game.pipeSpeed;
            
            % 更新管道图形
            set(pipes(i).bottom.handle, 'XData', [0, game.pipeWidth, game.pipeWidth, 0]+pipes(i).bottom.x);
            set(pipes(i).top.handle, 'XData', [0, game.pipeWidth, game.pipeWidth, 0]+pipes(i).top.x);
            
            % 检查是否通过管道
            if ~pipes(i).passed && pipes(i).bottom.x + game.pipeWidth < bird.x
                pipes(i).passed = true;
                game.score = game.score + 1;
                set(scoreText, 'String', num2str(game.score));
            end
        end
        
        % 移除屏幕外的管道
        if ~isempty(pipes) && pipes(1).bottom.x + game.pipeWidth < 0
            delete(pipes(1).bottom.handle);
            delete(pipes(1).top.handle);
            pipes(1) = [];
        end
        
        % 生成新管道
        if isempty(pipes) || pipes(end).bottom.x < game.screenWidth - 200
            generatePipe();
        end
        
        % 碰撞检测
        if bird.y < game.groundHeight || bird.y + game.birdSize > game.screenHeight
            gameOver();
        end
        
        for i = 1:length(pipes)
            % 检测与下管道的碰撞
            if bird.x + game.birdSize > pipes(i).bottom.x && ...
               bird.x < pipes(i).bottom.x + game.pipeWidth && ...
               bird.y < pipes(i).bottom.height
                gameOver();
            end
            
            % 检测与上管道的碰撞
            if bird.x + game.birdSize > pipes(i).top.x && ...
               bird.x < pipes(i).top.x + game.pipeWidth && ...
               bird.y + game.birdSize > pipes(i).top.y
                gameOver();
            end
        end
        
        % 强制刷新图形
        drawnow;
    end

3.6. 用户输入处理

实现键盘控制:

    function keyPress(~, event)
        if strcmp(event.Key, 'space') && ~game.gameOver
            bird.velocity = game.jumpStrength;
        elseif strcmp(event.Key, 'r') && game.gameOver
            restartGame();
        end
    end

3.7. 游戏结束处理

    function gameOver()
        game.gameOver = true;
        stop(gameTimer);
        
        % 显示游戏结束文字
        text(game.screenWidth/2, game.screenHeight/2, '游戏结束!', ...
             'FontSize', 40, 'Color', [1 0 0], ...
             'HorizontalAlignment', 'center', 'FontWeight', 'bold');
        text(game.screenWidth/2, game.screenHeight/2-50, ['得分: ' num2str(game.score)], ...
             'FontSize', 30, 'Color', [1 1 1], ...
             'HorizontalAlignment', 'center', 'FontWeight', 'bold');
        text(game.screenWidth/2, game.screenHeight/2-100, '按R键重新开始', ...
             'FontSize', 20, 'Color', [1 1 1], ...
             'HorizontalAlignment', 'center');
         
        % 保存GIF
        saveGif();
    end

3.8. 重新开始游戏

    function restartGame()
        % 清除现有管道
        for i = 1:length(pipes)
            delete(pipes(i).bottom.handle);
            delete(pipes(i).top.handle);
        end
        pipes = [];
        
        % 重置小鸟
        bird.y = game.screenHeight / 2;
        bird.velocity = 0;
        set(bird.handle, 'Position', [bird.x, bird.y, game.birdSize, game.birdSize]);
        rotate(bird.handle, [0 0 1], 0, [bird.x, bird.y, 0]);
        
        % 重置分数
        game.score = 0;
        set(scoreText, 'String', '0');
        
        % 删除游戏结束文本
        delete(findall(gcf, 'Type', 'text', 'String', '游戏结束!'));
        delete(findall(gcf, 'Type', 'text', 'String', ['得分: ' num2str(game.score)]));
        delete(findall(gcf, 'Type', 'text', 'String', '按R键重新开始'));
        
        % 生成新管道
        generatePipe();
        
        % 重置游戏状态
        game.gameOver = false;
        
        % 重新开始计时器
        start(gameTimer);
    end

3.9. GIF保存功能

    function saveGif()
        % 创建GIF文件名
        timestamp = datestr(now, 'yyyy-mm-dd_HH-MM-SS');
        gifFilename = ['flappy_bird_' timestamp '.gif'];
        
        % 获取当前图形窗口内容
        frame = getframe(fig);
        im = frame2im(frame);
        [imind, cm] = rgb2ind(im, 256);
        
        % 写入GIF文件
        imwrite(imind, cm, gifFilename, 'gif', 'Loopcount', inf, 'DelayTime', 0.1);
        disp(['游戏已保存为GIF: ' gifFilename]);
    end
end

4.完整可运行代码 🎮

将以下所有代码合并到一个.m文件中即可运行:

function flappy_bird()
    % 清空工作区
    clc; clear; close all;
    
    % 游戏参数设置
    game.screenWidth = 400;       % 屏幕宽度
    game.screenHeight = 600;      % 屏幕高度
    game.gravity = 0.25;          % 重力加速度
    game.jumpStrength = -5;       % 跳跃力度
    game.pipeSpeed = 2;           % 管道移动速度
    game.pipeGap = 150;           % 管道间隙
    game.pipeWidth = 50;          % 管道宽度
    game.birdSize = 20;           % 小鸟大小
    game.groundHeight = 50;       % 地面高度
    game.score = 0;               % 初始分数
    game.gameOver = false;        % 游戏状态标志
    
    % 创建图形窗口
    fig = figure('Name', 'Flappy Bird - MATLAB', ...
                'NumberTitle', 'off', ...
                'Position', [100, 100, game.screenWidth, game.screenHeight], ...
                'Color', [0.7 0.9 1], ...
                'KeyPressFcn', @keyPress, ...
                'MenuBar', 'none', ...
                'ToolBar', 'none');
    
    % 创建坐标轴
    ax = axes('Parent', fig, ...
              'Position', [0 0 1 1], ...
              'XLim', [0 game.screenWidth], ...
              'YLim', [0 game.screenHeight], ...
              'Visible', 'off');
    hold(ax, 'on');
    
    % 创建小鸟
    bird.x = game.screenWidth / 4;
    bird.y = game.screenHeight / 2;
    bird.velocity = 0;
    bird.handle = rectangle('Position', [bird.x, bird.y, game.birdSize, game.birdSize], ...
                           'FaceColor', [1 1 0], ...
                           'EdgeColor', [0 0 0], ...
                           'Curvature', [1 1]);
    
    % 创建地面
    ground.handle = patch([0, game.screenWidth, game.screenWidth, 0], ...
                          [0, 0, game.groundHeight, game.groundHeight], ...
                          [0.6 0.4 0.2], 'EdgeColor', 'none');
    
    % 创建分数显示
    scoreText = text(game.screenWidth/2, game.screenHeight-50, num2str(game.score), ...
                    'FontSize', 30, 'Color', [1 1 1], ...
                    'HorizontalAlignment', 'center', 'FontWeight', 'bold');
    
    % 初始化管道
    pipes = [];
    
    function generatePipe()
        % 随机生成管道间隙的位置
        gapY = randi([game.groundHeight+50, game.screenHeight-50-game.pipeGap]);
        
        % 下管道
        bottomPipe = struct();
        bottomPipe.x = game.screenWidth;
        bottomPipe.height = gapY - game.pipeGap/2;
        bottomPipe.handle = patch([0, game.pipeWidth, game.pipeWidth, 0]+bottomPipe.x, ...
                                  [0, 0, bottomPipe.height, bottomPipe.height], ...
                                  [0 0.8 0], 'EdgeColor', 'none');
        
        % 上管道
        topPipe = struct();
        topPipe.x = game.screenWidth;
        topPipe.height = game.screenHeight - gapY - game.pipeGap/2;
        topPipe.y = gapY + game.pipeGap/2;
        topPipe.handle = patch([0, game.pipeWidth, game.pipeWidth, 0]+topPipe.x, ...
                               [0, 0, topPipe.height, topPipe.height]+topPipe.y, ...
                               [0 0.8 0], 'EdgeColor', 'none');
        
        % 添加到管道列表
        pipes = [pipes; struct('bottom', bottomPipe, 'top', topPipe, 'passed', false)];
    end
    
    % 创建游戏计时器
    gameTimer = timer('ExecutionMode', 'fixedRate', ...
                      'Period', 0.02, ...
                      'TimerFcn', @gameLoop);
    
    % 生成初始管道
    generatePipe();
    
    % 启动背景音乐
    try
        [y, Fs] = audioread('background_music.mp3');
        player = audioplayer(y, Fs);
        play(player);
    catch
        warning('背景音乐文件未找到,游戏将继续但没有背景音乐');
    end
    
    % 开始游戏
    start(gameTimer);
    
    function gameLoop(~, ~)
        if game.gameOver
            return;
        end
        
        % 更新小鸟位置
        bird.velocity = bird.velocity + game.gravity;
        bird.y = bird.y + bird.velocity;
        set(bird.handle, 'Position', [bird.x, bird.y, game.birdSize, game.birdSize]);
        
        % 旋转小鸟(根据速度)
        rotationAngle = min(max(bird.velocity * 5, -30), 30);
        rotate(bird.handle, [0 0 1], rotationAngle, [bird.x, bird.y, 0]);
        
        % 移动管道
        for i = 1:length(pipes)
            pipes(i).bottom.x = pipes(i).bottom.x - game.pipeSpeed;
            pipes(i).top.x = pipes(i).top.x - game.pipeSpeed;
            
            % 更新管道图形
            set(pipes(i).bottom.handle, 'XData', [0, game.pipeWidth, game.pipeWidth, 0]+pipes(i).bottom.x);
            set(pipes(i).top.handle, 'XData', [0, game.pipeWidth, game.pipeWidth, 0]+pipes(i).top.x);
            
            % 检查是否通过管道
            if ~pipes(i).passed && pipes(i).bottom.x + game.pipeWidth < bird.x
                pipes(i).passed = true;
                game.score = game.score + 1;
                set(scoreText, 'String', num2str(game.score));
            end
        end
        
        % 移除屏幕外的管道
        if ~isempty(pipes) && pipes(1).bottom.x + game.pipeWidth < 0
            delete(pipes(1).bottom.handle);
            delete(pipes(1).top.handle);
            pipes(1) = [];
        end
        
        % 生成新管道
        if isempty(pipes) || pipes(end).bottom.x < game.screenWidth - 200
            generatePipe();
        end
        
        % 碰撞检测
        if bird.y < game.groundHeight || bird.y + game.birdSize > game.screenHeight
            gameOver();
        end
        
        for i = 1:length(pipes)
            % 检测与下管道的碰撞
            if bird.x + game.birdSize > pipes(i).bottom.x && ...
               bird.x < pipes(i).bottom.x + game.pipeWidth && ...
               bird.y < pipes(i).bottom.height
                gameOver();
            end
            
            % 检测与上管道的碰撞
            if bird.x + game.birdSize > pipes(i).top.x && ...
               bird.x < pipes(i).top.x + game.pipeWidth && ...
               bird.y + game.birdSize > pipes(i).top.y
                gameOver();
            end
        end
        
        % 强制刷新图形
        drawnow;
    end
    
    function keyPress(~, event)
        if strcmp(event.Key, 'space') && ~game.gameOver
            bird.velocity = game.jumpStrength;
        elseif strcmp(event.Key, 'r') && game.gameOver
            restartGame();
        end
    end
    
    function gameOver()
        game.gameOver = true;
        stop(gameTimer);
        
        % 显示游戏结束文字
        text(game.screenWidth/2, game.screenHeight/2, '游戏结束!', ...
             'FontSize', 40, 'Color', [1 0 0], ...
             'HorizontalAlignment', 'center', 'FontWeight', 'bold');
        text(game.screenWidth/2, game.screenHeight/2-50, ['得分: ' num2str(game.score)], ...
             'FontSize', 30, 'Color', [1 1 1], ...
             'HorizontalAlignment', 'center', 'FontWeight', 'bold');
        text(game.screenWidth/2, game.screenHeight/2-100, '按R键重新开始', ...
             'FontSize', 20, 'Color', [1 1 1], ...
             'HorizontalAlignment', 'center');
         
        % 保存GIF
        saveGif();
    end
    
    function restartGame()
        % 清除现有管道
        for i = 1:length(pipes)
            delete(pipes(i).bottom.handle);
            delete(pipes(i).top.handle);
        end
        pipes = [];
        
        % 重置小鸟
        bird.y = game.screenHeight / 2;
        bird.velocity = 0;
        set(bird.handle, 'Position', [bird.x, bird.y, game.birdSize, game.birdSize]);
        rotate(bird.handle, [0 0 1], 0, [bird.x, bird.y, 0]);
        
        % 重置分数
        game.score = 0;
        set(scoreText, 'String', '0');
        
        % 删除游戏结束文本
        delete(findall(gcf, 'Type', 'text', 'String', '游戏结束!'));
        delete(findall(gcf, 'Type', 'text', 'String', ['得分: ' num2str(game.score)]));
        delete(findall(gcf, 'Type', 'text', 'String', '按R键重新开始'));
        
        % 生成新管道
        generatePipe();
        
        % 重置游戏状态
        game.gameOver = false;
        
        % 重新开始计时器
        start(gameTimer);
    end
    
    function saveGif()
        % 创建GIF文件名
        timestamp = datestr(now, 'yyyy-mm-dd_HH-MM-SS');
        gifFilename = ['flappy_bird_' timestamp '.gif'];
        
        % 获取当前图形窗口内容
        frame = getframe(fig);
        im = frame2im(frame);
        [imind, cm] = rgb2ind(im, 256);
        
        % 写入GIF文件
        imwrite(imind, cm, gifFilename, 'gif', 'Loopcount', inf, 'DelayTime', 0.1);
        disp(['游戏已保存为GIF: ' gifFilename]);
    end
end

5.游戏操作说明 🎯

  • 空格键:让小鸟跳跃
  • R键:游戏结束后重新开始
  • 游戏会自动保存为GIF动画

在这里插入图片描述 在这里插入图片描述

6.扩展建议 💡

如果你想进一步改进这个游戏,可以考虑:

  1. 添加更多的视觉效果,如云朵、背景滚动等
  2. 实现难度递增机制(随着分数增加,管道移动速度加快)
  3. 添加音效(跳跃音效、碰撞音效等)
  4. 实现高分记录功能

希望你喜欢这个MATLAB版的Flappy Bird!通过这个项目,你不仅学会了游戏开发的基本原理,还巩固了MATLAB的图形编程技巧。快去挑战你的最高分吧!🏆

如果有任何问题或建议,欢迎在评论区留言讨论哦!😊


网站公告

今日签到

点亮在社区的每一天
去签到