Traversal
- AnishCS529 C++ Game Engine
ROLE
Producer | Gameplay Programmer
DESCRIPTION
A first person parkour platformer where the player uses a variety of wall running, dashing, and jumping abilities to navigate the environment and reach the goal.
PROJECT TIMELINE
January 25 - April 25
GENRE
First Person Parkour Platformer
PLATFORM
PC
DEVELOPMENT
Custom C++ Engine

TAKEAWAYS
This was the most challenging project I have worked on to date. Building and using a custom engine was a great challenge but taught me about the inner workings of games and how the control and data flows as the game program runs.
PROGRAMMING
Working on engine development allowed me to see behind the curtain of game development and gain a better understanding of how games work behind the scenes. I contributed to basic graphics and physics implementation as well as scene and level management giving me practical experience in engine development
DESIGNING
My team and I spent many hours discussing the architecture and development of our engine in addition to the game. I focused on ensuring our engine was built with the game in mind and that each component was utilized effectively
LEADING
I learned about what the producer is responsible for on a more demanding project. I was tasked with managing not just the project and open tasks but also my teammates. I had to leverage each of their individual strengths to the task most suited to their skillsets
THE GAME

Engaging Puzzles
Unique Environments


Challenging Mechanincs
Downloads
/*!**************************************************************************** * \file RaycastManager.cpp * \author Nicholas Shaw (nick.shaw@digipen.edu) * * Copyright © 2025 DIGIPEN Institute of Technology. All rights reserved. * *****************************************************************************/ #include "precompiled.h" #include "RaycastManager.h" #include "PhysicsBody.h" /*! * @brief This is the primary Raycasting function. It detects the nearest object along the ray. * Instruction: * 1) Create a Ray with the desired origin and direction * 2) Create a RayCastHit Object to store the data * 3) Call the funtion * @param ray This is the Ray being cast. It contains an origin and a direction * @param sceneGraph The scenegraph that the ray is beign cast in, checks each game object in the node * @param hit Stores the data of the intersection if one occurs * @param maxDistance The max distance the ray is cast * @param tagToIgnore A Vector containing Tags of Gameobject that should be ignored for collisions * @return Boolean signifying if there was a successful intersection. The data of the intersection is stored in the RaycastHit object */ bool RaycastManager::Raycast(const Ray& ray, RaycastHit& hit, float maxDistance, const std::vector tagToCollide, const std::vector tagToIgnore) { if (!sceneGraph) { return false; } return processNode(sceneGraph->getRootNode(), ray, hit, maxDistance, tagToCollide, tagToIgnore); } /*! * @brief Recursivly checks each object for an intersection * @param node The node to check for intersection * @param ray The Ray being cast * @param hit The hit object to store the data * @param closeHitDistance distance which to cast the ray * @param tagToIgnore The Tags the mark ignorable objects * @return Bool if there was an intersection. Hit stores the data of the intersection */ bool RaycastManager::processNode(const std::shared_ptr& node, const Ray& ray, RaycastHit& hit, float& closeHitDistance, const std::vector tagToCollide, const std::vector tagToIgnore ) { if (!node) { return false; } bool hasHit = false; RaycastHit tempHit; // Convert the Ray to the Node's local space Matrix4 worldToLocal = node->getLocalTransform().getInverseLocalMatrix(); Ray localRay = ray.transformRay(worldToLocal); //Scaling Factors of the Ray Vector3 scaleX(worldToLocal[0][0], worldToLocal[1][0], worldToLocal[2][0]); Vector3 scaleY(worldToLocal[0][1], worldToLocal[1][1], worldToLocal[2][1]); Vector3 scaleZ(worldToLocal[0][2], worldToLocal[1][2], worldToLocal[2][2]); float scaleFactorX = scaleX.magnitude(); float scaleFactorY = scaleY.magnitude(); float scaleFactorZ = scaleZ.magnitude(); float scaleFactor = ray.getDirection().abs().dot(Vector3(scaleFactorX, scaleFactorY, scaleFactorZ)); float localClosestHitDistance = scaleFactor * closeHitDistance; // If the node has a physics body, check for intersection auto gameObject = std::dynamic_pointer_cast(node); //std::string name = gameObject->getName(); if (gameObject) { auto pbComp = gameObject->findComponent(); bool isIgnored = (std::find(tagToIgnore.begin(), tagToIgnore.end(), gameObject->getTag()) != tagToIgnore.end()); bool isValidForCollision = tagToCollide.empty() || (std::find(tagToCollide.begin(), tagToCollide.end(), gameObject->getTag()) != tagToCollide.end()); if (!isIgnored && isValidForCollision && pbComp) { if (pbComp->getShape()->raycastIntersect(localRay, tempHit, localClosestHitDistance)) { tempHit.distance /= scaleFactor; // Only update if this is the closest hit if (tempHit.distance getWorldTransform().getLocalMatrix() * tempHit.point; tempHit.normal = node->getWorldTransform().getLocalMatrix().transformDirection(tempHit.normal); hit = tempHit; hit.object = gameObject; hasHit = true; } } } } // Recursively check children for (const auto& child : node->getChildren()) { if (processNode(child, ray, hit, closeHitDistance, tagToCollide, tagToIgnore)) { hasHit = true; } } return hasHit; }
/*!**************************************************************************** * \file LevelManager.cpp * \author Nicholas Shaw (nick.shaw@digipen.edu) (Base class and manager code) * \author Anish Murthy (anish.murthy@digipen.edu) (Graphics/Physics functions) * \author Daoming Wang (daoming.wang@digipen.edu) (Controller and Pause menu) * \author Li Kuang (li.kuang@digipen.edu) (Map Management/Raycasting) * * Copyright © 2025 DIGIPEN Institute of Technology. All rights reserved. * *****************************************************************************/ #include "precompiled.h" #include "LevelManager.h" void LevelManager::SystemInitalization() { mainWindow = new GameWindow; //isFullscreen = false; mainWindow->setTitle("Traversal")->setInitialFullscreen(isFullscreen); mainWindow->initialize(); /* Renderer setup */ mainRenderer = new Renderer; mainRenderer->setGameWindow(mainWindow); mainRenderer->initialize(); mainRenderer->setClearColor(0.0f, 0.0f, 0.0f, 1.0f); //glfwSwapInterval(0); /* IMGUI Init */ ImGui::CreateContext(); ImGui_ImplGlfw_InitForOpenGL(mainWindow->getNativeWindow(), true); ImGui_ImplOpenGL3_Init(); int textureMode = 1; //auto skydomePass = std::make_shared("media/beach.jpg"); //mainRenderer->getRenderGraph()->addPass(skydomePass); //mainWindow->setVsync(true); /* Input setup */ mainInput = new Input; std::vector keysToMonitor = { KEY_W, KEY_A, KEY_S, KEY_D, KEY_R, KEY_F, KEY_UP, KEY_LEFT, KEY_DOWN, KEY_RIGHT, KEY_ESCAPE }; mainInput->setGameWindow(mainWindow); mainInput->initialize(); /* XInput setup */ gamepad = new GamePad; gamepad->initialize(); gamepadState = gamepad->CheckConnection(); /* Framerate controller setup */ mainFramerateController = FFramerateController::getController(); PauseMenu::Instance().setInputSystem(mainInput); PauseMenu::Instance().setFramerateController(mainFramerateController); PauseMenu::Instance().setGamePad(gamepad); PauseMenu::Instance().registerPauseCallback(mainWindow); /* Audio System Initalization */ AudioManager::instance().initialize(); AudioManager::instance().loadSound("music", "media/audio/FG15-SpyVsSpy-Pfrommer.mp3", false, true); AudioManager::instance().loadSound("walk", "media/audio/walk.mp3", true); AudioManager::instance().loadSound("run", "media/audio/footstep.mp3", true); AudioManager::instance().loadSound("slide", "media/audio/slide.mp3", true); AudioManager::instance().loadSound("jump", "media/audio/jump.mp3", true); AudioManager::instance().loadSound("key", "media/audio/key.ogg", true); AudioManager::instance().loadSound("hurt", "media/audio/hurt.mp3", true); AudioManager::instance().loadSound("click", "media/audio/click.ogg", false); AudioManager::instance().playSound("music", Vector3(0.0f, 0.0f, 0.0f), 0.15f); //AudioManager::instance().playSound("radio", Vector3(2.0f, 0.5f, 0.0f), 0.3f); /* Scenegraph setup */ //SceneGraph mainSceneGraph; /* Raycast Manager Setup */ RaycastManager::Instance().setSceneGraph(&mainSceneGraph); } void LevelManager::SystemShutdown() { ImGui::DestroyContext(); mainInput->shutdown(); mainRenderer->shutdown(); mainWindow->shutdown(); gamepad->shutdown(); AudioManager::instance().shutdown(); delete mainInput; delete mainRenderer; delete mainWindow; delete gamepad; } void LevelManager::MeshMatInitializations() { #pragma region Player boxMesh = Mesh::getShapeMesh(Mesh::Cube); sphereMesh = Mesh::getShapeMesh(Mesh::Sphere); shadowMaterial = Material::getMaterial("shadow"); shadowMaterial->setProperty("specular", Vector3(0.009f, 0.009f, 0.009f)); shadowMaterial->setProperty("shininess", 1.0f); shadowMaterial->addTexture("media/textures/shadow5.png", 1.0f, 1.0f); // Digi Material digiMaterial = Material::getMaterial("digi"); digiMaterial->setProperty("specular", Vector3(0.009f, 0.009f, 0.009f)); digiMaterial->setProperty("shininess", 10.0f); digiMaterial->addTexture("media/textures/DigiPenLogo.jpg", 1.0f, 1.0f); // FMOD Material fmodMaterial = Material::getMaterial("fmod"); fmodMaterial->setProperty("specular", Vector3(0.009f, 0.009f, 0.009f)); fmodMaterial->setProperty("shininess", 10.0f); fmodMaterial->addTexture("media/textures/FmodLogo.jpg", 1.0f, 1.0f); //doorMaterial = Material::getMaterial("door", mainRenderer->getRenderGraph()); //doorMaterial->setProperty("diffuse", Vector3(87 / 255.0f, 51 / 255.0f, 35 / 255.0f)); //doorMaterial->setProperty("specular", Vector3(0.009f, 0.009f, 0.009f)); //doorMaterial->setProperty("shininess", 20.0f); //doorMaterial->addTexture("media/textures/door.png", 1.0f, 1.0f); #pragma endregion /*Map Loader*/ std::shared_ptr rg = mainRenderer->getRenderGraph(); MapLoader::instance().initializeResources(); } void LevelManager::DisplayLogos() { SceneGraph sceneGraph; //Create Object auto Logo = std::make_shared("Logo"); sceneGraph.addNode(Logo); Logo->setLocalPosition(Vector3(0.0f, 0.0f, -10.0f)); auto renderComp = Logo->addComponent(); renderComp->setMaterial(digiMaterial)->setProperty("useTexture", 1); const Renderer::Viewport& viewPort = mainRenderer->getCurrentState().viewport; Logo->setLocalScaling(Vector3((float) viewPort.width/2, (float) viewPort.width/2, 1.0f)); //Create Camera auto cam = std::make_shared("uicam"); cam->lookAt(Vector3(0.0f, 0.0f, -1.0f))->setOrthographicProjection( -(viewPort.width / 2), (viewPort.width / 2), -(viewPort.height / 2), (viewPort.height / 2), 1.0f,100.0f ); sceneGraph.addCamera(cam); mainRenderer->getRenderGraph()->addPass(); mainRenderer->setClearColor(0.5f, 0.5f, 0.5f, 1.0f); //Count the Logos float logoTimer = 0.0f; float logoDuration = 5.0f; int currentLogo = 0; int numLogos = 2; int expectedFrameRate = 60; static int windowedPosX, windowedPosY, windowedWidth, windowedHeight; mainFramerateController->setTargetFramerate(expectedFrameRate); //sceneGraph.printSceneTree(); bool mouseClicked = false; bool prevMouseClicked = true; while (currentLogo clear(); mainFramerateController->startFrame(); // record the time from frame start //Update the Input Manager mainInput->update(); //Update the GamePad gamepad->update(); //Full Screen Toggle if (mainInput->isKeyPressed(KEY_F11)) { isFullscreen = !isFullscreen; mainWindow->setFullscreen(isFullscreen); } //Update Logo Timer float deltaTime = 1.0f / expectedFrameRate; logoTimer += deltaTime; //Ambient Light float ambientIntensity = 1.0f; float fadeInTime = 0.5f; float fadeOutTime = 1.0f; if (logoTimer logoDuration - fadeOutTime) { ambientIntensity = std::max(0.0f, (logoDuration - logoTimer) / fadeOutTime); } renderComp->setProperty("transparency", ambientIntensity); bool mouseClicked = mainInput->isMouseButtonDown(0) || mainInput->isMouseButtonDown(1); bool skipped = (!prevMouseClicked && mouseClicked) || (mainInput->isKeyPressed(KEY_SPACE))|| (mainInput->isKeyPressed(KEY_ENTER))|| (mainInput->isKeyPressed(KEY_ESCAPE)); if ( skipped || logoTimer >= logoDuration) { logoTimer = 0.0f; currentLogo++; if (currentLogo setMaterial(digiMaterial); break; case 1: renderComp->setMaterial(fmodMaterial); break; default: break; } } } prevMouseClicked = mouseClicked; //Draw the Scene mainRenderer->getRenderGraph()->draw(&sceneGraph); //Update Scenegraph mainFramerateController->endFrame(); //Swap Buffers and Update Window mainRenderer->swapBuffers(); mainWindow->update(); } mainRenderer->getRenderGraph()->removePass(); mainRenderer->setClearColor(0.0f, 0.0f, 0.0f, 1.0f); } void LevelManager::RunLevels() { levelSwapFlag = false; createPlayerObject(); initalizePlayerInLevel(); switch (currentLevel) { case -1: LoadLevelMenu(); break; case 0: LoadLevel0(); break; case 1: LoadLevel1(); break; case 2: LoadLevel2(); break; case 3: LoadLevel3(); break; case 4: LoadLevel4(); break; case 5: LoadLevel5(); break; case 6: LoadLevel6(); break; default: break; } ExecuteMainLoop(); ClearLevel(); } void LevelManager::ExecuteMainLoop() { /* Main Loop Variables */ float angleX = 0.0f; float angleY = 0.0f; float angleZ = 0.0f; float speed = 10.0f; float deltaTime = 0.0f; int expectedFrameRate = 60; // 1000; static int windowedPosX, windowedPosY, windowedWidth, windowedHeight; mainFramerateController->setTargetFramerate(expectedFrameRate); mainSceneGraph.printSceneTree(); mainRenderer->getRenderGraph()->lightsSet = false; while (!levelSwapFlag) { mainRenderer->clear(); mainFramerateController->startFrame(); // record the time from frame start //Update the Input Manager mainInput->update(); //Update the GamePad gamepad->update(); //If Escape is Pressed Exit Loop if (PauseMenu::Instance().isQuit()) { currentLevel = numLevels + 1; break; } if (mainInput->isKeyPressed(KEY_F11)) { isFullscreen = !isFullscreen; mainWindow->setFullscreen(isFullscreen); } // Physics update loop fixedStepTime /*while (mainFramerateController->shouldUpdatePhysics()) { PhysicsManager::Instance().update(mainFramerateController->getPhysicsTimestep()); mainFramerateController->consumePhysicsTime(); }*/ auto fpc = playerBox->findComponent(); if (fpc && fpc->isCreativeMode()) { for (int level = 0; level isKeyPressed(levelKey)) { if (level == 9) { resetToMenu(); } if (level < numLevels) { currentLevel = level; levelSwapFlag = true; } else { std::cerr <CheckConnection() != gamepadState) { PauseMenu::Instance().setState(true); gamepadState = gamepad->CheckConnection(); } if (PauseMenu::Instance().gameIsPaused()) { mainRenderer->getRenderGraph()->draw(&mainSceneGraph); mainInput->controlMouse(false); PauseMenu::Instance().run(); } else { while(mainFramerateController->shouldUpdatePhysics()) { PhysicsManager::Instance().update(mainFramerateController->getPhysicsTimestep()); mainFramerateController->consumePhysicsTime(); } //Audio Update AudioManager::instance().update(); AudioManager::instance().setListenerPosition(playerBox.get()->getWorldTransform().getPosition()); if (mainInput->isKeyPressed(KEY_V)) { AudioManager::instance().togglePlaybackSpeed(0.7f); } AudioManager::instance().setListenerPosition(playerBox->getWorldPosition()); checkPlayerBoundaries(); mainRenderer->getRenderGraph()->draw(&mainSceneGraph); mainSceneGraph.update(mainFramerateController->getFrameTime()); } mainFramerateController->endFrame(); // //#pragma region IMGUI // ImGui_ImplOpenGL3_NewFrame(); // ImGui_ImplGlfw_NewFrame(); // ImGui::NewFrame(); // //ImGui::SetNextWindowPos(ImVec2(10, 10)); // Position at top-left // //ImGui::SetNextWindowBgAlpha(0.35f); // Make it semi-transparent // // ImGui::Begin("Overlay", nullptr, // ImGuiWindowFlags_NoTitleBar | // ImGuiWindowFlags_NoResize | // ImGuiWindowFlags_AlwaysAutoResize | // ImGuiWindowFlags_NoMove | // ImGuiWindowFlags_NoScrollbar | // ImGuiWindowFlags_NoInputs); // // ImGui::Text("FPS: %.1f", mainFramerateController->getFPS()); // ImGui::Text("Frametime: %f", mainFramerateController->getFrameTime()); // ImGui::Text("RenderTime: %f", mainFramerateController->getRenderTime()); // // Vector3 playerPos = playerBox->getWorldTransform().getPosition(); // ImGui::Text("x: %.2f y: %.2f z: %.2f", playerPos.x, playerPos.y, playerPos.z); // ImGui::Text("Timer: %.2f seconds", mainFramerateController->getTime()); // Vector3 listenerPos = AudioManager::instance().getListenerPosition(); // ImGui::Text("HP: %d", fpc->getHP()); // if (auto fpc = playerBox->findComponent()) { // std::string diffStr; // switch (fpc->getDifficulty()) { // case FirstPersonControllerComponent::EASY: // diffStr = "EASY"; // break; // case FirstPersonControllerComponent::NORMAL: // diffStr = "NORMAL"; // break; // case FirstPersonControllerComponent::HARD: // diffStr = "HARD"; // break; // case FirstPersonControllerComponent::CHEATING: // diffStr = "CHEATING"; // break; // default: // diffStr = "UNKNOWN"; // break; // } // ImGui::Text("Difficulty: %s", diffStr.c_str()); // } // // ImGui::End(); // // ImGui::Render(); // ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); //#pragma endregion mainRenderer->swapBuffers(); mainWindow->update(); //glfwSwapBuffers(window); } } void LevelManager::checkPlayerBoundaries() { Vector3 playerPos = playerBox->getWorldTransform().getPosition(); float maxX, minX, maxY, minY, minZ, maxZ; switch (currentLevel) { case 0: maxX = 10.0f; minX = -400.0f; maxY = 100.0f; minY = -45.0f; maxZ = 11.0f; minZ = -11.0f; break; case 1: maxX = 150.0f; minX = -400.0f; maxY = 150.0f; minY = -45.0f; maxZ = 11.0f; minZ = -11.0f; break; case 2: maxX = 150.0f; minX = -400.0f; maxY = 150.0f; minY = -45.0f; maxZ = 11.0f; minZ = -11.0f; break; case 5: maxX = 1000.0f; minX = -1000.0f; maxY = 1000.0f; minY = -5.0f; maxZ = 1000.0f; minZ = -1000.0f; break; default: maxX = 1000.0f; minX = -1000.0f; maxY = 1000.0f; minY = -20.0f; maxZ = 1000.0f; minZ = -1000.0f; break; } if (playerDifficulty == FirstPersonControllerComponent::HARD) { minY = -100.0f; } if (playerPos.x > maxX || playerPos.x maxY || playerPos.y maxZ || playerPos.z findComponent(); if (fpc && !fpc->isCreativeMode()) { fpc->respawnPlayer(true); } } } void LevelManager::NextLevel() { if (currentLevel == 6) { currentLevel = -1; } else { currentLevel++; } levelSwapFlag = true; } void LevelManager::ClearLevel() { //Clear Physics and Scenegraph PhysicsManager::Instance().clearPhysicsManager(); mainSceneGraph.clear(); } void LevelManager::ShutdownLevels() { // Empty for now, but we can add stuff later. } void LevelManager::initialize() { SystemInitalization(); MeshMatInitializations(); } void LevelManager::shutdown() { SystemShutdown(); ShutdownLevels(); } #pragma region LevelLoaders void LevelManager::LoadLevelMenu() { MapLoader::instance().loadMap(-1, 0, 0, 0, mainSceneGraph); } void LevelManager::LoadLevel0() { MapLoader::instance().loadMap(0, 0, 0, 0, mainSceneGraph); } void LevelManager::LoadLevel1() { MapLoader::instance().loadMap(1, 0, 0, 0, mainSceneGraph); } void LevelManager::LoadLevel2() { MapLoader::instance().loadMap(2, 0, 0, 0, mainSceneGraph); } void LevelManager::LoadLevel3() { MapLoader::instance().loadMap(3, 0, 0, 0, mainSceneGraph); } void LevelManager::LoadLevel4() { MapLoader::instance().loadMap(4, 0, 0, 0, mainSceneGraph); } void LevelManager::LoadLevel5() { MapLoader::instance().loadMap(5, 0, 0, 0, mainSceneGraph); } void LevelManager::LoadLevel6() { MapLoader::instance().loadMap(6, 0, 0, 0, mainSceneGraph); } #pragma endregion bool LevelManager::GameComplete() { return currentLevel >= numLevels; } void LevelManager::createPlayerObject() { #pragma region Initalizations playerBox = std::make_shared("PlayerBox", GameObject::Tag::PLAYER); mainSceneGraph.addNode(playerBox); cameraGameObject = std::make_shared("mainCamera"); playerBox->addChild(cameraGameObject); camera = std::make_shared("mainCamera"); camera->attachToNode(cameraGameObject); mainSceneGraph.addCamera(camera); #pragma endregion #pragma region Camera //Perspective camera->setPerspectiveProjection( 45.0f * 3.14159f / 180.0f, mainWindow->getAspectRatio(), 0.1f, 5000.0f); //Transform cameraGameObject->setLocalPosition(Vector3(0.0f, 34.0f, 0.0f)); //auto cameraShape = std::make_shared( // Vector3(0.0f, 0.0f, 0.0f), // half width/height of 50 for 100x100 box // Vector3(0.5f, 0.5f, 0.5f)); //camera->addComponent() // ->setMass(10.0f)->setDrag(1.0f)->setAngularDrag(1.0f) // ->setShape(cameraShape) // //->setDebug(true) // ->registerToPhysicsManager(PhysicsManager::Instance()); #pragma endregion Vector3 LightDirection = Vector3(0.75f, -1.0f, 0.75f).normalized(); mainSceneGraph.addAmbientLight( AmbientLight(Vector3(1, 1, 1), 0.3f)); mainSceneGraph.addDirectionalLight( DirectionalLight(LightDirection, 4.0f, Vector3(1.0f, 1.0f, 1.0f))); #pragma region PlayerBox //Transform Values playerBox->setLocalPosition(startingPos1) ->setLocalScaling(Vector3(1.0f, 0.1f, 1.0f)); playerBox->addComponent(); //Render Component auto box1RenderComponent = playerBox->addComponent(); box1RenderComponent ->setMesh(sphereMesh) ->setMaterial(shadowMaterial); //Create Shape auto shape1 = std::make_shared( Vector3(0.0f, 0.0f, 0.0f), // half width/height of 50 for 100x100 box Vector3(0.5f, 5.0f, 0.5f)); // Create instances of bodies for boxes auto playerBoxPB = playerBox->addComponent() ->usingGravity(true) ->setMass(10.0f) ->setDrag(1.5f) ->setShape(shape1) ->registerToPhysicsManager(PhysicsManager::Instance()); playerBoxPB->initialize(); auto playerBoxInputComponent = playerBox->addComponent() ->setInputSystem(mainInput) ->setGamePad(gamepad) ->setPhysicsBody(playerBoxPB.get()) ->setBody(playerBox.get()) ->setSceneRoot(mainSceneGraph.getRootNode()) ->setCamera(cameraGameObject.get()) //->setActionKey(FirstPersonControllerComponent::MoveForward, KEY_W) //->setActionKey(FirstPersonControllerComponent::MoveBackward, KEY_S) //->setActionKey(FirstPersonControllerComponent::MoveLeft, KEY_A) //->setActionKey(FirstPersonControllerComponent::MoveRight, KEY_D) //->setActionKey(FirstPersonControllerComponent::Jump, KEY_SPACE) //->setActionKey(FirstPersonControllerComponent::Sprint, KEY_LEFT_SHIFT) //->setActionKey(FirstPersonControllerComponent::Slide, KEY_LEFT_CONTROL) ->setActionKey(FirstPersonControllerComponent::Respawn, KEY_R) ->setActionKey(FirstPersonControllerComponent::Debug, KEY_9) ->setActionKey(FirstPersonControllerComponent::Creative, KEY_C) ->setActionKey(FirstPersonControllerComponent::Music, KEY_M) //->setActionKey(FirstPersonControllerComponent::Freeze, KEY_F) ->setActionKey(FirstPersonControllerComponent::Pause, KEY_ESCAPE) ->setGPActionKey(FirstPersonControllerComponent::Jump, XINPUT_GAMEPAD_A) //->setGPActionKey(FirstPersonControllerComponent::Sprint, XINPUT_GAMEPAD_LEFT_THUMB) ->setGPActionKey(FirstPersonControllerComponent::Sprint, XINPUT_GAMEPAD_LEFT_SHOULDER) ->setGPActionKey(FirstPersonControllerComponent::Slide, XINPUT_GAMEPAD_B) ->setGPActionKey(FirstPersonControllerComponent::Respawn, XINPUT_GAMEPAD_X) ->setGPActionKey(FirstPersonControllerComponent::Music, XINPUT_GAMEPAD_Y) ->setGPActionKey(FirstPersonControllerComponent::Pause, XINPUT_GAMEPAD_START); const Key* keys = PauseMenu::Instance().getActionKeys(); playerBoxInputComponent->setActionKey(FirstPersonControllerComponent::MoveForward, keys[PauseMenu::MoveForward]); playerBoxInputComponent->setActionKey(FirstPersonControllerComponent::MoveBackward, keys[PauseMenu::MoveBackward]); playerBoxInputComponent->setActionKey(FirstPersonControllerComponent::MoveLeft, keys[PauseMenu::MoveLeft]); playerBoxInputComponent->setActionKey(FirstPersonControllerComponent::MoveRight, keys[PauseMenu::MoveRight]); playerBoxInputComponent->setActionKey(FirstPersonControllerComponent::Jump, keys[PauseMenu::Jump]); playerBoxInputComponent->setActionKey(FirstPersonControllerComponent::Sprint, keys[PauseMenu::Sprint]); playerBoxInputComponent->setActionKey(FirstPersonControllerComponent::Slide, keys[PauseMenu::Slide]); playerBoxInputComponent->setDifficulty(playerDifficulty); #pragma endregion PauseMenu::Instance().setPlayer(playerBox); } void LevelManager::initalizePlayerInLevel() { Vector3 activeSpawnPoint; Quaternion activeSpawnRotation; switch (currentLevel) { case -1: activeSpawnPoint = startingPos0; activeSpawnRotation = startingRot0; break; case 0: activeSpawnPoint = startingPos0; activeSpawnRotation = startingRot0; break; case 1: activeSpawnPoint = startingPos1; activeSpawnRotation = startingRot1; break; case 2: activeSpawnPoint = startingPos2; activeSpawnRotation = startingRot2; break; case 3: activeSpawnPoint = startingPos3; activeSpawnRotation = startingRot3; break; case 4: activeSpawnPoint = startingPos4; activeSpawnRotation = startingRot4; break; case 5: activeSpawnPoint = startingPos5; activeSpawnRotation = startingRot5; break; case 6: activeSpawnPoint = startingPos6; activeSpawnRotation = startingRot6; break; default: activeSpawnPoint = Vector3(); activeSpawnRotation = Quaternion(); break; } auto pbFPCController = playerBox->findComponent(); pbFPCController->setRespawnCheckpoint(activeSpawnPoint, activeSpawnRotation); pbFPCController->respawnPlayer(true, true); mainRenderer->getRenderGraph()->addPass(); mainRenderer->getRenderGraph()->addPass(); //mainRenderer->getRenderGraph()->addPass(); } void LevelManager::SetPlayerDifficulty(FirstPersonControllerComponent::Difficulty diff) { playerDifficulty = diff; } FirstPersonControllerComponent::Difficulty LevelManager::getDifficulty() const { return playerDifficulty; } void LevelManager::resetToMenu() { currentLevel = -1; levelSwapFlag = true; }
/*!**************************************************************************** * \file FirstPersonControllerComponent.cpp * \author Nicholas Shaw (nick.shaw@digipen.edu) (Level Management) * \author Anish Murthy (anish.murthy@digipen.edu) (Reparenting functions) * \author Daoming Wang (daoming.wang@digipen.edu) (Controller and Pause menu) * * Copyright © 2025 DIGIPEN Institute of Technology. All rights reserved. * *****************************************************************************/ #include "precompiled.h" #include "FirstPersonControllerComponent.h" #include "Ray.h" #include "RaycastHit.h" #include "RaycastManager.h" #include "RigidBody.h" #include "LevelManager.h" void FirstPersonControllerComponent::initialize() { //Checks to make sure the values have been initialized assert(input && "Input system is null!"); assert(physicsBody && "Physics body is null!"); assert(body && "Body (GameObject) is null!"); assert(camera && "Camera is null!"); } #pragma region State Switching Function void FirstPersonControllerComponent::SwitchState(PlayerState originalState, PlayerState newState) { switch (originalState) { case FirstPersonControllerComponent::Free: switch (newState) { case FirstPersonControllerComponent::WallRunning: FreeToWallRunning(); return; break; case FirstPersonControllerComponent::Sliding: FreeToSliding(); return; break; case FirstPersonControllerComponent::Grounded: FreeToGrounded(); return; break; default: break; } break; case FirstPersonControllerComponent::WallRunning: switch (newState) { case FirstPersonControllerComponent::Free: WallRunningToFree(); return; break; case FirstPersonControllerComponent::Grounded: WallRunningToGrounded(); return; break; default: break; } break; case FirstPersonControllerComponent::Sliding: switch (newState) { case FirstPersonControllerComponent::Free: SlidingToFree(); return; break; case FirstPersonControllerComponent::Grounded: SlidingToGrounded(); return; break; default: break; } break; case FirstPersonControllerComponent::Grounded: switch (newState) { case FirstPersonControllerComponent::Free: GroundedToFree(); return; break; case FirstPersonControllerComponent::Sliding: GroundedToSliding(); return; break; case FirstPersonControllerComponent::Grounded: return; break; default: break; } break; default: break; } assert("Invalid state transition triggered for FPC" && false); } inline void FirstPersonControllerComponent::FreeToGrounded() { unanchoredTime = 0.0f; hasSlidSinceAnchored = false; physicsBody->setDrag(anchoredDrag); physicsToAnchor(); playerState = Grounded; } inline void FirstPersonControllerComponent::FreeToSliding() { hasSlidSinceAnchored = true; sinceLastSlideTime = 0.0f; playerState = Sliding; physicsBody->setDrag(anchoredDrag); } inline void FirstPersonControllerComponent::FreeToWallRunning() { unanchoredTime = 0.0f; hasSlidSinceAnchored = false; physicsBody->setDrag(anchoredDrag); physicsToAnchor(); playerState = WallRunning; RigidBody* const rb = static_cast(physicsBody); rb->usingGravity(false); } inline void FirstPersonControllerComponent::GroundedToFree() { physicsBody->setDrag(airDrag); physicsToAir(); playerState = Free; } inline void FirstPersonControllerComponent::GroundedToSliding() { sinceLastSlideTime = 0.0f; physicsToAir(); playerState = Sliding; } inline void FirstPersonControllerComponent::WallRunningToFree() { physicsBody->setDrag(airDrag); physicsToAir(); playerState = Free; RigidBody* const rb = static_cast(physicsBody); rb->usingGravity(true); lastWallRunObject = anchorInfo.object; wallRunLockoutTimer = wallRunLockoutDuration; } inline void FirstPersonControllerComponent::WallRunningToGrounded() { physicsToAnchor(); playerState = Grounded; RigidBody* const rb = static_cast(physicsBody); rb->usingGravity(true); } inline void FirstPersonControllerComponent::SlidingToGrounded() { unanchoredTime = 0.0f; hasSlidSinceAnchored = false; physicsToAnchor(); playerState = Grounded; physicsBody->setDrag(anchoredDrag); } inline void FirstPersonControllerComponent::SlidingToFree() { physicsToAir(); playerState = Free; physicsBody->setDrag(airDrag); } #pragma endregion #pragma region Jumping void FirstPersonControllerComponent::GroundedJump() { //-----Handle Jumping-----// #pragma region Jumping //Apply Jump //Reset Cooldown Timer sinceLastJumpTime = 0.0f; //Apply Jump Force const Vector3 currentVelocity = physicsBody->getVelocity(); /* Todo: Consider adding forward momentum? */ const Vector3 newVelocity = Vector3(currentVelocity.x, jumpSpeed, currentVelocity.z); physicsBody->setVelocity(newVelocity); AudioManager::instance().playSound("jump", Vector3(body->getWorldPosition()), PauseMenu::Instance().getSFXVolume()); // Prevent multiple jumps until grounded again sinceLastJumpPressedTime = jumpBufferTime + 1.0f; #pragma endregion } void FirstPersonControllerComponent::SlidingJump() { sinceLastJumpTime = 0.0f; sinceLastJumpPressedTime = jumpBufferTime + 1.0f; const float slideJumpMultiplier = 1.5f; Vector3 currentVelocity = physicsBody->getVelocity(); Vector3 newVelocity = Vector3(currentVelocity.x / 2, jumpSpeed * slideJumpMultiplier, currentVelocity.z / 2); physicsBody->setVelocity(newVelocity); AudioManager::instance().playSound("jump", Vector3(body->getWorldPosition()), PauseMenu::Instance().getSFXVolume()); } void FirstPersonControllerComponent::WallrunningJump() { sinceLastJumpTime = 0.0f; sinceLastJumpPressedTime = jumpBufferTime + 1.0f; lastWallRunObject = anchorInfo.object; wallRunLockoutTimer = wallRunLockoutDuration; const float wallJumpMultiplier = 2.0f; Vector3 currentVelocity = physicsBody->getVelocity(); Vector3 wallJumpVelocity = anchorInfo.normal * wallJumpForce * wallJumpMultiplier + Vector3(0.0f, wallJumpForce * 2 * wallJumpMultiplier, 0.0f); Vector3 combinedVelocity = (currentVelocity + wallJumpVelocity) / 2.0f; physicsBody->setVelocity(combinedVelocity); AudioManager::instance().playSound("jump", Vector3(body->getWorldPosition()), PauseMenu::Instance().getSFXVolume()); } inline bool FirstPersonControllerComponent::passedCoyoteTime() { return (unanchoredTime > coyoteTime); } inline bool FirstPersonControllerComponent::JumpBuffered() { return (sinceLastJumpPressedTime jumpCooldown); } #pragma endregion #pragma region Sliding inline bool FirstPersonControllerComponent::SlideBuffered() { return (sinceLastSlidePressedTime slideCoolDown); } bool FirstPersonControllerComponent::SlidingTimedOut() { return (sinceLastSlideTime > slideEffectTime); } #pragma endregion #pragma region Physics Anchoring void FirstPersonControllerComponent::UpdateAnchorInfo() { #pragma region Grounded const Transform bodyWorldTransform = body->getWorldTransform(); const Vector3 currentPos = bodyWorldTransform.getPosition(); RaycastHit hitGround; /* Todo: This currently assumes z is down, fix for rotated objects. * Consider using getFarthestExtent()? */ const bool isGrounded = RaycastManager::Instance().Raycast( Ray(currentPos, Vector3(0.0f, -1.0f, 0.0f)), hitGround, (bodyWorldTransform.getScaling().z) * 2 + 0.25 ); const bool isGroundedRB = isGrounded && (hitGround.object->findComponent() != nullptr); if (isGroundedRB) { anchorInfo.object = hitGround.object; anchorInfo.direction = 'd'; anchorInfo.normal = hitGround.normal; //If the ground is a checkpoint if (hitGround.object->getTag() == GameObject::CHECKPOINT) { static std::shared_ptr currentCheckpoint = nullptr; if (currentCheckpoint != hitGround.object) { currentCheckpoint = hitGround.object; Vector3 newRespawCheckpoint = hitGround.object->getLocalPosition() + Vector3(0.0f, 2.0f, 0.0f); Quaternion newRespawnRotation = hitGround.object->getLocalRotation(); setRespawnCheckpoint(newRespawCheckpoint, newRespawnRotation); } else { //std::cout << "Checkpoint already set to this object." <getForwardVector(); const Vector3 rightVector = body->getRightVector(); const float rayDist = parent->getWorldTransform().getScaling().x * 3; RaycastHit leftWallHit, rightWallHit; #pragma region RightWallAndBoth if (anchorInfo.direction == 'r' || anchorInfo.direction == '0' || anchorInfo.direction == 'd') { const Ray rightRay = Ray(currentPos, rightVector); const Ray right45Ray = Ray(currentPos, (rightVector + forwardVector).normalized()); const bool isRightWall = RaycastManager::Instance().Raycast(rightRay, rightWallHit, rayDist, { GameObject::RUNNABLE_WALL }) || RaycastManager::Instance().Raycast(right45Ray, rightWallHit, rayDist, { GameObject::RUNNABLE_WALL }); const bool isRightWallRB = isRightWall && (rightWallHit.object->findComponent() != nullptr); if (isRightWallRB) { anchorInfo.direction = 'r'; anchorInfo.object = rightWallHit.object; anchorInfo.normal = rightWallHit.normal; return; } else { const Ray leftRay = Ray(currentPos, -rightVector); const Ray left45Ray = Ray(currentPos, (-rightVector + forwardVector).normalized()); const bool isLeftWall = RaycastManager::Instance().Raycast(leftRay, leftWallHit, rayDist, { GameObject::RUNNABLE_WALL }) || RaycastManager::Instance().Raycast(left45Ray, leftWallHit, rayDist, { GameObject::RUNNABLE_WALL }); const bool isLeftWallRB = isLeftWall && (leftWallHit.object->findComponent() != nullptr); if (isLeftWallRB) { anchorInfo.direction = 'l'; anchorInfo.object = leftWallHit.object; anchorInfo.normal = leftWallHit.normal; return; } else { anchorInfo.Reset(); return; } } } #pragma endregion #pragma region LeftWall else if (anchorInfo.direction == 'l') { const Ray leftRay = Ray(currentPos, -rightVector); const Ray left45Ray = Ray(currentPos, (-rightVector + forwardVector).normalized()); const bool isLeftWall = RaycastManager::Instance().Raycast(leftRay, leftWallHit, rayDist, { GameObject::RUNNABLE_WALL }) || RaycastManager::Instance().Raycast(left45Ray, leftWallHit, rayDist, { GameObject::RUNNABLE_WALL }); const bool isLeftWallRB = isLeftWall && (leftWallHit.object->findComponent() != nullptr); if (isLeftWallRB) { anchorInfo.object = leftWallHit.object; anchorInfo.normal = leftWallHit.normal; return; } else { const Ray rightRay = Ray(currentPos, rightVector); const Ray right45Ray = Ray(currentPos, (rightVector + forwardVector).normalized()); const bool isRightWall = RaycastManager::Instance().Raycast(rightRay, rightWallHit, rayDist, { GameObject::RUNNABLE_WALL }) || RaycastManager::Instance().Raycast(right45Ray, rightWallHit, rayDist, { GameObject::RUNNABLE_WALL }); const bool isRightWallRB = isRightWall && (rightWallHit.object->findComponent() != nullptr); if (isRightWallRB) { anchorInfo.direction = 'r'; anchorInfo.object = rightWallHit.object; anchorInfo.normal = rightWallHit.normal; return; } else { anchorInfo.Reset(); return; } } } anchorInfo.Reset(); return; #pragma endregion } void FirstPersonControllerComponent::physicsToAir() { Transform currentWorld = parent->getWorldTransform(); std::shared_ptr parentGameObject = std::dynamic_pointer_cast(parent->getParent()); if (parentGameObject) { std::shared_ptr anchorPhysics = parentGameObject->findComponent(); if (anchorPhysics) { physicsBody->setVelocity(anchorPhysics->getVelocity() + physicsBody->getVelocity()); physicsBody->setAcceleration(anchorPhysics->getAcceleration() + physicsBody->getAcceleration()); physicsBody->setForce(anchorPhysics->getAcceleration() + physicsBody->getAcceleration()); physicsBody->setRotationalVelocity(anchorPhysics->getRotationalVelocity() + physicsBody->getRotationalVelocity()); physicsBody->setRotationalAcceleration(anchorPhysics->getRotationalAcceleration() + physicsBody->getRotationalAcceleration()); physicsBody->setRotationalForce(anchorPhysics->getRotationalAcceleration() + physicsBody->getRotationalAcceleration()); } } parent->reparent(sceneRoot); parent->setWorldTransform(currentWorld); } void FirstPersonControllerComponent::physicsToAnchor() { Transform currentWorld = parent->getWorldTransform(); std::shared_ptr anchorGameObject = std::dynamic_pointer_cast(anchorInfo.object); if (anchorGameObject) { std::shared_ptr anchorPhysics = anchorInfo.object->findComponent(); if (anchorPhysics) { physicsBody->setVelocity(physicsBody->getVelocity() - anchorPhysics->getVelocity()); physicsBody->setAcceleration(physicsBody->getAcceleration() - anchorPhysics->getAcceleration()); physicsBody->setForce(physicsBody->getAcceleration() - anchorPhysics->getAcceleration()); physicsBody->setRotationalVelocity(physicsBody->getRotationalVelocity() - anchorPhysics->getRotationalVelocity()); physicsBody->setRotationalAcceleration(physicsBody->getRotationalAcceleration() - anchorPhysics->getRotationalAcceleration()); physicsBody->setRotationalForce(physicsBody->getRotationalAcceleration() - anchorPhysics->getRotationalAcceleration()); } parent->reparent(anchorInfo.object); } else { parent->reparent(sceneRoot); } parent->setWorldTransform(currentWorld); } #pragma endregion void FirstPersonControllerComponent::update(float deltaTime) { debugCheck(); //-----Input-----// #pragma region Input bool isMovingForward = input->isKeyHeld(ActionKey[MoveForward]); bool isMovingBackward = input->isKeyHeld(ActionKey[MoveBackward]); bool isMovingLeft = input->isKeyHeld(ActionKey[MoveLeft]); bool isMovingRight = input->isKeyHeld(ActionKey[MoveRight]); bool isSprinting = input->isKeyHeld(ActionKey[Sprint]); float forwardMotion = input->isKeyHeld(ActionKey[MoveForward]) - input->isKeyHeld(ActionKey[MoveBackward]); float lateralMotion = input->isKeyHeld(ActionKey[MoveRight]) - input->isKeyHeld(ActionKey[MoveLeft]); bool isJumping = input->isKeyPressed(ActionKey[Jump]); bool isSliding = input->isKeyPressed(ActionKey[Slide]); bool isRespawning = input->isKeyPressed(ActionKey[Respawn]); bool creative = input->isKeyPressed(ActionKey[Creative]); bool music = input->isKeyPressed(ActionKey[Music]); bool freezePressed = input->isKeyPressed(ActionKey[Freeze]); float upMotion = input->isKeyHeld(ActionKey[Jump]) - input->isKeyHeld(ActionKey[Slide]); bool pause = input->isKeyPressed(ActionKey[Pause]); //Mouse float mouseXDelta = 0.0f; float mouseYDelta = 0.0f; #pragma endregion //GamePad Input #pragma region GamePad if (gp != nullptr) { if (gp->leftStickY != 0) { forwardMotion = gp->leftStickY; if (forwardMotion > 0) { isMovingForward = true; isMovingBackward = false; } else if (forwardMotion leftStickX != 0) { lateralMotion = gp->leftStickX; if (lateralMotion 0) { isMovingLeft = false; isMovingRight = true; } } if (gp->rightStickX != 0) { mouseXDelta = static_cast(gp->rightStickX) * gp->getRXSensitivity(); Quaternion currentBodyRotation = body->getLocalRotation(); Quaternion mouseRotation = Quaternion::axisAngleToQuaternion(Vector3(0.0f, 1.0f, 0.0f), (-mouseXDelta * 3.14159265f / 180.0f)); body->setLocalRotation(currentBodyRotation * mouseRotation); } if (gp->rightStickY != 0) { mouseYDelta = -static_cast(gp->rightStickY) * gp->getRYSensitivity(); //Rotate Camera Quaternion currentCameraRoation = camera->getLocalRotation(); Vector3 currentEuler = currentCameraRoation.toEuler(); float newPitch = currentEuler.x + (-mouseYDelta * 3.14159265f / 180.0f); newPitch = std::clamp(newPitch, -pitchLimit * (3.14159265f / 180.0f), pitchLimit * (3.14159265f / 180.0f)); // Convert degrees to radians Quaternion newCameraRotation = Quaternion::fromEuler(Vector3(newPitch, currentEuler.y, currentEuler.z)); camera->setLocalRotation(newCameraRotation); } if (gp->isPressed(GamePadActionKey[Sprint])) isSprinting = gp->isPressed(GamePadActionKey[Sprint]); if (gp->isPressed(GamePadActionKey[Jump])) isJumping = gp->isPressed(GamePadActionKey[Jump]); if (gp->isPressed(GamePadActionKey[Slide])) isSliding = gp->isPressed(GamePadActionKey[Slide]); if (gp->isPressed(GamePadActionKey[Respawn])) isRespawning = gp->isPressed(GamePadActionKey[Respawn]); if (gp->isPressed(GamePadActionKey[Jump]) && gp->isPressed(GamePadActionKey[Slide])) upMotion = gp->isPressed(GamePadActionKey[Jump]) - gp->isPressed(GamePadActionKey[Slide]); if (gp->isReleased(GamePadActionKey[Creative])) creative = gp->isReleased(GamePadActionKey[Creative]); if (gp->isPressed(GamePadActionKey[Music])) music = gp->isPressed(GamePadActionKey[Music]); if (gp->isReleased(GamePadActionKey[Pause])) pause = gp->isReleased(GamePadActionKey[Pause]); } #pragma endregion //Creative mode if (isCreative) { isSliding = false; isJumping = false; isCreative = !creative; if (!isCreative) body->findComponent()->usingGravity(true); } else { upMotion = 0; isCreative = creative; if (isCreative) body->findComponent()->usingGravity(false); } //music on/off if (playsMusic) { playsMusic = !music; if (!playsMusic) AudioManager::instance().stopSound("music"); } else { playsMusic = music; if (playsMusic) AudioManager::instance().playSound("music", PauseMenu::Instance().getMusicVolume()); } //back to start lobby if (PauseMenu::Instance().isStart()) { LevelManager::Instance().resetToMenu(); PauseMenu::Instance().setStart(false); } //Pause Menu bool inConsistent = false; if (isPaused != PauseMenu::Instance().gameIsPaused() && isPaused) { isPaused = PauseMenu::Instance().gameIsPaused(); inConsistent = true; } if (inConsistent) { isFrozen = false; } if (isPaused) { isPaused = !pause; } else { isPaused = pause; //Frozen Mode if (freezePressed) { isFrozen = !isFrozen; input->controlMouse(!isFrozen); } } PauseMenu::Instance().setState(isPaused); if (wallRunLockoutTimer > 0) { wallRunLockoutTimer -= deltaTime; if (wallRunLockoutTimer < 0) wallRunLockoutTimer = 0; } //Player HP system if (damageTimer < damageCooldown) damageTimer += deltaTime; if (timeSinceDamage < recoveryDelay) { timeSinceDamage += deltaTime; } else { if (hp < maxHP) { hp = maxHP; std::cout <getMouseState(); if (mouseState.deltaX != 0) { mouseXDelta = static_cast(mouseState.deltaX) * mouseXSensitivity; //Rotate Body const Quaternion currentBodyRotation = body->getLocalRotation(); const Quaternion mouseRotation = Quaternion::axisAngleToQuaternion(Vector3(0.0f, 1.0f, 0.0f), (-mouseXDelta * 3.14159265f / 180.0f)); body->setLocalRotation(currentBodyRotation * mouseRotation); } if (mouseState.deltaY != 0) { mouseYDelta = static_cast(mouseState.deltaY) * mouseYSensitivity; //Rotate Camera const Vector3 currentEuler = camera->getLocalRotation().toEuler(); float newPitch = currentEuler.x + (-mouseYDelta * 3.14159265f / 180.0f); newPitch = std::clamp(newPitch, -pitchLimit * (3.14159265f / 180.0f), pitchLimit * (3.14159265f / 180.0f)); // Convert degrees to radians const Quaternion newCameraRotation = Quaternion::fromEuler(Vector3(newPitch, currentEuler.y, currentEuler.z)); camera->setLocalRotation(newCameraRotation); } //Reset Mouse Delta input->resetMouseDelta(); #pragma endregion if (isJumping) sinceLastJumpPressedTime = 0.0f; if (isSliding) sinceLastSlidePressedTime = 0.0f; //----Some Cached Variables----// const Vector3 forwardVector = body->getForwardVector(); const Vector3 upVector = body->getUpVector(); const Vector3 rightVector = body->getRightVector(); const Transform bodyWorldTransform = body->getWorldTransform(); const Vector3 currentPos = bodyWorldTransform.getPosition(); const float rayDist = parent->getWorldTransform().getScaling().x * 2.5f; RigidBody* const rb = static_cast(physicsBody); UpdateAnchorInfo(); //Check if the player is Respawning if (isRespawning && difficulty != HARD) { respawnPlayer(true,false); } //------------------------------STATES------------------------------// //-----State Switching-----// #pragma region StateSwitching if (playerState == Free) { if (sinceLastJumpTime > jumpCooldown && anchorInfo.direction == 'd') SwitchState(Free, Grounded); else if (SlideBuffered() && CanSlide()) { if (!forwardMotion && !lateralMotion) { slideVector = forwardVector * slideForce; } const Vector3 forwardMotionVector = forwardVector * forwardMotion; //Lateral const Vector3 lateralMotionVector = rightVector * lateralMotion; //Combine Forward and Lateral Movement and Apply Force const Vector3 combinedMotionVector = forwardMotionVector + lateralMotionVector; slideVector = combinedMotionVector * slideForce; if (slideVector.magnitude() == 0) { slideVector = forwardVector * slideForce; } physicsBody->setVelocity(slideVector); AudioManager::instance().playSound("slide", Vector3(body->getWorldPosition()), PauseMenu::Instance().getSFXVolume()); SwitchState(Free, Sliding); } else if ((anchorInfo.direction == 'l' || anchorInfo.direction == 'r') && isMovingForward && (wallRunLockoutTimer -maxDotThreshold && dotProduct setVelocity(slideVector); AudioManager::instance().playSound("slide", Vector3(body->getWorldPosition()), PauseMenu::Instance().getSFXVolume()); SwitchState(Grounded, Sliding); } else if (anchorInfo.direction != 'd' && anchorInfo.direction != '0') { SwitchState(Grounded, Free); if ((anchorInfo.direction == 'l' || anchorInfo.direction == 'r') && isMovingForward) SwitchState(Free, WallRunning); } } else if (playerState == WallRunning) { if (passedCoyoteTime()) { SwitchState(WallRunning, Free); } else if (anchorInfo.direction == 'd') SwitchState(WallRunning, Grounded); else if (!isMovingForward || anchorInfo.direction == '0') SwitchState(WallRunning, Free); else if (isJumping) { SwitchState(WallRunning, Free); WallrunningJump(); } } else if (playerState == Sliding) { if (SlidingTimedOut()) SwitchState(Sliding, (anchorInfo.direction == 'd') ? Grounded : Free); else if (isJumping) { SwitchState(Sliding, Free); SlidingJump(); } } #pragma endregion //-----State Processing-----// #pragma region StateProcessing //-----First Person Ground Movement-----// #pragma region GroundMovement if (playerState == Grounded) { const float movementForce = (isSprinting ? runForceMultiplier : 1.0f) * walkForce * 100.0f; const float maxMovementSpeed = isSprinting ? maxRunSpeed : maxWalkSpeed; //Forward const Vector3 forwardMotionVector = forwardVector * forwardMotion; //Lateral const Vector3 lateralMotionVector = rightVector * lateralMotion; const Vector3 verticalMotionVector = upVector * upMotion; //Combine Forward and Lateral Movement and Apply Force if (upMotion == 0 && isCreative && physicsBody->getVelocity().y != 0) { physicsBody->applyForce(Vector3(0.0f, -100 * physicsBody->getVelocity().y, 0.0f)); } const Vector3 combinedMotionVector = forwardMotionVector + lateralMotionVector + verticalMotionVector; if (combinedMotionVector.magnitude() > 0.0f) { const Vector3 movementVector = combinedMotionVector.normalized() * movementForce; physicsBody->applyForce(movementVector); if (isSprinting) { AudioManager::instance().playSound("run", Vector3(body->getWorldPosition()), PauseMenu::Instance().getSFXVolume()); } else { AudioManager::instance().playSound("walk", Vector3(body->getWorldPosition()), PauseMenu::Instance().getSFXVolume()); } } } #pragma endregion //-----First Person Air Movement-----// #pragma region FreeMovement else if (playerState == Free) { //---Applying Movement Force---// const float movementForce = (isSprinting ? runForceMultiplier : 1.0f) * walkForce * 50.0f; const float maxMovementSpeed = isSprinting ? maxRunSpeed : maxWalkSpeed; //Forward const Vector3 forwardMotionVector = forwardVector * forwardMotion; //Lateral const Vector3 lateralMotionVector = rightVector * lateralMotion; const Vector3 verticalMotionVector = upVector * upMotion; if (upMotion == 0 && isCreative && physicsBody->getVelocity().y != 0) { physicsBody->applyForce(Vector3(0.0f, -100 * physicsBody->getVelocity().y, 0.0f)); } //Combine Forward and Lateral Movement and Apply Force const Vector3 combinedMotionVector = forwardMotionVector + lateralMotionVector + verticalMotionVector; if (combinedMotionVector.magnitude() > 0.0f) { const Vector3 movementVector = combinedMotionVector.normalized() * movementForce; physicsBody->applyForce(movementVector); } } #pragma endregion //-----Handle Sliding-----// #pragma region Sliding else if (playerState == Sliding) { //Apply a Slide Force //Forward /* // Force based slideVector = combinedMotionVector * slideForce * 10.0f; physicsBody->applyForce(slideVector); */ Vector3 driftVector = Vector3(0.0f, 0.0f, 0.0f); Vector3 driftDirection = Vector3(-slideVector.z, 0.0f, slideVector.x).normalized(); if (isMovingLeft) { driftVector = driftDirection * -0.2f; } if (isMovingRight) { driftVector = driftDirection * 0.2f; } Vector3 newSlideVector = slideVector + driftVector; newSlideVector = newSlideVector.normalized() * slideVector.magnitude(); //Continue to Maintain Velocity physicsBody->setVelocity(newSlideVector); } #pragma endregion //-----Handle Wallrunning-----// #pragma region WallRunning else if (playerState == WallRunning) { /* Todo: verify that this code works regardless of wall angle. */ /* Todo: consider wallrun max time */ //Get the direction to move along the wall in Vector3 wallRunDirection = Vector3(0, 1, 0).cross(anchorInfo.normal); if (wallRunDirection.dot(forwardVector) setVelocity(wallRunDirection * wallRunSpeed); AudioManager::instance().playSound("run", Vector3(body->getWorldPosition()), PauseMenu::Instance().getSFXVolume()); const float wallWidth = (anchorInfo.normal * anchorInfo.object->getWorldScaling()).magnitude(); const float playerWidth = 1.415; const float wallOffset = wallWidth / 2 + playerWidth / 2; // Maintain floating offset from the wall float distanceToWall = anchorInfo.normal.dot(currentPos - anchorInfo.object->getWorldPosition()); Vector3 correctedPosition = currentPos - anchorInfo.normal * (distanceToWall - wallOffset); body->setWorldPosition(correctedPosition); } //End Wallrunning State #pragma endregion #pragma endregion //-----Timers-----// #pragma region Timers if ((anchorInfo.direction != 'd') && playerState != WallRunning) unanchoredTime += deltaTime; sinceLastJumpTime += deltaTime; sinceLastJumpPressedTime += deltaTime; sinceLastSlideTime += deltaTime; sinceLastSlidePressedTime += deltaTime; #pragma endregion } void FirstPersonControllerComponent::shutdown() { } std::shared_ptr FirstPersonControllerComponent::setInputSystem(Input* _inputSystem) { input = _inputSystem; return shared_from_this(); } std::shared_ptr FirstPersonControllerComponent::setGamePad(GamePad* _gp) { gp = _gp; return shared_from_this(); } void FirstPersonControllerComponent::setMouseXSensivity(float var) { mouseXSensitivity = var; return; } void FirstPersonControllerComponent::setMouseYSensivity(float var) { mouseYSensitivity = var; return; } FirstPersonControllerComponent* FirstPersonControllerComponent::getSelf() { return this; } std::shared_ptr FirstPersonControllerComponent::setState(PlayerState state) { SwitchState(playerState, state); return shared_from_this(); } std::shared_ptr FirstPersonControllerComponent::setPhysicsBody(PhysicsBody* _physicsBody) { physicsBody = _physicsBody; return shared_from_this(); } std::shared_ptr FirstPersonControllerComponent::setBody(GameObject* _body) { body = _body; return shared_from_this(); } std::shared_ptr FirstPersonControllerComponent::setCamera(GameObject* _camera) { camera = _camera; return shared_from_this(); } /*!**************************************************************************** * \brief Set Camera rotation to a specific angle * * This function can be used to specifically set the camera to look at a * specific X and Y axis rotation. Note, this is disabled for Z axis. * * \param rotation Pitch and Yaw to set the camera to, as specified by the * first two elements of the Vector3. * \return \b std::shared_ptr Self. *****************************************************************************/ std::shared_ptr FirstPersonControllerComponent::setCameraRotation(Vector3 rotation) { body->setLocalRotation( body->getLocalRotation() * Quaternion::fromEuler(Vector3(0.0f, rotation.y, 0.0f))); Quaternion currentCameraRoation = camera->getLocalRotation(); Vector3 currentEuler = currentCameraRoation.toEuler(); float newPitch = currentEuler.x + rotation.x; newPitch = std::clamp( newPitch, -pitchLimit * (3.14159265f / 180.0f), pitchLimit * (3.14159265f / 180.0f)); // Convert degrees to radians Quaternion newCameraRotation = Quaternion::fromEuler( Vector3(newPitch, currentEuler.y, currentEuler.z)); camera->setLocalRotation(newCameraRotation); return shared_from_this(); } std::shared_ptr FirstPersonControllerComponent::setSceneRoot(std::shared_ptr root) { sceneRoot = root; return shared_from_this(); } std::shared_ptr FirstPersonControllerComponent::setActionKey(Action _action, Key _key) { ActionKey[_action] = _key; return shared_from_this(); } std::shared_ptr FirstPersonControllerComponent::setGPActionKey(Action _action, WORD _key) { GamePadActionKey[_action] = _key; return shared_from_this(); } bool FirstPersonControllerComponent::getIsGrounded() { return (anchorInfo.direction == 'd'); } std::shared_ptr FirstPersonControllerComponent::getAnchoredSurface() { return anchorInfo.object; } #pragma region Respawn void FirstPersonControllerComponent::respawnPlayer(bool silence, bool resetRotation) { //Set the player state to Free if (playerState != Free) { setState(Free); } //Reset Velocity physicsBody->setVelocity(Vector3()); //Set Position body->setLocalPosition(respawnCheckpoint); if (!silence) { AudioManager::instance().playSound("hurt", body->getWorldPosition(), PauseMenu::Instance().getSFXVolume()); } hp = maxHP; //Set Rotation if (resetRotation) { body->setLocalRotation(respawnRotation); } body->updateTransforms(); camera->updateTransforms(); } void FirstPersonControllerComponent::setRespawnCheckpoint(Vector3 _checkpoint, Quaternion _rotation) { respawnCheckpoint = _checkpoint; respawnRotation = _rotation; } Vector3 FirstPersonControllerComponent::getRespawnCheckpoint() { return respawnCheckpoint; } #pragma endregion void FirstPersonControllerComponent::takeDamage() { if (damageTimer < damageCooldown) return; hp--; // reset the damage timer damageTimer = 0; timeSinceDamage = 0.0f; // Respawn if no hp if (hp getWorldPosition(), PauseMenu::Instance().getSFXVolume()); LevelManager::Instance().resetToMenu(); return; } else { respawnPlayer(); } } else { AudioManager::instance().playSound("hurt", body->getWorldPosition(), PauseMenu::Instance().getSFXVolume()); } std::cerr << "Current HP: " << hp <isKeyPressed(ActionKey[Debug])) { std::cout << "Here" << std::endl; } } std::shared_ptr FirstPersonControllerComponent::setDifficulty(Difficulty diff) { difficulty = diff; switch (diff) { case EASY: maxHP = 3; break; case NORMAL: case HARD: maxHP = 1; break; case CHEATING: maxHP = 10; break; } hp = maxHP; return shared_from_this(); }
