The game works fine, but its using rectangle shapes and I want to render the snake parts using Sprites. Here is the code (Yeah It's not that clean I just wanted to get this working):
#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);
}
The code above does not have sprites functionality currently, but here is an approach I have tried (which worked for the rendering the sprites): Created a map of Textures and Sprites, loaded the textures from the files, mapped them to the Sprites, and then I had a map of Sprites ready. This part was not that hard I had flaws in my rendering logic most likely. Code was taken from when I had the sprite logic, Im basically using a string to determine which sprite I should load.
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
}
}
}
Link for sprites: opengameart.org/content/snake-game-assets. Basically, what I want is some feedback on how to choose the correct sprite to load and for which situation (head and tail would be appreciated too 🥹). Thanks!