탑다운 미니맵 구현

This commit is contained in:
2026-03-09 00:09:20 +09:00
parent ddbe89615d
commit 39acbac09e
2 changed files with 191 additions and 20 deletions

View File

@@ -25,6 +25,13 @@
#include "MainScene.h" #include "MainScene.h"
#if defined(AX_ENABLE_3D_PHYSICS)
# include "physics3d/Physics3DWorld.h"
#endif
#if defined(AX_ENABLE_NAVMESH)
# include "navmesh/NavMesh.h"
#endif
using namespace ax; using namespace ax;
static int s_sceneID = 1000; static int s_sceneID = 1000;
@@ -68,7 +75,8 @@ bool MainScene::init()
this->addChild(menu, 1); this->addChild(menu, 1);
///////////////////////////// /////////////////////////////
// 3. 3D Setup // 3D Setup
const unsigned short cameraMask3D = (unsigned short)CameraFlag::USER1 | (unsigned short)CameraFlag::USER2;
// Grid settings // Grid settings
const int gridSize = 10; const int gridSize = 10;
@@ -82,19 +90,19 @@ bool MainScene::init()
Vec3 gridCenter(gridWidth / 2.0f, 0.0f, gridWidth / 2.0f); Vec3 gridCenter(gridWidth / 2.0f, 0.0f, gridWidth / 2.0f);
// Create a single cube at the center of the grid area // Create a single cube at the center of the grid area
auto centerCube = MeshRenderer::create("cube.obj"); _playerCube = MeshRenderer::create("cube.obj");
if (centerCube) if (_playerCube)
{ {
centerCube->setPosition3D(gridCenter); _playerCube->setPosition3D(gridCenter);
centerCube->setCameraMask((unsigned short)CameraFlag::USER1); _playerCube->setCameraMask(cameraMask3D);
this->addChild(centerCube); this->addChild(_playerCube);
} }
// Add Lights // Add Lights
// Directional Light: Like sunlight, creating shadows/depth // Directional Light: Like sunlight, creating shadows/depth
Vec3 lightDir(-1.0f, -1.0f, -0.5f); Vec3 lightDir(-1.0f, -1.0f, -0.5f);
auto dirLight = DirectionLight::create(lightDir, Color3B::WHITE); auto dirLight = DirectionLight::create(lightDir, Color3B::WHITE);
dirLight->setCameraMask((unsigned short)CameraFlag::USER1); dirLight->setCameraMask(cameraMask3D);
this->addChild(dirLight); this->addChild(dirLight);
// Visual marker for the Directional Light source // Visual marker for the Directional Light source
@@ -109,13 +117,13 @@ bool MainScene::init()
lightMarker->setPosition3D(markerPos); lightMarker->setPosition3D(markerPos);
lightMarker->setScale(0.5f); lightMarker->setScale(0.5f);
lightMarker->setColor(Color3B::YELLOW); lightMarker->setColor(Color3B::YELLOW);
lightMarker->setCameraMask((unsigned short)CameraFlag::USER1); lightMarker->setCameraMask(cameraMask3D);
this->addChild(lightMarker); this->addChild(lightMarker);
} }
// Ambient Light: To ensure dark sides are still somewhat visible // Ambient Light: To ensure dark sides are still somewhat visible
auto ambLight = AmbientLight::create(Color3B(80, 80, 80)); auto ambLight = AmbientLight::create(Color3B(80, 80, 80));
ambLight->setCameraMask((unsigned short)CameraFlag::USER1); ambLight->setCameraMask(cameraMask3D);
this->addChild(ambLight); this->addChild(ambLight);
// Add a Ground Plane // Add a Ground Plane
@@ -132,14 +140,15 @@ bool MainScene::init()
ground->setScaleY(0.25f); // Thickness set to 0.25 ground->setScaleY(0.25f); // Thickness set to 0.25
// Positioned so the top surface is at Y = -0.5 (cubes are at Y=0 with height 1) // Positioned so the top surface is at Y = -0.5 (cubes are at Y=0 with height 1)
ground->setPosition3D(Vec3(gridCenter.x, -0.5f - 0.125f, gridCenter.z)); ground->setPosition3D(Vec3(gridCenter.x, -0.5f - 0.125f, gridCenter.z));
ground->setCameraMask((unsigned short)CameraFlag::USER1); ground->setCameraMask(cameraMask3D);
ground->setColor(Color3B(100, 100, 100)); ground->setColor(Color3B(100, 100, 100));
this->addChild(ground); this->addChild(ground);
} }
// Setup Camera for Quarter View (Isometric-like) // Setup Main Camera for Quarter View (Isometric-like)
_camera3D = Camera::createPerspective(60.0f, visibleSize.width / visibleSize.height, 0.1f, 1000.0f); _camera3D = Camera::createPerspective(60.0f, visibleSize.width / visibleSize.height, 0.1f, 1000.0f);
_camera3D->setCameraFlag(CameraFlag::USER1); _camera3D->setCameraFlag(CameraFlag::USER1);
_camera3D->setDepth(1);
// Initial camera position setup // Initial camera position setup
_targetPos = gridCenter; _targetPos = gridCenter;
@@ -150,6 +159,36 @@ bool MainScene::init()
this->addChild(_camera3D); this->addChild(_camera3D);
// Setup Mini-map Camera for Top-down View
float miniMapSize = 200.0f;
_miniMapCamera = Camera::createPerspective(60.0f, 1.0f, 0.1f, 1000.0f);
_miniMapCamera->setCameraFlag(CameraFlag::USER2);
_miniMapCamera->setDepth(2);
_miniMapCamera->setPosition3D(gridCenter + Vec3(0, 30.0f, 0.01f)); // Offset slightly on Z to avoid gimbal lock with up vector
_miniMapCamera->lookAt(gridCenter, Vec3(0, 0, -1));
this->addChild(_miniMapCamera);
// Mini-map Border (UI)
auto miniMapBorder = DrawNode::create();
if (miniMapBorder)
{
Vec2 pos(visibleSize.width - miniMapSize - 20, visibleSize.height - miniMapSize - 20);
// Draw the border line
miniMapBorder->drawRect(pos, pos + Vec2(miniMapSize, miniMapSize), Color4F::WHITE);
// Add a semi-transparent dark background for the map area
miniMapBorder->drawSolidRect(pos, pos + Vec2(miniMapSize, miniMapSize), Color4F(0, 0, 0, 0.4f));
// This belongs to the UI (DEFAULT camera)
miniMapBorder->setCameraMask((unsigned short)CameraFlag::DEFAULT);
this->addChild(miniMapBorder, 10);
_miniMapBorder = miniMapBorder;
}
// Ensure Default Camera (UI) has higher depth to be on top
this->getDefaultCamera()->setDepth(3);
// Mouse Listener // Mouse Listener
_mouseListener = EventListenerMouse::create(); _mouseListener = EventListenerMouse::create();
_mouseListener->onMouseMove = AX_CALLBACK_1(MainScene::onMouseMove, this); _mouseListener->onMouseMove = AX_CALLBACK_1(MainScene::onMouseMove, this);
@@ -158,6 +197,12 @@ bool MainScene::init()
_mouseListener->onMouseScroll = AX_CALLBACK_1(MainScene::onMouseScroll, this); _mouseListener->onMouseScroll = AX_CALLBACK_1(MainScene::onMouseScroll, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(_mouseListener, this); _eventDispatcher->addEventListenerWithSceneGraphPriority(_mouseListener, this);
// Keyboard Listener
_keyboardListener = EventListenerKeyboard::create();
_keyboardListener->onKeyPressed = AX_CALLBACK_2(MainScene::onKeyPressed, this);
_keyboardListener->onKeyReleased = AX_CALLBACK_2(MainScene::onKeyReleased, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(_keyboardListener, this);
// Disable the default 2D camera for these 3D objects by using CameraFlag::USER1 // Disable the default 2D camera for these 3D objects by using CameraFlag::USER1
// The label and menu will use the default camera (CameraFlag::DEFAULT) // The label and menu will use the default camera (CameraFlag::DEFAULT)
@@ -174,6 +219,95 @@ bool MainScene::init()
return true; return true;
} }
// Helper to access protected member of Camera
namespace ax
{
class CameraHacker : public Camera
{
public:
static void setVisitingCamera(Camera* cam) { _visitingCamera = cam; }
};
}
void MainScene::render(Renderer* renderer, const Mat4& eyeTransform, const Mat4* eyeProjection)
{
auto visibleSize = _director->getVisibleSize();
const auto& transform = getNodeToParentTransform();
for (const auto& camera : getCameras())
{
if (!camera->isVisible())
continue;
ax::CameraHacker::setVisitingCamera(camera);
// Set Viewport for this camera
if (camera == _miniMapCamera)
{
float miniMapSize = 200.0f;
Camera::setDefaultViewport(Viewport().set(
(int)(visibleSize.width - miniMapSize - 20),
(int)(visibleSize.height - miniMapSize - 20),
(int)miniMapSize,
(int)miniMapSize));
}
else
{
Camera::setDefaultViewport(Viewport().set(0, 0, (int)visibleSize.width, (int)visibleSize.height));
}
if (eyeProjection)
camera->setAdditionalProjection(*eyeProjection * camera->getProjectionMatrix().getInversed());
camera->setAdditionalTransform(eyeTransform.getInversed());
_director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
_director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, camera->getViewProjectionMatrix());
camera->apply();
// clear background with max depth
camera->clearBackground();
// visit the scene
visit(renderer, transform, 0);
#if defined(AX_ENABLE_NAVMESH)
if (_navMesh && _navMeshDebugCamera == camera)
{
_navMesh->debugDraw(renderer);
}
#endif
renderer->render();
_director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
}
#if defined(AX_ENABLE_3D_PHYSICS)
if (_physics3DWorld && _physics3DWorld->isDebugDrawEnabled())
{
Camera* physics3dDebugCamera = _physics3dDebugCamera != nullptr ? _physics3dDebugCamera : this->getDefaultCamera();
if (eyeProjection)
physics3dDebugCamera->setAdditionalProjection(*eyeProjection *
physics3dDebugCamera->getProjectionMatrix().getInversed());
physics3dDebugCamera->setAdditionalTransform(eyeTransform.getInversed());
_director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
_director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION,
physics3dDebugCamera->getViewProjectionMatrix());
physics3dDebugCamera->apply();
physics3dDebugCamera->clearBackground();
_physics3DWorld->debugDraw(renderer);
renderer->render();
_director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
}
#endif
ax::CameraHacker::setVisitingCamera(nullptr);
}
void MainScene::onTouchesBegan(const std::vector<ax::Touch*>& touches, ax::Event* event) void MainScene::onTouchesBegan(const std::vector<ax::Touch*>& touches, ax::Event* event)
{ {
for (auto&& t : touches) for (auto&& t : touches)
@@ -272,12 +406,12 @@ void MainScene::updateCameraPosition()
void MainScene::onKeyPressed(EventKeyboard::KeyCode code, Event* event) void MainScene::onKeyPressed(EventKeyboard::KeyCode code, Event* event)
{ {
AXLOGD("Scene: #{} onKeyPressed, keycode: {}", _sceneID, static_cast<int>(code)); _keyStates[code] = true;
} }
void MainScene::onKeyReleased(EventKeyboard::KeyCode code, Event* event) void MainScene::onKeyReleased(EventKeyboard::KeyCode code, Event* event)
{ {
AXLOGD("onKeyReleased, keycode: {}", static_cast<int>(code)); _keyStates[code] = false;
} }
void MainScene::update(float delta) void MainScene::update(float delta)
@@ -292,13 +426,43 @@ void MainScene::update(float delta)
case GameState::update: case GameState::update:
{ {
///////////////////////////// // Movement logic for _playerCube
// Add your codes below...like.... if (_playerCube)
// {
// UpdateJoyStick(); float speed = 10.0f * delta;
// UpdatePlayer(); Vec3 move(0, 0, 0);
// UpdatePhysics();
// ... if (_keyStates[EventKeyboard::KeyCode::KEY_W] || _keyStates[EventKeyboard::KeyCode::KEY_UP_ARROW]) move.z -= 1.0f;
if (_keyStates[EventKeyboard::KeyCode::KEY_S] || _keyStates[EventKeyboard::KeyCode::KEY_DOWN_ARROW]) move.z += 1.0f;
if (_keyStates[EventKeyboard::KeyCode::KEY_A] || _keyStates[EventKeyboard::KeyCode::KEY_LEFT_ARROW]) move.x -= 1.0f;
if (_keyStates[EventKeyboard::KeyCode::KEY_D] || _keyStates[EventKeyboard::KeyCode::KEY_RIGHT_ARROW]) move.x += 1.0f;
if (move != Vec3::ZERO)
{
move.normalize();
Vec3 currentPos = _playerCube->getPosition3D();
Vec3 newPos = currentPos + (move * speed);
// Boundary check against ground
// gridCenter = (5.625, 0, 5.625)
// groundSize = 14.25
// cube half-size = 0.5
float halfGround = 14.25f / 2.0f;
float minX = 5.625f - halfGround + 0.5f; // -1.0
float maxX = 5.625f + halfGround - 0.5f; // 12.25
float minZ = 5.625f - halfGround + 0.5f; // -1.0
float maxZ = 5.625f + halfGround - 0.5f; // 12.25
newPos.x = std::clamp(newPos.x, minX, maxX);
newPos.z = std::clamp(newPos.z, minZ, maxZ);
_playerCube->setPosition3D(newPos);
// Camera follows the player
_targetPos = newPos;
updateCameraPosition();
}
}
break; break;
} }

View File

@@ -42,6 +42,7 @@ class MainScene : public ax::Scene
public: public:
bool init() override; bool init() override;
void update(float delta) override; void update(float delta) override;
void render(ax::Renderer* renderer, const ax::Mat4& eyeTransform, const ax::Mat4* eyeProjection = nullptr) override;
// touch // touch
void onTouchesBegan(const std::vector<ax::Touch*>& touches, ax::Event* event); void onTouchesBegan(const std::vector<ax::Touch*>& touches, ax::Event* event);
@@ -73,8 +74,14 @@ private:
ax::EventListenerMouse* _mouseListener = nullptr; ax::EventListenerMouse* _mouseListener = nullptr;
int _sceneID = 0; int _sceneID = 0;
// Game Objects
ax::MeshRenderer* _playerCube = nullptr;
ax::DrawNode* _miniMapBorder = nullptr;
std::map<ax::EventKeyboard::KeyCode, bool> _keyStates;
// Camera Orbit State // Camera Orbit State
ax::Camera* _camera3D = nullptr; ax::Camera* _camera3D = nullptr;
ax::Camera* _miniMapCamera = nullptr;
ax::Vec3 _targetPos = ax::Vec3::ZERO; ax::Vec3 _targetPos = ax::Vec3::ZERO;
float _pitch = 45.0f; // degrees float _pitch = 45.0f; // degrees
float _yaw = 45.0f; // degrees float _yaw = 45.0f; // degrees