diff --git a/.gitignore b/.gitignore index 9bb5b95..20f0baf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ bin *.bak* PCbuild .vs +obj diff --git a/assets/boom.wav b/assets/boom.wav new file mode 100644 index 0000000..b5ace4c Binary files /dev/null and b/assets/boom.wav differ diff --git a/assets/kenney_tinydungeon.png b/assets/kenney_tinydungeon.png new file mode 100644 index 0000000..f6e8b93 Binary files /dev/null and b/assets/kenney_tinydungeon.png differ diff --git a/build_linux.sh b/build_linux.sh index f626916..ccfc876 100755 --- a/build_linux.sh +++ b/build_linux.sh @@ -2,6 +2,8 @@ #rm -R bin/linux mkdir -p bin/linux/lib +mkdir -p obj +rm obj/* # copy shared objects, squish "linux" subdirectory in bin/linux/lib #cp -R lib/linux/* bin/linux/lib @@ -15,13 +17,48 @@ cp -R src/scripts bin/linux/scripts # work from output directory and change every g++ path to relative D:< cd bin/linux +# prepare object files of engine classes +abort_compile() +{ + echo "Compilation failed on $fn.cpp" + exit 1 +} + +# Precompile engine classes. Get errors in their file, not where they're included +for fn in $(ls ../../src/*.cpp -1 | cut -d/ -f4 | cut -d. -f1) +do + # Skip combined_poc.cpp, it has a duplicate main + if [ "$fn" = "combined_poc" ]; then continue; fi + + echo "Compile $fn.cpp" + g++ \ + -I../../deps_linux \ + -I../../deps_linux/Python-3.11.1 \ + -I../../platform/linux \ + --std=c++17 \ + -c ../../src/$fn.cpp \ + -o ../../obj/$fn.o \ + -lm \ + -ldl \ + -lutil \ + -lpthread \ + -lpython3.11 \ + -lsfml-graphics \ + -lsfml-window \ + -lsfml-system \ + -lsfml-audio \ + -ltcod \ + || abort_compile $fn +done + +# Final executable g++ \ --std=c++17 \ -I../../deps_linux \ -I../../deps_linux/Python-3.11.1 \ -I../../platform/linux \ - ../../src/combined_poc.cpp \ - -o poc \ + ../../obj/*.o \ + -o mcrogueface \ -Wl,-rpath lib \ -L../../deps_linux \ -lm \ @@ -32,5 +69,6 @@ g++ \ -lsfml-graphics \ -lsfml-window \ -lsfml-system \ - -ltcod + -lsfml-audio \ + -ltcod \ diff --git a/run_linux.sh b/run_linux.sh index 84cbccf..77a4f16 100755 --- a/run_linux.sh +++ b/run_linux.sh @@ -1,3 +1,3 @@ #!/bin/bash cd bin/linux -./poc +./mcrogueface diff --git a/src/ActionCode.h b/src/ActionCode.h new file mode 100644 index 0000000..2a6a0ad --- /dev/null +++ b/src/ActionCode.h @@ -0,0 +1,34 @@ +#include + +class ActionCode +{ +public: + enum CodeType { Key = 0, Mousebutton, Mousewheel }; + const static int KEY = 4096; + const static int MOUSEBUTTON = 8192; + const static int MOUSEWHEEL = 16384; + + const static int WHEEL_NUM = 4; + const static int WHEEL_NEG = 2; + const static int WHEEL_DEL = 1; + static int keycode(sf::Keyboard::Key& k) { return KEY + (int)k; } + static int keycode(sf::Mouse::Button& b) { return MOUSEBUTTON + (int)b; } + //static int keycode(sf::Mouse::Wheel& w, float d) { return MOUSEWHEEL + (((int)w)<<12) + int(d*16) + 512; } + static int keycode(sf::Mouse::Wheel& w, float d) { + int neg = 0; + if (d < 0) { neg = 1; } + return MOUSEWHEEL + (w * WHEEL_NUM) + (neg * WHEEL_NEG) + 1; + } + + static int key(int a) { return a & KEY; } + static int mouseButton(int a) { return a & MOUSEBUTTON; } + static bool isMouseWheel(int a) { return a & MOUSEWHEEL; } + //static int wheel(int a) { return a || MOUSEWHEEL >> 12; } + static int wheel(int a) { return (a & WHEEL_NUM) / WHEEL_NUM; } + //static float delta(int a) { return (a || MOUSEWHEEL || 2047) / 16.0f - 512; } + static int delta(int a) { + int factor = 1; + if (a & WHEEL_NEG) factor = -1; + return (a & WHEEL_DEL) * factor; + } +}; diff --git a/src/Button.cpp b/src/Button.cpp new file mode 100644 index 0000000..bb40fe5 --- /dev/null +++ b/src/Button.cpp @@ -0,0 +1,24 @@ +#include "Button.h" + +void Button::render(sf::RenderWindow & window) +{ + window.draw(rect); + window.draw(caption); +} + +Button::Button(int x, int y, int w, int h, + sf::Color _background, sf::Color _textcolor, + const char * _caption, sf::Font & font, + const char * _action) +{ + rect.setPosition(sf::Vector2f(x, y)); + rect.setSize(sf::Vector2f(w, h)); + rect.setFillColor(_background); + + caption.setFillColor(_textcolor); + caption.setPosition(sf::Vector2f(x, y)); + caption.setString(_caption); + caption.setFont(font); + + action = _action; +} diff --git a/src/Button.h b/src/Button.h new file mode 100644 index 0000000..c72bc08 --- /dev/null +++ b/src/Button.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Common.h" + +class Button +{ + +protected: + sf::RectangleShape rect; + sf::Text caption; + std::string action; + +public: + Button() {}; + Button(int x, int y, int w, int h, + sf::Color _background, sf::Color _textcolor, + const char * _caption, sf::Font & font, + const char * _action); + void setPosition(sf::Vector2f v) { rect.setPosition(v); caption.setPosition(v); } + void setSize(sf::Vector2f & v) { rect.setSize(v); } + void setBackground(sf::Color c) { rect.setFillColor(c); } + void setCaption(std::string & s) { caption.setString(s); } + void setTextColor(sf::Color c) { caption.setFillColor(c); } + void render(sf::RenderWindow & window); + auto contains(sf::Vector2i p) { return rect.getGlobalBounds().contains(p.x, p.y); } + auto getAction() { return action; } +private: +}; diff --git a/src/Common.h b/src/Common.h new file mode 100644 index 0000000..91c777a --- /dev/null +++ b/src/Common.h @@ -0,0 +1,10 @@ +# pragma once +#include +#include + +#include +#include +#include +#include +#include +#include diff --git a/src/Components.h b/src/Components.h new file mode 100644 index 0000000..4f0603f --- /dev/null +++ b/src/Components.h @@ -0,0 +1,79 @@ +#pragma once + +#include "Common.h" + +/* +class CTransform +{ +public: + Vec2 pos = { 0.0, 0.0 }; + Vec2 velocity = { 0.0, 0.0 }; + float angle = 0; + + CTransform(const Vec2 & p, const Vec2 & v, float a) + : pos(p), velocity(v), angle(a) {} +}; +*/ + +class CShape +{ +public: + sf::CircleShape circle; + + CShape(float radius, int points, const sf::Color & fill, const sf::Color & outline, float thickness) + : circle(radius, points) + { + circle.setFillColor(fill); + circle.setOutlineColor(outline); + circle.setOutlineThickness(thickness); + circle.setOrigin(radius, radius); + } +}; + +class CCollision +{ +public: + float radius = 0; + CCollision(float r) + : radius(r) {} +}; + +class CScore +{ +public: + int score = 0; + CScore(int s) + : score(s) {} +}; + +class CLifespan +{ +public: + int remaining = 0; + int total = 0; + CLifespan(int t) + : remaining(t), total(t) {} +}; + +class CInput +{ +public: + bool up = false; + bool left = false; + bool right = false; + bool down = false; + bool fire = false; + + CInput() {} +}; + +class CSteer +{ +public: + sf::Vector2f position; + sf::Vector2f velocity; + float v_max; + float dv_max; + float theta_max; + float dtheta_max; +}; diff --git a/src/Entity.cpp b/src/Entity.cpp new file mode 100644 index 0000000..38ac1b7 --- /dev/null +++ b/src/Entity.cpp @@ -0,0 +1,25 @@ +#include "Entity.h" + +Entity::Entity(const size_t i, const std::string & t) + : m_id(i), m_tag(t) {} + +bool Entity::isActive() const +{ + return m_active; +} + +const std::string & Entity::tag() const +{ + return m_tag; +} + +const size_t Entity::id() const +{ + return m_id; +} + +void Entity::destroy() +{ + m_active = false; +} + diff --git a/src/Entity.h b/src/Entity.h new file mode 100644 index 0000000..d6b3c8f --- /dev/null +++ b/src/Entity.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Common.h" + +#include "Components.h" + +class Entity +{ + friend class EntityManager; + + bool m_active = true; + size_t m_id = 0; + std::string m_tag = "default"; + + //constructor and destructor + Entity(const size_t id, const std::string & t); + +public: + // component pointers + //std::shared_ptr cTransform; + std::shared_ptr cShape; + std::shared_ptr cCollision; + std::shared_ptr cInput; + std::shared_ptr cScore; + std::shared_ptr cLifespan; + + //private member access functions + bool isActive() const; + const std::string & tag() const; + const size_t id() const; + void destroy(); +}; diff --git a/src/EntityManager.cpp b/src/EntityManager.cpp new file mode 100644 index 0000000..0a2eb01 --- /dev/null +++ b/src/EntityManager.cpp @@ -0,0 +1,65 @@ +#include "EntityManager.h" + +EntityManager::EntityManager() + :m_totalEntities(0) {} + +void EntityManager::update() +{ + //TODO: add entities from m_entitiesToAdd to all vector / tag map + removeDeadEntities(m_entities); + + // C++17 way of iterating! + for (auto& [tag, entityVec] : m_entityMap) + { + removeDeadEntities(entityVec); + } + + for (auto& e : m_entitiesToAdd) + { + m_entities.push_back(e); + m_entityMap[e->tag()].push_back(e); + } + //if (m_entitiesToAdd.size()) + // m_entitiesToAdd.erase(m_entitiesToAdd.begin(), m_entitiesToAdd.end()); + m_entitiesToAdd = EntityVec(); +} + +void EntityManager::removeDeadEntities(EntityVec & vec) +{ + EntityVec survivors; // New vector + for (auto& e : m_entities) + { + if (e->isActive()) survivors.push_back(e); // populate new vector + } + //std::cout << "All entities: " << m_entities.size() << " Survivors: " << survivors.size() << std::endl; + m_entities = survivors; // point to new vector + for (auto& [tag, entityVec] : m_entityMap) + { + EntityVec tag_survivors; // New vector + for (auto& e : m_entityMap[tag]) + { + if (e->isActive()) tag_survivors.push_back(e); // populate new vector + } + m_entityMap[tag] = tag_survivors; // point to new vector + //std::cout << tag << " entities: " << m_entityMap[tag].size() << " Survivors: " << tag_survivors.size() << std::endl; + } +} + +std::shared_ptr EntityManager::addEntity(const std::string & tag) +{ + // create the entity shared pointer + auto entity = std::shared_ptr(new Entity(m_totalEntities++, tag)); + m_entitiesToAdd.push_back(entity); + return entity; +} + +const EntityVec & EntityManager::getEntities() +{ + return m_entities; +} + +const EntityVec & EntityManager::getEntities(const std::string & tag) +{ + return m_entityMap[tag]; +} + diff --git a/src/EntityManager.h b/src/EntityManager.h new file mode 100644 index 0000000..744089e --- /dev/null +++ b/src/EntityManager.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Common.h" +#include "Entity.h" + +typedef std::vector> EntityVec; +typedef std::map EntityMap; + +class EntityManager +{ + EntityVec m_entities; + EntityVec m_entitiesToAdd; + EntityMap m_entityMap; + size_t m_totalEntities; + + void removeDeadEntities(EntityVec & vec); + +public: + EntityManager(); + void update(); + std::shared_ptr addEntity(const std::string & tag); + const EntityVec & getEntities(); + const EntityVec & getEntities(const std::string & tag); +}; + diff --git a/src/GameEngine.cpp b/src/GameEngine.cpp new file mode 100644 index 0000000..8c5db53 --- /dev/null +++ b/src/GameEngine.cpp @@ -0,0 +1,96 @@ +#include "GameEngine.h" +#include "MenuScene.h" +#include "UITestScene.h" +#include "ActionCode.h" + +GameEngine::GameEngine() +{ + font.loadFromFile("./assets/JetbrainsMono.ttf"); + window.create(sf::VideoMode(640, 480), "McRogueFace Engine by John McCardle"); + visible = window.getDefaultView(); + window.setFramerateLimit(30); + scene = "menu"; + //std::cout << "Constructing MenuScene" << std::endl; + scenes["menu"] = new MenuScene(this); + //std::cout << "Constructed MenuScene" <update(); + sUserInput(); + if (!paused) + { + } + currentScene()->sRender(); + currentFrame++; + } +} + +void GameEngine::sUserInput() +{ + sf::Event event; + while (window.pollEvent(event)) + { + std::string actionType; + int actionCode = 0; + + if (event.type == sf::Event::Closed) { running = false; continue; } + else if (event.type == sf::Event::Resized) { + sf::FloatRect area(0.f, 0.f, event.size.width, event.size.height); + visible = sf::View(area); + window.setView(visible); + std::cout << "Visible area set to (0, 0, " << event.size.width << ", " << event.size.height <<")"< " << (actionCode && ActionCode::WHEEL_NEG) << "; actionCode && WHEEL_DEL -> " << (actionCode && ActionCode::WHEEL_DEL) << ";" << std::endl; + */ + } + // float d = event.MouseWheelScrollEvent.delta; + // actionCode = ActionCode::keycode(0, d); + } + else + continue; + + //std::cout << "Event produced action code " << actionCode << ": " << actionType << std::endl; + + if (currentScene()->hasAction(actionCode)) + { + std::string name = currentScene()->action(actionCode); + currentScene()->doAction(name, actionType); + } + else + { + std::cout << "[GameEngine] Action not registered for input: " << actionCode << ": " << actionType << std::endl; + } + } +} diff --git a/src/GameEngine.h b/src/GameEngine.h new file mode 100644 index 0000000..11f6f64 --- /dev/null +++ b/src/GameEngine.h @@ -0,0 +1,31 @@ +#pragma once + +#include "Common.h" +#include "Entity.h" +#include "EntityManager.h" +#include "Scene.h" + +class GameEngine +{ + sf::RenderWindow window; + sf::Font font; + std::string scene; + std::map scenes; + bool running = true; + bool paused = false; + int currentFrame = 0; + sf::View visible; + +public: + GameEngine(); + Scene* currentScene(); + void changeScene(std::string); + void quit(); + void setPause(bool); + sf::Font & getFont(); + sf::RenderWindow & getWindow(); + void run(); + void sUserInput(); + int getFrame() { return currentFrame; } + sf::View getView() { return visible; } +}; diff --git a/src/MenuScene.cpp b/src/MenuScene.cpp new file mode 100644 index 0000000..998b054 --- /dev/null +++ b/src/MenuScene.cpp @@ -0,0 +1,45 @@ +#include "MenuScene.h" +#include "ActionCode.h" + +MenuScene::MenuScene(GameEngine* g) : Scene(g) +{ + text.setFont(game->getFont()); + text.setString("McRogueFace Engine - Testing & Early Access"); + text.setCharacterSize(24); + //std::cout << "MenuScene Initialized. " << game << std::endl; + //std::cout << "Font: " << game->getFont().getInfo().family << std::endl; + + text2.setFont(game->getFont()); + text2.setString("Press 'Spacebar' to begin; 'Up' and 'Down' switch Resolution"); + text2.setCharacterSize(16); + text2.setPosition(0.0f, 50.0f); + + registerAction(ActionCode::KEY + sf::Keyboard::Space, "start_game"); + registerAction(ActionCode::KEY + sf::Keyboard::Up, "up"); + registerAction(ActionCode::KEY + sf::Keyboard::Down, "down"); +} + +void MenuScene::update() +{ + //std::cout << "MenuScene update" << std::endl; +} + +void MenuScene::doAction(std::string name, std::string type) +{ + //std::cout << "MenuScene doAction: " << name << ", " << type << std::endl; + //if (name.compare("start_game") == 0 and type.compare("start") == 0) + if(ACTION("start_game", "start")) + game->changeScene("play"); + else if(ACTIONONCE("up")) + game->getWindow().setSize(sf::Vector2u(1024, 768)); + else if(ACTIONONCE("down")) + game->getWindow().setSize(sf::Vector2u(640, 480)); +} + +void MenuScene::sRender() +{ + game->getWindow().clear(); + game->getWindow().draw(text); + game->getWindow().draw(text2); + game->getWindow().display(); +} diff --git a/src/MenuScene.h b/src/MenuScene.h new file mode 100644 index 0000000..70ec13f --- /dev/null +++ b/src/MenuScene.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Common.h" +#include "Scene.h" +#include "GameEngine.h" + +class MenuScene: public Scene +{ + sf::Text text; + sf::Text text2; + +public: + MenuScene(GameEngine*); + void update() override final; + void doAction(std::string, std::string) override final; + void sRender() override final; +}; diff --git a/src/Scene.cpp b/src/Scene.cpp new file mode 100644 index 0000000..a6e32ae --- /dev/null +++ b/src/Scene.cpp @@ -0,0 +1,25 @@ +#include "Scene.h" + +//Scene::Scene() { game = 0; std::cout << "WARN: default Scene constructor called. (game = " << game << ")" << std::endl;}; +Scene::Scene(GameEngine* g) { game = g; } +void Scene::registerAction(int code, std::string name) +{ + actions[code] = name; + actionState[name] = false; +} +bool Scene::hasAction(std::string name) +{ + for (auto& item : actions) + if (item.second == name) return true; + return false; +} + +bool Scene::hasAction(int code) +{ + return (actions.find(code) != actions.end()); +} + +std::string Scene::action(int code) +{ + return actions[code]; +} diff --git a/src/Scene.h b/src/Scene.h new file mode 100644 index 0000000..0eec797 --- /dev/null +++ b/src/Scene.h @@ -0,0 +1,35 @@ +#pragma once + +// macros for scene input +#define ACTION(X, Y) (name.compare(X) == 0 && type.compare(Y) == 0) +#define ACTIONONCE(X) ((name.compare(X) == 0 && type.compare("start") == 0 && !actionState[name])) + +#include "Common.h" +//#include "GameEngine.h" + +class GameEngine; // forward declare + +class Scene +{ +protected: + bool hasEnded = false; + bool paused = false; + std::map actions; + std::map actionState; + GameEngine* game; + + void simulate(int); + void registerAction(int, std::string); + + +public: + //Scene(); + Scene(GameEngine*); + virtual void update() = 0; + virtual void sRender() = 0; + virtual void doAction(std::string, std::string) = 0; + bool hasAction(std::string); + bool hasAction(int); + std::string action(int); + +}; diff --git a/src/UIMenu.cpp b/src/UIMenu.cpp new file mode 100644 index 0000000..c16452a --- /dev/null +++ b/src/UIMenu.cpp @@ -0,0 +1,48 @@ +#include "UIMenu.h" + +UIMenu::UIMenu(sf::Font & _font) +: font(_font) +{ + //font = _font; + box.setSize(sf::Vector2f(300, 400)); + box.setPosition(sf::Vector2f(300, 250)); + box.setFillColor(sf::Color(0,0,255)); +} + +void UIMenu::render(sf::RenderWindow & window) +{ + window.draw(box); + for (auto& c : captions) { + //auto s = std::string(c.getString()); + //std::cout << s << std::flush << std::endl; + window.draw(c); + } + for (auto& b : buttons) { b.render(window); } +} + +void UIMenu::refresh() +{ + +} + +void UIMenu::add_caption(const char* text, int tsize, sf::Color color) +{ + auto c = sf::Text(); + auto bpos = box.getPosition(); + + c.setFillColor(color); + c.setPosition(bpos.x + 10, bpos.y + next_text); + next_text += 50; + c.setCharacterSize(tsize); + c.setString(text); + c.setFont(font); + captions.push_back(c); + +} + +void UIMenu::add_button(Button b) +{ + b.setPosition(box.getPosition() + sf::Vector2f(box.getSize().x / 2.0f, next_button)); + next_button += 50; + buttons.push_back(b); +} diff --git a/src/UIMenu.h b/src/UIMenu.h new file mode 100644 index 0000000..1b2b0b1 --- /dev/null +++ b/src/UIMenu.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Common.h" +#include "Button.h" + +class UIMenu +{ +public: + //UIMenu() {}; + sf::Font & font; + UIMenu(sf::Font & _font); + std::vector captions; + std::vector