如何使用 SFML 和 C++ 渲染贪吃蛇游戏的精灵

问题描述 投票:0回答:1

游戏运行良好,但它使用矩形形状,我想使用精灵渲染蛇部分。这是代码(是的,它不是那么干净,我只是想让它工作):

#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。基本上,我想要的是一些关于如何选择要加载的正确精灵以及在哪种情况下加载的反馈(头部和尾部也将受到赞赏🥹)。谢谢!

c++ logic rendering sprite sfml
1个回答
0
投票

这是一个稍微干净一点的代码,

// 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”]

分别。 我可能错误地旋转/反转了精灵 因为我不确定上一个还是下一个是当前的(“假设上一个是”),但这就是基本思想

© www.soinside.com 2019 - 2024. All rights reserved.