ADD: Building Preview

This commit is contained in:
sebastian 2026-02-21 10:36:37 +01:00
parent ca526e0253
commit 9602197cb8
21 changed files with 292 additions and 1 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -0,0 +1,5 @@
//
// Created by sebastian on 21.02.26.
//
#include "RenderStateComponent.h"

View File

@ -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

View File

@ -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<ModelStateComponent>(id);
auto ownerComponent = entityManager.getComponent<OwnerComponent>(id);
auto worldSpriteComponent = entityManager.getComponent<WorldSpriteComponent>(id);
auto renderStateComponent = entityManager.getComponent<RenderStateComponent>(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>(entity));
}

View File

@ -7,6 +7,7 @@
#include <memory>
#include <utility>
#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<RenderStateComponent> 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<RenderStateComponent> renderState) {this->renderState = std::move(renderState);}
[[nodiscard]] const std::shared_ptr<TexturedModel> 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<RenderStateComponent> getRenderState() const {return renderState;}
};

View File

@ -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() {

View File

@ -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");
}

View File

@ -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;

View File

@ -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);

View File

@ -91,4 +91,8 @@ bool GameMode::isUpgradeMode() const {
return upgradeMode;
}
void GameMode::resetActiveBuilding() {
activeBuilding.reset();
}

View File

@ -43,6 +43,8 @@ public:
void setUpgradeMode(bool upgradeMode);
bool isUpgradeMode() const;
void resetActiveBuilding();
private:
std::unordered_map<PlayerID, Player> players;
std::optional<BuildingType> activeBuilding;

View File

@ -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>();
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<ModelComponent>(AssetManager::getModelStages(def.model), def.model));
if (def.type == BuildingType::FOREST_HUT) {
em.addComponent(e, std::make_shared<TransformComponent>(
tileTransform.position, glm::vec3(0), 1.f
));
} else {
em.addComponent(e, std::make_shared<TransformComponent>(
tileTransform.position, glm::vec3(0), 2.f
));
}
const auto modelStateComponent = std::make_shared<ModelStateComponent>();
modelStateComponent->params["fillRatio"] = 0.f;
modelStateComponent->params["level"] = 1.f;
em.addComponent(e, modelStateComponent);
const auto previewComponent = std::make_shared<BuildingPreviewComponent>();
em.addComponent(e, previewComponent);
const auto renderStateComponent = std::make_shared<RenderStateComponent>();
renderStateComponent->ghostMode = true;
em.addComponent(e, renderStateComponent);
auto previewPulseAnim = std::make_shared<AnimationComponent>();
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;
}

View File

@ -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);
};

View File

@ -0,0 +1,5 @@
//
// Created by sebastian on 21.02.26.
//
#include "BuildingPreviewComponent.h"

View File

@ -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

View File

@ -0,0 +1,95 @@
//
// Created by sebastian on 21.02.26.
//
#include "BuildPreviewSystem.h"
#include <iostream>
#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<TileRenderComponent>(e);
const auto ownerComponent = em.getComponent<OwnerComponent>(e);
const auto gameplayComponent = em.getComponent<TileGameplayComponent>(e);
if (tileRenderComponent && ownerComponent && ownerComponent->playerID == player && tileRenderComponent->isHighlighted) {
const auto renderStateComponent = em.getComponent<RenderStateComponent>(e);
if (renderStateComponent) renderStateComponent->visible = true;
}
}
for (EntityID e : em.getAllEntities()) {
if (const auto previewComponent = em.getComponent<BuildingPreviewComponent>(e)) {
em.destroyEntity(e);
}
}
previewEntity = 0;
return;
}
bool foundHighlightedTile = false;
for (EntityID e : em.getAllEntities()) {
const auto tileRenderComponent = em.getComponent<TileRenderComponent>(e);
const auto gameplayComponent = em.getComponent<TileGameplayComponent>(e);
if (!tileRenderComponent || !gameplayComponent) continue;
if (tileRenderComponent->isHighlighted) {
foundHighlightedTile = true;
for (EntityID entityOnTile : gameplayComponent->entitiesOnTile) {
const auto renderStateComponent = em.getComponent<RenderStateComponent>(entityOnTile);
if (renderStateComponent) renderStateComponent->visible = false;
}
if (previewEntity == 0) {
const auto tileTransformComponent = em.getComponent<TransformComponent>(e);
if (tileTransformComponent) previewEntity = BuildingFactory::createPreview(em, gameMode.getActiveBuilding().value(), *tileTransformComponent, e);
} else {
const auto previewComponent = em.getComponent<BuildingPreviewComponent>(previewEntity);
if (previewComponent) {
if (previewComponent->buildingType != gameMode.getActiveBuilding().value()) {
em.destroyEntity(previewEntity);
previewEntity = 0;
const auto tileTransformComponent = em.getComponent<TransformComponent>(e);
if (tileTransformComponent) previewEntity = BuildingFactory::createPreview(em, gameMode.getActiveBuilding().value(), *tileTransformComponent, e);
} else {
const auto tileTransformComponent = em.getComponent<TransformComponent>(e);
const auto renderStateComponent = em.getComponent<RenderStateComponent>(previewEntity);
if (tileTransformComponent) {
const auto previewTransformComponent = em.getComponent<TransformComponent>(previewEntity);
if (previewTransformComponent) {
previewTransformComponent->position = tileTransformComponent->position;
}
}
}
}
}
} else {
for (EntityID entityOnTile : gameplayComponent->entitiesOnTile) {
const auto renderStateComponent = em.getComponent<RenderStateComponent>(entityOnTile);
if (renderStateComponent) renderStateComponent->visible = true;
}
}
}
}
void BuildPreviewSystem::disableBuildPreview(EntityManager &em) {
if (previewEntity != 0) {
//em.destroyEntity(previewEntity);
}
}

View File

@ -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

View File

@ -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<int>(def.levels.size() + 1));
entityManager.addComponent(buildingEntity, std::make_shared<UpgradeComponent>(upgrade_component));
}
break;
}
};

View File

@ -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"

View File

@ -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<EntityID> ForestTileGenerator::generateHexTile(EntityManager& em, Ra
const auto transformComponent = std::make_shared<TransformComponent>(treePos, glm::vec3(0), 1.f);
const auto modelComponent = std::make_shared<ModelComponent>(treeModels[treeModelIndex], "hexModelWood");
const auto renderStateComponent = std::make_shared<RenderStateComponent>();
const EntityID entityID = em.createEntity();
em.addComponent(entityID, transformComponent);
em.addComponent(entityID, modelComponent);
em.addComponent(entityID, renderStateComponent);
entitiyIDs.push_back(entityID);
}
return entitiyIDs;
}