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

C++游戏开发入门:Timber!!!项目

C++

“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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值