小游戏和GUI编程(4) | 基于 SFML 的黑客帝国字符雨

2024-02-10 18:20

本文主要是介绍小游戏和GUI编程(4) | 基于 SFML 的黑客帝国字符雨,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

小游戏和GUI编程(4) | 基于 SFML 的黑客帝国字符雨

文章目录

  • 小游戏和GUI编程(4) | 基于 SFML 的黑客帝国字符雨
    • 1. 简介
    • 2. 规划
    • 3. 一个字符的下落
    • 3. 一个雨滴的下落
    • 4. 每个雨滴都是独一无二的
    • 5. 重构后, 降落多个雨滴
    • 6. 总结
    • 7. 参考

1. 简介

使用 SFML 实现黑客帝国字符雨的动态下落效果。

2. 规划

  • 一个字符下落
  • 一个雨滴的下落
  • 每个雨滴都是独一无二的
  • 重构后, 降落多个雨滴

3. 一个字符的下落

请添加图片描述

显示 SFML 窗口, 在窗口的中轴线上绘制一个降落的 H 字母, 字母是绿色的, 背景是黑色的。 每更新一帧, H 的位置就下降 16 个像素。 H 字母的大小也是 16 像素。

由于降落的比较快, 手动控制帧率为 10 FPS.

关键代码:

window.setFramerateLimit(10);...window.clear();sf::Text text;
text.setFont(font);
text.setFillColor(sf::Color::Green);
text.setCharacterSize(16);
text.setString(std::string(1, 'H'));
text.setPosition(x, y);
y = (y + 16) % win_height;
window.draw(text);window.display();

完整代码:

#include <SFML/Graphics.hpp>int main()
{constexpr int win_width = 640;constexpr int win_height = 480;sf::VideoMode videomode(win_width, win_height);const std::string title = "Matrix Rain SFML";sf::RenderWindow window(videomode, title);window.setFramerateLimit(10);sf::Font font;const std::string asset_dir = "../Resources";if (!font.loadFromFile(asset_dir + "/Arial.ttf")){printf("Error: font not found\n");return 1;}int x = win_width / 2;int y = 0;while (window.isOpen()){sf::Event event;while (window.pollEvent(event)){if (event.type == sf::Event::Closed) { window.close(); }}window.clear();sf::Text text;text.setFont(font);text.setFillColor(sf::Color::Green);text.setCharacterSize(16);text.setString(std::string(1, 'H'));text.setPosition(x, y);y = (y + 16) % win_height;window.draw(text);window.display();}return 0;
}

3. 一个雨滴的下落

一个雨滴的下落, 说的是一个动态的过程, 由于它下落的很快, 可以同时显示它的多个位置, 因此纵向方向绘制多个字符, 就表示出了一个时间间隔内的雨滴降落轨迹。

    std::string seq = "abcdefghijklmnopqrstuvwxyz";for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color::Green);text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16;text.setPosition(x, y);window.draw(text);}

雨滴轨迹中最下方的是现在的位置, 颜色最深, 最上面的颜色最浅。 因此可以根据雨滴的的长度和位置, 使用渐变的颜色, 来表达雨滴的轨迹:

    std::string seq = "abcdefghijklmnopqrstuvwxyz";for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color(0, ((i+1) * 1.0/seq.size())*255, 0));text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16;text.setPosition(x, y);window.draw(text);}

雨滴的轨迹, 是从上往下降落的, 因此当我们只关注单个雨滴的颜色最深的绿色字符时, 它的位置应该是从上往下的:

int base_y = 0;while(window.isOpen())
{std::string seq = "hello world";for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color(0, ((i+1) * 1.0/seq.size())*255, 0));text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16 + base_y;text.setPosition(x, y);window.draw(text);}base_y = (base_y + 16) % win_height;
}

效果:
请添加图片描述

4. 每个雨滴都是独一无二的

每个雨滴对应的字符串是不同的(字符数量, 字符内容), 出现位置也不相同。

对于单个雨滴, 首先随机化它的出现位置, 也就是 x 坐标; 而在这个雨滴降落到窗口最下方之前, x 坐标需要保持不变; 当降落到窗口最下方时, 重新随机化 x 坐标:

x = rand() % (win_width / 16) * 16;while (window.isOpen())
{std::string seq = "hello world";for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color(0, ((i+1) * 1.0/seq.size())*255, 0));text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16 + base_y;text.setPosition(x, y);window.draw(text);}base_y = base_y + 16;if (base_y >= win_height){base_y = 0;x = rand() % (win_width / 16) * 16;}
}

请添加图片描述

对于字符内容、 字符长度, 也需要在每次雨滴到达窗口最下方的时候更新:

static std::string get_random_seq(int& seqlen)
{seqlen = rand() % 10 + 5;std::string seq(seqlen, ' ');for (int i = 0; i < seq.size(); i++){seq[i] = (rand() % 26) + 'a';}return seq;
}std::string seq = get_random_seq(seqlen);
int base_y = 0;
while (window.isOpen())
{window.clear();for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color(0, ((i+1) * 1.0/seq.size())*255, 0));text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16 + base_y;text.setPosition(x, y);window.draw(text);}base_y = base_y + 16;if (base_y >= win_height){base_y = 0;x = rand() % (win_width / 16) * 16;seq = get_random_seq(seqlen);}window.display();
}

5. 重构后, 降落多个雨滴

首先, 针对单个雨滴的降落, 把之前的面向过程的写法重构为 OOP 写法. 通过封装为 class Rain, 提供的 drop() 方法完成了渲染, 使得 main loop 代码变得清爽:

#include <SFML/Graphics.hpp>static std::string get_random_seq(int& seqlen)
{seqlen = rand() % 10 + 5;std::string seq(seqlen, ' ');for (int i = 0; i < seq.size(); i++){seq[i] = (rand() % 26) + 'a';}return seq;
}class Rain
{
public:Rain(int a_win_width, int a_win_height, sf::Font& a_font){seqlen = rand() % 10 + 5;seq = get_random_seq(seqlen);x = rand() % (win_width / 16) * 16;font = a_font;base_y = 0;win_width = a_win_width;win_height = a_win_height;}std::string seq;int seqlen;int x;sf::Font font;int base_y;int y = 0;int win_width;int win_height;void drop(sf::RenderWindow& window){for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color(0, ((i+1) * 1.0/seq.size())*255, 0));text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16 + base_y;text.setPosition(x, y);window.draw(text);}base_y = base_y + 16;if (base_y >= win_height){base_y = 0;x = rand() % (win_width / 16) * 16;seq = get_random_seq(seqlen);}}
};int main()
{srand((unsigned)time(NULL));constexpr int win_width = 640;constexpr int win_height = 480;sf::VideoMode videomode(win_width, win_height);const std::string title = "Matrix Rain SFML";sf::RenderWindow window(videomode, title);window.setFramerateLimit(10);sf::Font font;const std::string asset_dir = "../Resources";if (!font.loadFromFile(asset_dir + "/Arial.ttf")){printf("Error: font not found\n");return 1;}Rain rain(win_width, win_height, font);while (window.isOpen()){sf::Event event;while (window.pollEvent(event)){if (event.type == sf::Event::Closed) { window.close(); }}window.clear(sf::Color::Black);rain.drop(window);window.display();}return 0;
}

其次, 为了渲染多个雨滴, 每个雨滴的都需要执行 drop() 函数。 由于 drop() 函数里的最后判断重置的代码, 和构造函数里重复了, 因此进一步提取出 reset() 成员函数。

最终代码

#include <SFML/Graphics.hpp>static std::string get_random_seq(int& seqlen)
{seqlen = rand() % 20 + 8;std::string seq(seqlen, ' ');for (int i = 0; i < seq.size(); i++){seq[i] = (rand() % 26) + 'a';}return seq;
}class Rain
{
public:Rain(int a_win_width, int a_win_height, sf::Font& a_font){srand((unsigned)time(NULL));font = a_font;win_width = a_win_width;win_height = a_win_height;reset();}void reset(){seq = get_random_seq(seqlen);x = rand() % (win_width / 16) * 16;base_y = 0;}std::string seq;int seqlen;int x;sf::Font font;int base_y;int y = 0;int win_width;int win_height;void drop(sf::RenderWindow& window){for (int i = 0; i < seq.size(); i++){sf::Text text;text.setFont(font);text.setFillColor(sf::Color(0, ((i+1) * 1.0/seq.size())*255, 0));text.setCharacterSize(16);text.setString(std::string(1, seq[i]));y = i * 16 + base_y;text.setPosition(x, y);window.draw(text);if (y >= win_height){reset();break;}}base_y += 16;}
};int main()
{constexpr int win_width = 800;constexpr int win_height = 600;sf::VideoMode videomode(win_width, win_height);const std::string title = "Matrix Rain SFML";sf::RenderWindow window(videomode, title);window.setFramerateLimit(10);sf::Font font;const std::string asset_dir = "../Resources";if (!font.loadFromFile(asset_dir + "/Arial.ttf")){printf("Error: font not found\n");return 1;}std::vector<Rain> rains(40, Rain(win_width, win_height, font));for (int i = 0; i < rains.size(); i++){rains[i].reset();}while (window.isOpen()){sf::Event event;while (window.pollEvent(event)){if (event.type == sf::Event::Closed) { window.close(); }}window.clear(sf::Color::Black);for (auto& rain: rains){rain.drop(window);}window.display();}return 0;
}

最终效果:

请添加图片描述

6. 总结

本文提供了基于 SFML 的仿黑客帝国的雨滴下落效果的的动态窗口显示, 从最简单的单个字符下落显示, 到最终的多雨滴、随机出现位置、序列长度随机化, 每一步都给出了代码和基本的解释。 在简介阶段只做了初步的任务划分, 在每一小节则进一步细化需求, 通过一步或多步实现了效果, 并在必要的时候(单个雨滴到多个雨滴)时, 通过重构代码, 降低了代码调用的复杂度, 从而为多个雨滴的降落效果提供了便利。

7. 参考

  • https://www.cnblogs.com/zjutzz/p/17067234.html

这篇关于小游戏和GUI编程(4) | 基于 SFML 的黑客帝国字符雨的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/697778

相关文章

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Python GUI框架中的PyQt详解

《PythonGUI框架中的PyQt详解》PyQt是Python语言中最强大且广泛应用的GUI框架之一,基于Qt库的Python绑定实现,本文将深入解析PyQt的核心模块,并通过代码示例展示其应用场... 目录一、PyQt核心模块概览二、核心模块详解与示例1. QtCore - 核心基础模块2. QtWid

Python异步编程中asyncio.gather的并发控制详解

《Python异步编程中asyncio.gather的并发控制详解》在Python异步编程生态中,asyncio.gather是并发任务调度的核心工具,本文将通过实际场景和代码示例,展示如何结合信号量... 目录一、asyncio.gather的原始行为解析二、信号量控制法:给并发装上"节流阀"三、进阶控制

C语言字符函数和字符串函数示例详解

《C语言字符函数和字符串函数示例详解》本文详细介绍了C语言中字符分类函数、字符转换函数及字符串操作函数的使用方法,并通过示例代码展示了如何实现这些功能,通过这些内容,读者可以深入理解并掌握C语言中的字... 目录一、字符分类函数二、字符转换函数三、strlen的使用和模拟实现3.1strlen函数3.2st

C# string转unicode字符的实现

《C#string转unicode字符的实现》本文主要介绍了C#string转unicode字符的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录1. 获取字符串中每个字符的 Unicode 值示例代码:输出:2. 将 Unicode 值格式化

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

Go语言使用Buffer实现高性能处理字节和字符

《Go语言使用Buffer实现高性能处理字节和字符》在Go中,bytes.Buffer是一个非常高效的类型,用于处理字节数据的读写操作,本文将详细介绍一下如何使用Buffer实现高性能处理字节和... 目录1. bytes.Buffer 的基本用法1.1. 创建和初始化 Buffer1.2. 使用 Writ