当前位置: 首页 > news >正文

C++游戏编程入门(第三版)——Timber!!! 项目(章节 1–5)

C++

文章目录

  • 第1章 —— 环境搭建 + 初步“可视化窗口”
  • 第2章 —— 变量 / 运算符 / 判断:让场景有生气
  • 第3章 —— 字符串 + 时间 / HUD:让游戏具有用户界面
  • 第4章 —— 循环、数组、switch / 枚举、函数:游戏机制(树枝运动)
  • 第5章 —— 碰撞、音效与游戏终结:让游戏真正可玩
  • 游戏源代码

“Timber!!!” 是一本书用来带你实践 C++ + SFML 做小游戏的第一个项目。在前五章里,作者一步步带读者从环境搭建、绘制背景、显示精灵、处理输入、动画、分数 /时间、游戏机制(如枝条移动)、碰撞检测、音效、游戏结束判定等,把一个能玩的小游戏“拼出来”。

每章的进度大致如下:

  • 第1章:环境搭建 + “Hello, world”层面,用 SFML 打开窗口、渲染背景。

  • 第2章:变量、运算符、判断 — 在屏幕上显示树、蜂、云,制作动画效果(蜜蜂飞、云飘)。

  • 第3章:字符串 + SFML 时间 / 文字显示 — 添加 HUD(分数/时间文本)、暂停 / 重启机制等。

  • 第4章:循环、数组、switch、枚举、函数 — 实现游戏核心机制(树枝生成 / 移动 / 分支选择等)。

  • 第5章:碰撞检测、音效、游戏结束条件 — 完成游戏:玩家砍树动作判定、斧头 / 树枝碰撞、播放音效、重启等。

这样到第5章结束,你就能得到一个比较完整的 Timber!!! 游戏原型(可玩但可能还简单)。后面的章节才引入面向对象、类、管理多个游戏模块等。

下面我按章讲解。

第1章 —— 环境搭建 + 初步“可视化窗口”

目标 / 内容
介绍使用的技术栈(C++、SFML、Visual Studio 等)

安装 / 配置 SFML 库
在 IDE 中创建项目、链接库、设置编译选项
在主函数中写出最简单的 SFML 窗口程序(打开窗口、main loop)
渲染背景图 / 清屏 /显示等等基础流程
理解“游戏循环”(Game Loop)的基本结构
这是让读者“看到东西动起来”的第一步,让读者不至于被一大堆配置/环境难题吓退。

核心讲解
SFML 简介 & C++ 选用理由
SFML(Simple and Fast Multimedia Library)是一个跨平台的多媒体库,封装了窗口、图形、输入、声音、时间、网络等模块,很适合作为小游戏原型 / 入门游戏编程环境。书中选它是因为它比直接用 OpenGL / DirectX 更简单,上手门槛低。

项目与库配置
在 Visual Studio 中配置 SFML:要添加 include 目录、库目录、链接对应的 .lib(或 .dll / 动态库)等。初始化顺序要注意。

主程序框架 / 游戏循环
通常会写一个 main() 函数,大致结构:

int main()
{sf::RenderWindow window(sf::VideoMode(,), "Timber!!!");// 加载纹理 / 资源while (window.isOpen()){// 事件处理(poll events)// 更新逻辑// 渲染 —— 清屏、绘制、显示}return 0;
}

在这个循环里,每帧做的事情是:“处理输入 → 更新状态 → 渲染画面”。这是游戏编程最核心的循环模式。

纹理 / 精灵 / 背景绘制
引入 SFML 的 sf::Texture (纹理) 和 sf::Sprite(精灵)概念。通常做法是:
加载一张图片到 sf::Texture
用 sf::Sprite 与这个纹理关联
在渲染阶段把 sprite 画到窗口上
背景图通常使用一个大贴图,作为窗口底层图层绘制。

双缓冲 / 显示(display)
在每帧末尾调用 window.display(),将后台缓冲区绘制内容显示出来。与之配对的是 window.clear() 用于清除前一帧内容。

常见问题 / 陷阱
链接库时忘记链接 SFML 的依赖(graphics、window、system 模块等)
运行时缺少动态库(DLL)文件
各帧时间不一致导致帧率波动,需控制帧率或使用时间步长
不理解 “窗口仍响应但不关闭” 的事件循环写法

第2章 —— 变量 / 运算符 / 判断:让场景有生气

这一章主要带你学习 C++ 基础语法(变量、运算符、if/else),同时把一些简单的动画效果加入到 Timber 中:比如云朵飘、蜜蜂飞动、树静态显示等。

目标 / 内容

  • C++ 变量、常量、类型、初始化
  • 运算符(+, -, *, /, %, 赋值、自增自减等)
  • if / else 逻辑判断 / 逻辑运算符
  • 随机数(生成随机数以决定蜜蜂 / 云 /树的位置 /速度)
  • 把云 / 蜂 / 树绘制出来,并用变量控制它们的运动

核心讲解
变量与初始化
介绍基本类型(int, float, bool 等),以及怎样声明与初始化。书中或教程推荐使用统一初始化(花括号)风格。

运算 / 表达式
如何用算术运算符组合变量,如何写表达式,括号优先级等。

条件判断
用 if / else 判断某些条件,控制程序走向。比如:

if (蜂的 x 坐标 > 某值)翻转方向 / 重置位置

随机数
利用标准库( + std::rand() / 更现代的 )生成随机数,用来设置云 / 蜜蜂的初始速度 /方向 /位置,使得每次运行效果不同,富有变化感。

动画 / 更新
每一帧,你更新每个对象的坐标(例如:cloudX += cloudSpeed;),然后绘制它。这样云就会向右/左飘。
对于蜜蜂,你可能还要让它在某个范围内来回飞。

结合变量与渲染
将变量和图形对象(Sprite)关联起来:变量控制位置 / 速度 /方向,Sprite 或纹理绘制这些图形。

第3章 —— 字符串 + 时间 / HUD:让游戏具有用户界面

这一章你会给游戏加上 “人机交互层面” 的东西:得分 / 时间文本、暂停 / 重启机制、显示文字等。

目标 / 内容

  • C++ 字符串操作(std::string, 拼接, 长度, stringstream 等)
  • SFML 的 sf::Text、sf::Font 类,显示文字
  • 时间 / 计时:用 SFML 的 sf::Clock, sf::Time 测量游戏进行时间
  • 添加 HUD(Heads-Up Display,头上显示界面元素,比如得分、剩余时间)
  • 暂停 / 重启逻辑:如何暂停游戏更新 / 重置状态

核心讲解

  • 字符串操作
    用 std::string 存储文本信息,比如 “Score: 0”。
    常用操作:拼接(+ 或 append)、length() / size()、格式化数字转字符串(用 std::stringstream)等。

  • SFML 的文字 / 字体
    先加载字体文件(.ttf)到 sf::Font 对象
    创建 sf::Text,设置字体、大小、颜色、位置
    在渲染阶段把 Text 对象绘制在窗口上

比如:

sf::Font font;
font.loadFromFile("arial.ttf");
sf::Text scoreText;
scoreText.setFont(font);
scoreText.setString("Score: 0");
scoreText.setCharacterSize(24);
scoreText.setFillColor(sf::Color::White);
scoreText.setPosition(10, 10);

时间 / 计时
使用 sf::Clock 来测量时间的推移。clock.restart() 或 clock.getElapsedTime() 提供了一个 sf::Time 对象,能转换为秒数(asSeconds())等。
你可以用时间来控制游戏节奏、倒计时、延迟等。

暂停 / 重启机制
在游戏状态机中引入一个变量(例如 bool isPaused 或枚举状态)来控制“是否在游戏更新阶段”执行逻辑更新。若处于暂停状态,仅处理输入 / 渲染,不更新游戏实体。
对于重启,则对游戏变量(分数、时间、对象位置等)做重置。

HUD 显示
在窗口上绘制文本:剩余时间、得分、提示信息(如 “Game Over”, “Press Enter to Restart”)等。

切换文字 / 动态更新
每帧你可能要根据当前得分、剩余时间动态设置 text.setString(…)。要注意字符串转换效率(大量转换可能有开销)。

第4章 —— 循环、数组、switch / 枚举、函数:游戏机制(树枝运动)

这一章可以说是把前面准备好的素材 + 基础语法拼成游戏核心机制的一章。你要让“树枝”这个元素动起来,玩家面对树干两边有枝条要判断是否安全砍伐。

目标 / 内容
C++ 的循环(while / for),跳出 / 继续语句

  • 数组 / 数组初始化 / 遍历
  • switch 语句、enum 枚举类型
  • 函数和函数原型(声明 / 定义 /调用),作用域与返回值
  • 用这些工具实现树枝生成 / 移动逻辑、玩家砍伐方向判断、树枝掉落逻辑等

核心讲解

循环结构
while (…) { … } 用于游戏主循环
for (…) { … } 用于遍历数组或控制一定次数操作
break / continue 控制流程跳转

数组

用来存储树枝在各个高度 /索引处的位置(是左边 / 右边 / 无)
初始化:可以指定初始值,也可以循环赋值
遍历 / 访问:branch[i]

枚举 / switch
定义一个枚举类型来表示树枝位置(例如 enum Side { LEFT, RIGHT, NONE })
在数组中用这个枚举类型存储枝条状态
用 switch(branch[i]) { case LEFT: …; case RIGHT: …; } 来判断不同逻辑

函数
把一些逻辑拆成函数(例如 updateBranches(), drawBranches(), getBranchSide(int index) 等)
函数有声明和定义、参数传入 / 返回、作用域、局部变量等
用函数来避免在主循环里写一大串逻辑,使代码更清晰

树枝运动 / 掉落 / 生成逻辑
你可以设定一个常数 NUM_BRANCHES = 6,表示树干有 6 个枝条层级
每帧(或每次砍倒树干时)让树干“往下移动”一格:最底层枝条被移除,其它上升 / 下移一个位置
在最顶层随机生成一个新的枝条(LEFT / RIGHT / NONE),用随机数决定
玩家砍树时,你检查玩家所在一侧是否有枝条:如果有,表示玩家被砍到,游戏结束;否则安全
主循环中整合
在主 game loop 中,每帧要调用函数更新树枝、处理玩家砍树逻辑、检测安全 / 危险、然后渲染枝条与树干等。

第5章 —— 碰撞、音效与游戏终结:让游戏真正可玩

第五章是让前面积累的模块协同起来,让游戏具备“可玩性”:玩家砍树结果判断(碰撞 / 死亡 / 成功)、播放音效、检测结束 / 重启。

目标 / 内容

  • 玩家输入处理:砍树动作检测、键释放检测
  • 碰撞检测 / 判断玩家是否被树枝砍中
  • 斧头 / 树干 /枝条动画(砍树动作中的动画切换)
  • 游戏过(死亡)判断与重启机制
  • 音效:加载音效文件、在适当时机播放音效
  • 改进 / 增强游戏体验的小建议

核心讲解
玩家输入
监听键盘按下 / 抬起(sf::Event::KeyPressed / KeyReleased)
通过检验键是左 / 右,决定玩家砍树方向
当玩家按下方向键时,启动一次砍树动作(触发判断、动画、得分等)

斧头 / 斧头动画
玩家砍树时,要把斧头与玩家角色贴图 / 精灵切换位置 /朝向
如果玩家在左侧砍树,斧头移到左边,判断是否有枝条;右侧同理
对于动画切换,可以用时间延迟或状态机来管理(例如砍树动画持续一小段时间)

碰撞 / 死亡判断
当玩家砍树时,查看玩家一侧对应层级 branch[0] 是否有枝条。如果有,说明玩家被砍中,游戏结束
若玩家安全,则增加得分、时间奖励、继续游戏
游戏结束后,切换游戏状态到 “Game Over” 状态,禁止继续更新,只允许等待重启

重启 / 新游戏
在 Game Over 状态下,监听 “Enter” 键(或其他键)来重置游戏状态:重置得分、时间、树枝数组、玩家位置等
切换回游戏进行状态

声音 / 音效
使用 SFML 的 sf::SoundBuffer 与 sf::Sound 类来加载音效(如砍树声、失败声等)
在适当时机调用 sound.play() 来播放
注意音效文件格式、路径、加载失败处理等

改进 / 可选功能
在本章结尾,作者通常给一些额外建议让读者扩展游戏,例如增加更多树 /枝条种类、视觉效果、动画效果、音效丰富性、难度提升机制等。

游戏源代码

#include <sstream>
#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>using namespace sf;const int NUM_BRANCHES = 6;
Sprite branches[NUM_BRANCHES];// 玩家/枝杈在哪?
// 左侧还是右侧
enum class side { LEFT, RIGHT, NONE };
side branchPositions[NUM_BRANCHES];// 函数声明
void updateBranches(int seed)
{// 把所有树杈移到一个位置for (int j = NUM_BRANCHES - 1; j > 0; j--){branchPositions[j] = branchPositions[j - 1];}// 在位置为0处生成新枝杈// 取LEFT、RIGHT、NONE?srand((int)time(0) + seed);int r = (rand() % 5);switch (r){case 0:branchPositions[0] = side::LEFT;break;case 1:branchPositions[0] = side::RIGHT;break;default:branchPositions[0] = side::NONE;break;}
}int main()
{VideoMode vm(1920, 1080);RenderWindow window(vm, "Timber!!!", Style::Fullscreen);// 创建Texture对象以便在GPU中保存图片Texture textureBackground;// 向此对象中加载图片textureBackground.loadFromFile("graphics/background.png");// 创建Sprite对象Sprite spriteBackground;// 让Sprite对象与Texture对象建立关联spriteBackground.setTexture(textureBackground);// 让spriteBackground占据整个屏幕spriteBackground.setPosition(0, 0);// 构建树精灵Texture textureTree;textureTree.loadFromFile("graphics/tree.png");Sprite spriteTree;spriteTree.setTexture(textureTree);const float TREE_HORIZONTAL_POSITION = 810;const float TREE_VERTICAL_POSITION = 0;spriteTree.setPosition(TREE_HORIZONTAL_POSITION, TREE_VERTICAL_POSITION);// 构建蜜蜂精灵Texture textureBee;textureBee.loadFromFile("graphics/bee.png");Sprite spriteBee;spriteBee.setTexture(textureBee);spriteBee.setPosition(0, 800);// 蜜蜂当前能否移动?bool beeActive = false;// 蜜蜂的飞行速度float beeSpeed = 0.0f;// 通过一个纹理对象构建三个云朵对象Texture textureCloud;// 加载新纹理textureCloud.loadFromFile("graphics/cloud.png");const int NUM_CLOUDS = 6;Sprite clouds[NUM_CLOUDS];int cloudSpeeds[NUM_CLOUDS];bool cloudsActive[NUM_CLOUDS];for (int i = 0; i < NUM_CLOUDS; i++){clouds[i].setTexture(textureCloud);clouds[i].setPosition(-300, i * 150);cloudsActive[i] = false;cloudSpeeds[i] = 0;}//// 三个纹理相同的新精灵//Sprite spriteCloud1;//Sprite spriteCloud2;//Sprite spriteCloud3;//spriteCloud1.setTexture(textureCloud);//spriteCloud2.setTexture(textureCloud);//spriteCloud3.setTexture(textureCloud);//// 让三个云朵精灵位于屏幕左端不同高度上//spriteCloud1.setPosition(0, 0);//spriteCloud2.setPosition(0, 250);//spriteCloud3.setPosition(0, 500);//// 云朵是否处于屏幕内//bool clound1Active = false;//bool clound2Active = false;//bool clound3Active = false;//// 云朵精灵的移动速度//float clound1Speed = 0.0f;//float clound2Speed = 0.0f;//float clound3Speed = 0.0f;// 控制时间的变量Clock clock;// 时间棒RectangleShape timeBar;float timeBarStartWidth = 400;float timeBarHeight = 80;timeBar.setSize(Vector2f(timeBarStartWidth, timeBarHeight));timeBar.setFillColor(Color::Red);timeBar.setPosition((1920 / 2) - timeBarStartWidth / 2, 980);Time gameTimeTotal;float timeRemaining = 6.0f;float timeBarWidthPerSecond = timeBarStartWidth / timeRemaining;// 跟踪游戏是否在运行bool paused = true;// 绘制一些文本int score = 0;Text messageText;Text scoreText;// 需要选择字体Font font;font.loadFromFile("fonts/KOMIKAP_.ttf");// 为消息设置字体messageText.setFont(font);scoreText.setFont(font);// 实际设置消息messageText.setString("Press Enter to start!");scoreText.setString("Score = 0");// 让消息大一些messageText.setCharacterSize(75);scoreText.setCharacterSize(100);// 选择颜色messageText.setFillColor(Color::White);scoreText.setFillColor(Color::White);// 放置消息文本出现在屏幕中心FloatRect textRect = messageText.getLocalBounds();messageText.setOrigin(textRect.left + textRect.width / 2.0f, textRect.top + textRect.height / 2.0f);messageText.setPosition(1920 / 2.0f, 1080 / 2.0f);scoreText.setPosition(20, 20);Texture textureBranch;textureBranch.loadFromFile("graphics/branch.png");// 为每一个枝杈精灵设定纹理for (int i = 0; i < NUM_BRANCHES; i++){branches[i].setTexture(textureBranch);branches[i].setPosition(-2000, -2000);// 设定精灵的原点为其中心// 这样在旋转时便不会改动其位置branches[i].setPosition(220, 20);}// 准备玩家Texture texturePlayer;texturePlayer.loadFromFile("graphics/player.png");Sprite spritePlayer;spritePlayer.setTexture(texturePlayer);spritePlayer.setPosition(580, 720);// 玩家从左侧开始side playerSide = side::LEFT;// 准备墓碑Texture textureRIP;textureRIP.loadFromFile("graphics/rip.png");Sprite spriteRIP;spriteRIP.setTexture(textureRIP);spriteRIP.setPosition(600, 860);// 准备斧头Texture textureAxe;textureAxe.loadFromFile("graphics/axe.png");Sprite spriteAxe;spriteAxe.setTexture(textureAxe);spriteAxe.setPosition(700, 830);// 让斧头与树木并列const float AXE_POSITION_LEFT = 700;const float AXE_POSITION_RIGHT = 1075;// 准备木料Texture textureLog;textureLog.loadFromFile("graphics/log.png");Sprite spriteLog;spriteLog.setTexture(textureLog);spriteLog.setPosition(810, 720);// 木料的辅助变量bool logActive = false;float logSpeedX = 1000;float logSpeedY = -1500;// 控制玩家输入bool acceptInput = false;// 准备声音SoundBuffer chopBuffer;chopBuffer.loadFromFile("sound/chop.wav");Sound chop;chop.setBuffer(chopBuffer);SoundBuffer deathBuffer;deathBuffer.loadFromFile("sound/death.wav");Sound death;death.setBuffer(deathBuffer);SoundBuffer ootBuffer;ootBuffer.loadFromFile("sound/out_of_time.wav");Sound outOfTime;outOfTime.setBuffer(ootBuffer);while (window.isOpen()){/****************************************处理玩家的输入****************************************/Event event;while (window.pollEvent(event)){if (event.type == Event::KeyReleased && !paused){// 重新监听按键动作acceptInput = true;// 隐藏斧头spriteAxe.setPosition(2000, spriteAxe.getPosition().y);}}if (Keyboard::isKeyPressed(Keyboard::Escape)){window.close();}// 开始游戏if (Keyboard::isKeyPressed(Keyboard::Return)){paused = false;// 重置时间与分数score = 0;timeRemaining = 6;// 去除所有枝杈for (int i = 0; i < NUM_BRANCHES; i++){branchPositions[i] = side::NONE;}// 隐藏墓碑spriteRIP.setPosition(650, 2000);// 令玩家就位spritePlayer.setPosition(580, 720);acceptInput = true;}// 封装玩家控制代码以仅在接受输入时加以处理if (acceptInput){// TODO// 处理右方向键if (Keyboard::isKeyPressed(Keyboard::Right)){// 令玩家角色位于右侧playerSide = side::RIGHT;score++;// 增加剩余时间timeRemaining += (2.0 / score) + 0.15;spriteAxe.setPosition(AXE_POSITION_RIGHT, spriteAxe.getPosition().y);spritePlayer.setPosition(1200, 720);// 更新枝杈updateBranches(score);// 将木料置于左侧spriteLog.setPosition(810, 720);logSpeedX = -5000;logActive = true;acceptInput = false;// 播放砍树音效chop.play();}// 处理左方向键if (Keyboard::isKeyPressed(Keyboard::Left)){// 令玩家角色位于左侧playerSide = side::LEFT;score++;// 增加剩余时间timeRemaining += (2.0 / score) + 0.15;spriteAxe.setPosition(AXE_POSITION_LEFT, spriteAxe.getPosition().y);spritePlayer.setPosition(580, 720);// 更新枝杈updateBranches(score);// 设定木料spriteLog.setPosition(810, 720);logSpeedX = 5000;logActive = true;acceptInput = false;}}/****************************************更新场景****************************************/if (!paused){// 测量时间Time dt = clock.restart();// 扣除所消耗的时间timeRemaining -= dt.asSeconds();// 重设时间棒的大小timeBar.setSize(Vector2f(timeBarWidthPerSecond * timeRemaining, timeBarHeight)); // 宽度正比于时间if ((timeRemaining <= 0.0f)){// 暂停游戏paused = true;// 更改显示给玩家的消息messageText.setString("Out of time!!");// 根据新文本重新定位FloatRect textRect = messageText.getLocalBounds();messageText.setOrigin(textRect.left + textRect.width / 2.0f, textRect.top + textRect.height / 2.0f);messageText.setPosition(1920 / 2.0f, 1080 / 2.0f);// 播放超时音效outOfTime.play();}// 设定蜜蜂if (!beeActive){// 蜜蜂的移动速度srand((int)time(0));beeSpeed = (rand() % 200) + 200;// 蜜蜂的移动高度srand((int)time(0) * 10);float height = (rand() % 500) + 500;spriteBee.setPosition(2000, height);beeActive = true;}else// 移动蜜蜂{spriteBee.setPosition(spriteBee.getPosition().x - (beeSpeed * dt.asSeconds()),spriteBee.getPosition().y);// 蜜蜂是否到达屏幕左边界?if (spriteBee.getPosition().x < -100){// 修改此蜜蜂的状态,将其在下一帧中重新出现beeActive = false;}}for (int i = 0; i < NUM_CLOUDS; i++){if (!cloudsActive[i]){// 云朵的移动速度srand((int)time(0) * i);cloudSpeeds[i] = (rand() % 200);// 云朵的移动高度srand((int)time(0) * i);float height = (rand() % 150);clouds[i].setPosition(-200, height);cloudsActive[i] = true;}else{clouds[i].setPosition(clouds[i].getPosition().x +(cloudSpeeds[i] * dt.asSeconds()),clouds[i].getPosition().y);// 云朵是否到达屏幕右边界?if (clouds[i].getPosition().x > 1920){// 修改此云朵的状态,将其在下一帧中重新出现cloudsActive[i] = false;}}}//// 管理云朵//// 云朵1//if (!clound1Active)//{//	// 云朵的移动速度//	srand((int)time(0) * 10);//	clound1Speed = (rand() % 200);//	// 云朵的移动高度//	srand((int)time(0) * 10);//	float height = (rand() % 150);//	spriteCloud1.setPosition(-200, height);//	clound1Active = true;//}//else//{//	spriteCloud1.setPosition(//		spriteCloud1.getPosition().x + (clound1Speed * dt.asSeconds()),//		spriteCloud1.getPosition().y//	);//	// 云朵是否到达屏幕右边界?//	if (spriteCloud1.getPosition().x > 1920)//	{//		// 修改此云朵的状态,将其在下一帧中重新出现//		clound1Active = false;//	}//}//// 云朵2//if (!clound2Active)//{//	// 云朵的移动速度//	srand((int)time(0) * 20);//	clound2Speed = (rand() % 200);//	// 云朵的移动高度//	srand((int)time(0) * 20);//	float height = (rand() % 300) - 150;//	spriteCloud2.setPosition(-200, height);//	clound2Active = true;//}//else//{//	spriteCloud2.setPosition(//		spriteCloud2.getPosition().x + (clound2Speed * dt.asSeconds()),//		spriteCloud2.getPosition().y//	);//	// 云朵是否到达屏幕右边界?//	if (spriteCloud2.getPosition().x > 1920)//	{//		// 修改此云朵的状态,将其在下一帧中重新出现//		clound2Active = false;//	}//}//// 云朵3//if (!clound3Active)//{//	// 云朵的移动速度//	srand((int)time(0) * 30);//	clound3Speed = (rand() % 200);//	// 云朵的移动高度//	srand((int)time(0) * 30);//	float height = (rand() % 450) - 150;//	spriteCloud3.setPosition(-200, height);//	clound3Active = true;//}//else//{//	spriteCloud3.setPosition(//		spriteCloud3.getPosition().x + (clound3Speed * dt.asSeconds()),//		spriteCloud3.getPosition().y//	);//	// 云朵是否到达屏幕右边界?//	if (spriteCloud3.getPosition().x > 1920)//	{//		// 修改此云朵的状态,将其在下一帧中重新出现//		clound3Active = false;//	}//}// 更新分数文本std::stringstream ss;ss << "Score = " << score;scoreText.setString(ss.str());// 更新枝杈精灵for (int i = 0; i < NUM_BRANCHES; i++){float height = i * 150;if (branchPositions[i] == side::LEFT){// 将精灵放在左侧branches[i].setPosition(610, height);// 横向翻转精灵branches[i].setOrigin(220, 40);branches[i].setRotation(180);}else if (branchPositions[i] == side::RIGHT){// 将精灵放在右侧branches[i].setPosition(1330, height);// 重置精灵旋转角branches[i].setOrigin(220, 40);branches[i].setRotation(0);}else{// 隐藏枝杈branches[i].setPosition(3000, height);}}// 处理木料if (logActive){spriteLog.setPosition(spriteLog.getPosition().x + (logSpeedX * dt.asSeconds()),spriteLog.getPosition().y + (logSpeedY * dt.asSeconds()));// 木料是否到达右边界?if (spriteLog.getPosition().x < -100 || spriteLog.getPosition().x > 2000){// 调整木料设定,使其在下一帧中成为新的实例logActive = false;spriteLog.setPosition(810, 720);}}// 玩家是否被枝杈压扁?if (branchPositions[5] == playerSide){// 死亡paused = true;acceptInput = false;// 绘制墓碑spriteRIP.setPosition(525, 760);// 隐藏玩家spritePlayer.setPosition(2000, 660);// 更新消息文本messageText.setString("SQUISHED!!");// 将其置于屏幕中央FloatRect textRect = messageText.getLocalBounds();messageText.setOrigin(textRect.left + textRect.width / 2.0f, textRect.top + textRect.height / 2.0f);messageText.setPosition(1920 / 2.0f, 1080 / 2.0f);// 播放死亡音效death.play();}}/****************************************绘制场景****************************************/// 清空上一帧的内容window.clear();// 绘制游戏场景window.draw(spriteBackground);// 绘制云朵for (int i = 0; i < NUM_CLOUDS; i++){window.draw(clouds[i]);}//window.draw(spriteCloud1);//window.draw(spriteCloud2);//window.draw(spriteCloud3);// 绘制树杈for (int i = 0; i < NUM_BRANCHES; i++){window.draw(branches[i]);}// 绘制树木window.draw(spriteTree);// 绘制玩家window.draw(spritePlayer);// 绘制斧头window.draw(spriteAxe);// 绘制木料window.draw(spriteLog);// 绘制墓碑window.draw(spriteRIP);// 绘制那只昆虫window.draw(spriteBee);// 绘制分数window.draw(scoreText);// 绘制时间棒window.draw(timeBar);if (paused){window.draw(messageText);}// 展示所绘制的全部游戏场景以及内容,有助于避免画面撕裂问题,这就是双重缓冲区技术。window.display();}return 0;
}
http://www.dtcms.com/a/457725.html

相关文章:

  • [Linux系统编程——Lesson4.进程状态]
  • PostIn入门到实战(8) - 如何对接口进行全方位自动化测试,有效确保接口质量
  • 平顶山网站网站建设有赞小程序定制开发
  • 冲床电脑控制器说明书
  • 企业网站优化推广怎么做宁波信息港
  • SortedList
  • 【LeetCode热题100(37/100)】二叉树的最大深度
  • 茂名公司制作网站如何制作网站和软件
  • 网站的种类诺邯郸网站建设
  • 河北建设广州分公司网站黄浦网站设计
  • uniapp 设置主备请求地址切换
  • 深入洞察:华为数字化转型之战略规划
  • 集团网站 wordpress长春朝阳学校网站建设
  • 如何创立网站 优帮云wordpress用户注册插件下载
  • 【2026计算机毕设选题参考】Springboot项目 赋能AI
  • Windows下安装Miniforge3的指南(避坑anaconda收费)
  • Qt C++ :QLayout 布局管理
  • 网站下载app连接怎么做长沙房产
  • 内容网站设计范例百度直播推广
  • 基于AIGC的图表狐深度评测:自然语言生成专业级统计图表的技术实现
  • 怎样做京东网站iis做网站上传速度慢
  • 软考系规:基础篇核心知识整理及助记词分享
  • 5分钟上手 MongoDB:从零安装到第一条数据插入(Windows / macOS / Linux 全平台图解)
  • AI人工智能智域天演电子沙盘数字沙盘系统
  • 各大网站怎么把世界杯做头条泰安网络公司行情
  • 东莞市非凡网站建设网站建设员招聘
  • FreeRTOS任务同步与通信--任务通知
  • 从数据到智能:数据驱动时代下的技术实践与AI融合方法论
  • 2100AI相亲(二)
  • C++游戏编程入门(第三版)——Pong 项目(章节 6 - 7)