538 lines
18 KiB
C++
538 lines
18 KiB
C++
/****************************************************************************
|
|
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
|
Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md).
|
|
|
|
https://axmol.dev/
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
#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;
|
|
|
|
static int s_sceneID = 1000;
|
|
|
|
// Print useful error message instead of segfaulting when files are not there.
|
|
static void problemLoading(const char* filename)
|
|
{
|
|
printf("Error while loading: %s\n", filename);
|
|
printf(
|
|
"Depending on how you compiled you might have to add 'Content/' in front of filenames in "
|
|
"MainScene.cpp\n");
|
|
}
|
|
|
|
// on "init" you need to initialize your instance
|
|
bool MainScene::init()
|
|
{
|
|
//////////////////////////////
|
|
// 1. super init first
|
|
if (!Scene::init())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto visibleSize = _director->getVisibleSize();
|
|
auto origin = _director->getVisibleOrigin();
|
|
auto safeArea = _director->getSafeAreaRect();
|
|
|
|
// 2. Add a menu item with "X" image, which is clicked to quit the program
|
|
auto closeItem = MenuItemImage::create("CloseNormal.png", "CloseSelected.png",
|
|
AX_CALLBACK_1(MainScene::menuCloseCallback, this));
|
|
|
|
if (closeItem != nullptr)
|
|
{
|
|
float x = origin.x + visibleSize.width - closeItem->getContentSize().width / 2;
|
|
float y = origin.y + closeItem->getContentSize().height / 2;
|
|
closeItem->setPosition(Vec2(x, y));
|
|
}
|
|
|
|
auto menu = Menu::create(closeItem, NULL);
|
|
menu->setPosition(Vec2::ZERO);
|
|
this->addChild(menu, 1);
|
|
|
|
/////////////////////////////
|
|
// 3D Setup
|
|
const unsigned short cameraMask3D = (unsigned short)CameraFlag::USER1 | (unsigned short)CameraFlag::USER2;
|
|
|
|
// Grid settings
|
|
const int gridSize = 10;
|
|
const float cubeDim = 1.0f;
|
|
const float spacing = 0.25f;
|
|
const float step = cubeDim + spacing;
|
|
|
|
// Calculate center of the grid
|
|
// Grid goes from 0 to (gridSize-1)*step
|
|
float gridWidth = (gridSize - 1) * step;
|
|
Vec3 gridCenter(gridWidth / 2.0f, 0.0f, gridWidth / 2.0f);
|
|
|
|
// Create a single cube at the center of the grid area
|
|
_playerCube = MeshRenderer::create("cube.obj");
|
|
if (_playerCube)
|
|
{
|
|
_playerCube->setPosition3D(gridCenter);
|
|
_playerCube->setCameraMask(cameraMask3D);
|
|
this->addChild(_playerCube);
|
|
}
|
|
|
|
// Add Lights
|
|
// Directional Light: Like sunlight, creating shadows/depth
|
|
Vec3 lightDir(-1.0f, -1.0f, -0.5f);
|
|
auto dirLight = DirectionLight::create(lightDir, Color3B::WHITE);
|
|
dirLight->setCameraMask(cameraMask3D);
|
|
this->addChild(dirLight);
|
|
|
|
// Visual marker for the Directional Light source
|
|
auto lightMarker = MeshRenderer::create("cube.obj");
|
|
if (lightMarker)
|
|
{
|
|
// Normalize direction and place the marker 20 units away from the center
|
|
Vec3 normalizedDir = lightDir;
|
|
normalizedDir.normalize();
|
|
Vec3 markerPos = gridCenter - (normalizedDir * 20.0f);
|
|
|
|
lightMarker->setPosition3D(markerPos);
|
|
lightMarker->setScale(0.5f);
|
|
lightMarker->setColor(Color3B::YELLOW);
|
|
lightMarker->setCameraMask(cameraMask3D);
|
|
this->addChild(lightMarker);
|
|
}
|
|
|
|
// Ambient Light: To ensure dark sides are still somewhat visible
|
|
auto ambLight = AmbientLight::create(Color3B(80, 80, 80));
|
|
ambLight->setCameraMask(cameraMask3D);
|
|
this->addChild(ambLight);
|
|
|
|
// Add a Ground Plane
|
|
auto ground = MeshRenderer::create("cube.obj");
|
|
if (ground)
|
|
{
|
|
// Calculate the exact size needed to cover the grid
|
|
// Grid size is (gridSize-1)*step + cubeDim
|
|
float actualSize = gridWidth + cubeDim;
|
|
float padding = 2.0f; // Increased padding to 2.0
|
|
|
|
ground->setScaleX(actualSize + padding);
|
|
ground->setScaleZ(actualSize + padding);
|
|
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)
|
|
ground->setPosition3D(Vec3(gridCenter.x, -0.5f - 0.125f, gridCenter.z));
|
|
ground->setCameraMask(cameraMask3D);
|
|
ground->setColor(Color3B(100, 100, 100));
|
|
this->addChild(ground);
|
|
}
|
|
|
|
// Setup Main Camera for Quarter View (Isometric-like)
|
|
_camera3D = Camera::createPerspective(60.0f, visibleSize.width / visibleSize.height, 0.1f, 1000.0f);
|
|
_camera3D->setCameraFlag(CameraFlag::USER1);
|
|
_camera3D->setDepth(1);
|
|
|
|
// Initial camera position setup
|
|
_targetPos = gridCenter;
|
|
_pitch = 45.0f;
|
|
_yaw = 45.0f;
|
|
_distance = 25.0f;
|
|
updateCameraPosition();
|
|
|
|
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
|
|
_mouseListener = EventListenerMouse::create();
|
|
_mouseListener->onMouseMove = AX_CALLBACK_1(MainScene::onMouseMove, this);
|
|
_mouseListener->onMouseUp = AX_CALLBACK_1(MainScene::onMouseUp, this);
|
|
_mouseListener->onMouseDown = AX_CALLBACK_1(MainScene::onMouseDown, this);
|
|
_mouseListener->onMouseScroll = AX_CALLBACK_1(MainScene::onMouseScroll, 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
|
|
// The label and menu will use the default camera (CameraFlag::DEFAULT)
|
|
|
|
// Add a label for confirmation
|
|
auto label = Label::createWithTTF("Center Cube", "fonts/Marker Felt.ttf", 24);
|
|
if (label)
|
|
{
|
|
label->setPosition(Vec2(origin.x + visibleSize.width / 2, origin.y + visibleSize.height - 30));
|
|
this->addChild(label, 1);
|
|
}
|
|
|
|
scheduleUpdate();
|
|
|
|
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)
|
|
{
|
|
for (auto&& t : touches)
|
|
{
|
|
// AXLOGD("onTouchesBegan detected, X:{} Y:{}", t->getLocation().x, t->getLocation().y);
|
|
}
|
|
}
|
|
|
|
void MainScene::onTouchesMoved(const std::vector<ax::Touch*>& touches, ax::Event* event)
|
|
{
|
|
for (auto&& t : touches)
|
|
{
|
|
// AXLOGD("onTouchesMoved detected, X:{} Y:{}", t->getLocation().x, t->getLocation().y);
|
|
}
|
|
}
|
|
|
|
void MainScene::onTouchesEnded(const std::vector<ax::Touch*>& touches, ax::Event* event)
|
|
{
|
|
for (auto&& t : touches)
|
|
{
|
|
// AXLOGD("onTouchesEnded detected, X:{} Y:{}", t->getLocation().x, t->getLocation().y);
|
|
}
|
|
}
|
|
|
|
bool MainScene::onMouseDown(Event* event)
|
|
{
|
|
EventMouse* e = static_cast<EventMouse*>(event);
|
|
if (e->getMouseButton() == EventMouse::MouseButton::BUTTON_LEFT)
|
|
{
|
|
_isMouseDown = true;
|
|
_lastMousePos = e->getLocationInView();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MainScene::onMouseUp(Event* event)
|
|
{
|
|
EventMouse* e = static_cast<EventMouse*>(event);
|
|
if (e->getMouseButton() == EventMouse::MouseButton::BUTTON_LEFT)
|
|
{
|
|
_isMouseDown = false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MainScene::onMouseMove(Event* event)
|
|
{
|
|
EventMouse* e = static_cast<EventMouse*>(event);
|
|
Vec2 currentPos = e->getLocationInView();
|
|
|
|
if (_isMouseDown)
|
|
{
|
|
Vec2 delta = currentPos - _lastMousePos;
|
|
float sensitivity = 0.15f; // 감도 조절 (기존 0.5f에서 하향)
|
|
|
|
_yaw += delta.x * sensitivity;
|
|
_pitch += delta.y * sensitivity; // 마우스 이동 방향에 맞춰 상하 각도 변경
|
|
|
|
// Clamp pitch to avoid flipping (수직 방향 각도 제한)
|
|
_pitch = std::clamp(_pitch, 10.0f, 85.0f);
|
|
|
|
updateCameraPosition();
|
|
}
|
|
_lastMousePos = currentPos;
|
|
return true;
|
|
}
|
|
|
|
bool MainScene::onMouseScroll(Event* event)
|
|
{
|
|
EventMouse* e = static_cast<EventMouse*>(event);
|
|
float zoomSensitivity = 0.5f; // 줌 감도 조절
|
|
_distance -= e->getScrollY() * zoomSensitivity;
|
|
|
|
// Clamp distance
|
|
_distance = std::clamp(_distance, 5.0f, 100.0f);
|
|
|
|
updateCameraPosition();
|
|
return true;
|
|
}
|
|
|
|
void MainScene::updateCameraPosition()
|
|
{
|
|
if (!_camera3D) return;
|
|
|
|
// Convert spherical coordinates to Cartesian
|
|
float pitchRad = AX_DEGREES_TO_RADIANS(_pitch);
|
|
float yawRad = AX_DEGREES_TO_RADIANS(_yaw);
|
|
|
|
float x = _distance * cosf(pitchRad) * cosf(yawRad);
|
|
float y = _distance * sinf(pitchRad);
|
|
float z = _distance * cosf(pitchRad) * sinf(yawRad);
|
|
|
|
_camera3D->setPosition3D(_targetPos + Vec3(x, y, z));
|
|
_camera3D->lookAt(_targetPos, Vec3(0, 1, 0));
|
|
}
|
|
|
|
void MainScene::onKeyPressed(EventKeyboard::KeyCode code, Event* event)
|
|
{
|
|
_keyStates[code] = true;
|
|
}
|
|
|
|
void MainScene::onKeyReleased(EventKeyboard::KeyCode code, Event* event)
|
|
{
|
|
_keyStates[code] = false;
|
|
}
|
|
|
|
void MainScene::update(float delta)
|
|
{
|
|
switch (_gameState)
|
|
{
|
|
case GameState::init:
|
|
{
|
|
_gameState = GameState::update;
|
|
break;
|
|
}
|
|
|
|
case GameState::update:
|
|
{
|
|
// Movement logic for _playerCube
|
|
if (_playerCube)
|
|
{
|
|
float speed = 10.0f * delta;
|
|
Vec3 move(0, 0, 0);
|
|
|
|
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;
|
|
}
|
|
|
|
case GameState::pause:
|
|
{
|
|
/////////////////////////////
|
|
// Add your codes below...like....
|
|
//
|
|
// anyPauseStuff()
|
|
|
|
break;
|
|
}
|
|
|
|
case GameState::menu1:
|
|
{ /////////////////////////////
|
|
// Add your codes below...like....
|
|
//
|
|
// UpdateMenu1();
|
|
break;
|
|
}
|
|
|
|
case GameState::menu2:
|
|
{ /////////////////////////////
|
|
// Add your codes below...like....
|
|
//
|
|
// UpdateMenu2();
|
|
break;
|
|
}
|
|
|
|
case GameState::end:
|
|
{ /////////////////////////////
|
|
// Add your codes below...like....
|
|
//
|
|
// CleanUpMyCrap();
|
|
menuCloseCallback(this);
|
|
break;
|
|
}
|
|
|
|
} // switch
|
|
}
|
|
|
|
void MainScene::menuCloseCallback(ax::Object* sender)
|
|
{
|
|
// Close the axmol game scene and quit the application
|
|
_director->end();
|
|
|
|
/*To navigate back to native iOS screen(if present) without quitting the application ,do not use
|
|
* _director->end() as given above,instead trigger a custom event created in RootViewController.mm
|
|
* as below*/
|
|
|
|
// EventCustom customEndEvent("game_scene_close_event");
|
|
//_eventDispatcher->dispatchEvent(&customEndEvent);
|
|
}
|
|
|
|
MainScene::MainScene()
|
|
{
|
|
_sceneID = ++s_sceneID;
|
|
AXLOGD("Scene: ctor: #{}", _sceneID);
|
|
}
|
|
|
|
MainScene::~MainScene()
|
|
{
|
|
AXLOGD("~Scene: dtor: #{}", _sceneID);
|
|
|
|
if (_touchListener)
|
|
_eventDispatcher->removeEventListener(_touchListener);
|
|
if (_keyboardListener)
|
|
_eventDispatcher->removeEventListener(_keyboardListener);
|
|
if (_mouseListener)
|
|
_eventDispatcher->removeEventListener(_mouseListener);
|
|
_sceneID = -1;
|
|
}
|