Working on fog, dialogs, etc

This commit is contained in:
Vladislav Khorev 2026-04-20 00:21:51 +03:00
parent fd098e8346
commit d557c4b530
18 changed files with 424 additions and 40 deletions

View File

@ -0,0 +1,33 @@
{
"root": {
"type": "LinearLayout",
"orientation": "vertical",
"horizontal_align": "center",
"vertical_align": "center",
"spacing": 20,
"x": 0,
"y": 0,
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "StaticImage",
"name": "title",
"width": 512,
"height": 512,
"texture": "resources/e/menu/final_loose.png"
},
{
"type": "Button",
"name": "backButton",
"width": 512,
"height": 128,
"textures": {
"normal": "resources/e/menu/restart_button.png",
"hover": "resources/e/menu/restart_button.png",
"pressed": "resources/e/menu/restart_button.png"
}
}
]
}
}

View File

@ -0,0 +1,22 @@
{
"root": {
"type": "LinearLayout",
"orientation": "vertical",
"horizontal_align": "center",
"vertical_align": "center",
"spacing": 20,
"x": 0,
"y": 0,
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "StaticImage",
"name": "title",
"width": 512,
"height": 512,
"texture": "resources/e/menu/final_win.png"
}
]
}
}

View File

@ -0,0 +1,15 @@
attribute vec3 vPosition;
attribute vec2 vTexCoord;
varying vec2 texCoord;
varying float fogDistance;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
void main()
{
vec4 eyePos = ModelViewMatrix * vec4(vPosition.xyz, 1.0);
fogDistance = length(eyePos.xyz);
gl_Position = ProjectionModelViewMatrix * vec4(vPosition.xyz, 1.0);
texCoord = vTexCoord;
}

View File

@ -0,0 +1,18 @@
//precisionmediump float;
uniform sampler2D Texture;
varying vec2 texCoord;
varying float fogDistance;
void main()
{
vec4 color = texture2D(Texture,texCoord).rgba;
if(color.a < 0.1)
discard;
vec3 fogColor = vec3(0.53, 0.81, 0.92);
float fogFactor = clamp((fogDistance - 90.0) / 30.0, 0.0, 1.0);
vec3 finalColor = mix(color.rgb, fogColor, fogFactor);
gl_FragColor = vec4(finalColor, color.a);
}

View File

@ -0,0 +1,54 @@
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec4 aBoneIndices0;
attribute vec2 aBoneIndices1;
attribute vec4 aBoneWeights0;
attribute vec2 aBoneWeights1;
varying vec2 texCoord;
varying float fogDistance;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform mat4 uBoneMatrices[64];
void main()
{
vec4 skinnedPos = vec4(0.0, 0.0, 0.0, 0.0);
vec4 originalPos = vec4(vPosition, 1.0);
float totalWeight = 0.0;
if (aBoneWeights0.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.x)] * originalPos * aBoneWeights0.x;
totalWeight += aBoneWeights0.x;
}
if (aBoneWeights0.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.y)] * originalPos * aBoneWeights0.y;
totalWeight += aBoneWeights0.y;
}
if (aBoneWeights0.z > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.z)] * originalPos * aBoneWeights0.z;
totalWeight += aBoneWeights0.z;
}
if (aBoneWeights0.w > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.w)] * originalPos * aBoneWeights0.w;
totalWeight += aBoneWeights0.w;
}
if (aBoneWeights1.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.x)] * originalPos * aBoneWeights1.x;
totalWeight += aBoneWeights1.x;
}
if (aBoneWeights1.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.y)] * originalPos * aBoneWeights1.y;
totalWeight += aBoneWeights1.y;
}
if (totalWeight < 0.001) {
skinnedPos = originalPos;
}
vec4 eyePos = ModelViewMatrix * skinnedPos;
fogDistance = length(eyePos.xyz);
gl_Position = ProjectionModelViewMatrix * skinnedPos;
texCoord = vTexCoord;
}

View File

@ -0,0 +1,18 @@
precision mediump float;
uniform sampler2D Texture;
varying vec2 texCoord;
varying float fogDistance;
void main()
{
vec4 color = texture2D(Texture,texCoord).rgba;
if(color.a < 0.1)
discard;
vec3 fogColor = vec3(0.53, 0.81, 0.92);
float fogFactor = clamp((fogDistance - 90.0) / 30.0, 0.0, 1.0);
vec3 finalColor = mix(color.rgb, fogColor, fogFactor);
gl_FragColor = vec4(finalColor, color.a);
}

View File

@ -291,7 +291,8 @@ void Character::draw(Renderer& renderer) {
auto it = animations.find(drawState);
if (it == animations.end() || texturePerMesh.empty()) return;
renderer.shaderManager.PushShader(defaultShaderName);
static const std::string fogShaderName = "fog";
renderer.shaderManager.PushShader(fogShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
@ -337,7 +338,7 @@ void Character::drawGpuSkinning(Renderer& renderer) {
if (anim.gpuSkinningShaderData.skinningMatrices.empty()) return;
}
static const std::string skinningShaderName = "skinning";
static const std::string skinningShaderName = "fog_skinning";
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
renderer.shaderManager.PushShader(skinningShaderName);

View File

@ -133,6 +133,8 @@ namespace ZL
renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("fog", "resources/shaders/fog.vertex", "resources/shaders/fog_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("fog_skinning", "resources/shaders/fog_skinning.vertex", "resources/shaders/fog_web.fragment", CONST_ZIP_FILE);
#else
renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_desktop.fragment", CONST_ZIP_FILE);
@ -142,26 +144,14 @@ namespace ZL
renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_desktop.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_desktop.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_desktop.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("fog", "resources/shaders/fog.vertex", "resources/shaders/fog_desktop.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("fog_skinning", "resources/shaders/fog_skinning.vertex", "resources/shaders/fog_desktop.fragment", CONST_ZIP_FILE);
#endif
std::cout << "Load resurces step 4" << std::endl;
forestLocation = std::make_shared<Location>(renderer, inventory, "forest");
forestLocation->setup();
forestLocation->onLocationChangeRequest = [this](const std::string& locId) {
this->changeLocation(locId);
};
defaultLocation = std::make_shared<Location>(renderer, inventory, "default");
defaultLocation->setup();
defaultLocation->onLocationChangeRequest = [this](const std::string& locId) {
this->changeLocation(locId);
};
//currentLocation = defaultLocation;
currentLocation = forestLocation;
createLocations();
//currentLocation = forestLocation;
std::cout << "Load resurces step 5" << std::endl;
@ -309,6 +299,7 @@ namespace ZL
menuManager.getState() == GameState::HelpScreen ||
menuManager.getState() == GameState::AboutMenu)
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
drawUI();
return;
@ -404,6 +395,7 @@ namespace ZL
void Game::render() {
ZL::CheckGlError(__FILE__, __LINE__);
//glClearColor(0.53f, 0.81f, 0.92f, 1.0f);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@ -599,6 +591,10 @@ namespace ZL
case SDLK_r:
restartGame();
break;
case SDLK_p:
x = x + 1;
break;
@ -641,12 +637,6 @@ namespace ZL
}
}
if (event.type == SDL_KEYUP) {
if (event.key.keysym.sym == SDLK_r) {
std::cout << "Camera position: x=" << x << " y=" << y << " z=" << z << std::endl;
}
}
if (event.type == SDL_KEYUP) {
switch (event.key.keysym.sym) {
case SDLK_w:
@ -767,6 +757,42 @@ namespace ZL
}
}
void Game::createLocations()
{
forestLocation = std::make_shared<Location>(renderer, inventory, "forest");
forestLocation->setup();
forestLocation->onLocationChangeRequest = [this](const std::string& locId) {
this->changeLocation(locId);
};
defaultLocation = std::make_shared<Location>(renderer, inventory, "default");
defaultLocation->setup();
defaultLocation->onLocationChangeRequest = [this](const std::string& locId) {
this->changeLocation(locId);
};
currentLocation = defaultLocation;
}
void Game::restartGame()
{
std::cout << "[GAME] Restarting..." << std::endl;
// Keep the Location objects alive to avoid reloading textures / meshes /
// skeletal animations. setup() short-circuits to resetState() on locations
// that already have assets loaded.
inventory.clear();
pickedUpObject = nullptr;
if (forestLocation) forestLocation->setup();
if (defaultLocation) defaultLocation->setup();
currentLocation = defaultLocation;
if (menuManager.getState() == GameState::Gameplay) {
updateMusicForLocation("default");
}
}
void Game::changeLocation(const std::string& locId)
{
if (locId == "forest") {

View File

@ -57,10 +57,13 @@ namespace ZL {
MenuManager menuManager;
void changeLocation(const std::string& locId);
void restartGame();
void updateMusicForGameState(GameState newState);
void updateMusicForLocation(const std::string& locationId);
private:
void createLocations();
float mainMenuTimeout = 500.f;
bool rightMouseDown = false;
int lastMouseX = 0;

View File

@ -45,8 +45,13 @@ namespace ZL
void Location::setup()
{
// On restart, assets (textures / meshes / skeletal animations) are reused from
// the first call. Only runtime state (positions, dialogue flags, car/NPC modes)
// is reset via resetState().
if (assetsLoaded) {
resetState();
return;
}
tileMesh.data = LoadFromTextFile02("resources/e/land/land003.txt", CONST_ZIP_FILE);
tileMesh.RefreshVBO();
@ -258,6 +263,9 @@ void Location::setup()
dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json");
std::cout << "[FOREST] Setup complete, loaded " << gameObjects.size() << " custom models" << std::endl;
npcCar.texture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/car003_police.png", CONST_ZIP_FILE));
}
else if (locationId == "barn")
@ -317,6 +325,9 @@ void Location::setup()
firstPersonMode = false;
std::cout << "[BARK] Setup complete, loaded " << gameObjects.size() << " models" << std::endl;
npcCar.texture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/car003_police.png", CONST_ZIP_FILE));
}
else // default
@ -407,6 +418,8 @@ void Location::setup()
scriptEngine.init(this, &inventory);
dialogueSystem.init(renderer, CONST_ZIP_FILE);
dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json");
npcCar.texture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/car_bandit001.png", CONST_ZIP_FILE));
}
carTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/e/car002.png", CONST_ZIP_FILE));
@ -421,7 +434,6 @@ void Location::setup()
carWheelMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
carWheelMesh.RefreshVBO();
npcCar.texture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/car003_police.png", CONST_ZIP_FILE));
//npcCar.position = carPosition + Eigen::Vector3f(0, 0.f, 14.f);//Eigen::Vector3f(-12.f, 0.f, 8.f);
npcCar.rotation = 0.f;
npcCar.mode = NpcCar::Mode::NONE;
@ -445,6 +457,155 @@ void Location::setup()
Eigen::Vector3f(-12.f, 0.f, -8.f),
};*/
//npcCar.currentWaypoint = 0;
assetsLoaded = true;
}
void Location::resetState()
{
dialogueSystem.clearState();
targetInteractiveObject = nullptr;
rightMouseDown = false;
lastMouseX = 0;
lastMouseY = 0;
mouseInitialized = false;
wasKeyForward = false;
keyForward = keyBackward = keyLeft = keyRight = false;
dialoguePlayedOffroad = false;
dialoguePlayedCrash = false;
dialoguePlayedDrivingGas1 = false;
dialogueDrivingGas1Finished = false;
dialoguePlayedGas1 = false;
dialoguePlayedGirlfriend1 = false;
dialoguePlayedPhone1 = false;
dialoguePhone1Finished = false;
npcCarSpawnedAfterPhone = false;
carOutOfGas = false;
dialoguePlayedDrivingGasOut = false;
distanceRemaining = 4000.f;
dialoguePlayedDistance7000 = false;
dialoguePlayedDistance5000 = false;
dialoguePlayedDistance2000 = false;
dialoguePlayedDistance0 = false;
npcCarSpawnedAfterDistance0 = false;
dialoguePlayedDrivingFinal = false;
policeFollow = false;
policeEncounterStage = PoliceEncounterStage::Idle;
playerFrozen = false;
policeDrivingDialogueTimer = 8.0f;
policeChaseDistance = 0.f;
banditFollowingPlayer = false;
dialoguePlayedBanditCaught1 = false;
dialoguePlayedBanditCaught3 = false;
banditEncounterFrozePlayer = false;
banditLastFollowTarget = Eigen::Vector3f::Zero();
banditLastFollowTargetValid = false;
playerOnFootSeconds = 0.f;
npcBanditCarSpawned = false;
dialoguePlayedBandit1 = false;
banditExitedNpcBanditCar = false;
dialoguePlayedDrivingChase1 = false;
dialoguePlayedVillageRescue1 = false;
dialoguePlayedVillageIntro1 = false;
dialogueVillageIntro1Finished = false;
dialoguePlayedVillageIntro2 = false;
dialoguePlayedVillageFinal1 = false;
dialoguePlayedVillageFinal2 = false;
girlfriendLastFollowTarget = Eigen::Vector3f::Zero();
girlfriendLastFollowTargetValid = false;
carVelocity = 0.f;
carSteeringAngle = 0.f;
carRotation = 0.f;
cameraAzimuth = 0.0f;
cameraInclination = M_PI * 30.f / 180.f;
firstPersonMode = false;
savedCameraAzimuth = 0.f;
savedCameraInclination = 0.f;
if (locationId == "forest")
{
girlfriendRescued = true;
inCar = true;
girlfriendInCar = true;
if (salesperson) {
salesperson->position = Vector3f{ -8.31099f - 17.0f, 0.f, -4.26868f };
salesperson->setTarget(salesperson->position);
}
if (police) {
police->position = Vector3f{ 1000.f, 0.f, 10.f };
police->setTarget(police->position);
}
if (bandit) {
bandit->position = Vector3f{ 1000.f, 0.f, 1000.f };
bandit->setTarget(bandit->position);
}
carPosition = Vector3f{ 7.f, 0.f, -7.f + 300.f };
npcCar.position = Vector3f(9.f, 0.f, -335.f) + Vector3f(1000.f, 0.f, 0.f);
if (girlfriend) {
girlfriend->position = Vector3f{ 5.f, 0.f, 0.9f + 300.f };
girlfriend->setTarget(girlfriend->position);
}
if (player) {
player->position = Vector3f{ 9.5f, 0.f, 0.95f + 300.f };
player->setTarget(player->position);
}
}
else // default
{
girlfriendRescued = false;
inCar = false;
girlfriendInCar = false;
if (bandit) {
bandit->position = Vector3f{ 12.1782f, 0.f, 62.4014f };
bandit->setTarget(bandit->position);
}
carPosition = Vector3f{ -6.61929f, 0.f, -30.7197f - 300.f };
carRotation = (float)M_PI;
npcCar.position = Vector3f{ 7.1782f, 0.f, 68.4014f };
if (player) {
player->position = Vector3f{ -6.61929f, 0.f, -40.f - 300.f };
player->setTarget(player->position);
}
cameraAzimuth = (float)M_PI;
if (girlfriend) {
girlfriend->position = Vector3f{ 27.6714f, 0.f, 73.3165f };
girlfriend->setTarget(girlfriend->position);
}
}
npcCar.rotation = 0.f;
npcCar.mode = NpcCar::Mode::NONE;
npcCar.velocity = 0.f;
npcCar.steeringAngle = 0.f;
npcCar.currentWaypoint = 0;
npcBanditCar.rotation = 0.f;
npcBanditCar.mode = NpcCar::Mode::NONE;
npcBanditCar.maxSpeed = 26.0f;
npcBanditCar.maxReverseSpeed = 8.0f;
npcBanditCar.followMinDistance = 10.f;
npcBanditCar.followMaxDistance = 11.5f;
npcBanditCar.position = Vector3f(-1000.f, 0.f, -1000.f);
npcBanditCar.velocity = 0.f;
npcBanditCar.steeringAngle = 0.f;
npcBanditCar.currentWaypoint = 0;
}
void Location::setupNavigation()
@ -621,11 +782,12 @@ void Location::setup()
void Location::drawGame()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClearColor(0.53f, 0.81f, 0.92f, 1.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
renderer.shaderManager.PushShader(defaultShaderName);
static const std::string fogShaderName = "fog";
renderer.shaderManager.PushShader(fogShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,

View File

@ -184,6 +184,8 @@ namespace ZL
void setup();
void resetState();
bool assetsLoaded = false;
void setupNavigation();
InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
Character* raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance = 100.0f);

View File

@ -62,6 +62,27 @@ void DialogueRuntime::stop() {
if (cb) cb();
}
void DialogueRuntime::clearState() {
// Like stop(), but also wipes flags / consumed choices so a fresh run
// can replay dialogues whose conditions depend on those.
activeDialogue = nullptr;
activeCutscene = nullptr;
currentNodeId.clear();
pendingNodeAfterCutscene.clear();
visibleChoices.clear();
selectedChoice = 0;
revealCharacters = 0.0f;
currentCutsceneLine = -1;
cutsceneTimerMs = 0;
cutsceneElapsedMs = 0;
cutsceneTotalDurationMs = 0;
mode = Mode::Inactive;
presentation = {};
onFinishedCallback = nullptr;
flags.clear();
consumedChoices.clear();
}
void DialogueRuntime::update(int deltaMs) {
if (mode == Mode::PresentingLine) {
if (!presentation.revealCompleted) {

View File

@ -18,6 +18,7 @@ public:
bool startDialogue(const std::string& dialogueId, std::function<void()> onFinished = nullptr);
void stop();
void clearState();
void update(int deltaMs);

View File

@ -151,6 +151,13 @@ void DialogueSystem::stopDialogue() {
runtime.stop();
}
void DialogueSystem::clearState() {
runtime.clearState();
triggerZones.clear();
autoSkipTrackedText.clear();
autoSkipTimerMs = 0.0f;
}
void DialogueSystem::addTriggerZone(const TriggerZone& zone) {
triggerZones.push_back(zone);
}

View File

@ -40,6 +40,7 @@ public:
bool startDialogue(const std::string& dialogueId, std::function<void()> onFinished = nullptr);
void stopDialogue();
void clearState();
bool isActive() const { return runtime.isActive(); }
bool blocksGameplayInput() const { return runtime.isActive(); }