diff --git a/enemy.cpp b/enemy.cpp new file mode 100644 index 0000000..3057e53 --- /dev/null +++ b/enemy.cpp @@ -0,0 +1,58 @@ +#include "enemy.hpp" + +Enemy::Enemy(float x, float y, float leftB, float rightB) + : shape(sf::Vector2f(40.f, 50.f)), + leftBound(leftB), + rightBound(rightB), + speed(200.f), + movingRight(true), + aggro(false), + platformY(y) +{ + shape.setPosition(x, y); + shape.setFillColor(sf::Color::Red); // просто колір, без текстури +} + +void Enemy::update(float dt, const sf::Vector2f& playerPos) { + float x = shape.getPosition().x; + + if (aggro) { + if (playerPos.x < x) + x -= speed * dt; + else + x += speed * dt; + } else { + if (movingRight) { + x += speed * dt; + if (x + shape.getSize().x >= rightBound) movingRight = false; + } else { + x -= speed * dt; + if (x <= leftBound) movingRight = true; + } + } + + if (x < leftBound) x = leftBound; + if (x + shape.getSize().x > rightBound) x = rightBound - shape.getSize().x; + + shape.setPosition(x, platformY); +} + +void Enemy::draw(sf::RenderWindow& window) { + window.draw(shape); +} + +void Enemy::setAggro(bool value) { + aggro = value; +} + +float Enemy::getPlatformY() const { + return platformY; +} + +sf::FloatRect Enemy::getBounds() const { + return shape.getGlobalBounds(); +} + +sf::RectangleShape& Enemy::getShape() { + return shape; +} diff --git a/enemy.hpp b/enemy.hpp new file mode 100644 index 0000000..42276d9 --- /dev/null +++ b/enemy.hpp @@ -0,0 +1,25 @@ +#pragma once +#include + +class Enemy { +private: + sf::RectangleShape shape; + + float leftBound; + float rightBound; + float speed; + bool movingRight; + bool aggro; + float platformY; + +public: + Enemy(float x, float y, float leftB, float rightB); + + void update(float dt, const sf::Vector2f& playerPos); + void draw(sf::RenderWindow& window); + + void setAggro(bool value); + float getPlatformY() const; + sf::FloatRect getBounds() const; + sf::RectangleShape& getShape(); +}; diff --git a/game.cpp b/game.cpp index a9a3d5d..f3b3d6c 100644 --- a/game.cpp +++ b/game.cpp @@ -1,23 +1,52 @@ #include #include "vokzal.hpp" +#include "enemy.hpp" +#include +#include + using namespace std; int main() { - // Створюємо вікно 1950x1200 - sf::RenderWindow window(sf::VideoMode(1950, 1200), "Test Game Window"); -// + // Завантаження фону + sf::Texture backgroundTexture; + if (!backgroundTexture.loadFromFile("img/background.png")) { + cout << "ERROR: Cannot load background.png\n"; + } + sf::Sprite backgroundSprite; + backgroundSprite.setTexture(backgroundTexture); + float scaleX = 1950.f / backgroundTexture.getSize().x; + float scaleY = 1200.f / backgroundTexture.getSize().y; + backgroundSprite.setScale(scaleX, scaleY); + + sf::RenderWindow window(sf::VideoMode(1950, 1200), "2D Platformer"); + window.setFramerateLimit(65); + + // Платформи vector platforms; platforms.push_back(Platform(299.f, 900.f, 200.f, 40.f)); platforms.push_back(Platform(700.f, 725.f, 200.f, 40.f)); platforms.push_back(Platform(1300.f, 700.f, 300.f, 50.f)); platforms.push_back(Platform(400.f, 600.f, 250.f, 30.f)); - + platforms.push_back(Platform(700.f, 900.f, 400.f, 30.f)); Platform ground(100.f, 1050.f, 300.f, 50.f); - window.setFramerateLimit(90); //вище 200 не рокимендую піднімати частоту після 600 буде чути писк дроселів - + // Гравець Player player; + // Вороги + vector enemies; + for (auto& platform : platforms) { + float left = platform.getBounds().left; + float top = platform.getBounds().top; + float width = platform.getBounds().width; + enemies.push_back(Enemy(left + 10.f, top - 50.f, left, left + width)); + } + + // Кулі + vector bullets; + sf::Clock fireClock; + float fireCooldown = 0.3f; + sf::Clock gameClock; while (window.isOpen()) { @@ -29,22 +58,102 @@ int main() { float deltaTime = gameClock.restart().asSeconds(); - player.update(deltaTime, platforms); // логіка гравця + // Рух гравця + player.update(deltaTime, platforms); - window.clear(sf::Color::Black); // очистка вікна - - // малюємо платформи - for (auto& platform : platforms) { - platform.draw(window); + // Створення кулі при натиску пробілу + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Space) && + fireClock.getElapsedTime().asSeconds() >= fireCooldown) + { + fireClock.restart(); + sf::Vector2f playerPos( + player.getShape().getPosition().x + player.getShape().getSize().x / 2, + player.getShape().getPosition().y + player.getShape().getSize().y / 2 + ); + bullets.push_back(Bullet(playerPos, player.isFacingRight())); } + + // Оновлення і видалення куль + for (auto it = bullets.begin(); it != bullets.end(); ) { + it->update(deltaTime); + if (it->isOffScreen() || it->collidesWithPlatform(platforms)) { + it = bullets.erase(it); + } else ++it; + } + + // Оновлення ворогів (агро на гравця) + sf::Vector2f playerPosVec(player.getShape().getPosition().x, player.getShape().getPosition().y); + for (auto& enemy : enemies) { + float playerBottom = player.getShape().getPosition().y + player.getShape().getSize().y; + float platformY = enemy.getPlatformY(); + float tolerance = 10.f; + + // якщо гравець на тій же платформі + if (playerBottom >= platformY - tolerance && playerBottom <= platformY + tolerance) { + enemy.setAggro(true); + } else { + enemy.setAggro(false); + } + + enemy.update(deltaTime, playerPosVec); + } + + // Колізія куль з ворогами + for (auto it = bullets.begin(); it != bullets.end(); ) { + bool bulletRemoved = false; + for (auto enemyIt = enemies.begin(); enemyIt != enemies.end(); ++enemyIt) { + if (it->getBounds().intersects(enemyIt->getBounds())) { + it = bullets.erase(it); + enemies.erase(enemyIt); + bulletRemoved = true; + break; + } + } + if (!bulletRemoved) ++it; + } + + // Кінець гри, якщо всі вороги вбиті + if (enemies.empty()) { + cout << "Вітаємо! Ви перемогли всіх ворогів!\n"; + window.close(); + } + + // Колізія ворогів з гравцем + bool playerHit = false; + for (auto& enemy : enemies) { + if (enemy.getBounds().intersects(sf::FloatRect(player.getShape().getPosition(), player.getShape().getSize()))) { + playerHit = true; + break; + } + } + + // Ресет гри + if (playerHit) { + player.setPosition(sf::Vector2f(50.f, 1040.f)); + player.setVelocityY(0.f); + player.setOnGround(true); + + enemies.clear(); + for (auto& platform : platforms) { + float left = platform.getBounds().left; + float top = platform.getBounds().top; + float width = platform.getBounds().width; + enemies.push_back(Enemy(left + 10.f, top - 50.f, left, left + width)); + } + + bullets.clear(); + } + + // Відмалювання + window.clear(sf::Color::Black); + window.draw(backgroundSprite); + for (auto& platform : platforms) platform.draw(window); ground.draw(window); - - // малюємо гравця player.draw(window); - - window.display(); // відображення + for (auto& enemy : enemies) enemy.draw(window); + for (auto& bullet : bullets) bullet.draw(window); + window.display(); } return 0; } - diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..41730ef --- /dev/null +++ b/main.cpp @@ -0,0 +1,37 @@ +#include + +int main() +{ + // Create the main window + sf::RenderWindow app(sf::VideoMode(800, 600), "SFML window"); + + // Load a sprite to display + sf::Texture texture; + if (!texture.loadFromFile("cb.bmp")) + return EXIT_FAILURE; + sf::Sprite sprite(texture); + + // Start the game loop + while (app.isOpen()) + { + // Process events + sf::Event event; + while (app.pollEvent(event)) + { + // Close window : exit + if (event.type == sf::Event::Closed) + app.close(); + } + + // Clear screen + app.clear(); + + // Draw the sprite + app.draw(sprite); + + // Update the window + app.display(); + } + + return EXIT_SUCCESS; +} diff --git a/player.cpp b/player.cpp index 300a984..4f2392f 100644 --- a/player.cpp +++ b/player.cpp @@ -1,99 +1,140 @@ -#include "player.hpp" // Включаємо заголовок класу Player, щоб компілятор знав про його методи та змінні. +#include "player.hpp" +#include using namespace std; -// Конструктор класу Player. Він автоматично викликається при створенні об'єкта Player. Player::Player() - : shape(sf::Vector2f(50.f, 50.f)), // тут лише "чисте" створення - speed(500.f), //швидкість + : shape(sf::Vector2f(60.f, 60.f)), + speed(500.f), velocityY(0.f), - jumpStrength(700.f), // пиржок + jumpStrength(700.f), onGround(true), gravity(1500.f), - groundLevel(1040.f) + groundLevel(1040.f), + facingRight(true), + runFrames(), + idleFrame(), + sprite(), + currentFrame(0), + animationTimer(0.f), + animationSpeed(0.1f), + isMoving(false) { - // А тут уже викликаємо методи, які налаштовують shape shape.setFillColor(sf::Color::Green); - shape.setPosition(50.f, 1040.f); + shape.setPosition(50.f, groundLevel); + + loadIdleFrame(); + loadRunAnimation(); + + sprite.setTexture(idleFrame); + sprite.setOrigin(idleFrame.getSize().x / 2.f, idleFrame.getSize().y / 2.f); + + float scaleX = shape.getSize().x / idleFrame.getSize().x; + float scaleY = shape.getSize().y / idleFrame.getSize().y; + + // Початкове віддзеркалення: PNG дивиться вліво, потрібно вправо + sprite.setScale(-scaleX, scaleY); + + sprite.setPosition( + shape.getPosition().x + shape.getSize().x / 2.f, + shape.getPosition().y + shape.getSize().y / 2.f + ); } +void Player::loadIdleFrame() { + if (!idleFrame.loadFromFile("img/FA_PENGUIN_Idle_000.png")) { + cout << "Cannot load idle frame\n"; + } +} - -// Закоментований метод `update` (робота на потім). -// Він призначений для оновлення логіки гравця у кожному кадрі гри. -// Усередині цього методу, ви будете обробляти рух, зіткнення, анімацію тощо. -// `deltaTime` — це час, що минув з попереднього кадру, і він забезпечує плавний рух незалежно від швидкості комп'ютера. -void Player::update(float deltaTime, const std::vector& platforms) { // Тут буде логіка руху гравця - float moveX = 0.f; - - - - if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)){ - moveX -= speed * deltaTime;} //рух вліво - - if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)){ - moveX += speed * deltaTime;} //рух вправо - - if (sf::Keyboard::isKeyPressed(sf::Keyboard::W) && onGround==true) { - velocityY = -jumpStrength; - } - velocityY += gravity * deltaTime; - - shape.move(moveX, 0.f); - shape.move(0.f , velocityY * deltaTime); - - //перевірка чи є земля та обмеження щоб фігура не вилитіла - - if(shape.getPosition().y + shape.getSize().y >= groundLevel){ - shape.setPosition(shape.getPosition().x, groundLevel -shape.getSize().y); - velocityY = 0; - onGround = true; - }else{ - onGround = false; +void Player::loadRunAnimation() { + runFrames.clear(); + for (size_t i = 0; i < 10; ++i) { // припустимо 10 кадрів + sf::Texture tex; + string filename = "img/04-Run/FA_PENGUIN_Run_00" + to_string(i) + ".png"; + if (!tex.loadFromFile(filename)) { + cout << "Cannot load " << filename << "\n"; + } else { + runFrames.push_back(tex); } - ///////////////////////////////////////////////////// + } +} - sf::FloatRect playerBounds = shape.getGlobalBounds(); +void Player::update(float deltaTime, const std::vector& platforms) { + float moveX = 0.f; + isMoving = false; - for (const auto& platform : platforms) { - sf::FloatRect platformBounds = platform.getBounds(); - if (playerBounds.intersects(platformBounds)){ - if (playerBounds.intersects(platformBounds)) { - // Додатковий if — перевіряємо, чи гравець зверху - if (playerBounds.top + playerBounds.height <= platformBounds.top + 20.f) { - shape.setPosition(shape.getPosition().x, platformBounds.top - shape.getSize().y); + if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) { + moveX -= speed * deltaTime; + facingRight = false; + isMoving = true; + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) { + moveX += speed * deltaTime; + facingRight = true; + isMoving = true; + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::W) && onGround) + velocityY = -jumpStrength; + + velocityY += gravity * deltaTime; + shape.move(moveX, 0.f); + shape.move(0.f, velocityY * deltaTime); + + // Земля + if (shape.getPosition().y + shape.getSize().y >= groundLevel) { + shape.setPosition(shape.getPosition().x, groundLevel - shape.getSize().y); velocityY = 0.f; onGround = true; - }else if(playerBounds.top> platformBounds.top){shape.setPosition(shape.getPosition().x, platformBounds.top +platformBounds.height); - velocityY =0.f;} - } - } + } else onGround = false; + + // Колізії з платформами + for (const auto& platform : platforms) { + sf::FloatRect playerBounds = shape.getGlobalBounds(); + sf::FloatRect platBounds = platform.getBounds(); + if (playerBounds.intersects(platBounds)) { + if (playerBounds.top + playerBounds.height <= platBounds.top + 20.f) { + shape.setPosition(shape.getPosition().x, platBounds.top - shape.getSize().y); + velocityY = 0.f; + onGround = true; + } else if (playerBounds.top > platBounds.top) { + shape.setPosition(shape.getPosition().x, platBounds.top + platBounds.height); + velocityY = 0.f; } - - - - - - ///////////////////////////////////////////////////// - - if(shape.getPosition().x < 0.f) - shape.setPosition(0.f, shape.getPosition().y); - - if(shape.getPosition().x+shape.getSize().x>1925.f) - shape.setPosition(1925.f - shape.getSize().x ,shape.getPosition().y); - - - if(shape.getPosition().y < 0.f) - shape.setPosition(shape.getPosition().x,0.f); - - if(shape.getPosition().y+shape.getSize().y>1200.f) - shape.setPosition(shape.getPosition().x, 1200.f - shape.getSize().y); } + } + // Межі екрану + if (shape.getPosition().x < 0.f) shape.setPosition(0.f, shape.getPosition().y); + if (shape.getPosition().x + shape.getSize().x > 1925.f) + shape.setPosition(1925.f - shape.getSize().x, shape.getPosition().y); -// Метод `draw` для відмальовування гравця на екрані. -// Приймає посилання на вікно, щоб знати, де малювати. -void Player::draw(sf::RenderWindow& window) -{ - // Викликаємо метод `draw` вікна, щоб намалювати наш об'єкт `shape`. - window.draw(shape); + // Анімація + animationTimer += deltaTime; + if (isMoving && !runFrames.empty()) { + if (animationTimer >= animationSpeed) { + animationTimer = 0.f; + currentFrame = (currentFrame + 1) % runFrames.size(); + sprite.setTexture(runFrames[currentFrame]); + } + } else { + sprite.setTexture(idleFrame); + } + + // Центруємо спрайт по хітбоксу + sprite.setOrigin(sprite.getTexture()->getSize().x / 2.f, + sprite.getTexture()->getSize().y / 2.f); + + sprite.setPosition(shape.getPosition().x + shape.getSize().x / 2.f, + shape.getPosition().y + shape.getSize().y / 2.f); + + float scaleX = shape.getSize().x / sprite.getTexture()->getSize().x; + float scaleY = shape.getSize().y / sprite.getTexture()->getSize().y; + + // Віддзеркалення за напрямком погляду + sprite.setScale(facingRight ? -scaleX : scaleX, scaleY); } + +void Player::draw(sf::RenderWindow& window) { + window.draw(sprite); +} + diff --git a/player.hpp b/player.hpp index 49557f5..3d1e4cd 100644 --- a/player.hpp +++ b/player.hpp @@ -1,33 +1,45 @@ - #pragma once #include #include #include "platform.hpp" - -//float velocityY; -//float gravity; -//float jumpStrength; -//bool isOnGround; - - class Player { + sf::RectangleShape shape; + float speed; + float velocityY; + float jumpStrength; + bool onGround; + float gravity; + float groundLevel; + bool facingRight; -private: - sf::RectangleShape shape; // форма гравця - float speed; //швидкість гравця - float velocityY; // швидкість по вертикалі - float jumpStrength; // сила стрибка - bool onGround; //чи стоїть на землі - float gravity; // сила гравітації - float groundLevel; // висота "землі" + std::vector runFrames; + sf::Texture idleFrame; + sf::Sprite sprite; + size_t currentFrame; + float animationTimer; + float animationSpeed; + bool isMoving; + void loadRunAnimation(); + void loadIdleFrame(); public: - Player(); // порожній конструктор - void update(float deltaTime, const std::vector& platforms); + Player(); + void update(float deltaTime, const std::vector& platforms); void draw(sf::RenderWindow& window); + // Геттери + sf::RectangleShape& getShape() { return shape; } + float getVelocityY() const { return velocityY; } + bool isOnGround() const { return onGround; } + + // Сеттери + void setPosition(const sf::Vector2f& pos) { shape.setPosition(pos); } + void setVelocityY(float v) { velocityY = v; } + void setOnGround(bool val) { onGround = val; } + + bool isFacingRight() const { return facingRight; } }; diff --git a/vokzal.hpp b/vokzal.hpp index 02bef39..b0bd00d 100644 --- a/vokzal.hpp +++ b/vokzal.hpp @@ -5,4 +5,3 @@ #include "platform.hpp" #include "bullet.hpp" -