added inventory & active object
This commit is contained in:
parent
e1b491be73
commit
b3fda88784
@ -76,6 +76,10 @@ add_executable(space-game001
|
|||||||
|
|
||||||
../src/items/GameObjectLoader.h
|
../src/items/GameObjectLoader.h
|
||||||
../src/items/GameObjectLoader.cpp
|
../src/items/GameObjectLoader.cpp
|
||||||
|
../src/items/Item.h
|
||||||
|
../src/items/Item.cpp
|
||||||
|
../src/items/InteractiveObject.h
|
||||||
|
../src/items/InteractiveObject.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Установка проекта по умолчанию для Visual Studio
|
# Установка проекта по умолчанию для Visual Studio
|
||||||
|
|||||||
@ -10,7 +10,8 @@
|
|||||||
"positionX": 0.0,
|
"positionX": 0.0,
|
||||||
"positionY": 0.0,
|
"positionY": 0.0,
|
||||||
"positionZ": 0.0,
|
"positionZ": 0.0,
|
||||||
"scale": 1.0
|
"scale": 1.0,
|
||||||
|
"interactive": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "inai",
|
"name": "inai",
|
||||||
@ -22,7 +23,8 @@
|
|||||||
"positionX": 2.5,
|
"positionX": 2.5,
|
||||||
"positionY": 1.4,
|
"positionY": 1.4,
|
||||||
"positionZ": -9.9,
|
"positionZ": -9.9,
|
||||||
"scale": 1.0
|
"scale": 1.0,
|
||||||
|
"interactive": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bench",
|
"name": "bench",
|
||||||
@ -34,7 +36,29 @@
|
|||||||
"positionX": -2.1,
|
"positionX": -2.1,
|
||||||
"positionY": 0.5,
|
"positionY": 0.5,
|
||||||
"positionZ": -7.9,
|
"positionZ": -7.9,
|
||||||
"scale": 3.0
|
"scale": 3.0,
|
||||||
|
"interactive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "medik",
|
||||||
|
"texturePath": "resources/fire2.png",
|
||||||
|
"meshPath": "resources/w/firebox.txt",
|
||||||
|
"rotationX": 0.0,
|
||||||
|
"rotationY": -1.5707963267948966,
|
||||||
|
"rotationZ": 0.0,
|
||||||
|
"positionX": 0.0,
|
||||||
|
"positionY": 0.0,
|
||||||
|
"positionZ": 4.0,
|
||||||
|
"scale": 1.0,
|
||||||
|
"interactive": true,
|
||||||
|
"item": {
|
||||||
|
"id": "health_potion",
|
||||||
|
"name": "Health Potion",
|
||||||
|
"description": "Restores 50 HP",
|
||||||
|
"icon": "resources/fire2.png",
|
||||||
|
"radius": 2.0
|
||||||
|
},
|
||||||
|
"activateFunction" : "on_health_pickup"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
67
resources/config2/ui_inventory.json
Normal file
67
resources/config2/ui_inventory.json
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"root": {
|
||||||
|
"type": "FrameLayout",
|
||||||
|
"name": "inventory_root",
|
||||||
|
"width": "match_parent",
|
||||||
|
"height": "match_parent",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "TextButton",
|
||||||
|
"name": "inventory_button",
|
||||||
|
"x": 50.0,
|
||||||
|
"y": 50.0,
|
||||||
|
"width": 150.0,
|
||||||
|
"height": 60.0,
|
||||||
|
"text": "Inventory",
|
||||||
|
"fontSize": 24,
|
||||||
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
|
"textCentered": true,
|
||||||
|
"color": [1.0, 1.0, 1.0, 1.0],
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/connecting.png",
|
||||||
|
"hover": "resources/connecting.png",
|
||||||
|
"pressed": "resources/connecting.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FrameLayout",
|
||||||
|
"name": "inventory_items_panel",
|
||||||
|
"x": 100.0,
|
||||||
|
"y": 150.0,
|
||||||
|
"width": 500.0,
|
||||||
|
"height": 600.0,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "TextView",
|
||||||
|
"name": "inventory_items_text",
|
||||||
|
"x": 100.0,
|
||||||
|
"y": 30.0,
|
||||||
|
"width": 460.0,
|
||||||
|
"height": 450.0,
|
||||||
|
"text": "Inventory (Empty)",
|
||||||
|
"fontSize": 18,
|
||||||
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
|
"centered": false,
|
||||||
|
"color": [1.0, 1.0, 1.0, 1.0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "TextButton",
|
||||||
|
"name": "close_inventory_button",
|
||||||
|
"x": 200.0,
|
||||||
|
"y": 20.0,
|
||||||
|
"width": 40.0,
|
||||||
|
"height": 40.0,
|
||||||
|
"text": "X",
|
||||||
|
"fontSize": 20,
|
||||||
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
|
"textCentered": true,
|
||||||
|
"color": [1.0, 1.0, 1.0, 1.0],
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/spark.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
-- Chain of waypoints for NPC 0.
|
-- ============================================
|
||||||
-- Each on_arrived callback issues the next walk command.
|
-- NPC PATROL WAYPOINTS
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
local function step3()
|
local function step3()
|
||||||
game_api.npc_walk_to(0, 0.0, 0.0, -30.0, step1)
|
game_api.npc_walk_to(0, 0.0, 0.0, -30.0, step1)
|
||||||
@ -14,3 +15,25 @@ function step1()
|
|||||||
end
|
end
|
||||||
|
|
||||||
step1()
|
step1()
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- ITEM PICKUP HANDLER
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
function on_health_pickup()
|
||||||
|
game_api.pickup_item("health_potion")
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function on_item_pickup(object_name)
|
||||||
|
print("========================================")
|
||||||
|
print("on_item_pickup CALLBACK CALLED!")
|
||||||
|
print("Player picked up item from: " .. tostring(object_name))
|
||||||
|
print("========================================")
|
||||||
|
|
||||||
|
game_api.pickup_item(object_name)
|
||||||
|
|
||||||
|
--game_api.show_inventory()
|
||||||
|
end
|
||||||
|
|
||||||
|
print("Lua script loaded successfully!")
|
||||||
166
src/Game.cpp
166
src/Game.cpp
@ -148,34 +148,11 @@ namespace ZL
|
|||||||
roomMesh.RefreshVBO();
|
roomMesh.RefreshVBO();
|
||||||
std::cout << "Load resurces step 5" << std::endl;
|
std::cout << "Load resurces step 5" << std::endl;
|
||||||
|
|
||||||
|
// Load static game objects
|
||||||
gameObjects = GameObjectLoader::loadAndCreateGameObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
|
gameObjects = GameObjectLoader::loadAndCreateGameObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
|
||||||
|
|
||||||
/*
|
// Load interactive objects
|
||||||
std::cout << "Load resurces step 5" << std::endl;
|
interactiveObjects = GameObjectLoader::loadAndCreateInteractiveObjects("resources/config2/gameobjects.json", renderer, CONST_ZIP_FILE);
|
||||||
fireboxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/Cube001.png", CONST_ZIP_FILE));
|
|
||||||
fireboxMesh.data = LoadFromTextFile02("resources/w/firebox.txt", CONST_ZIP_FILE);
|
|
||||||
fireboxMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
||||||
fireboxMesh.RefreshVBO();
|
|
||||||
|
|
||||||
|
|
||||||
std::cout << "Load resurces step 6" << std::endl;
|
|
||||||
inaiTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/inai001.png", CONST_ZIP_FILE));
|
|
||||||
std::cout << "Load resurces step 6.1" << std::endl;
|
|
||||||
inaiMesh.data = LoadFromTextFile02("resources/w/inai001.txt", CONST_ZIP_FILE);
|
|
||||||
inaiMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
||||||
inaiMesh.data.Move({ 2.5, 1.4, -9.9 });
|
|
||||||
inaiMesh.RefreshVBO();
|
|
||||||
|
|
||||||
std::cout << "Load resurces step 7" << std::endl;
|
|
||||||
benchTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/w/bench001opt.png", CONST_ZIP_FILE));
|
|
||||||
benchMesh.data = LoadFromTextFile02("resources/w/bench002opt.txt", CONST_ZIP_FILE);
|
|
||||||
benchMesh.data.Scale(3.f);
|
|
||||||
benchMesh.data.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
|
||||||
benchMesh.data.Move({ -2.1, 0.5, -7.9 });
|
|
||||||
benchMesh.RefreshVBO();
|
|
||||||
*/
|
|
||||||
|
|
||||||
std::cout << "Load resurces step 8" << std::endl;
|
|
||||||
|
|
||||||
auto violaTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/viola.png", CONST_ZIP_FILE));
|
auto violaTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/viola.png", CONST_ZIP_FILE));
|
||||||
|
|
||||||
@ -236,8 +213,91 @@ namespace ZL
|
|||||||
scriptEngine.init(this);
|
scriptEngine.init(this);
|
||||||
|
|
||||||
std::cout << "Load resurces step 13" << std::endl;
|
std::cout << "Load resurces step 13" << std::endl;
|
||||||
|
|
||||||
|
// Load UI with inventory button
|
||||||
|
try {
|
||||||
|
menuManager.uiManager.loadFromFile("resources/config2/ui_inventory.json", renderer, CONST_ZIP_FILE);
|
||||||
|
std::cout << "UI loaded successfully" << std::endl;
|
||||||
|
|
||||||
|
menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
|
||||||
|
menuManager.uiManager.setNodeVisible("close_inventory_button", false);
|
||||||
|
|
||||||
|
menuManager.uiManager.setTextButtonCallback("inventory_button", [this](const std::string& name) {
|
||||||
|
std::cout << "[UI] Inventory button clicked" << std::endl;
|
||||||
|
//scriptEngine.callItemPickupCallback("toggle_inventory");
|
||||||
|
scriptEngine.showInventory(this);
|
||||||
|
//scriptEngine
|
||||||
|
//scriptEngine
|
||||||
|
});
|
||||||
|
|
||||||
|
menuManager.uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string& name) {
|
||||||
|
std::cout << "[UI] Close button clicked" << std::endl;
|
||||||
|
menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
|
||||||
|
menuManager.uiManager.setNodeVisible("close_inventory_button", false);
|
||||||
|
inventoryOpen = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
std::cerr << "Failed to load UI: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InteractiveObject* Game::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) {
|
||||||
|
if (interactiveObjects.empty()) {
|
||||||
|
std::cout << "[RAYCAST] No interactive objects to check" << std::endl;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "[RAYCAST] Starting raycast with " << interactiveObjects.size() << " objects" << std::endl;
|
||||||
|
std::cout << "[RAYCAST] Ray origin: (" << rayOrigin.x() << ", " << rayOrigin.y() << ", " << rayOrigin.z() << ")" << std::endl;
|
||||||
|
std::cout << "[RAYCAST] Ray dir: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
|
||||||
|
|
||||||
|
float closestDistance = FLT_MAX;
|
||||||
|
InteractiveObject* closestObject = nullptr;
|
||||||
|
|
||||||
|
for (auto& intObj : interactiveObjects) {
|
||||||
|
std::cout << "[RAYCAST] Checking object: " << intObj.name << " (active: " << intObj.isActive << ")" << std::endl;
|
||||||
|
|
||||||
|
if (!intObj.isActive) {
|
||||||
|
std::cout << "[RAYCAST] -> Object inactive, skipping" << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", "
|
||||||
|
<< intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl;
|
||||||
|
|
||||||
|
Eigen::Vector3f toObject = intObj.position - rayOrigin;
|
||||||
|
std::cout << "[RAYCAST] Vector to object: (" << toObject.x() << ", " << toObject.y() << ", " << toObject.z() << ")" << std::endl;
|
||||||
|
|
||||||
|
float distanceAlongRay = toObject.dot(rayDir);
|
||||||
|
std::cout << "[RAYCAST] Distance along ray: " << distanceAlongRay << std::endl;
|
||||||
|
|
||||||
|
if (distanceAlongRay < 0.1f) {
|
||||||
|
std::cout << "[RAYCAST] -> Object behind camera, skipping" << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Vector3f closestPointOnRay = rayOrigin + rayDir * distanceAlongRay;
|
||||||
|
float distToObject = (closestPointOnRay - intObj.position).norm();
|
||||||
|
|
||||||
|
std::cout << "[RAYCAST] Distance to object: " << distToObject
|
||||||
|
<< " (interaction radius: " << intObj.interactionRadius << ")" << std::endl;
|
||||||
|
|
||||||
|
if (distToObject <= intObj.interactionRadius && distanceAlongRay < closestDistance) {
|
||||||
|
std::cout << "[RAYCAST] *** HIT DETECTED! ***" << std::endl;
|
||||||
|
closestDistance = distanceAlongRay;
|
||||||
|
closestObject = &intObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestObject) {
|
||||||
|
std::cout << "[RAYCAST] *** RAYCAST SUCCESS: Found object " << closestObject->name << " ***" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "[RAYCAST] No objects hit" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return closestObject;
|
||||||
|
}
|
||||||
|
|
||||||
void Game::drawUI()
|
void Game::drawUI()
|
||||||
{
|
{
|
||||||
@ -291,6 +351,11 @@ namespace ZL
|
|||||||
glBindTexture(GL_TEXTURE_2D, benchTexture->getTexID());
|
glBindTexture(GL_TEXTURE_2D, benchTexture->getTexID());
|
||||||
renderer.DrawVertexRenderStruct(benchMesh);
|
renderer.DrawVertexRenderStruct(benchMesh);
|
||||||
*/
|
*/
|
||||||
|
for (auto& intObj : interactiveObjects) {
|
||||||
|
if (intObj.isActive) {
|
||||||
|
intObj.draw(renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (player) player->draw(renderer);
|
if (player) player->draw(renderer);
|
||||||
for (auto& npc : npcs) npc->draw(renderer);
|
for (auto& npc : npcs) npc->draw(renderer);
|
||||||
@ -374,9 +439,23 @@ namespace ZL
|
|||||||
|
|
||||||
if (player) player->update(delta);
|
if (player) player->update(delta);
|
||||||
for (auto& npc : npcs) npc->update(delta);
|
for (auto& npc : npcs) npc->update(delta);
|
||||||
|
|
||||||
|
// Check if player reached target interactive object
|
||||||
|
if (targetInteractiveObject && player) {
|
||||||
|
float distToObject = (player->position - targetInteractiveObject->position).norm();
|
||||||
|
|
||||||
|
// If player is close enough to pick up the item
|
||||||
|
if (distToObject <= targetInteractiveObject->interactionRadius + 1.0f) {
|
||||||
|
std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl;
|
||||||
|
std::cout << "[PICKUP] Calling Lua callback for: " << targetInteractiveObject->id << std::endl;
|
||||||
|
|
||||||
|
// Call Lua callback
|
||||||
|
scriptEngine.callItemPickupCallback(targetInteractiveObject->id);
|
||||||
|
|
||||||
|
targetInteractiveObject = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::render() {
|
void Game::render() {
|
||||||
@ -456,13 +535,14 @@ namespace ZL
|
|||||||
|
|
||||||
if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
|
if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
|
||||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||||
// Преобразуем экранные пиксели в проекционные единицы
|
|
||||||
int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth);
|
int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth);
|
||||||
int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight);
|
int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight);
|
||||||
|
|
||||||
if (event.type == SDL_MOUSEBUTTONDOWN) {
|
if (event.type == SDL_MOUSEBUTTONDOWN) {
|
||||||
|
std::cout << "\n========== MOUSE DOWN EVENT ==========" << std::endl;
|
||||||
handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||||
|
|
||||||
// Unproject click to ground plane (y=0) for Viola's walk target
|
// Calculate ray for picking
|
||||||
float ndcX = 2.0f * event.button.x / Environment::width - 1.0f;
|
float ndcX = 2.0f * event.button.x / Environment::width - 1.0f;
|
||||||
float ndcY = 1.0f - 2.0f * event.button.y / Environment::height;
|
float ndcY = 1.0f - 2.0f * event.button.y / Environment::height;
|
||||||
float aspect = (float)Environment::width / (float)Environment::height;
|
float aspect = (float)Environment::width / (float)Environment::height;
|
||||||
@ -479,11 +559,33 @@ namespace ZL
|
|||||||
|
|
||||||
Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized();
|
Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized();
|
||||||
|
|
||||||
if (rayDir.y() < -0.001f && player) {
|
std::cout << "[CLICK] Camera position: (" << camPos.x() << ", " << camPos.y() << ", " << camPos.z() << ")" << std::endl;
|
||||||
|
std::cout << "[CLICK] Ray direction: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
|
||||||
|
|
||||||
|
// First check if we clicked on interactive object
|
||||||
|
InteractiveObject* clickedObject = raycastInteractiveObjects(camPos, rayDir);
|
||||||
|
if (clickedObject && player && clickedObject->isActive) {
|
||||||
|
std::cout << "[CLICK] *** SUCCESS: Clicked on interactive object: " << clickedObject->name << " ***" << std::endl;
|
||||||
|
std::cout << "[CLICK] Object position: (" << clickedObject->position.x() << ", "
|
||||||
|
<< clickedObject->position.y() << ", " << clickedObject->position.z() << ")" << std::endl;
|
||||||
|
std::cout << "[CLICK] Player position: (" << player->position.x() << ", "
|
||||||
|
<< player->position.y() << ", " << player->position.z() << ")" << std::endl;
|
||||||
|
|
||||||
|
targetInteractiveObject = clickedObject;
|
||||||
|
player->setTarget(clickedObject->position);
|
||||||
|
std::cout << "[CLICK] Player moving to object..." << std::endl;
|
||||||
|
}
|
||||||
|
else if (rayDir.y() < -0.001f && player) {
|
||||||
|
// Otherwise, unproject click to ground plane for Viola's walk target
|
||||||
float t = -camPos.y() / rayDir.y();
|
float t = -camPos.y() / rayDir.y();
|
||||||
Eigen::Vector3f hit = camPos + rayDir * t;
|
Eigen::Vector3f hit = camPos + rayDir * t;
|
||||||
|
std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl;
|
||||||
player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z()));
|
player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z()));
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "[CLICK] No valid target found" << std::endl;
|
||||||
|
}
|
||||||
|
std::cout << "========================================\n" << std::endl;
|
||||||
} else {
|
} else {
|
||||||
handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/Game.h
18
src/Game.h
@ -10,6 +10,8 @@
|
|||||||
#include "Projectile.h"
|
#include "Projectile.h"
|
||||||
#include "utils/TaskManager.h"
|
#include "utils/TaskManager.h"
|
||||||
#include "items/GameObjectLoader.h"
|
#include "items/GameObjectLoader.h"
|
||||||
|
#include "items/Item.h"
|
||||||
|
#include "items/InteractiveObject.h"
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -61,12 +63,24 @@ namespace ZL {
|
|||||||
std::shared_ptr<Texture> benchTexture;
|
std::shared_ptr<Texture> benchTexture;
|
||||||
VertexRenderStruct benchMesh;
|
VertexRenderStruct benchMesh;
|
||||||
*/
|
*/
|
||||||
|
std::vector<InteractiveObject> interactiveObjects;
|
||||||
|
Inventory inventory;
|
||||||
|
InteractiveObject* pickedUpObject = nullptr;
|
||||||
|
|
||||||
|
// Interactive object targeting
|
||||||
|
InteractiveObject* targetInteractiveObject = nullptr;
|
||||||
|
bool inventoryOpen = false;
|
||||||
|
|
||||||
std::unique_ptr<Character> player;
|
std::unique_ptr<Character> player;
|
||||||
std::vector<std::unique_ptr<Character>> npcs;
|
std::vector<std::unique_ptr<Character>> npcs;
|
||||||
|
|
||||||
float cameraAzimuth = 0.0f;
|
float cameraAzimuth = 0.0f;
|
||||||
float cameraInclination = M_PI * 30.f / 180.f;
|
float cameraInclination = M_PI * 30.f / 180.f;
|
||||||
|
|
||||||
|
// Public access for ScriptEngine
|
||||||
|
MenuManager menuManager;
|
||||||
|
ScriptEngine scriptEngine;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool rightMouseDown = false;
|
bool rightMouseDown = false;
|
||||||
int lastMouseX = 0;
|
int lastMouseX = 0;
|
||||||
@ -83,6 +97,7 @@ namespace ZL {
|
|||||||
void handleDown(int64_t fingerId, int mx, int my);
|
void handleDown(int64_t fingerId, 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 mx, int my);
|
void handleMotion(int64_t fingerId, int mx, int my);
|
||||||
|
InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
static Game* s_instance;
|
static Game* s_instance;
|
||||||
@ -97,9 +112,6 @@ namespace ZL {
|
|||||||
|
|
||||||
static const size_t CONST_TIMER_INTERVAL = 10;
|
static const size_t CONST_TIMER_INTERVAL = 10;
|
||||||
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
|
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
|
||||||
|
|
||||||
MenuManager menuManager;
|
|
||||||
ScriptEngine scriptEngine;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -7,55 +7,200 @@
|
|||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
struct ScriptEngine::Impl {
|
struct ScriptEngine::Impl {
|
||||||
sol::state lua;
|
sol::state lua;
|
||||||
};
|
};
|
||||||
|
|
||||||
ScriptEngine::ScriptEngine() = default;
|
ScriptEngine::ScriptEngine() = default;
|
||||||
ScriptEngine::~ScriptEngine() = default;
|
ScriptEngine::~ScriptEngine() = default;
|
||||||
|
|
||||||
void ScriptEngine::init(Game* game) {
|
void ScriptEngine::init(Game* game) {
|
||||||
impl = std::make_unique<Impl>();
|
impl = std::make_unique<Impl>();
|
||||||
sol::state& lua = impl->lua;
|
sol::state& lua = impl->lua;
|
||||||
|
|
||||||
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string, sol::lib::table);
|
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string, sol::lib::table);
|
||||||
|
|
||||||
auto api = lua.create_named_table("game_api");
|
auto api = lua.create_named_table("game_api");
|
||||||
|
|
||||||
// npc_walk_to(index, x, y, z [, on_arrived])
|
// npc_walk_to(index, x, y, z [, on_arrived])
|
||||||
// on_arrived is an optional Lua function called when the NPC reaches the target.
|
// on_arrived is an optional Lua function called when the NPC reaches the target.
|
||||||
// It can call npc_walk_to again (or anything else) to chain behaviour.
|
// It can call npc_walk_to again (or anything else) to chain behaviour.
|
||||||
api.set_function("npc_walk_to",
|
api.set_function("npc_walk_to",
|
||||||
[game](int index, float x, float y, float z, sol::object on_arrived) {
|
[game](int index, float x, float y, float z, sol::object on_arrived) {
|
||||||
auto& npcs = game->npcs;
|
auto& npcs = game->npcs;
|
||||||
if (index < 0 || index >= static_cast<int>(npcs.size())) {
|
if (index < 0 || index >= static_cast<int>(npcs.size())) {
|
||||||
std::cerr << "[script] npc_walk_to: index " << index
|
std::cerr << "[script] npc_walk_to: index " << index
|
||||||
<< " out of range (0.." << npcs.size() - 1 << ")\n";
|
<< " out of range (0.." << npcs.size() - 1 << ")\n";
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
std::function<void()> cb;
|
||||||
|
if (on_arrived.is<sol::protected_function>()) {
|
||||||
|
sol::protected_function fn = on_arrived.as<sol::protected_function>();
|
||||||
|
cb = [fn]() mutable {
|
||||||
|
auto result = fn();
|
||||||
|
if (!result.valid()) {
|
||||||
|
sol::error err = result;
|
||||||
|
std::cerr << "[script] on_arrived error: " << err.what() << "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
npcs[index]->setTarget(Eigen::Vector3f(x, y, z), std::move(cb));
|
||||||
|
});
|
||||||
|
|
||||||
|
// pickup_item(object_name)
|
||||||
|
api.set_function("pickup_item", [game](const std::string& objectName) {
|
||||||
|
std::cout << "[script] pickup_item: " << objectName << std::endl;
|
||||||
|
|
||||||
|
for (auto& intObj : game->interactiveObjects) {
|
||||||
|
if (intObj.id == objectName && intObj.isActive) {
|
||||||
|
// Add item to inventory
|
||||||
|
game->inventory.addItem(intObj.dropItem);
|
||||||
|
|
||||||
|
// Deactivate object
|
||||||
|
intObj.isActive = false;
|
||||||
|
|
||||||
|
std::cout << "[script] Item picked up successfully: " << intObj.dropItem.name << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
std::function<void()> cb;
|
std::cerr << "[script] Warning: Interactive object not found or already picked up: " << objectName << std::endl;
|
||||||
if (on_arrived.is<sol::protected_function>()) {
|
});
|
||||||
sol::protected_function fn = on_arrived.as<sol::protected_function>();
|
|
||||||
cb = [fn]() mutable {
|
|
||||||
auto result = fn();
|
// show_inventory()
|
||||||
if (!result.valid()) {
|
api.set_function("show_inventory", [game]() {
|
||||||
sol::error err = result;
|
std::cout << "[script] show_inventory called" << std::endl;
|
||||||
std::cerr << "[script] on_arrived error: " << err.what() << "\n";
|
|
||||||
}
|
game->menuManager.uiManager.setNodeVisible("inventory_items_panel", true);
|
||||||
};
|
game->menuManager.uiManager.setNodeVisible("close_inventory_button", true);
|
||||||
|
game->inventoryOpen = true;
|
||||||
|
|
||||||
|
// Update UI with current items
|
||||||
|
const auto& items = game->inventory.getItems();
|
||||||
|
std::string itemText;
|
||||||
|
|
||||||
|
if (items.empty()) {
|
||||||
|
itemText = "Inventory (Empty)";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
|
||||||
|
for (size_t i = 0; i < items.size(); ++i) {
|
||||||
|
itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
npcs[index]->setTarget(Eigen::Vector3f(x, y, z), std::move(cb));
|
|
||||||
});
|
|
||||||
|
|
||||||
runScript("resources/start.lua");
|
game->menuManager.uiManager.setText("inventory_items_text", itemText);
|
||||||
}
|
});
|
||||||
|
|
||||||
void ScriptEngine::runScript(const std::string& path) {
|
// hide_inventory()
|
||||||
auto result = impl->lua.safe_script_file(path, sol::script_pass_on_error);
|
api.set_function("hide_inventory", [game]() {
|
||||||
if (!result.valid()) {
|
std::cout << "[script] hide_inventory called" << std::endl;
|
||||||
sol::error err = result;
|
game->menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
|
||||||
std::cerr << "[script] Error in " << path << ": " << err.what() << "\n";
|
game->menuManager.uiManager.setNodeVisible("close_inventory_button", false);
|
||||||
|
game->inventoryOpen = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// add_item(item_id, name, description, icon)
|
||||||
|
api.set_function("add_item", [game](const std::string& id, const std::string& name,
|
||||||
|
const std::string& description, const std::string& icon) {
|
||||||
|
std::cout << "[script] add_item: " << name << std::endl;
|
||||||
|
Item newItem(id, name, description, icon);
|
||||||
|
game->inventory.addItem(newItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove_item(item_id)
|
||||||
|
api.set_function("remove_item", [game](const std::string& id) {
|
||||||
|
std::cout << "[script] remove_item: " << id << std::endl;
|
||||||
|
game->inventory.removeItem(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// deactivate_interactive_object(object_name)
|
||||||
|
api.set_function("deactivate_interactive_object", [game](const std::string& objectName) {
|
||||||
|
std::cout << "[script] deactivate_interactive_object: " << objectName << std::endl;
|
||||||
|
for (auto& intObj : game->interactiveObjects) {
|
||||||
|
if (intObj.id == objectName) {
|
||||||
|
intObj.isActive = false;
|
||||||
|
std::cout << "[script] Interactive object deactivated: " << objectName << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cerr << "[script] Warning: Interactive object not found: " << objectName << std::endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
// get_inventory_count()
|
||||||
|
api.set_function("get_inventory_count", [game]() {
|
||||||
|
return game->inventory.getCount();
|
||||||
|
});
|
||||||
|
|
||||||
|
// has_item(item_id)
|
||||||
|
api.set_function("has_item", [game](const std::string& id) {
|
||||||
|
return game->inventory.hasItem(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
lua.script_file("resources/start.lua");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::runScript(const std::string& path) {
|
||||||
|
auto result = impl->lua.safe_script_file(path, sol::script_pass_on_error);
|
||||||
|
if (!result.valid()) {
|
||||||
|
sol::error err = result;
|
||||||
|
std::cerr << "[script] Error in " << path << ": " << err.what() << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::showInventory(Game* game) {
|
||||||
|
std::cout << "[script] toggle_inventory called" << std::endl;
|
||||||
|
|
||||||
|
bool isVisible = game->menuManager.uiManager.getNodeVisible("inventory_items_panel");
|
||||||
|
game->menuManager.uiManager.setNodeVisible("inventory_items_panel", !isVisible);
|
||||||
|
game->menuManager.uiManager.setNodeVisible("close_inventory_button", !isVisible);
|
||||||
|
|
||||||
|
game->inventoryOpen = !isVisible;
|
||||||
|
|
||||||
|
// Update UI with current items only if showing
|
||||||
|
if (!isVisible) {
|
||||||
|
const auto& items = game->inventory.getItems();
|
||||||
|
std::string itemText;
|
||||||
|
|
||||||
|
if (items.empty()) {
|
||||||
|
itemText = "Inventory (Empty)";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
|
||||||
|
for (size_t i = 0; i < items.size(); ++i) {
|
||||||
|
itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game->menuManager.uiManager.setText("inventory_items_text", itemText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::callItemPickupCallback(const std::string& objectName) {
|
||||||
|
if (!impl) {
|
||||||
|
std::cerr << "[SCRIPT] impl is null!" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sol::state& lua = impl->lua;
|
||||||
|
std::cout << "[SCRIPT] Looking for callback: on_item_pickup" << std::endl;
|
||||||
|
|
||||||
|
sol::function pickup = lua["on_item_pickup"];
|
||||||
|
|
||||||
|
if (pickup.valid()) {
|
||||||
|
std::cout << "[SCRIPT] Callback found! Calling with argument: " << objectName << std::endl;
|
||||||
|
auto result = pickup(objectName);
|
||||||
|
if (!result.valid()) {
|
||||||
|
sol::error err = result;
|
||||||
|
std::cerr << "[SCRIPT] on_item_pickup callback error: " << err.what() << "\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "[SCRIPT] Callback executed successfully!" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cerr << "[SCRIPT] on_item_pickup callback not found!" << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
|
|||||||
@ -17,6 +17,9 @@ public:
|
|||||||
// Execute a Lua script file. Logs errors to stderr.
|
// Execute a Lua script file. Logs errors to stderr.
|
||||||
void runScript(const std::string& path);
|
void runScript(const std::string& path);
|
||||||
|
|
||||||
|
void callItemPickupCallback(const std::string& objectName);
|
||||||
|
void showInventory(Game* game);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Impl;
|
struct Impl;
|
||||||
std::unique_ptr<Impl> impl;
|
std::unique_ptr<Impl> impl;
|
||||||
|
|||||||
@ -1092,24 +1092,55 @@ namespace ZL {
|
|||||||
renderer.PushMatrix();
|
renderer.PushMatrix();
|
||||||
renderer.LoadIdentity();
|
renderer.LoadIdentity();
|
||||||
|
|
||||||
for (const auto& img : staticImages) {
|
std::function<void(const std::shared_ptr<UiNode>&)> drawNode =
|
||||||
img->draw(renderer);
|
[&](const std::shared_ptr<UiNode>& node) {
|
||||||
}
|
if (!node || !node->visible) return;
|
||||||
for (const auto& b : buttons) {
|
|
||||||
b->draw(renderer);
|
renderer.PushMatrix();
|
||||||
}
|
renderer.TranslateMatrix({ node->screenRect.x + node->screenRect.w * 0.5f,
|
||||||
for (const auto& tb : textButtons) {
|
node->screenRect.y + node->screenRect.h * 0.5f, 0.0f });
|
||||||
tb->draw(renderer);
|
renderer.ScaleMatrix({ node->scaleX, node->scaleY, 1.0f });
|
||||||
}
|
renderer.TranslateMatrix({ -(node->screenRect.x + node->screenRect.w * 0.5f),
|
||||||
for (const auto& s : sliders) {
|
-(node->screenRect.y + node->screenRect.h * 0.5f), 0.0f });
|
||||||
s->draw(renderer);
|
|
||||||
}
|
|
||||||
for (const auto& tv : textViews) {
|
// 1. Сначала изображения
|
||||||
tv->draw(renderer);
|
if (node->staticImage) {
|
||||||
}
|
node->staticImage->draw(renderer);
|
||||||
for (const auto& tf : textFields) {
|
}
|
||||||
tf->draw(renderer);
|
|
||||||
}
|
// 2. Потом кнопки
|
||||||
|
if (node->button) {
|
||||||
|
node->button->draw(renderer);
|
||||||
|
}
|
||||||
|
if (node->textButton) {
|
||||||
|
node->textButton->draw(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Потом слайдеры
|
||||||
|
if (node->slider) {
|
||||||
|
node->slider->draw(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Потом текстовые поля
|
||||||
|
if (node->textField) {
|
||||||
|
node->textField->draw(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. ПОСЛЕДНИЙ - ТЕКСТ (поверх всего!)
|
||||||
|
if (node->textView) {
|
||||||
|
node->textView->draw(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Рисуем детей
|
||||||
|
for (auto& child : node->children) {
|
||||||
|
drawNode(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
};
|
||||||
|
|
||||||
|
drawNode(root);
|
||||||
|
|
||||||
renderer.PopMatrix();
|
renderer.PopMatrix();
|
||||||
renderer.PopProjectionMatrix();
|
renderer.PopProjectionMatrix();
|
||||||
@ -1641,4 +1672,23 @@ namespace ZL {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<UiNode> UiManager::findNode(const std::string& name) {
|
||||||
|
if (!root) return nullptr;
|
||||||
|
return findNodeByName(root, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UiManager::setNodeVisible(const std::string& nodeName, bool visible) {
|
||||||
|
if (!root) return false;
|
||||||
|
auto node = findNodeByName(root, nodeName);
|
||||||
|
if (!node) return false;
|
||||||
|
node->visible = visible;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UiManager::getNodeVisible(const std::string& nodeName) {
|
||||||
|
if (!root) return false;
|
||||||
|
auto node = findNodeByName(root, nodeName);
|
||||||
|
if (!node) return false;
|
||||||
|
return node->visible;
|
||||||
|
}
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
@ -228,6 +228,11 @@ namespace ZL {
|
|||||||
float width = 0;
|
float width = 0;
|
||||||
float height = 0;
|
float height = 0;
|
||||||
|
|
||||||
|
float scaleX = 1.0f;
|
||||||
|
float scaleY = 1.0f;
|
||||||
|
|
||||||
|
bool visible = true;
|
||||||
|
|
||||||
// Иерархия
|
// Иерархия
|
||||||
std::vector<std::shared_ptr<UiNode>> children;
|
std::vector<std::shared_ptr<UiNode>> children;
|
||||||
|
|
||||||
@ -353,6 +358,11 @@ namespace ZL {
|
|||||||
bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function<void()> cb);
|
bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function<void()> cb);
|
||||||
void updateAllLayouts();
|
void updateAllLayouts();
|
||||||
|
|
||||||
|
std::shared_ptr<UiNode> findNode(const std::string& name);
|
||||||
|
|
||||||
|
bool setNodeVisible(const std::string& nodeName, bool visible);
|
||||||
|
bool getNodeVisible(const std::string& nodeName);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void layoutNode(const std::shared_ptr<UiNode>& node, float parentX, float parentY, float parentW, float parentH, float finalLocalX, float finalLocalY);
|
void layoutNode(const std::shared_ptr<UiNode>& node, float parentX, float parentY, float parentW, float parentH, float finalLocalX, float finalLocalY);
|
||||||
void syncComponentRects(const std::shared_ptr<UiNode>& node);
|
void syncComponentRects(const std::shared_ptr<UiNode>& node);
|
||||||
|
|||||||
@ -50,6 +50,17 @@ namespace ZL {
|
|||||||
|
|
||||||
data.scale = item.value("scale", 1.0f);
|
data.scale = item.value("scale", 1.0f);
|
||||||
|
|
||||||
|
// Interactive object properties
|
||||||
|
data.interactive = item.value("interactive", false);
|
||||||
|
if (data.interactive && item.contains("item") && item["item"].is_object()) {
|
||||||
|
const auto& itemData = item["item"];
|
||||||
|
data.itemId = itemData.value("id", "");
|
||||||
|
data.itemName = itemData.value("name", "Unknown Item");
|
||||||
|
data.itemDescription = itemData.value("description", "");
|
||||||
|
data.itemIcon = itemData.value("icon", "");
|
||||||
|
data.interactionRadius = itemData.value("radius", 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
if (!data.meshPath.empty()) {
|
if (!data.meshPath.empty()) {
|
||||||
objects.push_back(data);
|
objects.push_back(data);
|
||||||
}
|
}
|
||||||
@ -73,6 +84,8 @@ namespace ZL {
|
|||||||
std::vector<GameObjectData> objectsData = loadFromJson(jsonPath, zipPath);
|
std::vector<GameObjectData> objectsData = loadFromJson(jsonPath, zipPath);
|
||||||
|
|
||||||
for (const auto& objData : objectsData) {
|
for (const auto& objData : objectsData) {
|
||||||
|
if (objData.interactive) continue; // Skip interactive objects, they're handled separately
|
||||||
|
|
||||||
std::cout << "Loading game object: " << objData.name << std::endl;
|
std::cout << "Loading game object: " << objData.name << std::endl;
|
||||||
|
|
||||||
LoadedGameObject gameObj;
|
LoadedGameObject gameObj;
|
||||||
@ -131,4 +144,104 @@ namespace ZL {
|
|||||||
return gameObjects;
|
return gameObjects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<InteractiveObject> GameObjectLoader::loadAndCreateInteractiveObjects(
|
||||||
|
const std::string& jsonPath,
|
||||||
|
Renderer& renderer,
|
||||||
|
const std::string& zipPath)
|
||||||
|
{
|
||||||
|
std::vector<InteractiveObject> interactiveObjects;
|
||||||
|
std::vector<GameObjectData> objectsData = loadFromJson(jsonPath, zipPath);
|
||||||
|
|
||||||
|
for (const auto& objData : objectsData) {
|
||||||
|
if (!objData.interactive) continue;
|
||||||
|
|
||||||
|
std::cout << "Loading interactive object: " << objData.name << std::endl;
|
||||||
|
|
||||||
|
InteractiveObject intObj;
|
||||||
|
intObj.id = objData.name;
|
||||||
|
intObj.name = objData.name;
|
||||||
|
intObj.interactionRadius = objData.interactionRadius;
|
||||||
|
|
||||||
|
// Load texture
|
||||||
|
try {
|
||||||
|
intObj.texture = std::make_shared<Texture>(CreateTextureDataFromPng(objData.texturePath, zipPath.c_str()));
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
std::cerr << "GameObjectLoader: Failed to load texture for interactive '" << objData.name << "': " << e.what() << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load mesh
|
||||||
|
try {
|
||||||
|
intObj.mesh.data = LoadFromTextFile02(objData.meshPath, zipPath);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
std::cerr << "GameObjectLoader: Failed to load mesh for interactive '" << objData.name << "': " << e.what() << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply rotation
|
||||||
|
Eigen::Quaternionf rotationQuat = Eigen::Quaternionf::Identity();
|
||||||
|
|
||||||
|
if (objData.rotationX != 0.0f) {
|
||||||
|
rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationX, Eigen::Vector3f::UnitX())) * rotationQuat;
|
||||||
|
}
|
||||||
|
if (objData.rotationY != 0.0f) {
|
||||||
|
rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationY, Eigen::Vector3f::UnitY())) * rotationQuat;
|
||||||
|
}
|
||||||
|
if (objData.rotationZ != 0.0f) {
|
||||||
|
rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationZ, Eigen::Vector3f::UnitZ())) * rotationQuat;
|
||||||
|
}
|
||||||
|
|
||||||
|
intObj.mesh.data.RotateByMatrix(rotationQuat.toRotationMatrix());
|
||||||
|
|
||||||
|
// Apply scale
|
||||||
|
if (objData.scale != 1.0f) {
|
||||||
|
intObj.mesh.data.Scale(objData.scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
intObj.mesh.RefreshVBO();
|
||||||
|
|
||||||
|
// Calculate mesh bounds to properly offset position
|
||||||
|
if (!intObj.mesh.data.PositionData.empty()) {
|
||||||
|
Eigen::Vector3f meshMin = intObj.mesh.data.PositionData[0];
|
||||||
|
Eigen::Vector3f meshMax = intObj.mesh.data.PositionData[0];
|
||||||
|
|
||||||
|
for (const auto& vert : intObj.mesh.data.PositionData) {
|
||||||
|
meshMin = meshMin.cwiseMin(vert);
|
||||||
|
meshMax = meshMax.cwiseMax(vert);
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Vector3f meshCenter = (meshMin + meshMax) * 0.5f;
|
||||||
|
std::cout << "[LOADER] Mesh bounds:" << std::endl;
|
||||||
|
std::cout << " Min: (" << meshMin.x() << ", " << meshMin.y() << ", " << meshMin.z() << ")" << std::endl;
|
||||||
|
std::cout << " Max: (" << meshMax.x() << ", " << meshMax.y() << ", " << meshMax.z() << ")" << std::endl;
|
||||||
|
std::cout << " Center: (" << meshCenter.x() << ", " << meshCenter.y() << ", " << meshCenter.z() << ")" << std::endl;
|
||||||
|
|
||||||
|
// Translate mesh so it's centered at origin
|
||||||
|
intObj.mesh.data.Move(-meshCenter);
|
||||||
|
intObj.mesh.RefreshVBO();
|
||||||
|
|
||||||
|
// Adjust position to account for mesh offset
|
||||||
|
intObj.position = Eigen::Vector3f(objData.positionX, objData.positionY, objData.positionZ) + meshCenter;
|
||||||
|
|
||||||
|
std::cout << "[LOADER] Corrected position: (" << intObj.position.x() << ", "
|
||||||
|
<< intObj.position.y() << ", " << intObj.position.z() << ")" << std::endl;
|
||||||
|
} else {
|
||||||
|
// Fallback if no vertices
|
||||||
|
intObj.position = Eigen::Vector3f(objData.positionX, objData.positionY, objData.positionZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create item
|
||||||
|
intObj.dropItem = Item(objData.itemId, objData.itemName, objData.itemDescription, objData.itemIcon);
|
||||||
|
|
||||||
|
interactiveObjects.push_back(intObj);
|
||||||
|
|
||||||
|
std::cout << "Successfully loaded interactive: " << objData.name << " (item: " << objData.itemName << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Total interactive objects loaded: " << interactiveObjects.size() << std::endl;
|
||||||
|
return interactiveObjects;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
@ -5,6 +5,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "external/nlohmann/json.hpp"
|
#include "external/nlohmann/json.hpp"
|
||||||
#include "TextModel.h"
|
#include "TextModel.h"
|
||||||
|
#include "InteractiveObject.h"
|
||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
@ -23,6 +24,12 @@ namespace ZL {
|
|||||||
float positionY = 0.0f;
|
float positionY = 0.0f;
|
||||||
float positionZ = 0.0f;
|
float positionZ = 0.0f;
|
||||||
float scale = 1.0f;
|
float scale = 1.0f;
|
||||||
|
bool interactive = false;
|
||||||
|
std::string itemId;
|
||||||
|
std::string itemName;
|
||||||
|
std::string itemDescription;
|
||||||
|
std::string itemIcon;
|
||||||
|
float interactionRadius = 2.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LoadedGameObject {
|
struct LoadedGameObject {
|
||||||
@ -40,6 +47,12 @@ namespace ZL {
|
|||||||
Renderer& renderer,
|
Renderer& renderer,
|
||||||
const std::string& zipPath = ""
|
const std::string& zipPath = ""
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static std::vector<InteractiveObject> loadAndCreateInteractiveObjects(
|
||||||
|
const std::string& jsonPath,
|
||||||
|
Renderer& renderer,
|
||||||
|
const std::string& zipPath = ""
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
37
src/items/InteractiveObject.cpp
Normal file
37
src/items/InteractiveObject.cpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#include "InteractiveObject.h"
|
||||||
|
#include "render/Renderer.h"
|
||||||
|
#include "render/TextureManager.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
extern const std::string textureUniformName;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
void InteractiveObject::draw(Renderer& renderer) const {
|
||||||
|
if (!isActive || !texture) return;
|
||||||
|
|
||||||
|
std::cout << "[DRAW] InteractiveObject::draw() called" << std::endl;
|
||||||
|
std::cout << "[DRAW] Object: " << name << std::endl;
|
||||||
|
std::cout << "[DRAW] Position: (" << position.x() << ", " << position.y() << ", " << position.z() << ")" << std::endl;
|
||||||
|
|
||||||
|
// Check mesh bounds
|
||||||
|
if (!mesh.data.PositionData.empty()) {
|
||||||
|
std::cout << "[DRAW] First vertex: (" << mesh.data.PositionData[0].x() << ", "
|
||||||
|
<< mesh.data.PositionData[0].y() << ", " << mesh.data.PositionData[0].z() << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply position transformation
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.TranslateMatrix(position);
|
||||||
|
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(mesh);
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
33
src/items/InteractiveObject.h
Normal file
33
src/items/InteractiveObject.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <Eigen/Core>
|
||||||
|
#include "Item.h"
|
||||||
|
#include "render/Renderer.h"
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
struct Texture;
|
||||||
|
struct VertexRenderStruct;
|
||||||
|
class Renderer;
|
||||||
|
|
||||||
|
struct InteractiveObject {
|
||||||
|
std::string id;
|
||||||
|
std::string name;
|
||||||
|
Eigen::Vector3f position;
|
||||||
|
float interactionRadius;
|
||||||
|
std::shared_ptr<Texture> texture;
|
||||||
|
VertexRenderStruct mesh;
|
||||||
|
Item dropItem;
|
||||||
|
bool isActive = true;
|
||||||
|
|
||||||
|
InteractiveObject() : interactionRadius(2.0f) {}
|
||||||
|
|
||||||
|
bool isInRange(const Eigen::Vector3f& playerPos) const {
|
||||||
|
return (playerPos - position).norm() <= interactionRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw(Renderer& renderer) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
26
src/items/Item.cpp
Normal file
26
src/items/Item.cpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#include "Item.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
void Inventory::addItem(const Item& item) {
|
||||||
|
items.push_back(item);
|
||||||
|
std::cout << "Item added to inventory: " << item.name << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inventory::removeItem(const std::string& itemId) {
|
||||||
|
auto it = std::find_if(items.begin(), items.end(),
|
||||||
|
[&itemId](const Item& item) { return item.id == itemId; });
|
||||||
|
if (it != items.end()) {
|
||||||
|
std::cout << "Item removed from inventory: " << it->name << std::endl;
|
||||||
|
items.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Inventory::hasItem(const std::string& itemId) const {
|
||||||
|
return std::find_if(items.begin(), items.end(),
|
||||||
|
[&itemId](const Item& item) { return item.id == itemId; }) != items.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
33
src/items/Item.h
Normal file
33
src/items/Item.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
struct Item {
|
||||||
|
std::string id;
|
||||||
|
std::string name;
|
||||||
|
std::string description;
|
||||||
|
std::string icon;
|
||||||
|
|
||||||
|
Item() = default;
|
||||||
|
Item(const std::string& _id, const std::string& _name, const std::string& _desc, const std::string& _icon)
|
||||||
|
: id(_id), name(_name), description(_desc), icon(_icon) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Inventory {
|
||||||
|
private:
|
||||||
|
std::vector<Item> items;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void addItem(const Item& item);
|
||||||
|
void removeItem(const std::string& itemId);
|
||||||
|
const std::vector<Item>& getItems() const { return items; }
|
||||||
|
bool hasItem(const std::string& itemId) const;
|
||||||
|
void clear() { items.clear(); }
|
||||||
|
size_t getCount() const { return items.size(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
Loading…
Reference in New Issue
Block a user