游戏运行良好,但它使用矩形形状,我想使用精灵渲染蛇部分。这是代码(是的,它不是那么干净,我只是想让它工作):
#include <SFML/Graphics.hpp> // Graphics module
#include <SFML/Audio.hpp> // Sound module
#include <iostream> // Basic input/output (for errors)
#include <vector> // Dynamic arrays (for snake body)
#include <random> // For random food positions
class Game {
private:
sf::Vector2u windowSize; // Size of the game window
int a, b; // Length and width of a block
sf::RenderWindow window; // Window object for drawing
sf::Font font; // Text font
sf::Clock clock; // For time measurement
std::vector<sf::Vector2i> body; // Snake body (segments as coordinates)
sf::Vector2i food; // Food position
sf::Vector2i direction; // Snake direction
int score; // Player's score
bool gameOver; // Game state: finished or not
int n, m; // Number of rows and columns
float delay, timer; // Update delay and elapsed time
sf::Music eat; // Sound effect for eating
public:
Game(unsigned short x, unsigned short y); // Constructor
void start(); // Start the game
private:
void loop(); // Main game loop
void events(); // Process events (keyboard, etc.)
void update(); // Update game logic
void render(); // Render elements on screen
void gameOverScreen(); // Show "Game Over" screen
sf::Vector2i getFoodPosition(); // Generate new food position
};
int WinMain() {
Game game(800, 600);
game.start();
}
int main() {
Game game(800, 600); // Create object with window size 800x600
game.start(); // Call the start function
}
Game::Game(uint16_t x, uint16_t y) {
windowSize = { x, y }; // Save window width and height
window.create(sf::VideoMode(windowSize.x, windowSize.y, 1), "Snake");
// Create the window
a = 50; // Set block width
b = 50; // Set block height
n = windowSize.x / a; // Calculate number of horizontal blocks
m = windowSize.y / b; // Calculate number of vertical blocks
font.loadFromFile("resources/Fonts/sfpro_bold.OTF"); // Load font for text
eat.openFromFile("resources/Audio/eating_apple.mp3");
}
void Game::start() {
body.clear(); // Clear the snake body
body.push_back({ 5,3 }); // Head
body.push_back({ 4,3 }); // Body segment
body.push_back({ 3,3 }); // Tail
direction = { 0, 0 }; // Direction
food = { getFoodPosition() }; // Initial food position
gameOver = false; // Game not over (false)
score = 0; // Start score from 0
loop(); // Main loop
}
void Game::loop() {
timer = 0.f; // Accumulated time
delay = 0.125f; // Game update delay
while (window.isOpen()) {
events(); // Handle user inputs (keyboard and mouse)
timer += clock.getElapsedTime().asSeconds(); // Add elapsed time in seconds to the timer
if (timer > delay) {
update(); // Update the game (move snake, etc.)
render(); // Render the screen (blocks, snake, food, text, etc.)
timer = 0; // Reset timer
}
clock.restart(); // Restart the clock for the next cycle
}
}
void Game::events() {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed)
window.close();
if (event.type == sf::Event::KeyPressed) {
if (event.key.code == sf::Keyboard::Escape) window.close();
else if (event.key.code == sf::Keyboard::Up && (direction.y != 1)) direction = { 0, -1 };
else if (event.key.code == sf::Keyboard::Down && (direction.y != -1)) direction = { 0, 1 };
else if (event.key.code == sf::Keyboard::Left && (direction.x != 1)) direction = { -1, 0 };
else if (event.key.code == sf::Keyboard::Right && (direction.x != -1)) direction = { 1, 0 };
else if (event.key.code == sf::Keyboard::R) /*if (gameOver)*/ start();
}
}
}
void Game::update() {
if (gameOver || direction == sf::Vector2i{ 0,0 }) return;
sf::Vector2i newHead = body[0] + direction;
for (size_t i = 1; i < body.size(); i++) {
if (newHead == body[i]) {
gameOver = true;
return;
}
}
if (newHead.x > n - 1 || newHead.x < 0 || newHead.y > m - 1 || newHead.y < 0) {
gameOver = true;
return;
}
if (newHead == food) {
body.insert(body.begin(), newHead);
food = getFoodPosition();
score++;
eat.play();
}
else {
body.insert(body.begin(), newHead);
body.pop_back();
}
}
void Game::render() {
window.clear();
sf::RectangleShape block(sf::Vector2f(a, b));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
block.setPosition(i * a, j * b);
int p = (i + j) % 2;
block.setFillColor(sf::Color(172 - p * 7, 214 - p * 7, 67 - p * 7, 255));
window.draw(block);
}
}
block.setPosition(food.x * a, food.y * b);
block.setFillColor(sf::Color::Red);
window.draw(block);
for (int i = 0; i < body.size(); i++) {
if (i == 0)
block.setFillColor(sf::Color::Magenta);
else
block.setFillColor(sf::Color(0, 71, 181));
block.setPosition(body[i].x * a, body[i].y * b);
window.draw(block);
}
sf::Text scr("Score: " + std::to_string(score), font, 32);
scr.setFillColor(sf::Color::White);
scr.setPosition(4, 0);
window.draw(scr);
if (gameOver) gameOverScreen();
window.display();
}
void Game::gameOverScreen() {
eat.stop();
sf::RectangleShape screen({ float(windowSize.x), float(windowSize.y) });
screen.setPosition({ 0,0 });
screen.setFillColor(sf::Color(0, 0, 0, 100));
sf::Text gameOverText(" Game over!\nPress R to restart", font, 38);
gameOverText.setFillColor(sf::Color::White);
gameOverText.setPosition((windowSize.x / 2) - 150, (windowSize.y / 2) - 20);
window.draw(screen);
window.draw(gameOverText);
}
sf::Vector2i Game::getFoodPosition() {
std::random_device randomDevice;
std::mt19937 randomNumberGenerator(randomDevice());
std::uniform_int_distribution<int> distX(0, n - 1);
std::uniform_int_distribution<int> distY(0, m - 1);
sf::Vector2i position;
do {
position.x = distX(randomNumberGenerator);
position.y = distY(randomNumberGenerator);
if (std::find(body.begin(), body.end(), position) == body.end()) {
return position;
}
} while (true);
}
上面的代码目前没有精灵功能,但这是我尝试过的一种方法(适用于渲染精灵):创建纹理和精灵的地图,从文件加载纹理,将它们映射到精灵,然后我准备好了一张精灵地图。这部分并不难,我的渲染逻辑很可能存在缺陷。代码是从我拥有精灵逻辑时获取的,我基本上使用字符串来确定我应该加载哪个精灵。
sf::Vector2i prev = body[i + 1]; //
sf::Vector2i next = body[i - 1]; //because i pop the tail and insert new part at the beginning
if (next.x == prev.x) {
spriteName = "body_vertical"; //vertical sprite |
}
else if (next.y == prev.y) {
spriteName = "body_horizontal"; // horizontal --
}
else {
if (prev.x < next.x) {
if (prev.y > next.y)
spriteName = "body_topright"; // start top curve right downwards
else
spriteName = "body_bottomleft"; // bottom -> left
}
else if (prev.y < next.y) {
spriteName = "body_topleft"; // top -> left
else
spriteName = "body_bottomright"; // bottom -> right
}
}
else{
if (prev.y > next.y) {
spriteName = "body_topleft"; // top -> left
else
spriteName = "body_bottomright"; // bottom -> right
}
else if (prev.y < next.y) {
spriteName = "body_topright"; // top -> right
else
spriteName = "body_bottomleft"; // bottom -> left
}
}
}
精灵链接:opengameart.org/content/snake-game-assets。基本上,我想要的是一些关于如何选择要加载的正确精灵以及在哪种情况下加载的反馈(头部和尾部也将受到赞赏🥹)。谢谢!
这是一个稍微干净一点的代码,
// choosing is based on the assumption
// that the head is the last element in the body array
// if not, you can simply reverse the conditions
if (next == /*last in body*/) {
spriteName == "head_sprite";
} else if (next == /*first in body*/) {
spriteName = "tail_sprite";
} else if (next.x == prev.x || next.y == prev.y) {
spriteName == "body_sprite";
} else {
spriteName = "curved_sprite";
}
// now rotate and reverse
if (prev.x == next.x) {
// rotate 90deg
} else if (prev.x > next.x) {
// reverse horizontally
}
if (prev.y > next.y) {
// reverse vertically
}
旋转/反转精灵比根据每种情况选择单独的精灵要方便得多。
我从您附加的那些精灵中选择了snake_graphics.zip
[“head_down”,“tail_down”,“body_vertical”,“body_bottomright”]
并将它们重命名为
[“head_sprite”、“tail_sprite”、“body_sprite”、“curved_sprite”]
分别。 我可能错误地旋转/反转了精灵 因为我不确定上一个还是下一个是当前的(“假设上一个是”),但这就是基本思想