《用MATLAB玩转游戏开发:从零开始打造你的数字乐园》基础篇(2D图形交互)-《打砖块:向量反射与实时物理模拟》MATLAB教程

《用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 物理模型关键点 ⚛️

  1. 碰撞检测:判断球与边界、挡板、砖块的接触
  2. 反射计算:球碰到物体后的运动方向变化
  3. 实时渲染:游戏画面的流畅更新
  4. 游戏逻辑:得分、生命、胜负判断

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 碰撞检测原理 🔄

我们需要检测球与以下物体的碰撞:

  1. 边界:比较球心坐标与边界位置
  2. 挡板:判断球是否在挡板的矩形区域内
  3. 砖块:类似挡板,但需要考虑砖块是否已被消除

2.3 游戏循环结构 🔁

标准游戏循环包含以下步骤:

  1. 初始化:创建游戏对象和变量
  2. 输入处理:读取玩家操作
  3. 状态更新:计算物理和游戏逻辑
  4. 渲染:绘制游戏画面
  5. 循环控制:控制帧率和退出条件

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. 扩展思路 💡

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

  1. 增加音效 :使用audioplayer添加碰撞音效
  2. 多种砖块:不同颜色砖块需要多次击中才能消除
  3. 特殊道具:球碰到某些砖块会掉落道具,如加长挡板、额外生命等
  4. 关卡设计:设计不同布局的砖块排列
  5. 粒子效果:砖块消除时添加爆炸效果

希望你喜欢这个MATLAB打砖块游戏教程!通过这个项目,你不仅学会了向量反射的原理,还掌握了实时物理模拟和游戏开发的基本技巧。Happy coding! 🚀

相关推荐
Scott9999HH8 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社9 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海9 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖10 小时前
JDK 26 新特性详解
java·开发语言
马优晨10 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区12 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大13 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai13 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜13 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa13 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio