diff --git a/CMakeLists.txt b/CMakeLists.txt index 91a2918..a2d17b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -254,6 +254,12 @@ add_executable(Dicewars_Siedler src/main.cpp src/game/hexWorld/ecs/systems/UpgradeSystem.h src/game/hexWorld/ecs/systems/SelectionSystem.cpp src/game/hexWorld/ecs/systems/SelectionSystem.h + src/engine/core/ECS/RenderStateComponent.cpp + src/engine/core/ECS/RenderStateComponent.h + src/game/hexWorld/ecs/systems/BuildPreviewSystem.cpp + src/game/hexWorld/ecs/systems/BuildPreviewSystem.h + src/game/hexWorld/ecs/components/BuildingPreviewComponent.cpp + src/game/hexWorld/ecs/components/BuildingPreviewComponent.h ) target_compile_options(Dicewars_Siedler PRIVATE diff --git a/assets/shaders/fragmentShader.glsl b/assets/shaders/fragmentShader.glsl index 5a6365d..1ec0781 100644 --- a/assets/shaders/fragmentShader.glsl +++ b/assets/shaders/fragmentShader.glsl @@ -9,6 +9,10 @@ out vec4 outColor; uniform sampler2D textureSampler; uniform vec3 lightColor; +uniform vec3 ghostColor; +uniform float pulse; +uniform bool ghostMode; + void main(void) { vec3 unitNormal = normalize(surfaceNormal); vec3 unitToLightDir = normalize(toLightVector); @@ -16,5 +20,11 @@ void main(void) { float cosTheta = dot(unitNormal, unitToLightDir); float brightness = max(cosTheta, 0.2); vec3 diffuse = brightness * lightColor; - outColor = vec4(diffuse, 1.0f) * texture(textureSampler, pass_textureCoords); + + vec4 finalColor = vec4(diffuse, 1.0f) * texture(textureSampler, pass_textureCoords); + if(ghostMode) { + finalColor = vec4(mix(finalColor.xyz, ghostColor, 0.5) * pulse, 1.0f); + } + + outColor = finalColor; } \ No newline at end of file diff --git a/src/engine/core/ECS/RenderStateComponent.cpp b/src/engine/core/ECS/RenderStateComponent.cpp new file mode 100644 index 0000000..75a8078 --- /dev/null +++ b/src/engine/core/ECS/RenderStateComponent.cpp @@ -0,0 +1,5 @@ +// +// Created by sebastian on 21.02.26. +// + +#include "RenderStateComponent.h" \ No newline at end of file diff --git a/src/engine/core/ECS/RenderStateComponent.h b/src/engine/core/ECS/RenderStateComponent.h new file mode 100644 index 0000000..cb8f290 --- /dev/null +++ b/src/engine/core/ECS/RenderStateComponent.h @@ -0,0 +1,19 @@ +// +// Created by sebastian on 21.02.26. +// + +#ifndef DICEWARS_SIEDLER_RENDERSTATECOMPONENT_H +#define DICEWARS_SIEDLER_RENDERSTATECOMPONENT_H +#include "Component.h" + + +class RenderStateComponent: public Component { +public: + bool visible = true; + glm::vec3 ghostColor = glm::vec3(1.0, 1.0,1.0f); + bool ghostMode = false; + float pulse = 1.0f; +}; + + +#endif //DICEWARS_SIEDLER_RENDERSTATECOMPONENT_H \ No newline at end of file diff --git a/src/engine/core/ECS/RenderSystem.cpp b/src/engine/core/ECS/RenderSystem.cpp index d55f61e..49f2aac 100644 --- a/src/engine/core/ECS/RenderSystem.cpp +++ b/src/engine/core/ECS/RenderSystem.cpp @@ -5,6 +5,7 @@ #include "RenderSystem.h" #include "ModelStateComponent.h" +#include "RenderStateComponent.h" #include "../../renderer/loader/AssetManager.h" void RenderSystem::render(EntityManager &entityManager, MasterRenderer &renderer) { @@ -16,6 +17,11 @@ void RenderSystem::render(EntityManager &entityManager, MasterRenderer &renderer auto modelStateComponent = entityManager.getComponent(id); auto ownerComponent = entityManager.getComponent(id); auto worldSpriteComponent = entityManager.getComponent(id); + auto renderStateComponent = entityManager.getComponent(id); + + if (renderStateComponent && !renderStateComponent->visible) { + continue; + } if (modelStateComponent) { updateModelStage(model.get(), modelStateComponent.get()); @@ -31,6 +37,7 @@ void RenderSystem::render(EntityManager &entityManager, MasterRenderer &renderer renderer.submitTerrainTile(transform, model, tileRenderingComponent, ownerComponent); } else { Entity entity = Entity(model->getActiveModel(), transform->position, transform->rotation.x, transform->rotation.y, transform->rotation.z, transform->scale); + entity.setRenderState(renderStateComponent); renderer.submitEntity(std::make_unique(entity)); } diff --git a/src/engine/layer/entities/Entity.h b/src/engine/layer/entities/Entity.h index be6e41f..6449b87 100644 --- a/src/engine/layer/entities/Entity.h +++ b/src/engine/layer/entities/Entity.h @@ -7,6 +7,7 @@ #include #include +#include "../../core/ECS/RenderStateComponent.h" #include "../../renderer/model/TexturedModel.h" #include "glm/vec3.hpp" @@ -17,6 +18,7 @@ private: glm::vec3 position; float rotX, rotY, rotZ; float scale; + std::shared_ptr renderState; public: std::string modelName; @@ -25,6 +27,9 @@ public: void increasePosition(const float dx, const float dy, const float dz) {position += glm::vec3(dx, dy, dz);} //( dx, dy, dz void increaseRotation(const float dx, const float dy, const float dz) {rotX += dx; rotY += dy; rotZ += dz;} + + void setRenderState(std::shared_ptr renderState) {this->renderState = std::move(renderState);} + [[nodiscard]] const std::shared_ptr getModel() const {return model;} [[nodiscard]] const glm::vec3 getPosition() const {return position;} [[nodiscard]] float getRotX() const {return rotX;} @@ -36,6 +41,8 @@ public: void setRotY(float rotY) {this->rotY = rotY;} void setRotZ(float rotZ) {this->rotZ = rotZ;} void setScale(float scale) {this->scale = scale;} + + [[nodiscard]] std::shared_ptr getRenderState() const {return renderState;} }; diff --git a/src/engine/renderer/Renderer.cpp b/src/engine/renderer/Renderer.cpp index b813136..994448e 100644 --- a/src/engine/renderer/Renderer.cpp +++ b/src/engine/renderer/Renderer.cpp @@ -59,6 +59,12 @@ void Renderer::unbindTexturedModel() { void Renderer::prepareInstance(const Entity &entity) { glm::mat4 transformationMatrix = MathUtils::createTransformationMatrix(entity.getPosition(), entity.getRotX(), entity.getRotY(), entity.getRotZ(), entity.getScale()); staticShader.loadTransformationMatrix(transformationMatrix); + + if (entity.getRenderState()) { + staticShader.loadGhostColor(entity.getRenderState()->ghostColor); + staticShader.loadGhostMode(entity.getRenderState()->ghostMode); + staticShader.loadPulse(entity.getRenderState()->pulse); + } } void Renderer::finalizeFrame() { diff --git a/src/engine/renderer/shaders/StaticShader.cpp b/src/engine/renderer/shaders/StaticShader.cpp index 11de371..987f054 100644 --- a/src/engine/renderer/shaders/StaticShader.cpp +++ b/src/engine/renderer/shaders/StaticShader.cpp @@ -26,6 +26,18 @@ void StaticShader::loadLight(glm::vec3 position, glm::vec3 color) { loadVector(location_lightColor, color); } +void StaticShader::loadGhostMode(bool ghostMode) { + loadBoolean(location_ghostMode, ghostMode); +} + +void StaticShader::loadGhostColor(glm::vec3 ghostColor) { + loadVector(location_ghostColor, ghostColor); +} + +void StaticShader::loadPulse(float pulse) { + loadFloat(location_pulse, pulse); +} + void StaticShader::bindAttributes() const { bindAttribute(0, "position"); bindAttribute(1, "textureCoords"); @@ -38,6 +50,10 @@ void StaticShader::getAllUniformLocations() { location_viewMatrix = getUniformLocation("viewMatrix"); location_lightPosition = getUniformLocation("lightPosition"); location_lightColor = getUniformLocation("lightColor"); + + location_ghostColor = getUniformLocation("ghostColor"); + location_pulse = getUniformLocation("pulse"); + location_ghostMode = getUniformLocation("ghostMode"); } diff --git a/src/engine/renderer/shaders/StaticShader.h b/src/engine/renderer/shaders/StaticShader.h index 14afc4f..683d3fd 100644 --- a/src/engine/renderer/shaders/StaticShader.h +++ b/src/engine/renderer/shaders/StaticShader.h @@ -15,6 +15,9 @@ public: void loadProjectionMatrix(glm::mat4 matrix); void loadViewMatrix(glm::mat4 matrix); void loadLight(glm::vec3 position, glm::vec3 color); + void loadGhostMode(bool ghostMode); + void loadGhostColor(glm::vec3 ghostColor); + void loadPulse(float pulse); private: inline static const std::string VERTEX_FILE = "assets/shaders/vertexShader.glsl"; inline static const std::string FRAGMENT_FILE = "assets/shaders/fragmentShader.glsl"; @@ -25,6 +28,10 @@ private: int location_lightPosition; int location_lightColor; + int location_ghostColor; + int location_pulse; + int location_ghostMode; + protected: void bindAttributes() const override; void getAllUniformLocations() override; diff --git a/src/game/GameLayer.cpp b/src/game/GameLayer.cpp index 17522d7..bb81e91 100644 --- a/src/game/GameLayer.cpp +++ b/src/game/GameLayer.cpp @@ -23,6 +23,7 @@ #include "hexWorld/building/BuildingFactory.h" #include "hexWorld/building/TemporaryBuildingDefinitionFactory.h" #include "hexWorld/ecs/components/ProducingComponent.h" +#include "hexWorld/ecs/systems/BuildPreviewSystem.h" #include "hexWorld/ecs/systems/CollectResourceSystem.h" #include "hexWorld/ecs/systems/ProducingSystem.h" #include "hexWorld/ecs/systems/SelectionSystem.h" @@ -119,12 +120,17 @@ void GameLayer::onUpdate() } } + + if (Application::getInstance().keyboard->keyPressEvent(GLFW_KEY_ESCAPE)) { + gameMode->resetActiveBuilding(); + } } AnimationSystem::update(*entityManager, EngineTime::totalTime); camera->move(moveDir, 0.5f); if (gameInputUser->isMouseEnabled()) { tileHighlightSystem->update(*entityManager, *mousePicker, *camera, *gameMode); + BuildPreviewSystem::updateBuildPreview(*entityManager, *gameMode, gameMode->getCurrentPlayer(), *turnState); SelectionSystem::update(*entityManager, *gameInputUser, *mousePicker); if (gameMode->isUpgradeMode()) { UpgradeSystem::tryUpdate(*entityManager, *gameMode, gameMode->getCurrentPlayer(), *turnState); diff --git a/src/game/GameMode.cpp b/src/game/GameMode.cpp index d8d5cac..b6438ac 100644 --- a/src/game/GameMode.cpp +++ b/src/game/GameMode.cpp @@ -91,4 +91,8 @@ bool GameMode::isUpgradeMode() const { return upgradeMode; } +void GameMode::resetActiveBuilding() { + activeBuilding.reset(); +} + diff --git a/src/game/GameMode.h b/src/game/GameMode.h index 00a2d53..dbdfc8d 100644 --- a/src/game/GameMode.h +++ b/src/game/GameMode.h @@ -43,6 +43,8 @@ public: void setUpgradeMode(bool upgradeMode); bool isUpgradeMode() const; + void resetActiveBuilding(); + private: std::unordered_map players; std::optional activeBuilding; diff --git a/src/game/hexWorld/building/BuildingFactory.cpp b/src/game/hexWorld/building/BuildingFactory.cpp index 82b36be..afbe814 100644 --- a/src/game/hexWorld/building/BuildingFactory.cpp +++ b/src/game/hexWorld/building/BuildingFactory.cpp @@ -4,10 +4,15 @@ #include "BuildingFactory.h" +#include "BuildingConfig.h" +#include "../../../engine/core/animations/AnimationComponent.h" #include "../../../engine/core/ECS/ModelComponent.h" #include "../../../engine/core/ECS/ModelStateComponent.h" +#include "../../../engine/core/ECS/RenderStateComponent.h" #include "../../../engine/core/ECS/TransformComponent.h" +#include "../../../engine/layer/entities/Entity.h" #include "../../../engine/renderer/loader/AssetManager.h" +#include "../ecs/components/BuildingPreviewComponent.h" #include "../ecs/components/ProducingComponent.h" EntityID BuildingFactory::create(EntityManager &em, const BuildingDefinition &def, @@ -43,5 +48,49 @@ EntityID BuildingFactory::create(EntityManager &em, const BuildingDefinition &de em.addComponent(e, modelStateComponent); em.addComponent(e, producingComponent); + auto renderStateComponent = std::make_shared(); + renderStateComponent->ghostMode = false; + em.addComponent(e, renderStateComponent); + + return e; +} + +EntityID BuildingFactory::createPreview(EntityManager &em, BuildingType buildingType, const TransformComponent &tileTransform, EntityID tileEntity) { + EntityID e = em.createEntity(); + + const auto def = BuildingConfig::get(buildingType); + em.addComponent(e, std::make_shared(AssetManager::getModelStages(def.model), def.model)); + if (def.type == BuildingType::FOREST_HUT) { + em.addComponent(e, std::make_shared( + tileTransform.position, glm::vec3(0), 1.f + )); + } else { + em.addComponent(e, std::make_shared( + tileTransform.position, glm::vec3(0), 2.f + )); + } + + const auto modelStateComponent = std::make_shared(); + modelStateComponent->params["fillRatio"] = 0.f; + modelStateComponent->params["level"] = 1.f; + em.addComponent(e, modelStateComponent); + + const auto previewComponent = std::make_shared(); + em.addComponent(e, previewComponent); + + const auto renderStateComponent = std::make_shared(); + renderStateComponent->ghostMode = true; + em.addComponent(e, renderStateComponent); + + auto previewPulseAnim = std::make_shared(); + previewPulseAnim->tracks.push_back({ + AnimationCurve{CurveType::Sine, 0.3f, 4.0f, 0.0f, 0.7f}, // offset=0.7, amplitude=0.3, frequency=4 + [renderStateComponent](float value) { + renderStateComponent->pulse = value; + } + }); + + em.addComponent(e, previewPulseAnim); + return e; } diff --git a/src/game/hexWorld/building/BuildingFactory.h b/src/game/hexWorld/building/BuildingFactory.h index fdfac25..341d054 100644 --- a/src/game/hexWorld/building/BuildingFactory.h +++ b/src/game/hexWorld/building/BuildingFactory.h @@ -11,6 +11,7 @@ class BuildingFactory { public: static EntityID create(EntityManager& em, const BuildingDefinition& def, const TransformComponent& tileTransform, EntityID tileEntity, PlayerID owner); + static EntityID createPreview(EntityManager& em, BuildingType buildingType, const TransformComponent& tileTransform, EntityID tileEntity); }; diff --git a/src/game/hexWorld/ecs/components/BuildingPreviewComponent.cpp b/src/game/hexWorld/ecs/components/BuildingPreviewComponent.cpp new file mode 100644 index 0000000..43290e7 --- /dev/null +++ b/src/game/hexWorld/ecs/components/BuildingPreviewComponent.cpp @@ -0,0 +1,5 @@ +// +// Created by sebastian on 21.02.26. +// + +#include "BuildingPreviewComponent.h" \ No newline at end of file diff --git a/src/game/hexWorld/ecs/components/BuildingPreviewComponent.h b/src/game/hexWorld/ecs/components/BuildingPreviewComponent.h new file mode 100644 index 0000000..bc7c618 --- /dev/null +++ b/src/game/hexWorld/ecs/components/BuildingPreviewComponent.h @@ -0,0 +1,17 @@ +// +// Created by sebastian on 21.02.26. +// + +#ifndef DICEWARS_SIEDLER_BUILDINGPREVIEWCOMPONENT_H +#define DICEWARS_SIEDLER_BUILDINGPREVIEWCOMPONENT_H +#include "BuildingComponent.h" +#include "../../../../engine/core/ECS/Component.h" + + +class BuildingPreviewComponent: public Component { +public: + BuildingType buildingType; +}; + + +#endif //DICEWARS_SIEDLER_BUILDINGPREVIEWCOMPONENT_H \ No newline at end of file diff --git a/src/game/hexWorld/ecs/systems/BuildPreviewSystem.cpp b/src/game/hexWorld/ecs/systems/BuildPreviewSystem.cpp new file mode 100644 index 0000000..fb8d600 --- /dev/null +++ b/src/game/hexWorld/ecs/systems/BuildPreviewSystem.cpp @@ -0,0 +1,95 @@ +// +// Created by sebastian on 21.02.26. +// + +#include "BuildPreviewSystem.h" + +#include + +#include "../../../../engine/core/ECS/RenderStateComponent.h" +#include "../../../../engine/core/ECS/TileRenderComponent.h" +#include "../../building/BuildingFactory.h" +#include "../components/BuildingPreviewComponent.h" +#include "../components/OwnerComponent.h" +#include "../components/TileGameplayComponent.h" +#include "../../../../engine/core/ECS/TransformComponent.h" + +EntityID BuildPreviewSystem::previewEntity = 0; + +void BuildPreviewSystem::updateBuildPreview(EntityManager &em, GameMode &gameMode, PlayerID player, const TurnState &turnState) { + if (gameMode.isUpgradeMode() || !gameMode.getActiveBuilding().has_value() || !gameMode.hasTurn(player, turnState.currentTurn)) { + for (EntityID e : em.getAllEntities()) { + const auto tileRenderComponent = em.getComponent(e); + const auto ownerComponent = em.getComponent(e); + const auto gameplayComponent = em.getComponent(e); + + if (tileRenderComponent && ownerComponent && ownerComponent->playerID == player && tileRenderComponent->isHighlighted) { + const auto renderStateComponent = em.getComponent(e); + if (renderStateComponent) renderStateComponent->visible = true; + } + } + + for (EntityID e : em.getAllEntities()) { + if (const auto previewComponent = em.getComponent(e)) { + em.destroyEntity(e); + } + } + previewEntity = 0; + return; + } + + + bool foundHighlightedTile = false; + for (EntityID e : em.getAllEntities()) { + const auto tileRenderComponent = em.getComponent(e); + const auto gameplayComponent = em.getComponent(e); + + if (!tileRenderComponent || !gameplayComponent) continue; + + if (tileRenderComponent->isHighlighted) { + foundHighlightedTile = true; + for (EntityID entityOnTile : gameplayComponent->entitiesOnTile) { + const auto renderStateComponent = em.getComponent(entityOnTile); + if (renderStateComponent) renderStateComponent->visible = false; + } + + + if (previewEntity == 0) { + const auto tileTransformComponent = em.getComponent(e); + if (tileTransformComponent) previewEntity = BuildingFactory::createPreview(em, gameMode.getActiveBuilding().value(), *tileTransformComponent, e); + } else { + const auto previewComponent = em.getComponent(previewEntity); + if (previewComponent) { + if (previewComponent->buildingType != gameMode.getActiveBuilding().value()) { + em.destroyEntity(previewEntity); + previewEntity = 0; + + const auto tileTransformComponent = em.getComponent(e); + if (tileTransformComponent) previewEntity = BuildingFactory::createPreview(em, gameMode.getActiveBuilding().value(), *tileTransformComponent, e); + + } else { + const auto tileTransformComponent = em.getComponent(e); + const auto renderStateComponent = em.getComponent(previewEntity); + if (tileTransformComponent) { + const auto previewTransformComponent = em.getComponent(previewEntity); + if (previewTransformComponent) { + previewTransformComponent->position = tileTransformComponent->position; + } + } + } + } + } + } else { + for (EntityID entityOnTile : gameplayComponent->entitiesOnTile) { + const auto renderStateComponent = em.getComponent(entityOnTile); + if (renderStateComponent) renderStateComponent->visible = true; + } + } + } +} + +void BuildPreviewSystem::disableBuildPreview(EntityManager &em) { + if (previewEntity != 0) { + //em.destroyEntity(previewEntity); + } +} diff --git a/src/game/hexWorld/ecs/systems/BuildPreviewSystem.h b/src/game/hexWorld/ecs/systems/BuildPreviewSystem.h new file mode 100644 index 0000000..a653248 --- /dev/null +++ b/src/game/hexWorld/ecs/systems/BuildPreviewSystem.h @@ -0,0 +1,22 @@ +// +// Created by sebastian on 21.02.26. +// + +#ifndef DICEWARS_SIEDLER_BUILDPREVIEWSYSTEM_H +#define DICEWARS_SIEDLER_BUILDPREVIEWSYSTEM_H +#include "../../../GameMode.h" +#include "../../gameplay/TurnState.h" + + +class EntityManager; + +class BuildPreviewSystem { +public: + static void updateBuildPreview(EntityManager& em, GameMode& gameMode, PlayerID player, const TurnState& turnState); + static void disableBuildPreview(EntityManager& em); +private: + static EntityID previewEntity; +}; + + +#endif //DICEWARS_SIEDLER_BUILDPREVIEWSYSTEM_H \ No newline at end of file diff --git a/src/game/hexWorld/ecs/systems/BuildingPlacementSystem.cpp b/src/game/hexWorld/ecs/systems/BuildingPlacementSystem.cpp index 5f8be89..6624bd5 100644 --- a/src/game/hexWorld/ecs/systems/BuildingPlacementSystem.cpp +++ b/src/game/hexWorld/ecs/systems/BuildingPlacementSystem.cpp @@ -4,6 +4,7 @@ #include "BuildingPlacementSystem.h" +#include "BuildPreviewSystem.h" #include "../../RessourceType.h" #include "../../../../engine/core/Application.h" #include "../../../../engine/core/ECS/ModelComponent.h" @@ -65,6 +66,7 @@ void BuildingPlacementSystem::update(EntityManager& entityManager, GameMode& gam UpgradeComponent upgrade_component = UpgradeComponent(1, static_cast(def.levels.size() + 1)); entityManager.addComponent(buildingEntity, std::make_shared(upgrade_component)); } + break; } }; diff --git a/src/game/hexWorld/ecs/systems/TileHighlightSystem.cpp b/src/game/hexWorld/ecs/systems/TileHighlightSystem.cpp index 66f499c..c265e8f 100644 --- a/src/game/hexWorld/ecs/systems/TileHighlightSystem.cpp +++ b/src/game/hexWorld/ecs/systems/TileHighlightSystem.cpp @@ -5,6 +5,7 @@ #include "TileHighlightSystem.h" #include "../../../GameMode.h" +#include "../../../../engine/core/ECS/RenderStateComponent.h" #include "../../../../engine/core/ECS/TileRenderComponent.h" #include "../../../../engine/core/ECS/TransformComponent.h" #include "../components/TileGameplayComponent.h" diff --git a/src/game/hexWorld/tileGenerator/ForestTileGenerator.cpp b/src/game/hexWorld/tileGenerator/ForestTileGenerator.cpp index 30b3419..ab95810 100644 --- a/src/game/hexWorld/tileGenerator/ForestTileGenerator.cpp +++ b/src/game/hexWorld/tileGenerator/ForestTileGenerator.cpp @@ -5,6 +5,7 @@ #include "ForestTileGenerator.h" #include "../../../engine/core/ECS/ModelComponent.h" +#include "../../../engine/core/ECS/RenderStateComponent.h" #include "../../../engine/core/ECS/TransformComponent.h" #include "../../../engine/renderer/loader/AssetManager.h" #include "glm/detail/func_geometric.inl" @@ -48,11 +49,14 @@ std::vector ForestTileGenerator::generateHexTile(EntityManager& em, Ra const auto transformComponent = std::make_shared(treePos, glm::vec3(0), 1.f); const auto modelComponent = std::make_shared(treeModels[treeModelIndex], "hexModelWood"); + const auto renderStateComponent = std::make_shared(); const EntityID entityID = em.createEntity(); em.addComponent(entityID, transformComponent); em.addComponent(entityID, modelComponent); + em.addComponent(entityID, renderStateComponent); entitiyIDs.push_back(entityID); + } return entitiyIDs; }