Added 3rd person view

This commit is contained in:
Vladislav Khorev 2026-04-18 19:58:44 +03:00
parent 57789eed24
commit 0d7d430909
6 changed files with 149 additions and 63 deletions

View File

@ -72,6 +72,23 @@ void Character::setPathPlanner(PathPlanner planner) {
pathPlanner = std::move(planner); pathPlanner = std::move(planner);
} }
void Character::clearPath() {
walkTarget = position;
requestedWalkTarget = position;
pathWaypoints.clear();
currentWaypointIndex = 0;
onArrivedCallback = nullptr;
}
void Character::setDirectWalkTarget(const Eigen::Vector3f& target) {
Eigen::Vector3f normalized(target.x(), 0.f, target.z());
walkTarget = normalized;
requestedWalkTarget = normalized;
pathWaypoints.clear();
currentWaypointIndex = 0;
onArrivedCallback = nullptr;
}
AnimationState Character::resolveActiveState() const { AnimationState Character::resolveActiveState() const {
if (animations.count(currentState)) return currentState; if (animations.count(currentState)) return currentState;

View File

@ -42,6 +42,8 @@ public:
std::function<void()> onArrived = nullptr); std::function<void()> onArrived = nullptr);
void setPathPlanner(PathPlanner planner); void setPathPlanner(PathPlanner planner);
void clearPath();
void setDirectWalkTarget(const Eigen::Vector3f& target);
void draw(Renderer& renderer); void draw(Renderer& renderer);
void drawShadowDepth(Renderer& renderer); void drawShadowDepth(Renderer& renderer);
void drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera); void drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
@ -49,6 +51,7 @@ public:
// Public: read by Game for camera tracking and ray-cast origin // Public: read by Game for camera tracking and ray-cast origin
Eigen::Vector3f position = Eigen::Vector3f(0.f, 0.f, 0.f); Eigen::Vector3f position = Eigen::Vector3f(0.f, 0.f, 0.f);
float facingAngle = 0.0f; float facingAngle = 0.0f;
float targetFacingAngle = 0.0f;
// Per-character tuning — set after construction, before first update // Per-character tuning — set after construction, before first update
float walkSpeed = 3.0f; float walkSpeed = 3.0f;
@ -89,7 +92,6 @@ private:
std::vector<Eigen::Vector3f> pathWaypoints; std::vector<Eigen::Vector3f> pathWaypoints;
size_t currentWaypointIndex = 0; size_t currentWaypointIndex = 0;
PathPlanner pathPlanner; PathPlanner pathPlanner;
float targetFacingAngle = 0.0f;
std::function<void()> onArrivedCallback; std::function<void()> onArrivedCallback;
static constexpr float WALK_THRESHOLD = 0.05f; static constexpr float WALK_THRESHOLD = 0.05f;

View File

@ -15,7 +15,7 @@ namespace ZL {
using std::max; using std::max;
#endif #endif
constexpr float DEFAULT_ZOOM = 36.f; constexpr float DEFAULT_ZOOM = 4.f;
class Environment { class Environment {
public: public:

View File

@ -144,7 +144,7 @@ namespace ZL
std::cout << "Load resurces step 4" << std::endl; std::cout << "Load resurces step 4" << std::endl;
currentLocation = std::make_unique<Location>(renderer, inventory); currentLocation = std::make_unique<Location>(renderer, inventory, "forest");
currentLocation->setup(); currentLocation->setup();
@ -374,6 +374,13 @@ namespace ZL
//space.clearTextRendererCache(); //space.clearTextRendererCache();
} }
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
SDL_SetRelativeMouseMode(SDL_TRUE);
}
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
SDL_SetRelativeMouseMode(SDL_FALSE);
}
#ifdef __ANDROID__ #ifdef __ANDROID__
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) { if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) {
Environment::exitGameLoop = true; Environment::exitGameLoop = true;
@ -468,8 +475,8 @@ namespace ZL
handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my); handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
if (currentLocation) if (currentLocation)
{ {
currentLocation->handleMotion(ZL::UiManager::MOUSE_FINGER_ID, event.motion.x, event.motion.y, mx, my); currentLocation->handleMotion(ZL::UiManager::MOUSE_FINGER_ID, event.motion.xrel, event.motion.yrel, mx, my);
} }
} }
if (event.type == SDL_MOUSEWHEEL) { if (event.type == SDL_MOUSEWHEEL) {
@ -532,6 +539,7 @@ namespace ZL
case SDLK_a: case SDLK_a:
case SDLK_d: case SDLK_d:
case SDLK_u: case SDLK_u:
case SDLK_i:
if (currentLocation) currentLocation->handleKeyDown(event.key.keysym.sym); if (currentLocation) currentLocation->handleKeyDown(event.key.keysym.sym);
break; break;
@ -568,6 +576,7 @@ namespace ZL
case SDLK_s: case SDLK_s:
case SDLK_a: case SDLK_a:
case SDLK_d: case SDLK_d:
case SDLK_i:
if (currentLocation) currentLocation->handleKeyUp(event.key.keysym.sym); if (currentLocation) currentLocation->handleKeyUp(event.key.keysym.sym);
break; break;
default: break; default: break;

View File

@ -32,6 +32,9 @@ namespace ZL
void Location::setup() void Location::setup()
{ {
carPosition = { 5.4005, 0, -3.811283 };
auto playerTexture0 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/male_packed0_diffuse.png", CONST_ZIP_FILE)); auto playerTexture0 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/male_packed0_diffuse.png", CONST_ZIP_FILE));
auto playerTexture1 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/male_packed1_diffuse.png", CONST_ZIP_FILE)); auto playerTexture1 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/male_packed1_diffuse.png", CONST_ZIP_FILE));
auto playerTexture2 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/male_packed2_diffuse.png", CONST_ZIP_FILE)); auto playerTexture2 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/male_packed2_diffuse.png", CONST_ZIP_FILE));
@ -95,8 +98,8 @@ void Location::setup()
salesperson->walkSpeed = 3.0f; salesperson->walkSpeed = 3.0f;
salesperson->rotationSpeed = 8.0f; salesperson->rotationSpeed = 8.0f;
salesperson->modelScale = 0.01f; salesperson->modelScale = 0.01f;
salesperson->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); salesperson->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(0, Eigen::Vector3f::UnitY()));
salesperson->position = Vector3f{ 10, -0, -10 }; salesperson->position = Vector3f{ -8.31099, -0, -3.56868 };
salesperson->setTarget(salesperson->position); salesperson->setTarget(salesperson->position);
auto policeTexture0 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/police_packed0_diffuse.png", CONST_ZIP_FILE)); auto policeTexture0 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/police_packed0_diffuse.png", CONST_ZIP_FILE));
@ -280,39 +283,12 @@ void Location::setup()
carWheelMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix()); carWheelMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
carWheelMesh.RefreshVBO(); carWheelMesh.RefreshVBO();
/*
auto girlfriendTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/e/female_packed0_diffuse.png", CONST_ZIP_FILE));
girlfriend = std::make_unique<Character>();
girlfriend->loadAnimation(AnimationState::STAND, "resources/e/woman_stand_idle001.txt", CONST_ZIP_FILE);
girlfriend->loadAnimation(AnimationState::WALK, "resources/e/woman_run001.txt", CONST_ZIP_FILE);
girlfriend->setTexture(girlfriendTexture);
girlfriend->walkSpeed = 3.0f;
girlfriend->rotationSpeed = 8.0f;
girlfriend->modelScale = 0.01f;
girlfriend->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
girlfriend->position = Vector3f{ -10, -0, -10 };
girlfriend->setTarget(girlfriend->position);*/
} }
void Location::setupNavigation() void Location::setupNavigation()
{ {
std::vector<PathFinder::ObstacleMesh> obstacles; std::vector<PathFinder::ObstacleMesh> obstacles;
/*obstacles.reserve(gameObjects.size() + interactiveObjects.size());
for (const auto& item : gameObjects) {
const LoadedGameObject& gameObj = item.second;
obstacles.push_back({ &gameObj.mesh.data, Eigen::Vector3f::Zero() });
}
for (const InteractiveObject& intObj : interactiveObjects) {
if (!intObj.isActive) {
continue;
}
obstacles.push_back({ &intObj.mesh.data, intObj.position });
}*/
if (locationId == "forest") { if (locationId == "forest") {
navigation.build(obstacles, "resources/config2/navigation2.json", CONST_ZIP_FILE); navigation.build(obstacles, "resources/config2/navigation2.json", CONST_ZIP_FILE);
@ -495,13 +471,14 @@ void Location::setup()
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
//renderer.TranslateMatrix({ 0, -6.f, 0 });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix()); renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix()); renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
Eigen::Vector3f camTarget = inCar ? carPosition : (player ? player->position : Eigen::Vector3f::Zero()); Eigen::Vector3f camTarget = inCar ? carPosition : (player ? player->position : Eigen::Vector3f::Zero());
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() }); renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
renderer.TranslateMatrix({ 0, -1.3f, 0 });
if (roomMesh.data.PositionData.size() > 0) if (roomMesh.data.PositionData.size() > 0)
{ {
@ -581,8 +558,8 @@ void Location::setup()
for (auto& npc : npcs) npc->draw(renderer); for (auto& npc : npcs) npc->draw(renderer);
//#ifdef SHOW_PATH //#ifdef SHOW_PATH
drawDebugNavigation(); //drawDebugNavigation();
drawDebugForbidden(); //drawDebugForbidden();
//#endif //#endif
renderer.PopMatrix(); renderer.PopMatrix();
@ -777,9 +754,52 @@ void Location::setup()
return navigation.setAreaAvailable(areaName, available); return navigation.setAreaAvailable(areaName, available);
} }
bool Location::isCarFootprintWalkable(const Eigen::Vector3f& center, float rotation) const
{
if (!navigation.isReady()) return true;
constexpr float carLength = 7.3f;
constexpr float carWidth = 2.8f;
constexpr float sampleSpacing = 0.35f;
const float halfLength = carLength * 0.5f;
const float halfWidth = carWidth * 0.5f;
const int longSteps = max(1, static_cast<int>(std::ceil(carLength / sampleSpacing)));
const int widthSteps = max(1, static_cast<int>(std::ceil(carWidth / sampleSpacing)));
const Eigen::Vector3f forward(-std::sin(rotation), 0.f, -std::cos(rotation));
const Eigen::Vector3f right(std::cos(rotation), 0.f, -std::sin(rotation));
for (int i = 0; i <= longSteps; ++i) {
const float fl = (static_cast<float>(i) / static_cast<float>(longSteps)) * 2.0f - 1.0f;
const Eigen::Vector3f lineOffset = forward * (fl * halfLength);
for (int j = 0; j <= widthSteps; ++j) {
const float fw = (static_cast<float>(j) / static_cast<float>(widthSteps)) * 2.0f - 1.0f;
const Eigen::Vector3f sample = center + lineOffset + right * (fw * halfWidth);
if (!navigation.isWalkable(sample)) {
return false;
}
}
}
return true;
}
void Location::update(int64_t delta) void Location::update(int64_t delta)
{ {
const Eigen::Vector3f playerPosBefore = player ? player->position : Eigen::Vector3f::Zero();
if (player) { if (player) {
if (!inCar) {
player->targetFacingAngle = cameraAzimuth;
if (keyForward) {
player->attackTarget = nullptr;
Eigen::Vector3f forward(std::sin(cameraAzimuth), 0.f, -std::cos(cameraAzimuth));
player->setDirectWalkTarget(player->position + forward * 5.0f);
} else if (wasKeyForward) {
player->clearPath();
}
wasKeyForward = keyForward;
}
player->update(delta); player->update(delta);
dialogueSystem.update(static_cast<int>(delta), player->position); dialogueSystem.update(static_cast<int>(delta), player->position);
if (!roofObjectKey.empty()) { if (!roofObjectKey.empty()) {
@ -824,25 +844,55 @@ void Location::setup()
carVelocity = max(-carMaxReverseSpeed, min(carMaxSpeed, carVelocity)); carVelocity = max(-carMaxReverseSpeed, min(carMaxSpeed, carVelocity));
const float oldRotation = carRotation;
if (std::abs(carVelocity) > 0.01f) { if (std::abs(carVelocity) > 0.01f) {
float speedFactor = carVelocity / carMaxSpeed; float speedFactor = carVelocity / carMaxSpeed;
if (keyLeft) carRotation += carTurnRate * dt * speedFactor; if (keyLeft) carRotation += carTurnRate * dt * speedFactor;
if (keyRight) carRotation -= carTurnRate * dt * speedFactor; if (keyRight) carRotation -= carTurnRate * dt * speedFactor;
} }
if (navigation.isReady() && !isCarFootprintWalkable(carPosition, carRotation)) {
carRotation = oldRotation;
}
float targetSteer = (keyLeft ? 1.f : 0.f) - (keyRight ? 1.f : 0.f); float targetSteer = (keyLeft ? 1.f : 0.f) - (keyRight ? 1.f : 0.f);
targetSteer *= carMaxSteerAngle; targetSteer *= carMaxSteerAngle;
float steerLerp = min(1.f, dt * 8.f); float steerLerp = min(1.f, dt * 8.f);
carSteeringAngle += (targetSteer - carSteeringAngle) * steerLerp; carSteeringAngle += (targetSteer - carSteeringAngle) * steerLerp;
Eigen::Vector3f forward(-std::sin(carRotation), 0.f, -std::cos(carRotation)); const Eigen::Vector3f forward(-std::sin(carRotation), 0.f, -std::cos(carRotation));
carPosition += forward * carVelocity * dt; const Eigen::Vector3f totalDelta = forward * carVelocity * dt;
const float totalDist = totalDelta.norm();
const float maxSubstep = 0.2f;
const int substeps = max(1, static_cast<int>(std::ceil(totalDist / maxSubstep)));
const Eigen::Vector3f stepDelta = totalDelta / static_cast<float>(substeps);
for (int s = 0; s < substeps; ++s) {
Eigen::Vector3f candidate = carPosition + stepDelta;
if (navigation.isReady() && !isCarFootprintWalkable(candidate, carRotation)) {
carVelocity = 0.f;
break;
}
carPosition = candidate;
}
if (player) { if (player) {
player->position = carPosition; player->position = carPosition;
} }
} }
if (player && !inCar && navigation.isReady() && !navigation.isWalkable(player->position)) {
const Eigen::Vector3f slideX(player->position.x(), playerPosBefore.y(), playerPosBefore.z());
const Eigen::Vector3f slideZ(playerPosBefore.x(), playerPosBefore.y(), player->position.z());
Eigen::Vector3f resolved;
if (navigation.isWalkable(slideX)) {
resolved = slideX;
} else if (navigation.isWalkable(slideZ)) {
resolved = slideZ;
} else {
resolved = playerPosBefore;
}
player->position = resolved;
}
// Check if player reached target interactive object // Check if player reached target interactive object
if (targetInteractiveObject && player) { if (targetInteractiveObject && player) {
float distToObject = (player->position - targetInteractiveObject->position).norm(); float distToObject = (player->position - targetInteractiveObject->position).norm();
@ -915,9 +965,14 @@ void Location::setup()
std::cout << "[CLICK] Player position: (" << player->position.x() << ", " std::cout << "[CLICK] Player position: (" << player->position.x() << ", "
<< player->position.y() << ", " << player->position.z() << ")" << std::endl; << player->position.y() << ", " << player->position.z() << ")" << std::endl;
targetInteractiveObject = clickedObject; float distance = (player->position - clickedObject->position).norm();
player->setTarget(clickedObject->position);
std::cout << "[CLICK] Player moving to object..." << std::endl; if (distance < 5.0)
{
targetInteractiveObject = clickedObject;
}
//player->setTarget(clickedObject->position);
//std::cout << "[CLICK] Player moving to object..." << std::endl;
} }
else { else {
@ -949,6 +1004,7 @@ void Location::setup()
} }
} }
/*
else if (rayDir.y() < -0.001f && player) { else if (rayDir.y() < -0.001f && player) {
// Otherwise, unproject click to ground plane for Viola's walk target // Otherwise, unproject click to ground plane for Viola's walk target
float t = -camPos.y() / rayDir.y(); float t = -camPos.y() / rayDir.y();
@ -959,7 +1015,7 @@ void Location::setup()
{ {
player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z())); player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z()));
} }
} }*/
else { else {
std::cout << "[CLICK] No valid target found" << std::endl; std::cout << "[CLICK] No valid target found" << std::endl;
} }
@ -970,29 +1026,24 @@ void Location::setup()
{ {
} }
void Location::handleMotion(int64_t fingerId, int eventX, int eventY, int mx, int my) void Location::handleMotion(int64_t fingerId, int dx, int dy, int mx, int my)
{ {
if (dialogueSystem.blocksGameplayInput()) { if (dialogueSystem.blocksGameplayInput()) {
dialogueSystem.handlePointerMoved( dialogueSystem.handlePointerMoved(
static_cast<float>(mx), static_cast<float>(mx),
Environment::projectionHeight - static_cast<float>(my) Environment::projectionHeight - static_cast<float>(my)
); );
return;
} }
if (rightMouseDown) { const float sensitivity = 0.005f;
int dx = eventX - lastMouseX; const float inclinationSign = invertCameraY ? -1.0f : 1.0f;
int dy = eventY - lastMouseY; cameraAzimuth += dx * sensitivity;
lastMouseX = eventX; cameraInclination += inclinationSign * dy * sensitivity;
lastMouseY = eventY;
const float sensitivity = 0.005f; const float minInclination = M_PI * 10.f / 180.f;
cameraAzimuth += dx * sensitivity; const float maxInclination = M_PI * 0.5f;
cameraInclination += dy * sensitivity; cameraInclination = max(minInclination, min(maxInclination, cameraInclination));
const float minInclination = M_PI * 30.f / 180.f;
const float maxInclination = M_PI * 0.5f;
cameraInclination = max(minInclination, min(maxInclination, cameraInclination));
}
} }
void Location::handleKeyDown(int sdlKey) void Location::handleKeyDown(int sdlKey)
@ -1002,8 +1053,10 @@ void Location::setup()
case SDLK_s: keyBackward = true; break; case SDLK_s: keyBackward = true; break;
case SDLK_a: keyLeft = true; break; case SDLK_a: keyLeft = true; break;
case SDLK_d: keyRight = true; break; case SDLK_d: keyRight = true; break;
case SDLK_u: case SDLK_i: invertCameraY = !invertCameraY; break;
girlfriend->setTarget(player->position); case SDLK_u:
std::cout << player->position << std::endl;
//girlfriend->setTarget(player->position);
break; break;
default: break; default: break;
} }

View File

@ -85,6 +85,9 @@ namespace ZL
bool rightMouseDown = false; bool rightMouseDown = false;
int lastMouseX = 0; int lastMouseX = 0;
int lastMouseY = 0; int lastMouseY = 0;
bool mouseInitialized = false;
bool wasKeyForward = false;
bool invertCameraY = false;
void setup(); void setup();
@ -102,7 +105,7 @@ namespace ZL
void handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my); void handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my);
void handleUp(int64_t fingerId, int mx, int my); void handleUp(int64_t fingerId, int mx, int my);
void handleMotion(int64_t fingerId, int eventX, int eventY, int mx, int my); void handleMotion(int64_t fingerId, int dx, int dy, int mx, int my);
void handleKeyDown(int sdlKey); void handleKeyDown(int sdlKey);
void handleKeyUp(int sdlKey); void handleKeyUp(int sdlKey);
@ -115,13 +118,15 @@ namespace ZL
protected: protected:
Renderer& renderer; Renderer& renderer;
Inventory& inventory; Inventory& inventory;
private: private:
std::string locationId; std::string locationId;
std::string roofObjectKey; std::string roofObjectKey;
bool roofVisible = true; bool roofVisible = true;
std::vector<Eigen::AlignedBox<float, 2>> roofHideZones; std::vector<Eigen::AlignedBox<float, 2>> roofHideZones;
bool isCarFootprintWalkable(const Eigen::Vector3f& center, float rotation) const;
}; };
} // namespace ZL } // namespace ZL