commit ac521c3129cc7d6cda602a998ae5cd5643ab6b50 Author: NaiJi Date: Sat Jan 9 13:41:58 2021 +0300 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94bcf1c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.ogg +*.png +*user* diff --git a/application.cpp b/application.cpp new file mode 100644 index 0000000..1fc7a7c --- /dev/null +++ b/application.cpp @@ -0,0 +1,37 @@ +#include "application.h" +#include "game.h" + +constexpr int SCREEN_WIDTH = 640; +constexpr int SCREEN_HEIGHT = 480; +const sf::Time TIME_PER_SECOND = sf::seconds(1.f / 60.f); + +Application::Application() : + render_window({SCREEN_WIDTH, SCREEN_HEIGHT}, "Test") +{} + +void Application::registerStates() +{ + map_states[State::Tag::Game] = std::make_unique(render_window, this); + + active_state_tag = State::Tag::Game; +} + +void Application::run() +{ + sf::Clock clock; + sf::Time since_last_update = sf::Time::Zero; + + while (render_window.isOpen()) + { + map_states[active_state_tag]->processInput(); + sf::Time delta = clock.restart(); + + since_last_update -= TIME_PER_SECOND; + map_states[active_state_tag]->processInput(); + + //if (!b_pause) + map_states[active_state_tag]->update(delta); + + map_states[active_state_tag]->draw(); + } +} diff --git a/application.h b/application.h new file mode 100644 index 0000000..a9743eb --- /dev/null +++ b/application.h @@ -0,0 +1,23 @@ +#ifndef APPLICATION_H +#define APPLICATION_H + +#include "state.h" +#include +#include + +class Application +{ +public: + Application(); + + void registerStates(); + void run(); + +private: + sf::RenderWindow render_window; + + std::map map_states; + State::Tag active_state_tag; +}; + +#endif // APPLICATION_H diff --git a/command.h b/command.h new file mode 100644 index 0000000..f754cdb --- /dev/null +++ b/command.h @@ -0,0 +1,36 @@ +#ifndef COMMAND_H +#define COMMAND_H + +#include + +namespace Category +{ + enum Type + { + None = 0, + Scene = 1 << 0, + Player = 1 << 1, + Enemy = 1 << 2 + }; +} + +class SceneNode; +namespace sf { class Time; } + +struct Command +{ + std::function action; + Category::Type category; +}; + +template +std::function derivedAction(Functon func) +{ + return [=] (SceneNode& node, const sf::Time& time) + { + //assert(dynamic_cast(&node)); + func(static_cast(node), time); + }; +} + +#endif // COMMAND_H diff --git a/danmaku.pro b/danmaku.pro new file mode 100644 index 0000000..1299662 --- /dev/null +++ b/danmaku.pro @@ -0,0 +1,31 @@ +TEMPLATE = app +CONFIG += c++17 +CONFIG -= app_bundle +CONFIG -= qt + +SOURCES += \ + application.cpp \ + entity.cpp \ + enemy.cpp \ + game.cpp \ + main.cpp \ + player.cpp \ + scenenode.cpp \ + spritenode.cpp \ + state.cpp \ + world.cpp + +HEADERS += \ + application.h \ + command.h \ + entity.h \ + enemy.h \ + game.h \ + player.h \ + resourceholder.h \ + scenenode.h \ + spritenode.h \ + state.h \ + world.h + +unix:LIBS += -lsfml-graphics -lsfml-audio -lsfml-window -lsfml-system diff --git a/enemy.cpp b/enemy.cpp new file mode 100644 index 0000000..4899ced --- /dev/null +++ b/enemy.cpp @@ -0,0 +1,48 @@ +#include "enemy.h" + +// /////////////////////////////////////////////////////////// // + +static Textures::Id toTextureID(const Enemy::Type& type) +{ + switch (type) + { + case Enemy::Type::Shotguner: + return Textures::Id::Shotguner; + + case Enemy::Type::Weakling: + return Textures::Id::Weakling; + + case Enemy::Type::Player: + return Textures::Id::Player; + } + + return Textures::Id::Weakling; +} + +// /////////////////////////////////////////////////////////// // + +Enemy::Enemy(Type type, const TextureHolder& texture_holder) : + enemy_type(type), + enemy_sprite(texture_holder.get(toTextureID(type))) +{ + const sf::FloatRect bounds = enemy_sprite.getLocalBounds(); + enemy_sprite.setOrigin(bounds.width / 2.f, bounds.height / 2.f); +} + +Enemy::~Enemy() +{} + +Enemy::Type Enemy::type() const +{ + return enemy_type; +} + +Category::Type Enemy::category() const +{ + return Category::Type::Enemy; +} + +void Enemy::drawCurrent(sf::RenderTarget& target, sf::RenderStates states) const +{ + target.draw(enemy_sprite, states); +} diff --git a/enemy.h b/enemy.h new file mode 100644 index 0000000..1c0f924 --- /dev/null +++ b/enemy.h @@ -0,0 +1,36 @@ +#ifndef FAIRY_H +#define FAIRY_H + +#include "entity.h" +#include "resourceholder.h" + +#include + +class Enemy : public Entity +{ +public: + + enum class Type + { + Player, + Weakling, + Shotguner + }; + + explicit Enemy(Type type, const TextureHolder& texture_holder); + virtual ~Enemy() override; + + Type type() const; + Category::Type category() const override; + +private: + Type enemy_type; + sf::Sprite enemy_sprite; + + virtual void drawCurrent(sf::RenderTarget& target, sf::RenderStates states) const override; +}; + +using EnemySPtr = std::shared_ptr; +using EnemyUPtr = std::unique_ptr; + +#endif // FAIRY_H diff --git a/entity.cpp b/entity.cpp new file mode 100644 index 0000000..bf220f5 --- /dev/null +++ b/entity.cpp @@ -0,0 +1,34 @@ +#include "entity.h" + +Entity::Entity() : + vector_velocity(0.f, 0.f) +{} + +Entity::~Entity() +{} + +void Entity::setVelocity(sf::Vector2f vel) +{ + vector_velocity = vel; +} + +void Entity::setVelocity(float vx, float vy) +{ + vector_velocity.x = vx; + vector_velocity.y = vy; +} + +const sf::Vector2f& Entity::velocity() const +{ + return vector_velocity; +} + +void Entity::accelerate(sf::Vector2f vel) +{ + vector_velocity += vel; +} + +void Entity::updateCurrent(const sf::Time& dt) +{ + move(vector_velocity * dt.asSeconds()); +} diff --git a/entity.h b/entity.h new file mode 100644 index 0000000..c67bf7c --- /dev/null +++ b/entity.h @@ -0,0 +1,26 @@ +#ifndef ENTITY_H +#define ENTITY_H + +#include "scenenode.h" + +#include + +class Entity : public SceneNode +{ +public: + explicit Entity(); + virtual ~Entity() = 0; + + void setVelocity(sf::Vector2f vel); + void setVelocity(float vx, float vy); + const sf::Vector2f& velocity() const; + void accelerate(sf::Vector2f vel); + + +private: + sf::Vector2f vector_velocity; + + virtual void updateCurrent(const sf::Time& dt) override; +}; + +#endif // ENTITY_H diff --git a/game.cpp b/game.cpp new file mode 100644 index 0000000..41b8c91 --- /dev/null +++ b/game.cpp @@ -0,0 +1,57 @@ +#include "game.h" + +Game::Game(sf::RenderWindow &window, Application *application) : + State(window, application), + world(render_window) +{} + +Game::~Game() +{} + +bool Game::processInput() +{ + std::queue& queue = world.getCommandQueue(); + + sf::Event event; + while (render_window.pollEvent(event)) + { + player.handleEvent(event, queue); + + switch (event.type) + { + case sf::Event::GainedFocus: + b_pause = false; + break; + + case sf::Event::LostFocus: + b_pause = true; + break; + + case sf::Event::Closed: + render_window.close(); + break; + + default: + break; + } + } + + player.handleRealtimeInput(queue); + + return true; +} + +bool Game::update(const sf::Time &dt) +{ + world.update(dt); + + return true; +} + +void Game::draw() +{ + render_window.clear(); + world.draw(); + render_window.setView(render_window.getDefaultView()); + render_window.display(); +} diff --git a/game.h b/game.h new file mode 100644 index 0000000..c174b46 --- /dev/null +++ b/game.h @@ -0,0 +1,31 @@ +#ifndef GAME_H +#define GAME_H + +#include "world.h" +#include "player.h" +#include "state.h" + +#include +#include + +class Game final : public State +{ +public: + Game(sf::RenderWindow& window, Application *application); + virtual ~Game() override; + + void run(); + +private: + World world; + Player player; + + bool b_pause; + bool b_to_right, b_to_left, b_to_up, b_to_down; + + bool processInput() override; + bool update(const sf::Time& dt) override; + void draw() override; +}; + +#endif // GAME_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..55c251b --- /dev/null +++ b/main.cpp @@ -0,0 +1,8 @@ +#include "application.h" + +int main() +{ + Application app; + app.registerStates(); + app.run(); +} diff --git a/player.cpp b/player.cpp new file mode 100644 index 0000000..81e5599 --- /dev/null +++ b/player.cpp @@ -0,0 +1,57 @@ +#include "player.h" +#include "command.h" +#include "enemy.h" + +Player::Player() : + player_speed(45.f) +{ + map_key_bindings[sf::Keyboard::Left] = Action::MoveLeft; + map_key_bindings[sf::Keyboard::Right] = Action::MoveRight; + map_key_bindings[sf::Keyboard::Up] = Action::MoveUp; + map_key_bindings[sf::Keyboard::Down] = Action::MoveDown; + + map_action_bindings[Action::MoveLeft].action = + [&] (SceneNode& node, const sf::Time& time) + { + node.move(-player_speed * time.asSeconds(), 0.f); + }; + + map_action_bindings[Action::MoveRight].action = + [&] (SceneNode& node, const sf::Time& time) + { + node.move(player_speed * time.asSeconds(), 0.f); + }; + + map_action_bindings[Action::MoveUp].action = + [&] (SceneNode& node, const sf::Time& time) + { + node.move(0.f, -player_speed * time.asSeconds()); + }; + + map_action_bindings[Action::MoveDown].action = + [&] (SceneNode& node, const sf::Time& time) + { + node.move(0.f, player_speed * time.asSeconds()); + }; + + for (auto& pair : map_action_bindings) + pair.second.category = Category::Enemy; +} + +void Player::handleRealtimeInput(std::queue& queue) +{ + for (const auto& pair : map_key_bindings) + if (sf::Keyboard::isKeyPressed(pair.first) && isRealtime(pair.second)) + queue.push(map_action_bindings[pair.second]); +} + +void Player::handleEvent(const sf::Event &event, std::queue &queue) +{ + switch (event.type) + { + case sf::Event::KeyPressed: + break; + default: + break; + } +} diff --git a/player.h b/player.h new file mode 100644 index 0000000..afd281a --- /dev/null +++ b/player.h @@ -0,0 +1,37 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#include +#include +#include + +class Command; + +class Player +{ +public: + Player(); + + enum class Action + { + MoveLeft, + MoveRight, + MoveUp, + MoveDown + }; + + void assignKey(Action action, sf::Keyboard::Key key); + sf::Keyboard::Key getAssignedKey(Action action) const; + + void handleEvent(const sf::Event& event, std::queue& queue); + void handleRealtimeInput(std::queue& queue); + +private: + float player_speed; + std::map map_key_bindings; + std::map map_action_bindings; + + static bool isRealtime(Action action) {return true;} +}; + +#endif // PLAYER_H diff --git a/resourceholder.h b/resourceholder.h new file mode 100644 index 0000000..5c220f5 --- /dev/null +++ b/resourceholder.h @@ -0,0 +1,55 @@ +#ifndef RESOURCEHOLDER_H +#define RESOURCEHOLDER_H + +#include +#include +#include +#include + +namespace sf { class Texture; class Font; } + +template +class ResourceHolder +{ +public: + bool load(Id id, const std::string& filename) + { + std::unique_ptr resource(new Resource()); + if (!resource->loadFromFile(filename)) + return false; + + map_resources[id] = std::move(resource); + + return true; + } + + Resource& get(Id id) const + { + const auto found = map_resources.find(id); + assert(found != map_resources.end()); + + return *found->second; + } + +private: + std::map> map_resources; +}; + +// /////////////////////////////////////////////////////////// // + +namespace Textures +{ + enum class Id + { + Player, + Weakling, + Shotguner, + + Background + }; +} + +using TextureHolder = ResourceHolder; +using FontHolder = ResourceHolder; + +#endif // RESOURCEHOLDER_H diff --git a/scenenode.cpp b/scenenode.cpp new file mode 100644 index 0000000..d1d5a9d --- /dev/null +++ b/scenenode.cpp @@ -0,0 +1,92 @@ +#include "scenenode.h" +#include + +SceneNode::SceneNode() +{} + +SceneNode::~SceneNode() +{} + +bool SceneNode::attachChild(const SceneNodeSPtr &child) +{ + child->parent = shared_from_this(); + vec_children.emplace_back(std::move(child)); + + return true; +} + +SceneNodeSPtr SceneNode::detachChild(const SceneNode &node) +{ + const auto found = std::find_if(vec_children.begin(), vec_children.end(), + [&] (SceneNodeSPtr& p) -> bool + { + return p.get() == &node; + }); + + if (found == vec_children.end()) + return nullptr; + + SceneNodeSPtr result = *found; + result->parent = nullptr; + vec_children.erase(found); + + return result; +} + +sf::Transform SceneNode::getWorldTransform() const +{ + sf::Transform transform = sf::Transform::Identity; + + for (SceneNodeConstSPtr node = shared_from_this(); node != nullptr; node = node->parent) + { + transform *= node->getTransform(); + } + + return transform; +} + +sf::Vector2f SceneNode::getWorldPosition() const +{ + return getWorldTransform() * sf::Vector2f(); +} + +void SceneNode::draw(sf::RenderTarget& target, sf::RenderStates states) const +{ + states.transform *= getTransform(); + drawCurrent(target, states); + + for (const auto& ch : vec_children) + ch->draw(target, states); +} + +void SceneNode::update(const sf::Time& dt) +{ + updateCurrent(dt); + updateChildren(dt); +} + +void SceneNode::onCommand(const Command& command, const sf::Time& dt) +{ + if (command.category & category()) + command.action(*shared_from_this(), dt); + + for (auto& ch : vec_children) + ch->onCommand(command, dt); +} + +Category::Type SceneNode::category() const +{ + return Category::Type::Scene; +} + +void SceneNode::drawCurrent(sf::RenderTarget& target, sf::RenderStates states) const +{} + +void SceneNode::updateCurrent(const sf::Time& dt) +{} + +void SceneNode::updateChildren(const sf::Time &dt) +{ + for (SceneNodeSPtr& child : vec_children) + child->update(dt); +} diff --git a/scenenode.h b/scenenode.h new file mode 100644 index 0000000..b5c9313 --- /dev/null +++ b/scenenode.h @@ -0,0 +1,45 @@ +#ifndef SCENENODE_H +#define SCENENODE_H + +#include "command.h" +#include +#include +#include + +class SceneNode; +using SceneNodeUPtr = std::unique_ptr; +using SceneNodeSPtr = std::shared_ptr; +using SceneNodeConstSPtr = std::shared_ptr; + +class SceneNode : public std::enable_shared_from_this, + public sf::Transformable, + public sf::Drawable, + private sf::NonCopyable +{ +public: + explicit SceneNode(); + virtual ~SceneNode(); + + bool attachChild(const SceneNodeSPtr &child); + SceneNodeSPtr detachChild(const SceneNode& node); + + sf::Transform getWorldTransform() const; + sf::Vector2f getWorldPosition() const; + + void update(const sf::Time& dt); + void onCommand(const Command& command, const sf::Time& dt); + + virtual Category::Type category() const; + +private: + std::vector vec_children; + SceneNodeSPtr parent; + + virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override final; + virtual void drawCurrent(sf::RenderTarget& target, sf::RenderStates states) const; + virtual void updateCurrent(const sf::Time& dt); + + void updateChildren(const sf::Time& dt); +}; + +#endif // SCENENODE_H diff --git a/spritenode.cpp b/spritenode.cpp new file mode 100644 index 0000000..8f40449 --- /dev/null +++ b/spritenode.cpp @@ -0,0 +1,20 @@ +#include "spritenode.h" + +SpriteNode::SpriteNode(const sf::Texture& texture) +{ + sprite.setTexture(texture); +} + +SpriteNode::~SpriteNode() +{} + +SpriteNode::SpriteNode(const sf::Texture& texture, const sf::IntRect& rect) +{ + sprite.setTexture(texture); + sprite.setTextureRect(rect); +} + +void SpriteNode::drawCurrent(sf::RenderTarget& target, sf::RenderStates states) const +{ + target.draw(sprite, states); +} diff --git a/spritenode.h b/spritenode.h new file mode 100644 index 0000000..8a398d6 --- /dev/null +++ b/spritenode.h @@ -0,0 +1,22 @@ +#ifndef SPRITENODE_H +#define SPRITENODE_H + +#include "scenenode.h" + +class SpriteNode : public SceneNode +{ +public: + explicit SpriteNode(const sf::Texture& texture); + explicit SpriteNode(const sf::Texture& texture, + const sf::IntRect& rect); + virtual ~SpriteNode(); + +private: + sf::Sprite sprite; + virtual void drawCurrent(sf::RenderTarget& target, sf::RenderStates states) const; +}; + +using SpriteNodeUPtr = std::unique_ptr; +using SpriteNodeSPtr = std::shared_ptr; + +#endif // SPRITENODE_H diff --git a/state.cpp b/state.cpp new file mode 100644 index 0000000..8d041d7 --- /dev/null +++ b/state.cpp @@ -0,0 +1,9 @@ +#include "state.h" + +State::State(sf::RenderWindow &window, Application *application) : + render_window(window), + app(application) +{} + +State::~State() +{} diff --git a/state.h b/state.h new file mode 100644 index 0000000..0cc132d --- /dev/null +++ b/state.h @@ -0,0 +1,35 @@ +#ifndef STATE_H +#define STATE_H + +#include +#include +#include +#include + +class Application; + +class State +{ +public: + explicit State(sf::RenderWindow& window, Application *application); + virtual ~State() = 0; + + virtual void draw() = 0; + virtual bool update(const sf::Time& dt) = 0; + virtual bool processInput() = 0; + + enum class Tag + { + Menu, + Game + }; + +protected: + sf::RenderWindow& render_window; + Application *app; +}; + +using StateUPtr = std::unique_ptr; +using StateSPtr = std::shared_ptr; + +#endif // STATE_H diff --git a/world.cpp b/world.cpp new file mode 100644 index 0000000..69d6e9d --- /dev/null +++ b/world.cpp @@ -0,0 +1,84 @@ +#include "world.h" +#include "spritenode.h" +#include "enemy.h" +#include + +World::World(sf::RenderWindow& window) : + world_window(window), + world_view(window.getDefaultView()), + scene_graph(new SceneNode()), + world_bounds(0.f, 0.f, world_view.getSize().x, 2000.f), + world_spawn_pos(world_view.getSize().x / 2.f, world_bounds.height - world_view.getSize().y), + player(nullptr) +{ + loadTextures(); + buildScene(); + + world_view.setCenter(world_spawn_pos); +} + +void World::update(const sf::Time &dt) +{ + world_view.move(0.f, world_speed * dt.asSeconds()); + player->setVelocity(0.f, 0.f); + + while (!queue_commands.empty()) + { + scene_graph->onCommand(queue_commands.front(), dt); + queue_commands.pop(); + } + + //sf::Vector2f position = player->getPosition(); + sf::Vector2f velocity = player->velocity(); + + if (velocity.x != 0.f && velocity.y != 0.f) + player->setVelocity(velocity / std::sqrt(2.f)); + + player->accelerate({0.f, world_speed}); + + scene_graph->update(dt); +} + +void World::draw() +{ + world_window.setView(world_view); + world_window.draw(*scene_graph); +} + +std::queue& World::getCommandQueue() +{ + return queue_commands; +} + +bool World::loadTextures() +{ + return /*texture_holder.load(Textures::Id::Weakling, "sprite_weakling.png") + && texture_holder.load(Textures::Id::Shotguner, "sprite_shotguner.png") + && */texture_holder.load(Textures::Id::Background, "sprite_back.png") + && texture_holder.load(Textures::Id::Player, "sprite.png"); +} + +void World::buildScene() +{ + for (std::size_t i = 0; i < LayerCount; ++i) + { + SceneNodeSPtr layer(new SceneNode()); + arr_layers[i] = layer; + + scene_graph->attachChild(layer); + } + + sf::Texture texture = texture_holder.get(Textures::Id::Background); + sf::IntRect texture_rect(world_bounds); + texture.setRepeated(true); + + SpriteNodeSPtr background_sprite(new SpriteNode(texture, texture_rect)); + background_sprite->setPosition(world_bounds.left, world_bounds.top); + + arr_layers[Background]->attachChild(background_sprite); + + player = std::make_shared(Enemy::Type::Player, texture_holder); + player->setPosition(world_spawn_pos); + player->setVelocity(40.f, world_speed); + arr_layers[Air]->attachChild(player); +} diff --git a/world.h b/world.h new file mode 100644 index 0000000..6f48770 --- /dev/null +++ b/world.h @@ -0,0 +1,46 @@ +#ifndef WORLD_H +#define WORLD_H + +#include "enemy.h" +#include "resourceholder.h" +#include "scenenode.h" +#include +#include +#include + +class World : private sf::NonCopyable +{ +public: + explicit World(sf::RenderWindow& window); + + void update(const sf::Time& dt); + void draw(); + + std::queue& getCommandQueue(); + +private: + bool loadTextures(); + void buildScene(); + + enum Layer + { + Background, + Air, + LayerCount + }; + + std::queue queue_commands; + + sf::RenderWindow& world_window; + sf::View world_view; + TextureHolder texture_holder; + SceneNodeSPtr scene_graph; + std::array arr_layers; + + sf::FloatRect world_bounds; + sf::Vector2f world_spawn_pos; + float world_speed; + EnemySPtr player; +}; + +#endif // WORLD_H