Time Attack Racing in 3D
This project was a roller coaster ride to work on. It’s a 3D time attack racing game that uses 3D physics and utilizes the NVidia PhysX vehicle SDK. It features a waypoint system, local multiplayer for up to 4 players, and uses the Xbox controller as the input device. Over the course of developing this project, I learned a lot about task planning, time management, and communication with stakeholders when developing games.
Here’s a video of the full loop play through of the demo developed. Inputs driven by 2 Xbox controllers using local multiplayer split-screen.
Project Details:
- Role: Engine and Gameplay Programmer
- Engine: Prodigy C++ Engine
- Development Time: 5 months
- Languages/Tools: C++ and HLSL on Prodigy Game Engine
Development Plan:
When developing this project, the most difficult part was coming up with a usable development plan. Although I used Trello and Github boards to plan my project at the start, I found myself most comfortable planning tasks and accounting for time using simple markdown or text files. Below is a plan I made post-mid-term milestone completion for my project as well as the hours logged throughout the development process:
# Time breakdowns //------------------------------------------------------------------------------------------------------------------------------ # Task: Description - Time Accounted For - Time Spent //------------------------------------------------------------------------------------------------------------------------------ // WEEK 1: //------------------------------------------------------------------------------------------------------------------------------ Task 1: Source Control Setup - 1Hr - 1Hr Task 2: Sourcing mod kit to use - 1Hr - 1.5Hr Task 3: Make the Project using DFS1 Project - 1Hr - 3 Hr Task 4: Update Vehicle Code from DFS 1 - 1Hr - 2Hr ///////////////////// Week 1 Summary Time planned: 4Hrs Time spent: 7.5Hrs Off by: 87.5% Hours Off: +3.5 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ WEEK 2: Plan Revision //------------------------------------------------------------------------------------------------------------------------------ Task 5: Check Collision Filters on Car - 1Hr - 1.5Hr Task 6: Make a Simple racetrack using modkit - 4Hr - 7Hr Task 7: Load OBJ files as PhysX collision mesh - 5Hr - 6Hr ///////////////////// Week 2 Summary Time planned: 10Hrs Time spent: 14.5Hrs Off by: 45% Hours Off: +4.5 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ WEEK 3: //------------------------------------------------------------------------------------------------------------------------------ Task 8: Event for mesh->PhysXCollider from XML - 3Hr - 3Hr Task 9: Road and Ramp mesh loading as obstacles - 4Hr - 6Hr Task 10: Load Multiple Convex obstacles PhysX - 5Hr - 5Hr Task 11: Check collision on collider corners - 1Hr - 0.5Hr ///////////////////// Week 3 Summary Time planned: 13Hrs Time spent: 14.5Hrs Off by: 11.53% Hours Off: +1.5 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ WEEK 4: //------------------------------------------------------------------------------------------------------------------------------ Task 12: Parent mesh meta-data setups for child collision meshes - 2Hr - 2Hr Task 13: Setting up common pivot on origin - 2Hr - 3Hr Task 14: Separating road to multiple OBJs - 4Hr - 5.5Hr Task 15: Bugs mesh loading interfering with PhysX - - 6Hr Other tasks I planned for but didn't do: Task 16: Make the time tracker - 1Hr - Task 17: Create Checkpoint System - 3Hr - ///////////////////// Week 4 Summary Time planned: 12Hrs Time spent: 16.5Hrs Off by: 37.5% Hours Off: +4.5 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ WEEK 5: Re-Plan Revision //------------------------------------------------------------------------------------------------------------------------------ Task 18: Make the time tracker - 1Hr - 1.5Hr Task 19: Create Checkpoint System - 3Hr - 3Hr Task 20: Renderer Pick Nvidia always - 2Hr - 3Hr DEBUG: More performance issues (No fixed time step, memory leaks) - 6 Hrs Other tasks I planned for but didn't do: Task 21: Create Split Screen System - 4Hr - Task 22: XML loading for checkpoint data - 3Hr - ///////////////////// Week 5 Summary Time planned: 13Hrs Time spent: 13.5Hrs Off by: 3.8% Hours Off: +0.5 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ WEEK 6: //------------------------------------------------------------------------------------------------------------------------------ Task 23: Create Split Screen System - 4Hr - 5Hr Task 24: New Schedule on Github Boards - 2Hr - 1.5Hr DEBUG: Performance Bug - 3Hr - 3Hr Task 25: Steps to fix performance Bug - 3Hr - 4Hr ///////////////////// Week 6 Summary Time planned: 12Hrs Time spent: 13.5Hrs Off by: 12.5% Hours Off: +1.5 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ WEEK 7: //------------------------------------------------------------------------------------------------------------------------------ Task 26: Waypoint Time Tracking tier 2 - 3Hr - 4Hr Task 27: Research on Vehicle Audio - 3Hr - 3Hr Other tasks I planned for but didn't do: Task 28: Game Menus - 3Hr - ///////////////////// Week 7 Summary Time planned: 9Hrs Time spent: 7Hrs Off by: 28.57% Hours Off: -2 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ WEEK 8: //------------------------------------------------------------------------------------------------------------------------------ Task 29: Update Vehicle Creation Logic - 3Hr - 6Hr Task 30: Tweaks from Presentation - 2Hr - 3Hr ///////////////////// Week 7 Summary Time planned: 5Hrs Time spent: 9Hrs Off by: 55.55% Hours Off: +4 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ WEEK 9: //------------------------------------------------------------------------------------------------------------------------------ // Other projects and professional development (Pre-allocated time) //------------------------------------------------------------------------------------------------------------------------------ WEEK 10: //------------------------------------------------------------------------------------------------------------------------------ Task 31: Setup Vehicle Audio system - 3Hr - 3Hr Task 32: Implement Audio for car - 3Hr - 4Hr Task 33: AudioSystem changes required - 3Hr - 2Hr DEBUG: Reset on car not working - - 2Hr ///////////////////// Week 10 Summary Time planned: 9Hrs Time spent: 11Hrs Off by: 22.22% Hours Off: +2 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ WEEK 11: Re-Re-Plan Revision //------------------------------------------------------------------------------------------------------------------------------ Task 34: Async Resource Loading - 3Hr - 3.5Hr Task 35: Game Menus - 3Hr - DEBUG: Waypoint Tracking not working - - 4Hr DEBUG: Car data retreival wrong in Game - - 2Hr Other tasks I planned for but didn't do: Task 36: Car Tuner - 3Hr - ///////////////////// Week 11 Summary Time planned: 9Hrs Time spent: 9.5Hrs Off by: 5.5% Hours Off: +0.5 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ WEEK 12: //------------------------------------------------------------------------------------------------------------------------------ Task 37: Simple Menu - 3Hr - 3.5Hr Other tasks I planned for but didn't do: Task 38: Car Tuner Tool - 3Hr - Task 39: Car HUD - 3Hr - Task 40: New Track - 3Hr - ///////////////////// Week 12 Summary Time planned: 12Hrs Time spent: 3.5Hrs Off by: 70% Hours Off: -8.5 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ WEEK 13: Re-Re-Re Plan Revision //------------------------------------------------------------------------------------------------------------------------------ Task 41: End Game Condition - 3Hr - 5Hr Task 42: Quaternion Math Car Reset - 1Hr - 4Hr Task 43: Car Tuner Tool - 3Hr - 9Hr Task 44: Tuning Car - 3Hr - 4Hr DEBUG: PhysX bugs on car for tuning - - 4Hr Other tasks I planned for but didn't do: Task 45: Car HUD - 3Hr - ///////////////////// Week 13 Summary Time planned: 13Hrs Time spent: 26Hrs Off by: 100% Hours Off: +13 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ WEEK 14: Re-Re-Re-Re Plan Revision to fuck making the car tool //------------------------------------------------------------------------------------------------------------------------------ Task 45: Car HUD - 3Hr - 3Hr Task 46: Complete Game Loop with Restart - 3Hr - 4Hr Task 47: Fixing load and save best time - 2Hr - 1Hr Task 48: Checkpoint art change - 2Hr - 3Hr Other tasks I planned for but didn't do: Task 49: Fix car velocity and input on restart - 2Hr - ///////////////////// Week 14 Summary Time planned: 12Hrs Time spent: 11Hrs Off by: 0.09% Hours Off: -1 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ WEEK 15: //------------------------------------------------------------------------------------------------------------------------------ Task 49: Fix car velocity and input on restart - 2Hr - 2Hr Task 50: Fix track scaling issues - 3Hr - 3Hr Task 51: Add borders to splits - 1Hr - 1Hr Task 52: Add more assets to scene - 3Hr - 3Hr Task 53: Audio Revisit - 2Hr - 3Hr Other tasks I planned for but didn't do: Task 54: 3,2,1 Countdown before start - 2Hr - ///////////////////// Week 15 Summary Time planned: 13Hrs Time spent: 12Hrs Off by: 0.08% Hours Off: -1 ///////////////////// //------------------------------------------------------------------------------------------------------------------------------ Total Hours planned: 144 Total Hours Worked: 169 Off by %: 0.15% Hours off by: +25 Hours
#Mid-Project Plan Revision Name : Pronay Cohort: C28 Project: TAR (Time Attack Racer) Learnings from previous plans: - Need a better way to organize per week plans - Need to handle hours planned vs hours worked better - Need some form of scrum when developing just to maintain sanity and ensure the correct tasks are being tackled Solution to the planning problem ##Task Management: - Keep all tasks listed in Github board for speed and ease of use - Keep To-Do, In-Progress, Done and Cancelled Task columns for general work flow - Keep a Debug Features column for when things that are implemented need to be re-worked ##Weekly plan management: - Since Github can't do everything, maintain a .md file that has a weekly breakdown - Account for hours on weekly breakdown for both planned and worked hours - Have a completion section where I can add the date of completion for task and number of hours used #TAR-3D Mid Term Plan: NOTE: Each task has it's hours planned in [ ] before the task header. Each task header also has a (Task ID: #) where each task is assigned an ID, these IDs correspond to matching tasks on Github project boards. Weekly breakdowns below: - [PlannedTime] Task Header (Task ID : ##) Find the TAR Project board here: https://github.com/pronaypeddiraju/TimeAttackRacerBoards/projects/1?fullscreen=true NOTE: The entire plan has been listed in this file along with hourly breakdowns and space to update actual hours worked for each task. The GitHub board is just for my personal use and contains no informaiton that this file doesn't contain. All Task IDs mentioned in this file correspond to tasks in the GitHub project board linked above. ##WEEK 9: VEHICLE AUDIO IMPLEMENTATION ###Plan Proposed: - [3] Research on Vehicle Audio (Task ID: 1) - How is Audio generally implemented for Racing Games - Types of Audio files I need to use - Any systemic changes required in AudioSystem? - [3] Vehicle Audio Setup (Task ID: 2) - [2] Design system for Audio playback on Car class - Where and when to play 3D spatial audio - Implement m_car.GetCarController().GetGearRatiosForAudio() - Implement m_car.PlayAudioForGear( gearRatios, currentGear); - [3] AudioSystem Implementation Changes (Task ID: 3) - Account for any changes required by Audio System class to handle playback changes from general ###On Plan Completion: Enter the actual house used here as well as the date you worked on this task in the only correct date format (DD/MM/YYYY). Fight me on date format. - [4] (00/03/2020) Research on Vehicle Audio (Task ID: 1) - [6] (00/03/2020) Vehicle Audio Setup (Task ID: 2) - [2] (00/03/2020) AudioSystem Implementation Changes (Task ID: 3) Additional Tasks Performed: - [3] Debugging and Bug fixing on SplitScreen System Total Hours Planned: 9 Hours Total Hours Worked: X Hours ##WEEK 10: WAYPOINT SYSTEM AND VEHICLE UPDATES ###Plan Proposed: - [3] Audio Debug and Integrate (Task ID: 4) - [3] Waypoint Time Tracking (Task ID: 5) - [1.5] Implement fastest time tracking on waypoint system for all 4P - [1.5] Write functions for save and load using xml - [3] Update Vehicle Creation Logic (Task ID: 6) - [1] Update vehicle mesh dimensions to be more accurate for car convex mesh - [1] Expose engine, differential and transmission variables - [1] Expose sprung mass settings for wheels ###On Plan Completion: Enter the actual house used here as well as the date you worked on this task in the only correct date format (DD/MM/YYYY). Fight me on date format. - [ ] (00/03/2020) Audio Debug and Integration (Task ID: 4) - [2] (00/03/2020) Waypoint Time Tracking (Task ID: 5) - [ ] (00/03/2020) Update Vehicle Creation Logic (Task ID: 6) Other Tasks Performed: - [5] Fixing issues with Debug build cause by using static variables - [1] Fixed issues with Audio for other players Total Hours Planned: 9 Hours Total Hours Worked: X Hours ##WEEK 11: CAR TUNING AND ASYNC RESOURCE LOAD ###Plan Proposed: - [3] Implement Car Tuning Tool (Task ID: 7) - [1] Expose all car variables to ImGUI debug widget interface - [2] Find good defaults for vehicle settings - [3] Async Resource Loader (Task ID: 8) - [1] Implement async resource loading thread - [1] Implement load logic for all game meshes - [1] Debug and Integration - [3] Make Game menus (Task ID: 9) - [1] Implement a UI main menu - [2] Implement navigation and selection logic for controller input ###On Plan Completion: Enter the actual house used here as well as the date you worked on this task in the only correct date format (DD/MM/YYYY). Fight me on date format. - [ ] (00/03/2020) Implement Car Tuning Tool (Task ID: 7) - [3] (00/03/2020) Async Resource Loader (Task ID: 8) - [ ] (00/03/2020) Make Game Menus (Task ID: 9) - [2] Debug Task for Car values (Multiple car data retrieval was not correct) - [4] Fixing timing on waypoint system on End of Race Total : Total Hours Planned: 9 Hours Total Hours Worked: X Hours ##WEEK 12: HUD IMPLEMENTATION AND NEW RACETRACK ###Plan Proposed: - [3] Create Vehicle HUD (Task ID: 10) - [1] Create a speedometer widget to use car momentum - [1] Display lap information and best times - [1] Render HUD with both UI widgets - [3] Test and Refine HUD (Task ID: 11) - [1] Adjust widget placement - [1] Debug speedometer using debug values on screen - [1] Debug lap system display for all 4P recorded times - [3] Model New Race Track (Task ID: 12) - [1] Scale all mod kit pieces to be larger - [2] Implement new tack design - [3] Model Race Track Collisions and meta-data (Task ID: 13) - [2] Model collision meshes to be used for PhysX - [1] Setup .mesh file to load all the collision mesh information ###On Plan Completion: Enter the actual house used here as well as the date you worked on this task in the only correct date format (DD/MM/YYYY). Fight me on date format. - [ ] (00/03/2020) Create Vehicle HUD (Task ID: 10) - [ ] (00/03/2020) Test and Refine HUD (Task ID: 11) - [ ] (00/04/2020) Model New Race Track (Task ID: 12) - [ ] (00/03/2020) Model Race Track Collisions and meta-data (Task ID: 13) Total Hours Planned: 12 Hours Total Hours Worked: X Hours ##WEEK 13: ###Plan Proposed: GHOST CAR AND DATA_DRIVEN CHECKPOINTS - [3] Implement Ghost Car Section 1 (Task ID: 14) - [1] Track car positions through run - [1] Save off ghost car positions in xml - [1] Load ghost car positions from xml - [3] Implement Ghost Car Section 2 (Task ID: 15) - [1] Track wheel rotation for vehicle - [2] Debug and Integration - [3] Checkpoint System Data Driving (Task ID: 16) - Implement fastest time tracking on waypoint system for all 4P - Write functions for save and load using xml ###On Plan Completion: Enter the actual house used here as well as the date you worked on this task in the only correct date format (DD/MM/YYYY). Fight me on date format. - [ ] (00/03/2020) Implement Ghost Car Section 1 (Task ID: 14) - [ ] (00/03/2020) Implement Ghost Car Section 2 (Task ID: 15) - [ ] (00/03/2020) Checkpoint System Data Driving (Task ID: 16) Total Hours Planned: 9 Hours Total Hours Worked: X Hours ##WEEK 14: ###Plan Proposed: CONVEYANCE AND JUICE - [3] Add Player Vehicle Conveyance (Task ID: 17) - [1] Setup different colors for different player cars - [1] Setup some on-screen P1,2,3,4 identification info - [1] Test and Debug - [3] Implement Controller Join Screen (Task ID: 18) - [1] Check for input before assigning contoller to cars - [1] Show player IDs for connected controllers - [1] Add join screen to OnClick play button - [3] Tier 1 Juice (Task ID: 19) - [2] Implement camera shake on hit cars - [1] Implement a skybox tier 1 - [3] Tier 2 Juice (Task ID: 20) - [2] Implement sky box tier 2 - [1] Refine UI ###On Plan Completion: Enter the actual house used here as well as the date you worked on this task in the only correct date format (DD/MM/YYYY). Fight me on date format. - [ ] (00/03/2020) Add Player Vehicle Conveyance (Task ID: 17) - [ ] (00/03/2020) Implement Controller Join Screen (Task ID: 18) - [ ] (00/03/2020) Tier 1 Juice (Task ID: 19) - [ ] (00/03/2020) Tier 2 Juice (Task ID: 20) Total Hours Planned: 12 Hours Total Hours Worked: X Hours ##WEEK 15: ###Plan Proposed: BUG FIXING AND WISHLIST TASKS - [3] Bug Fixing (Task ID: 21) Wishlist Tasks: - [5] Model 2nd Track (Wishlist ID: 1) - [2] Model a new track - [2] Model collisions - [1] Setup .mesh file - [6] Add a second car (Wishlist ID: 2) - [1] Acquire car off the internet - [2] Setup mesh file for car - [2] Tune car values ###On Plan Completion: Enter the actual house used here as well as the date you worked on this task in the only correct date format (DD/MM/YYYY). Fight me on date format. - [ ] (00/03/2020) Bug Fixing (Task ID: 21) - [ ] (00/03/2020) Model 2nd Track (Wishlist ID: 1) - [ ] (00/03/2020) Add a second car (Wishlist ID: 2) Total Hours Planned: 14 Hours Total Hours Worked: X Hours
Systems Developed:
When creating TAR 3D, I developed numerous Engine and Gameplay systems to assist in the rapid development and iteration of my project. Below are some of the notable systems I had developed during that time:
- Car and Car Tuning
- Data-driven OBJ file loader
- Event-driven collision mesh generator
- Waypoint system with time tracking
- Custom binary formats for fast OBJ file loading
- Split-screen system to support up to 4P split screen
Each of these systems have been described in detail below:
Car and Car Tuning
A racing game is not fun without a fun car. My first problem when developing this project was the results of my first play-test. The play-testers reported that my car was
- Sluggish
- Slow to turn
- Not natural
To address this I took a closer look at my vehicle implementation and decided to tweak some values being used by the car. The structure of my Car class is highlighted below:
#pragma once #include "Game/CarController.hpp" #include "Game/CarCamera.hpp" #include "Game/CarAudio.hpp" #include "Game/WaypointSystem.hpp" //Engine Systems #include "Engine/Renderer/BitmapFont.hpp" //------------------------------------------------------------------------------------------------------------------------------ class Shader; //------------------------------------------------------------------------------------------------------------------------------ static std::vector<std::string> CAR_FILE_PATHS = { "/C_5_ExhL_02142.wav", "/C_6_ExhL_02412.wav", "/C_9_ExhL_03775.wav", "/C_10_ExhL_04308.wav", "/C_11_ExhL_04984.wav", "/C_12_ExhL_05652.wav", "/C_13_ExhL_06177.wav", "/C_14_ExhL_06669.wav" }; //------------------------------------------------------------------------------------------------------------------------------ class Car { public: Car(); ~Car(); void StartUp(const Vec3& startPosition, int controllerID, float timeToBeat); void Update(float deltaTime, bool isInputEnabled = true); void FixedUpdate(float fixedTime); void Shutdown(); void SetupCarAudio(); const CarCamera& GetCarCamera() const; CarCamera* GetCarCameraEditable(); const CarController& GetCarController() const; CarController* GetCarControllerEditable(); const CarAudio& GetCarAudio() const; CarAudio* GetCarAudioEditable(); int GetCarIndex() const; PxRigidDynamic* GetCarRigidbody() const; Camera& GetCarHUDCamera() const; WaypointSystem& GetWaypointsEditable(); const WaypointSystem& GetWaypoints() const; void SetupNewPlaybackIDs(); //For reset car transform we will just orient it at current forward direction and set position at current pos and y += 10; void ResetCarPosition(); void ResetWaypointSystem(); void SetCameraColorTarget(ColorTargetView* colorTargetView); void SetCameraPerspectiveProjection(float m_camFOVDegrees, float nearZ, float farZ, float aspect); void UpdateCarCamera(float deltaTime); double GetRaceTime(); void RenderUIHUD() const; private: void RenderBackgroundBoxes() const; void RenderLapCounter() const; void RenderTimeTaken() const; void RenderTimeToBeat() const; void RenderGearIndicator() const; void RenderRevMeter() const; private: CarController* m_controller = nullptr; CarCamera* m_camera = nullptr; CarAudio* m_audio = nullptr; Camera* m_carHUD = nullptr; WaypointSystem m_waypoints; const std::string m_BASE_AUDIO_PATH = "Data/Audio/Ferrari944"; int m_carIndex = -1; float m_HUD_WIDTH = 300.f; float m_HUD_HEIGHT = 150.f; BitmapFont* m_HUDFont = nullptr; float m_HUDFontHeight = 5.f; Shader* m_HUDshader = nullptr; std::string m_shaderPath = "default_unlit.xml"; double m_raceTime = 0.0; float m_resetHeight = 2.0f; double m_timeToBeat = 0.0; };
#pragma once #include "Engine/PhysXSystem/PhysXSystem.hpp" class CarController { public: CarController(); ~CarController(); void HandleKeyPressed(unsigned char keyCode); void SetupVehicle(); void SetDigitalControlMode(bool digitalControlEnabled); bool IsDigitalInputEnabled() const; void Update(float deltaTime); void FixedUpdate(float deltaTime); void UpdateInputs(); void VehiclePhysicsUpdate(float deltaTime); void SetControllerIDToUse(int controllerID); //Vehicle Setter void SetVehicleDefaultOrientation(); void SetVehiclePosition(const Vec3& targetPosition); void SetVehicleTransform(const Vec3& targetPosition, const PxQuat& quaternion); void SetVehicleTransform(const PxTransform& transform); void SetNewPxVehicle(PxVehicleDrive4W* vehicle); //Vehicle Getters PxVehicleDrive4W* GetVehicle() const; PxVehicleDrive4WRawInputData* GetVehicleInputData() const; Vec3 GetVehiclePosition() const; Vec3 GetVehicleForwardBasis() const; Vec3 GetVehicleRightBasis() const; //Vehicle Controls void AccelerateForward(float analogAcc = 0.f); void AccelerateReverse(float analogAcc = 0.f); void Brake(); void Steer(float analogSteer = 0.f); void Handbrake(); void ReleaseAllControls(); void RemoveVehicleFromScene(); void ReleaseVehicle(); bool IsControlReleased(); private: int m_controllerID = 0; private: bool m_digitalControlEnabled = false; bool m_isVehicleInAir = false; PxVehicleDrive4W* m_vehicle4W = nullptr; PxVehicleDrive4WRawInputData* m_vehicleInputData = nullptr; public: bool m_controlReleased = false; PxFixedSizeLookupTable<8> m_SteerVsForwardSpeedTable; PxVehicleKeySmoothingData m_keySmoothingData = { { 6.0f, //rise rate eANALOG_INPUT_ACCEL 6.0f, //rise rate eANALOG_INPUT_BRAKE 6.0f, //rise rate eANALOG_INPUT_HANDBRAKE 2.5f, //rise rate eANALOG_INPUT_STEER_LEFT 2.5f, //rise rate eANALOG_INPUT_STEER_RIGHT }, { 10.0f, //fall rate eANALOG_INPUT_ACCEL 10.0f, //fall rate eANALOG_INPUT_BRAKE 10.0f, //fall rate eANALOG_INPUT_HANDBRAKE 5.0f, //fall rate eANALOG_INPUT_STEER_LEFT 5.0f //fall rate eANALOG_INPUT_STEER_RIGHT } }; PxVehiclePadSmoothingData m_padSmoothingData = { { 6.0f, //rise rate eANALOG_INPUT_ACCEL 6.0f, //rise rate eANALOG_INPUT_BRAKE 6.0f, //rise rate eANALOG_INPUT_HANDBRAKE 2.5f, //rise rate eANALOG_INPUT_STEER_LEFT 2.5f, //rise rate eANALOG_INPUT_STEER_RIGHT }, { 10.0f, //fall rate eANALOG_INPUT_ACCEL 10.0f, //fall rate eANALOG_INPUT_BRAKE 10.0f, //fall rate eANALOG_INPUT_HANDBRAKE 5.0f, //fall rate eANALOG_INPUT_STEER_LEFT 5.0f //fall rate eANALOG_INPUT_STEER_RIGHT } }; };
#pragma once #include "Engine/Renderer/Camera.hpp" class CarCamera : public Camera { public: CarCamera(); ~CarCamera(); void Update(const Vec3& carForward, float deltaTime); void SetFocalPoint(Vec3 const &pos); void SetZoom(float zoom); //Manipulates distance void SetAngleOffset(float angleOffset); // really is setting an angle offset void SetZoomDelta(float delta); void SetTiltValue(float tilt); void SetAngleValue(float angle); void SetHeightValue(float height); void SetDistanceValue(float distance); void SetLerpSpeed(float lerpSpeed); float GetAngleValue() const; float GetTiltValue() const; float GetHeightValue() const; float GetDistanceValue() const; float GetLerpSpeed() const; private: Vec3 m_focalPoint = Vec3::ZERO; float m_distance = 7.f; float m_height = 2.f; float m_currentZoom = 0.f; // configuration - mess with these numbers to get a view you like; float m_minDistance = 1.0f; float m_maxDistance = 32.0f; float m_defaultAngle = 90.0f; float m_defaultTilt = -70.0f; float m_lerpSpeed = 7.f; Vec2 m_tiltBounds = Vec2(10.f, 40.f); Vec3 m_camPosition = Vec3::ZERO; Vec3 m_targetPosition = Vec3::ZERO; Matrix44 m_modelMatrix = Matrix44::IDENTITY; //The actual tilt and angle for the camera float m_tilt; float m_angle; };
To quickly iterate on the car and be able to tune it, I implemented a car tuner using ImGUI where I was able to quickly iterate and change vehicle values that allowed me to test better vehicle settings. Unfortunately, I was unable to use the tool in real-time due to some bugs I encountered when using the NVidia Vehicle system but this tool still allowed me to achieve better vehicle tuning results.
Here’s how the tool’s logic was setup in code:
#pragma once #include "Engine/PhysXSystem/PhysXSystem.hpp" class CarTool { public: CarTool(); ~CarTool(); void UpdateImGUICarTool(); PxVehicleDrive4W* MakeNewCar(); private: void SetAllDefaults(); void SetDefaultVehicleDesc(); void SetDefaultDifferentialData(); void SetDefaultEngineData(); void SetDefaultGearData(); void SetDefaultClutchData(); void UpdateWheelData(); void UpdateChassisData(); void UpdateDifferentialData(); void UpdateEngineData(); void UpdateGearData(); void UpdateClutchData(); private: VehicleDesc m_vehicleDesc; PxVehicleDriveSimData4W m_driveSimData; //Internal for DriveSimData PxVehicleDifferential4WData m_differnetialData; PxVehicleEngineData m_engineData; PxVehicleGearsData m_gearData; PxVehicleClutchData m_clutchData; PxVehicleAckermannGeometryData m_ackermanGeometry; //Internal for VehicleDesc PxFilterData m_chassisSimFilter; PxFilterData m_wheelSimFilter; ActorUserData m_actorUserData; ShapeUserData m_shapeUserData[PX_MAX_NB_WHEELS]; float m_defaultColumnHeight = 150.f; float m_defaultColumnWidth = 500.f; };
//------------------------------------------------------------------------------------------------------------------------------ void CarTool::UpdateImGUICarTool() { ImGui::Begin("Car Tuner"); ImGui::Columns(3, NULL, false); ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); UpdateWheelData(); ImGui::NextColumn(); UpdateChassisData(); ImGui::NextColumn(); UpdateDifferentialData(); ImGui::NextColumn(); UpdateEngineData(); ImGui::NextColumn(); UpdateGearData(); ImGui::NextColumn(); ImGui::PopStyleVar(); ImGui::End(); }
Data-driven OBJ file loader
To allow for fast OBJ file loading and handling pivot, scale and orientation changes quickly, I utilized a data-driven OBJ file loader which would perform these steps and generate a mesh that can be used within Prodigy Engine.
To do so, I created a custom .mesh file that was an XML formatted file describing some information to the Object loader when loading and generating OBJ mesh files. Here’s an example of the custom .mesh file used to load the vehicle mesh in game:
<mesh id="PhysX/car" src="Car/Rally_Fighter.obj" invert="false" tangents="true" scale = "0.025f" transform="x y -z"> <material index="0" src="Car/Car.mat" /> </mesh>
Here’s the interface code for the Object loader which loads and creates a usable mesh:
//------------------------------------------------------------------------------------------------------------------------------ #pragma once // Engine Systems #include "Engine/Math/Vec2.hpp" #include "Engine/Math/Vec3.hpp" #include "Engine/Core/BufferWriteUtils.hpp" #include "Engine/Core/BufferReadUtils.hpp" #include "Engine/Commons/EngineCommon.hpp" // Others #include <string> #include <vector> class CPUMesh; class GPUMesh; class RenderContext; //------------------------------------------------------------------------------------------------------------------------------ struct ObjIndex { int vertexIndex = 0; int uvIndex = 0; int normalIndex = 0; }; //------------------------------------------------------------------------------------------------------------------------------ class ObjectLoader { public: ObjectLoader(); ~ObjectLoader(); static ObjectLoader* MakeLoaderAndLoadMeshFromFile(RenderContext* renderContext, const std::string& filePath, bool isDataDriven); void LoadMeshFromFile(RenderContext* renderContext, const std::string& fileName, bool isDataDriven); void LoadFromPMSH(const std::string& fileName, Buffer& readBuffer); void LoadFromXML(const std::string& fileName); void CreateFromString(const char* data); void AddIndexForMesh(const std::string& indices); void CreateCPUMesh(); void CreateGPUMesh(); void MakeCookedVersion(); public: std::vector<Vec3> m_positions; std::vector<Vec2> m_uvs; std::vector<Vec3> m_normals; std::vector<ObjIndex> m_indices; RenderContext* m_renderContext = nullptr; CPUMesh* m_cpuMesh = nullptr; GPUMesh* m_mesh = nullptr; std::string m_source = ""; std::string m_fullFileName = ""; std::string m_transform = ""; std::string m_defaultMaterialPath = ""; bool m_invert = false; bool m_tangents = false; bool m_isCooked = false; float m_scale = 0.f; bool m_cookingRun = RUN_COOKING; };
Event-driven collision mesh generator
In this game, I also needed to have numerous collision meshes for the racetrack. Although the racetrack currently being used in-game is fairly simple, it still required 210 different collision meshes for the track along with a few other static meshes that allowed for ramps to be added in the game.
In order to account for collision meshes, I created a single render mesh defined in a custom .mesh file with some set of collision meshes to be loaded separately which will not be used to render the scene but be used for the scene’s physics.
Below is the logic in Object Loader that determines what to do when a Collision tag is encountered, the logic on the Physics system that is event-driven that creates the required collision meshes and the .mesh file implementation I used for the racetrack mesh:
//------------------------------------------------------------------------------------------------------------------------------ void ObjectLoader::LoadFromXML(const std::string& fileName) { //Open the xml file and parse it tinyxml2::XMLDocument meshDoc; meshDoc.LoadFile(fileName.c_str()); if (meshDoc.ErrorID() != tinyxml2::XML_SUCCESS) { ERROR_AND_DIE(">> Error loading Mesh XML file "); return; } else { //We loaded the file successfully XMLElement* root = meshDoc.RootElement(); if (root->FindAttribute("src")) { m_source = ParseXmlAttribute(*root, "src", m_source); } if (root->FindAttribute("invert")) { m_invert = ParseXmlAttribute(*root, "invert", false); } if (root->FindAttribute("tangents")) { m_tangents = ParseXmlAttribute(*root, "tangents", false); } if (root->FindAttribute("scale")) { m_scale = ParseXmlAttribute(*root, "scale", 1.f); } m_transform = ParseXmlAttribute(*root, "transform", ""); CreateFromString((MODEL_PATH + m_source).c_str()); CreateCPUMesh(); XMLElement* elem = root->FirstChildElement("material"); if (elem != nullptr) { //Set the default material path for this model from XML m_defaultMaterialPath = ParseXmlAttribute(*elem, "src", ""); } elem = root->FirstChildElement("collision"); while (elem != nullptr) { //We requested to create a static collider with this model so generate that using the PhysX System NamedProperties eventArgs; eventArgs.SetValue("id", ParseXmlAttribute(*root, "id", "")); eventArgs.SetValue("src", ParseXmlAttribute(*elem, "src", "")); eventArgs.SetValue("physXFlags", ParseXmlAttribute(*elem, "physXFlags", "")); eventArgs.SetValue("position", ParseXmlAttribute(*elem, "position", Vec3::ZERO)); eventArgs.SetValue("transform", m_transform); eventArgs.SetValue("scale", m_scale); eventArgs.SetValue("invert", m_invert); eventArgs.SetValue("tangents", m_tangents); g_eventSystem->FireEvent("ReadCollisionMeshFromData", eventArgs); elem = elem->NextSiblingElement("collision"); } } }
//------------------------------------------------------------------------------------------------------------------------------ STATIC bool PhysXSystem::LoadCollisionMeshFromData(EventArgs& args) { //Load the mesh DebuggerPrintf("\n\n Called event LoadCollisionMeshFromData"); ObjectLoader loader; std::string id = ""; std::string src = ""; id = args.GetValue("id", id); src = args.GetValue("src", src); if (id == "") { //The id is invalid so just return return false; } else { //Id was valid so now let's load the mesh //Check the file extension std::vector<std::string> strings = SplitStringOnDelimiter(src, '.'); bool isDataDriven = false; if (strings.size() > 1) { if (strings[(strings.size() - 1)] == "mesh") { isDataDriven = true; } } //Before we can CreateMeshFromFile we need to send it the required transform, scale, invert, tangent information loader.m_tangents = args.GetValue("tangents", loader.m_tangents); loader.m_scale = args.GetValue("scale", loader.m_scale); loader.m_transform = args.GetValue("transform", loader.m_transform); loader.m_invert = args.GetValue("invert", loader.m_invert); //This will now load the mesh from file loader.LoadMeshFromFile(g_renderContext, MODEL_PATH + src, isDataDriven); } //Create a PxConvexMesh from the vertex array PxVec3* convexVerts = new PxVec3[loader.m_cpuMesh->GetVertexCount()]; g_PxPhysXSystem->AddVertMasterBufferToPxVecBuffer(convexVerts, loader.m_cpuMesh->GetVertices(), loader.m_cpuMesh->GetVertexCount()); //Create a pxDescription for the convexmesh PxConvexMeshDesc desc; desc.points.count = (PxU32)loader.m_cpuMesh->GetVertexCount(); desc.points.stride = sizeof(PxVec3); desc.points.data = convexVerts; desc.flags = PxConvexFlag::eCOMPUTE_CONVEX; //Use PxCooking to construct the PxConvexMesh PxDefaultMemoryOutputStream buffer; PxConvexMeshCookingResult::Enum result; if (!g_PxPhysXSystem->GetPhysXCookingModule()->cookConvexMesh(desc, buffer, &result)) { //There was a problem making the mesh //For now we are going to just yell and die ERROR_AND_DIE("Failed to cook collision mesh for object in LoadCollisionMeshFromData"); return false; } //We can actually create the convexMesh PxDefaultMemoryInputData input(buffer.getData(), buffer.getSize()); PxConvexMesh* convexMesh = g_PxPhysXSystem->GetPhysXSDK()->createConvexMesh(input); //Maintain a map of std::string id to std::vector<PxConvexMesh*> std::map<std::string, std::vector<PxConvexMesh*>>::iterator itr = g_PxPhysXSystem->m_collisionMeshRepository.find(id); if (itr != g_PxPhysXSystem->m_collisionMeshRepository.end()) { //We need to add to the existing vector of collision meshes itr->second.push_back(convexMesh); } else { //We need to make a new entry with the key recieved std::vector<PxConvexMesh*> meshVector{ convexMesh }; g_PxPhysXSystem->m_collisionMeshRepository[id] = meshVector; } //Finally add the new PxConvexMesh to the scene as a shape //Make the geometry PxConvexMeshGeometry geometry = PxConvexMeshGeometry(convexMesh); const PxMaterial* material = g_PxPhysXSystem->GetDefaultPxMaterial(); PxShape* shape = g_PxPhysXSystem->GetPhysXSDK()->createShape(geometry, *material); PxFilterData groundPlaneSimFilterData(COLLISION_FLAG_OBSTACLE, COLLISION_FLAG_OBSTACLE_AGAINST, 0, 0); PxFilterData qryFilterData; setupDrivableSurface(qryFilterData); shape->setQueryFilterData(qryFilterData); shape->setSimulationFilterData(groundPlaneSimFilterData); Vec3 position = args.GetValue("position", position); PxTransform localTm(VecToPxVector(position), PxQuat(PxIdentity)); PxRigidStatic* body = g_PxPhysXSystem->GetPhysXSDK()->createRigidStatic(localTm); body->attachShape(*shape); g_PxPhysXSystem->GetPhysXScene()->addActor(*body); PX_ASSERT(convex); return true; }
<mesh id="PhysX/ScaledTrack1Colliders" src="ScaledTrack/ScaledTrack1CollidersOnly.obj" invert="false" tangents="true" scale = "1.5f" transform="x y -z" position = "0.f, 0.f, 0.f"> <material index="0" src="ScaledTrack/defaultTrack.mat" /> <collision index="0" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_1.obj" physXFlags = "obstacle"/> <collision index="1" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_2.obj" physXFlags = "obstacle"/> <collision index="2" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_3.obj" physXFlags = "obstacle"/> <collision index="3" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_4.obj" physXFlags = "obstacle"/> <collision index="4" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_5.obj" physXFlags = "obstacle"/> <collision index="5" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_6.obj" physXFlags = "obstacle"/> <collision index="6" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_7.obj" physXFlags = "obstacle"/> <collision index="7" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_8.obj" physXFlags = "obstacle"/> <collision index="8" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_9.obj" physXFlags = "obstacle"/> <collision index="9" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_10.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_11.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_12.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_13.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_14.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_15.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_16.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_17.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_18.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_19.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_20.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_21.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_22.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_23.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_24.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_25.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_26.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_27.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_28.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_29.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_30.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_31.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_32.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_33.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_34.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_35.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_36.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_37.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_38.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_39.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_40.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_41.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_42.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_43.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_44.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_45.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_46.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_47.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_48.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_49.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_50.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_51.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_52.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_53.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_54.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_55.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_56.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_57.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_58.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_59.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_60.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_61.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_62.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_63.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_64.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_65.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_66.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_67.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_68.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_69.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_70.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_71.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_72.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_73.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_74.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_75.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_76.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_77.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_78.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_79.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_80.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_81.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_82.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_83.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_84.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_85.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_86.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_87.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_88.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_89.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_90.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_91.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_92.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_93.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_94.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_95.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_96.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_97.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_98.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_99.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_100.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_101.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_102.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_103.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_104.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_105.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_106.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_107.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_108.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_109.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_110.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_111.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_112.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_113.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_114.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_115.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_116.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_117.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_118.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_119.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_120.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_121.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_122.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_123.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_124.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_125.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_126.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_127.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_128.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_129.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_130.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_131.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_132.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_133.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_134.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_135.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_136.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_137.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_138.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_139.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_140.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_141.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_142.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_143.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_144.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_145.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_146.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_147.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_148.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_149.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_150.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_151.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_152.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_153.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_154.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_155.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_156.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_157.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_158.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_159.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_160.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_161.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_162.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_163.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_164.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_165.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_166.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_167.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_168.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_169.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_170.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_171.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_172.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_173.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_174.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_175.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_176.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_177.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_178.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_179.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_180.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_181.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_182.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_183.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_184.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_185.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_186.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_187.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_188.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_189.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_190.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_191.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_192.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_193.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_194.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_195.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_196.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_197.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_198.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_199.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_200.obj" physXFlags = "obstacle"/> <collision index="10" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_201.obj" physXFlags = "obstacle"/> <collision index="11" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_202.obj" physXFlags = "obstacle"/> <collision index="12" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_203.obj" physXFlags = "obstacle"/> <collision index="13" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_204.obj" physXFlags = "obstacle"/> <collision index="14" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_205.obj" physXFlags = "obstacle"/> <collision index="15" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_206.obj" physXFlags = "obstacle"/> <collision index="16" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_207.obj" physXFlags = "obstacle"/> <collision index="17" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_208.obj" physXFlags = "obstacle"/> <collision index="18" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_209.obj" physXFlags = "obstacle"/> <collision index="19" src="ScaledTrack/SimplexCollisions/ScaledTrack1_Simplex_210.obj" physXFlags = "obstacle"/> <collision index="98" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_9.obj" physXFlags = "obstacle"/> <collision index="99" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_10.obj" physXFlags = "obstacle"/> <collision index="910" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_11.obj" physXFlags = "obstacle"/> <collision index="911" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_12.obj" physXFlags = "obstacle"/> <collision index="912" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_13.obj" physXFlags = "obstacle"/> <collision index="913" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_14.obj" physXFlags = "obstacle"/> <collision index="914" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_15.obj" physXFlags = "obstacle"/> <collision index="915" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_16.obj" physXFlags = "obstacle"/> <collision index="916" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_17.obj" physXFlags = "obstacle"/> <collision index="917" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_18.obj" physXFlags = "obstacle"/> <collision index="918" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_19.obj" physXFlags = "obstacle"/> <collision index="919" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_20.obj" physXFlags = "obstacle"/> <collision index="63" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_1.obj" physXFlags = "obstacle"/> <collision index="64" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_2.obj" physXFlags = "obstacle"/> <collision index="65" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_3.obj" physXFlags = "obstacle"/> <collision index="66" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_4.obj" physXFlags = "obstacle"/> <collision index="67" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_5.obj" physXFlags = "obstacle"/> <collision index="68" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_6.obj" physXFlags = "obstacle"/> <collision index="93" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_7.obj" physXFlags = "obstacle"/> <collision index="94" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_8.obj" physXFlags = "obstacle"/> <collision index="95" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_9.obj" physXFlags = "obstacle"/> <collision index="96" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_10.obj" physXFlags = "obstacle"/> <collision index="97" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_11.obj" physXFlags = "obstacle"/> <collision index="98" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_12.obj" physXFlags = "obstacle"/> <collision index="297" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_13.obj" physXFlags = "obstacle"/> <collision index="298" src="ScaledTrack/RampRoadCollisions/ScaledTrack1_Collider_RoadPiece_14.obj" physXFlags = "obstacle"/> </mesh>
Waypoint system with time tracking
It’s not a time attack racing game without checkpoints/waypoints now is it? To implement waypoints in the game, I created a gameplay system called the WaypointSystem and a WaypointRegionBased class which defined the boundary and attributes of a waypoint.
The waypoint system would own all the waypoints in the level and each level is to have 1 waypoint system. The waypoints owned by the waypoint system would be stored in a list with waypoint IDs. On crossing a waypoint, the waypoint system will update and change the next waypoint ID to be crossed by the player.
Below is the implementation of the region based waypoint and waypoint system:
#pragma once #include "Engine/Math/Vec3.hpp" #include "Engine/Math/AABB3.hpp" #include "Engine/Commons/EngineCommon.hpp" //------------------------------------------------------------------------------------------------------------------------------ class WaypointRegionBased { public: WaypointRegionBased(); ~WaypointRegionBased(); explicit WaypointRegionBased(const Vec3& wayPointPosition, const Vec3& halfExtents, uint waypointIndex); explicit WaypointRegionBased(const Vec3& waypointPosition, const AABB3& waypointShape, uint waypointIndex); bool HasPointCrossedWaypoint(const Vec3& pointToCheck); const Vec3& GetWaypointMins() const; const Vec3& GetWaypointMaxs() const; void AssignWaypointNumber(uint numberToAssign); uint GetWaypointNumber() const; const Vec3& GetWaypointPosition() const; private: uint m_waypointIndex = UINT_MAX; Vec3 m_position = Vec3::ZERO; Vec3 m_halfExtents = Vec3::ZERO; AABB3 m_shape; };
#include "Game/WaypointRegionBased.hpp" //Engine Systems #include "Engine/Commons/EngineCommon.hpp" #include "Engine/Math/MathUtils.hpp" #include "Engine/Math/AABB3.hpp" //------------------------------------------------------------------------------------------------------------------------------ WaypointRegionBased::WaypointRegionBased() { } //------------------------------------------------------------------------------------------------------------------------------ WaypointRegionBased::WaypointRegionBased(const Vec3& wayPointPosition, const Vec3& halfExtents, uint waypointIndex) { m_position = wayPointPosition; m_halfExtents = halfExtents; m_shape = AABB3(m_position - m_halfExtents, m_position + m_halfExtents); m_waypointIndex = waypointIndex; } //------------------------------------------------------------------------------------------------------------------------------ WaypointRegionBased::WaypointRegionBased(const Vec3& waypointPosition, const AABB3& waypointShape, uint waypointIndex) { m_position = waypointPosition; m_shape = waypointShape; m_waypointIndex = waypointIndex; } //------------------------------------------------------------------------------------------------------------------------------ bool WaypointRegionBased::HasPointCrossedWaypoint(const Vec3& pointToCheck) { return m_shape.IsPointInsideAABB3(pointToCheck); } //------------------------------------------------------------------------------------------------------------------------------ const Vec3& WaypointRegionBased::GetWaypointMins() const { return m_shape.GetMins(); } //------------------------------------------------------------------------------------------------------------------------------ const Vec3& WaypointRegionBased::GetWaypointMaxs() const { return m_shape.GetMaxs(); } //------------------------------------------------------------------------------------------------------------------------------ void WaypointRegionBased::AssignWaypointNumber(uint numberToAssign) { m_waypointIndex = numberToAssign; } //------------------------------------------------------------------------------------------------------------------------------ uint WaypointRegionBased::GetWaypointNumber() const { return m_waypointIndex; } //------------------------------------------------------------------------------------------------------------------------------ const Vec3& WaypointRegionBased::GetWaypointPosition() const { return m_position; } //------------------------------------------------------------------------------------------------------------------------------ WaypointRegionBased::~WaypointRegionBased() { }
#pragma once //Engine Systems #include "Engine/Commons/EngineCommon.hpp" //Game Systems #include "Game/WaypointRegionBased.hpp" #include <vector> //------------------------------------------------------------------------------------------------------------------------------ class WaypointSystem { public: WaypointSystem(); ~WaypointSystem(); void AddNewWayPoint(const Vec3& waypointPosition, const Vec3& waypointHalfExtents, uint waypointIndex); uint GetNextWaypointIndex() const; uint GetCurrentWaypointIndex() const; const Vec3& GetNextWaypointPosition() const; Matrix44 GetNextWaypointModelMatrix() const; uint GetCurrentLapNumber() const; uint GetMaxLapCount() const; void SetMaxLapCount(uint maxLapCount); double GetTotalTime() const; bool AreLapsComplete() const; void Startup(); void Update(const Vec3& carPosition); void RenderNextWaypoint() const; void DebugRenderWaypoints() const; void UpdateImGUIForWaypoints(); void Reset(); private: void SetSystemToNextWaypoint(); void AddTimeStampForLap(); double GetLastLapTime(); double GetAccumulatedLapTimes() const; void ComputeBestLapTimeForRun(); private: std::vector<WaypointRegionBased> m_waypointList; uint m_crossedIndex = UINT_MAX; //This is Uint max. I'm not stupid this was on purpose uint m_lapIndex = 1; uint m_maxLaps = 1; bool m_lapsCompleted = false; double m_startTime = 0.0; std::vector<double> m_timeStamps; };
#include "Game/WaypointSystem.hpp" //Engine Systems #include "Engine/Math/Vertex_Lit.hpp" #include "Engine/Renderer/CPUMesh.hpp" #include "Engine/Renderer/GPUMesh.hpp" #include "Engine/Renderer/RenderContext.hpp" #include "Engine/Core/DevConsole.hpp" #include "Engine/Core/Time.hpp" #include "Engine/Core/NamedProperties.hpp" //------------------------------------------------------------------------------------------------------------------------------ WaypointSystem::WaypointSystem() { } //------------------------------------------------------------------------------------------------------------------------------ WaypointSystem::~WaypointSystem() { } //------------------------------------------------------------------------------------------------------------------------------ void WaypointSystem::AddNewWayPoint(const Vec3& waypointPosition, const Vec3& waypointHalfExtents, uint waypointIndex) { m_waypointList.emplace_back(waypointPosition, waypointHalfExtents, waypointIndex); } //------------------------------------------------------------------------------------------------------------------------------ uint WaypointSystem::GetNextWaypointIndex() const { uint nextIndex = m_crossedIndex + 1; if (nextIndex < m_waypointList.size()) { return nextIndex; } else { return 0; } } //------------------------------------------------------------------------------------------------------------------------------ uint WaypointSystem::GetCurrentWaypointIndex() const { return m_crossedIndex; } //------------------------------------------------------------------------------------------------------------------------------ const Vec3& WaypointSystem::GetNextWaypointPosition() const { int index = GetNextWaypointIndex(); return m_waypointList[index].GetWaypointPosition(); } //------------------------------------------------------------------------------------------------------------------------------ Matrix44 WaypointSystem::GetNextWaypointModelMatrix() const { Matrix44 modelMatrix = Matrix44::IDENTITY; modelMatrix.SetTranslation3D(GetNextWaypointPosition(), modelMatrix); return modelMatrix; } //------------------------------------------------------------------------------------------------------------------------------ uint WaypointSystem::GetCurrentLapNumber() const { return m_lapIndex; } //------------------------------------------------------------------------------------------------------------------------------ uint WaypointSystem::GetMaxLapCount() const { return m_maxLaps; } //------------------------------------------------------------------------------------------------------------------------------ void WaypointSystem::SetMaxLapCount(uint maxLapCount) { m_maxLaps = maxLapCount; } //------------------------------------------------------------------------------------------------------------------------------ double WaypointSystem::GetTotalTime() const { if (m_lapsCompleted) { return m_timeStamps[m_timeStamps.size() - 1]; } else { return GetAccumulatedLapTimes(); } } //------------------------------------------------------------------------------------------------------------------------------ bool WaypointSystem::AreLapsComplete() const { return m_lapsCompleted; } //------------------------------------------------------------------------------------------------------------------------------ void WaypointSystem::Startup() { m_startTime = GetCurrentTimeSeconds(); } //------------------------------------------------------------------------------------------------------------------------------ void WaypointSystem::Update(const Vec3& carPosition) { if (m_lapsCompleted) { //We finished the track. There are no more waypoints return; } std::vector<WaypointRegionBased>::iterator waypointItr; waypointItr = m_waypointList.begin(); while (waypointItr != m_waypointList.end()) { bool result = waypointItr->HasPointCrossedWaypoint(carPosition); if (result) { //Check if this is the valid next way point uint wayPointIndex = waypointItr->GetWaypointNumber(); if (wayPointIndex == GetNextWaypointIndex()) { g_devConsole->PrintString(Rgba::YELLOW, "Reached Next Waypoint"); //Call update waypoint and return from here SetSystemToNextWaypoint(); } } waypointItr++; } } //------------------------------------------------------------------------------------------------------------------------------ void WaypointSystem::RenderNextWaypoint() const { if (m_lapsCompleted) return; const WaypointRegionBased* nextWaypoint = &m_waypointList[GetNextWaypointIndex()]; CPUMesh boxMesh; CPUMesh postLeftMesh; CPUMesh postRightMesh; CPUMesh postTopMesh; Vec3 mins = nextWaypoint->GetWaypointMins(); Vec3 maxs = nextWaypoint->GetWaypointMaxs(); CPUMeshAddCube(&postLeftMesh, AABB3(mins - Vec3(0.25f, 0.f, 0.25f), mins + Vec3(0.25f, maxs.y * 2.f, 0.25f)), Rgba::ORGANIC_BLUE); CPUMeshAddCube(&postRightMesh, AABB3(Vec3(maxs.x, mins.y, maxs.z) - Vec3(0.25f, 0.f, 0.25f), Vec3(maxs.x, mins.y, maxs.z) + Vec3(0.25f, maxs.y * 2.f, 0.25f)), Rgba::ORGANIC_BLUE); Vec3 dir = maxs - mins; dir.y = 0.5f; CPUMeshAddCube(&postTopMesh, AABB3(Vec3(mins.x, maxs.y, mins.z), Vec3(mins.x, maxs.y, mins.z) + dir), Rgba::ORGANIC_BLUE); GPUMesh renderMeshLeftPost = GPUMesh(g_renderContext); renderMeshLeftPost.CreateFromCPUMesh<Vertex_Lit>(&postLeftMesh, GPU_MEMORY_USAGE_STATIC); GPUMesh renderMeshRightPost = GPUMesh(g_renderContext); renderMeshRightPost.CreateFromCPUMesh<Vertex_Lit>(&postRightMesh, GPU_MEMORY_USAGE_STATIC); GPUMesh renderMeshTopPost = GPUMesh(g_renderContext); renderMeshTopPost.CreateFromCPUMesh<Vertex_Lit>(&postTopMesh, GPU_MEMORY_USAGE_STATIC); g_renderContext->DrawMesh(&renderMeshLeftPost); g_renderContext->DrawMesh(&renderMeshRightPost); g_renderContext->DrawMesh(&renderMeshTopPost); } //------------------------------------------------------------------------------------------------------------------------------ void WaypointSystem::DebugRenderWaypoints() const { CPUMesh boxMesh; GPUMesh mesh = GPUMesh(g_renderContext); std::vector<WaypointRegionBased>::const_iterator waypointItr = m_waypointList.begin(); while (waypointItr != m_waypointList.end()) { boxMesh.Clear(); CPUMeshAddCube(&boxMesh, AABB3(waypointItr->GetWaypointMins(), waypointItr->GetWaypointMaxs()), Rgba::GREEN); mesh.CreateFromCPUMesh<Vertex_Lit>(&boxMesh, GPU_MEMORY_USAGE_STATIC); g_renderContext->DrawMesh(&mesh); waypointItr++; } } //------------------------------------------------------------------------------------------------------------------------------ void WaypointSystem::UpdateImGUIForWaypoints() { //Create the actual ImGUI widget ImGui::Begin("PhysX Scene Controls"); ImGui::Text("Number of Waypoints in System : %d", m_waypointList.size()); ImGui::Text("Max Number of laps allowed for this track: %d", m_maxLaps); ImGui::End(); } //------------------------------------------------------------------------------------------------------------------------------ void WaypointSystem::Reset() { m_lapsCompleted = false; m_startTime = GetCurrentTimeSeconds(); m_timeStamps.clear(); m_lapIndex = 1; m_crossedIndex = UINT_MAX; } //------------------------------------------------------------------------------------------------------------------------------ void WaypointSystem::SetSystemToNextWaypoint() { //Increase m_crossedIndex; m_crossedIndex += 1; if (m_crossedIndex == m_waypointList.size() - 1) { g_devConsole->PrintString(Rgba::ORGANIC_PURPLE, "Entered the next Lap"); AddTimeStampForLap(); std::string printString = "Time Taken: " + ToString(m_timeStamps[m_timeStamps.size() - 1]); g_devConsole->PrintString(Rgba::GREEN, printString); m_crossedIndex = UINT_MAX; m_lapIndex += 1; } if (m_lapIndex > m_maxLaps) { g_devConsole->PrintString(Rgba::GREEN, "Completed Race"); m_lapIndex = m_maxLaps; //Get it back to maxLaps just so that our UI doesn't say lap 4 of 3 m_lapsCompleted = true; } } //------------------------------------------------------------------------------------------------------------------------------ void WaypointSystem::AddTimeStampForLap() { m_timeStamps.push_back(GetLastLapTime()); } //------------------------------------------------------------------------------------------------------------------------------ double WaypointSystem::GetLastLapTime() { std::vector<double>::const_iterator timeItr = m_timeStamps.begin(); if (timeItr == m_timeStamps.end()) { return GetCurrentTimeSeconds() - m_startTime; } else { double accumulatedTime = 0.0; while (timeItr != m_timeStamps.end()) { accumulatedTime += *timeItr; timeItr++; } return GetCurrentTimeSeconds() - m_startTime - accumulatedTime; } } //------------------------------------------------------------------------------------------------------------------------------ double WaypointSystem::GetAccumulatedLapTimes() const { std::vector<double>::const_iterator timeItr = m_timeStamps.begin(); double accumulatedTime = 0.0; while (timeItr != m_timeStamps.end()) { accumulatedTime += *timeItr; timeItr++; } if (accumulatedTime == 0.0) { //We didn't get any accumulated time yet accumulatedTime = GetCurrentTimeSeconds() - m_startTime; } return accumulatedTime; }
Custom binary formats for fast OBJ file loading
The Prodigy C++ Engine used text parsing to load OBJ files which initially proved to be an acceptable solution. However, when over 200 OBJ files are to be loaded and the complexity of each of the OBJ files is high, the application would spend significant time on loading up meshes to use in-game. To counter this problem, I implemented a custom binary 3D file format to speed up the loading process. Below are the results of changing from a text parsing OBJ loader to .pmsh custom file loader:
--------------------------- TAR Cooked OBJ test --------------------------- Base Meshes = (2 Files) Track Meshes = (220 files approx) --------------------------- Pre-Cooking Performance: --------------------------- Release: Base Meshes: 1.632079 Track Meshes: 1.499031 --------------------------- DebugInline: Base Meshes: 227.814758 Track Meshes: 16.094894 --------------------------- Debug: Base Meshes: 726.031677 Track Meshes: 51.747803 --------------------------- --------------------------- Post Cooking Performance --------------------------- --------------------------- Release: Base Meshes: 0.019409 Track Meshes: 0.281304 --------------------------- DebugInline: Base Meshes: 0.128675 Track Meshes: 16.159714 --------------------------- Debug: Base Meshes: 0.289492 Track Meshes: 49.004589 --------------------------- ---------------------------
To do this, I implemented a cooking feature in my Object Loader class that allowed me to load OBJ files via XML and text parsing for the first mesh load and then saving a custom .pmsh file format. For each subsequent loads, the object loader would check if a .pmsh file exists and prefer to load that instead.
Below is the implementation of the custom file format:
# Cooking utils file specification .PMSH - Prodigy Mesh file # File Header //Header (4 Byte Array) FourCC : PMSH (1 Byte) Reserved (1 Byte) Major File Version Number: 1 currently (1 Byte) Minor File Version Number: 0 currently (1 Byte) Endianness (1 = little, 2 = Big) # Mesh Data //Mesh Data (4 Byte - uint32) Num Vertices (4 Byte - uint32) Num indices # Vertex Data //Vertex Data (Set of all Vertex info) .... (12 Bytes - Vec3) - position (12 Bytes - Vec3) - normal (12 Bytes - Vec3) - tangent (12 Bytes - Vec3) - biTangent (16 Bytes - Rgba) - color (8 Bytes - Vec2) - UVs ... # Index Data //Index Data (Set of all indics) ... (4 Bytes - uint32) - indice ...
//------------------------------------------------------------------------------------------------------------------------------ void ObjectLoader::MakeCookedVersion() { if (m_isCooked || !m_cookingRun) { DebuggerPrintf("\n Mesh is already a cooked mesh"); return; } //Write cooked version to disk Buffer buffer; BufferWriteUtils writeBufferUtil(buffer); //Header //Four CC writeBufferUtil.AppendByte('P'); writeBufferUtil.AppendByte('M'); writeBufferUtil.AppendByte('S'); writeBufferUtil.AppendByte('H'); //Reserved Byte writeBufferUtil.AppendByte(0); //File Number writeBufferUtil.AppendByte(1); writeBufferUtil.AppendByte(0); //Endianness writeBufferUtil.AppendByte(writeBufferUtil.m_endianMode); //Mesh Data uint numVertices = m_cpuMesh->GetVertexCount(); uint numIndices = m_cpuMesh->GetIndexCount(); writeBufferUtil.AppendUint32(numVertices); writeBufferUtil.AppendUint32(numIndices); //Vertex Data const VertexMaster* vertices = m_cpuMesh->GetVertices(); for (int vertexIndex = 0; vertexIndex < (int)numVertices; vertexIndex++) { writeBufferUtil.AppendVertexMaster(vertices[vertexIndex]); } //Index Data const uint* indices = m_cpuMesh->GetIndices(); for (int indiceIndex = 0; indiceIndex < (int)numIndices; indiceIndex++) { writeBufferUtil.AppendUint32(indices[indiceIndex]); } std::string fileSavePath = ""; std::vector<std::string> splits = SplitStringOnDelimiter(m_fullFileName, '.'); if (splits[splits.size() - 1] == "mesh" || splits[splits.size() - 1] == "obj") { //Write source except the extention for (int i = 0; i < splits.size() - 1; i++) { fileSavePath += splits[i]; } fileSavePath += ".pmsh"; } bool success = SaveBinaryFileFromBuffer(fileSavePath, buffer); if (success) { DebuggerPrintf("\n Sucessfully cooked %s PMSH to disk", fileSavePath.c_str()); } else { DebuggerPrintf("\n Failed to cook %s PMSH to disk", fileSavePath.c_str()); } }
//------------------------------------------------------------------------------------------------------------------------------ void ObjectLoader::LoadFromPMSH(const std::string& fileName, Buffer& readBuffer) { BufferReadUtils readUtils(readBuffer); //Check FourCC uchar* fourCC = new uchar[4]; readUtils.ParseByteArray(fourCC, 4); if (fourCC[0] != 'P' || fourCC[1] != 'M' || fourCC[2] != 'S' || fourCC[3] != 'H') { ERROR_AND_DIE("FourCC code mismatch for PMSH"); } uchar byte = readUtils.ParseByte(); uchar versionMajor = readUtils.ParseByte(); if (versionMajor != 1) { ERROR_AND_DIE("Major Version mismatch for PMSH"); } uchar versionMinor = readUtils.ParseByte(); if (versionMinor != 0) { ERROR_AND_DIE("Minor Version mismatch for PMSH"); } eBufferEndianness endianNess = (eBufferEndianness)readUtils.ParseByte(); readUtils.SetEndianMode(endianNess); uint numVerts = readUtils.ParseUint32(); uint numIndices = readUtils.ParseUint32(); m_cpuMesh = new CPUMesh(); m_cpuMesh->ReserveForNumVertices(numVerts); m_cpuMesh->ReserveForNumIndices(numIndices); //Copy all the verts VertexMaster tempVertex; for (int vertexIndex = 0; vertexIndex < (int)numVerts; vertexIndex++) { tempVertex = readUtils.ParseVertexMaster(); m_cpuMesh->AddVertex(tempVertex); } //Indice data uint tempIndice = 0; for (int indiceIndex = 0; indiceIndex < (int)numIndices; indiceIndex++) { tempIndice = readUtils.ParseUint32(); m_cpuMesh->AddIndex(tempIndice); } }
Split-screen system to support up to 4P split-screen
To incorporate an arcade racing feel, I wanted to implement a split-screen local multiplayer system. To do so, I allowed the game to use a maximum of 4 Xbox controllers and at startup figuring out how many cars needed to be spawned along with the desired screen splits.
To create the split-screen view, I created a set of fixed screen ratios that would be applied to the camera viewports. By allowing the camera to render to a specific region on the screen, I was able to achieve the desired results. The logic for the split-screen system is highlighted below:
//------------------------------------------------------------------------------------------------------------------------------ void Game::SetFrameColorTargetOnCameras() const { //Get the ColorTargetView from rendercontext ColorTargetView *colorTargetView = g_renderContext->GetFrameColorTarget(); //Setup what we are rendering to m_mainCamera->SetColorTarget(colorTargetView); m_mainCamera->SetViewport(Vec2(0.5f, 0.f), Vec2::ONE); m_devConsoleCamera->SetColorTarget(colorTargetView); m_UICamera->SetColorTarget(colorTargetView); m_UICamera->SetViewport(Vec2::ZERO, Vec2::ONE); if (m_initiateFromMenu) { //The cars exist and we can set the split screen system's color targets m_splitScreenSystem.SetColorTargets(colorTargetView); m_splitScreenSystem.ComputeViewPortSplits(m_splitMode); SetCarHUDColorTargets(colorTargetView); SetupCarHUDsFromSplits(m_splitMode); } }
//------------------------------------------------------------------------------------------------------------------------------ void Game::SetupCarHUDsFromSplits(eSplitMode splitMode) const { switch (m_numConnectedPlayers) { case 1: { //We have only 1 player so we should be fine with the viewport being the whole screen m_cars[0]->GetCarHUDCamera().SetViewport(Vec2::ZERO, Vec2::ONE); } break; case 2: { //We have 2 players, check the split mode for 2P and split accordingly if (splitMode == PREFER_VERTICAL_SPLIT) { //We need to split the screen into vertical halfs m_cars[0]->GetCarHUDCamera().SetViewport(SplitScreenSystem::VERTICAL_SPLIT_2P_FIRST_MIN, SplitScreenSystem::VERTICAL_SPLIT_2P_FIRST_MAX); m_cars[1]->GetCarHUDCamera().SetViewport(SplitScreenSystem::VERTICAL_SPLIT_2P_SECOND_MIN, SplitScreenSystem::VERTICAL_SPLIT_2P_SECOND_MAX); } else { //We need to split the screen into horizontal halfs m_cars[0]->GetCarHUDCamera().SetViewport(SplitScreenSystem::HORIZONTAL_SPLIT_2P_FIRST_MIN, SplitScreenSystem::HORIZONTAL_SPLIT_2P_FIRST_MAX); m_cars[1]->GetCarHUDCamera().SetViewport(SplitScreenSystem::HORIZONTAL_SPLIT_2P_SECOND_MIN, SplitScreenSystem::HORIZONTAL_SPLIT_2P_SECOND_MAX); } } break; case 3: { if (splitMode == PREFER_VERTICAL_SPLIT) { m_cars[0]->GetCarHUDCamera().SetViewport(SplitScreenSystem::VERTICAL_SPLIT_3P_FIRST_MIN, SplitScreenSystem::VERTICAL_SPLIT_3P_FIRST_MAX); m_cars[1]->GetCarHUDCamera().SetViewport(SplitScreenSystem::VERTICAL_SPLIT_3P_SECOND_MIN, SplitScreenSystem::VERTICAL_SPLIT_3P_SECOND_MAX); m_cars[2]->GetCarHUDCamera().SetViewport(SplitScreenSystem::VERTICAL_SPLIT_3P_THIRD_MIN, SplitScreenSystem::VERTICAL_SPLIT_3P_THIRD_MAX); } else { m_cars[0]->GetCarHUDCamera().SetViewport(SplitScreenSystem::HORIZONTAL_SPLIT_3P_FIRST_MIN, SplitScreenSystem::HORIZONTAL_SPLIT_3P_FIRST_MAX); m_cars[1]->GetCarHUDCamera().SetViewport(SplitScreenSystem::HORIZONTAL_SPLIT_3P_SECOND_MIN, SplitScreenSystem::HORIZONTAL_SPLIT_3P_SECOND_MAX); m_cars[2]->GetCarHUDCamera().SetViewport(SplitScreenSystem::HORIZONTAL_SPLIT_3P_THIRD_MIN, SplitScreenSystem::HORIZONTAL_SPLIT_3P_THIRD_MAX); } } break; case 4: { if (splitMode == PREFER_VERTICAL_SPLIT) { m_cars[0]->GetCarHUDCamera().SetViewport(SplitScreenSystem::VERTICAL_SPLIT_4P_FIRST_MIN, SplitScreenSystem::VERTICAL_SPLIT_4P_FIRST_MAX); m_cars[1]->GetCarHUDCamera().SetViewport(SplitScreenSystem::VERTICAL_SPLIT_4P_SECOND_MIN, SplitScreenSystem::VERTICAL_SPLIT_4P_SECOND_MAX); m_cars[2]->GetCarHUDCamera().SetViewport(SplitScreenSystem::VERTICAL_SPLIT_4P_THIRD_MIN, SplitScreenSystem::VERTICAL_SPLIT_4P_THIRD_MAX); m_cars[3]->GetCarHUDCamera().SetViewport(SplitScreenSystem::VERTICAL_SPLIT_4P_FOURTH_MIN, SplitScreenSystem::VERTICAL_SPLIT_4P_FOURTH_MAX); } else { m_cars[0]->GetCarHUDCamera().SetViewport(SplitScreenSystem::HORIZONTAL_SPLIT_4P_FIRST_MIN, SplitScreenSystem::HORIZONTAL_SPLIT_4P_FIRST_MAX); m_cars[1]->GetCarHUDCamera().SetViewport(SplitScreenSystem::HORIZONTAL_SPLIT_4P_SECOND_MIN, SplitScreenSystem::HORIZONTAL_SPLIT_4P_SECOND_MAX); m_cars[2]->GetCarHUDCamera().SetViewport(SplitScreenSystem::HORIZONTAL_SPLIT_4P_THIRD_MIN, SplitScreenSystem::HORIZONTAL_SPLIT_4P_THIRD_MAX); m_cars[3]->GetCarHUDCamera().SetViewport(SplitScreenSystem::HORIZONTAL_SPLIT_4P_FOURTH_MIN, SplitScreenSystem::HORIZONTAL_SPLIT_4P_FOURTH_MAX); } } break; default: break; } }
#pragma once #include "Game/CarCamera.hpp" #include <array> enum eSplitMode { PREFER_VERTICAL_SPLIT, PREFER_HORIZONTAL_SPLIT }; //------------------------------------------------------------------------------------------------------------------------------ class SplitScreenSystem { public: friend class Game; SplitScreenSystem(); ~SplitScreenSystem(); void AddCarCameraForPlayer(CarCamera* camera, int playerID); int GetNumPlayers() const; void SetNumPlayers(int numPlayers); void ComputeViewPortSplits(eSplitMode splitMode = PREFER_VERTICAL_SPLIT) const; const std::array<CarCamera*, 4>& GetAllCameras() const; const CarCamera* GetCameraForPlayerID(int playerID) const; void SetColorTargets(ColorTargetView * colorTargetView) const; private: //Vertical Split ratios for 2P const static Vec2 VERTICAL_SPLIT_2P_FIRST_MIN; const static Vec2 VERTICAL_SPLIT_2P_FIRST_MAX; const static Vec2 VERTICAL_SPLIT_2P_SECOND_MIN; const static Vec2 VERTICAL_SPLIT_2P_SECOND_MAX; //Horizontal Split ratios for 2P const static Vec2 HORIZONTAL_SPLIT_2P_FIRST_MIN; const static Vec2 HORIZONTAL_SPLIT_2P_FIRST_MAX; const static Vec2 HORIZONTAL_SPLIT_2P_SECOND_MIN; const static Vec2 HORIZONTAL_SPLIT_2P_SECOND_MAX; //Vertical Split ratios for 3P const static Vec2 VERTICAL_SPLIT_3P_FIRST_MIN; const static Vec2 VERTICAL_SPLIT_3P_FIRST_MAX; const static Vec2 VERTICAL_SPLIT_3P_SECOND_MIN; const static Vec2 VERTICAL_SPLIT_3P_SECOND_MAX; const static Vec2 VERTICAL_SPLIT_3P_THIRD_MIN; const static Vec2 VERTICAL_SPLIT_3P_THIRD_MAX; //Horizontal Split ratios for 3P const static Vec2 HORIZONTAL_SPLIT_3P_FIRST_MIN; const static Vec2 HORIZONTAL_SPLIT_3P_FIRST_MAX; const static Vec2 HORIZONTAL_SPLIT_3P_SECOND_MIN; const static Vec2 HORIZONTAL_SPLIT_3P_SECOND_MAX; const static Vec2 HORIZONTAL_SPLIT_3P_THIRD_MIN; const static Vec2 HORIZONTAL_SPLIT_3P_THIRD_MAX; //Vertical Split ratios for 4P const static Vec2 VERTICAL_SPLIT_4P_FIRST_MIN; const static Vec2 VERTICAL_SPLIT_4P_FIRST_MAX; const static Vec2 VERTICAL_SPLIT_4P_SECOND_MIN; const static Vec2 VERTICAL_SPLIT_4P_SECOND_MAX; const static Vec2 VERTICAL_SPLIT_4P_THIRD_MIN; const static Vec2 VERTICAL_SPLIT_4P_THIRD_MAX; const static Vec2 VERTICAL_SPLIT_4P_FOURTH_MIN; const static Vec2 VERTICAL_SPLIT_4P_FOURTH_MAX; //Horizontal Split ratios for 4P const static Vec2 HORIZONTAL_SPLIT_4P_FIRST_MIN; const static Vec2 HORIZONTAL_SPLIT_4P_FIRST_MAX; const static Vec2 HORIZONTAL_SPLIT_4P_SECOND_MIN; const static Vec2 HORIZONTAL_SPLIT_4P_SECOND_MAX; const static Vec2 HORIZONTAL_SPLIT_4P_THIRD_MIN; const static Vec2 HORIZONTAL_SPLIT_4P_THIRD_MAX; const static Vec2 HORIZONTAL_SPLIT_4P_FOURTH_MIN; const static Vec2 HORIZONTAL_SPLIT_4P_FOURTH_MAX; //Player and Camera data std::array<CarCamera*, 4> m_playerCameras = {nullptr, nullptr, nullptr, nullptr }; //We know we won't have more than 4 so this is fixed size int m_numPlayers = 0; eSplitMode m_defaultSplitMode = PREFER_VERTICAL_SPLIT; };
#include "Game/SplitScreenSystem.hpp" //Engine Systems #include "Engine/Math/Vec2.hpp" #include "Engine/Commons/EngineCommon.hpp" //------------------------------------------------------------------------------------------------------------------------------ ////////////////////////////////////////////////////////////////////////// //Static constant split values ////////////////////////////////////////////////////////////////////////// //2P Vertical Splits const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_2P_FIRST_MIN = Vec2(0.f, 0.f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_2P_FIRST_MAX = Vec2(0.5f, 1.f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_2P_SECOND_MIN = Vec2(0.5f, 0.f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_2P_SECOND_MAX = Vec2(1.f, 1.f); //2P Horizontal Splits const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_2P_FIRST_MIN = Vec2(0.f, 0.5f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_2P_FIRST_MAX = Vec2(1.f, 1.f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_2P_SECOND_MIN = Vec2(0.f, 0.f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_2P_SECOND_MAX = Vec2(1.f, 0.5f); //3P Vertical Splits const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_3P_FIRST_MIN = Vec2(0.f, 0.5f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_3P_FIRST_MAX = Vec2(0.5f, 1.f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_3P_SECOND_MIN = Vec2(0.5f, 0.5f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_3P_SECOND_MAX = Vec2(1.f, 1.f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_3P_THIRD_MIN = Vec2(0.f, 0.f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_3P_THIRD_MAX = Vec2(1.f, 0.5f); //3P Horizontal Splits const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_3P_FIRST_MIN = Vec2(0.f, 0.5f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_3P_FIRST_MAX = Vec2(1.f, 1.f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_3P_SECOND_MIN = Vec2(0.f, 0.f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_3P_SECOND_MAX = Vec2(0.5f, 0.5f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_3P_THIRD_MIN = Vec2(0.5f, 0.f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_3P_THIRD_MAX = Vec2(1.f, 0.5f); //4P Vertical Splits const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_4P_FIRST_MIN = Vec2(0.f, 0.5f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_4P_FIRST_MAX = Vec2(0.5f, 1.f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_4P_SECOND_MIN = Vec2(0.5f, 0.5f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_4P_SECOND_MAX = Vec2(1.f, 1.f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_4P_THIRD_MIN = Vec2(0.f, 0.f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_4P_THIRD_MAX = Vec2(0.5f, 0.5f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_4P_FOURTH_MIN = Vec2(0.5f, 0.f); const STATIC Vec2 SplitScreenSystem::VERTICAL_SPLIT_4P_FOURTH_MAX = Vec2(1.f, 0.5f); //4P Horizontal Splits const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_4P_FIRST_MIN = Vec2(0.f, 0.5f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_4P_FIRST_MAX = Vec2(0.f, 1.f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_4P_SECOND_MIN = Vec2(0.f, 0.f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_4P_SECOND_MAX = Vec2(0.5f, 0.5f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_4P_THIRD_MIN = Vec2(0.5f, 0.5f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_4P_THIRD_MAX = Vec2(1.f, 1.f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_4P_FOURTH_MIN = Vec2(0.5f, 0.f); const STATIC Vec2 SplitScreenSystem::HORIZONTAL_SPLIT_4P_FOURTH_MAX = Vec2(1.f, 0.5f); //------------------------------------------------------------------------------------------------------------------------------ SplitScreenSystem::SplitScreenSystem() { } //------------------------------------------------------------------------------------------------------------------------------ SplitScreenSystem::~SplitScreenSystem() { } //------------------------------------------------------------------------------------------------------------------------------ void SplitScreenSystem::AddCarCameraForPlayer(CarCamera* camera, int playerID) { ASSERT_OR_DIE(playerID < 4, "The player ID recieved by SplitScreenSystem is greater than 4"); if (m_playerCameras[playerID] == nullptr) { m_playerCameras[playerID] = camera; m_numPlayers++; } else { ERROR_AND_DIE("PlayerID passed to the SplitScreenSystem was already assigned"); } } //------------------------------------------------------------------------------------------------------------------------------ int SplitScreenSystem::GetNumPlayers() const { return m_numPlayers; } //------------------------------------------------------------------------------------------------------------------------------ void SplitScreenSystem::SetNumPlayers(int numPlayers) { m_numPlayers = numPlayers; } //------------------------------------------------------------------------------------------------------------------------------ void SplitScreenSystem::ComputeViewPortSplits(eSplitMode splitMode) const { ASSERT_OR_DIE(m_numPlayers > 0, "The number of players in the Split Screen System is 0"); switch (m_numPlayers) { case 1: { //We have only 1 player so we should be fine with the viewport being the whole screen m_playerCameras[0]->SetViewport(Vec2::ZERO, Vec2::ONE); } break; case 2: { //We have 2 players, check the split mode for 2P and split accordingly if (splitMode == PREFER_VERTICAL_SPLIT) { //We need to split the screen into vertical halfs m_playerCameras[0]->SetViewport(VERTICAL_SPLIT_2P_FIRST_MIN, VERTICAL_SPLIT_2P_FIRST_MAX); m_playerCameras[1]->SetViewport(VERTICAL_SPLIT_2P_SECOND_MIN, VERTICAL_SPLIT_2P_SECOND_MAX); } else { //We need to split the screen into horizontal halfs m_playerCameras[0]->SetViewport(HORIZONTAL_SPLIT_2P_FIRST_MIN, HORIZONTAL_SPLIT_2P_FIRST_MAX); m_playerCameras[1]->SetViewport(HORIZONTAL_SPLIT_2P_SECOND_MIN, HORIZONTAL_SPLIT_2P_SECOND_MAX); } } break; case 3: { if (splitMode == PREFER_VERTICAL_SPLIT) { m_playerCameras[0]->SetViewport(VERTICAL_SPLIT_3P_FIRST_MIN, VERTICAL_SPLIT_3P_FIRST_MAX); m_playerCameras[1]->SetViewport(VERTICAL_SPLIT_3P_SECOND_MIN, VERTICAL_SPLIT_3P_SECOND_MAX); m_playerCameras[2]->SetViewport(VERTICAL_SPLIT_3P_THIRD_MIN, VERTICAL_SPLIT_3P_THIRD_MAX); } else { m_playerCameras[0]->SetViewport(HORIZONTAL_SPLIT_3P_FIRST_MIN, HORIZONTAL_SPLIT_3P_FIRST_MAX); m_playerCameras[1]->SetViewport(HORIZONTAL_SPLIT_3P_SECOND_MIN, HORIZONTAL_SPLIT_3P_SECOND_MAX); m_playerCameras[2]->SetViewport(HORIZONTAL_SPLIT_3P_THIRD_MIN, HORIZONTAL_SPLIT_3P_THIRD_MAX); } } break; case 4: { if (splitMode == PREFER_VERTICAL_SPLIT) { m_playerCameras[0]->SetViewport(VERTICAL_SPLIT_4P_FIRST_MIN, VERTICAL_SPLIT_4P_FIRST_MAX); m_playerCameras[1]->SetViewport(VERTICAL_SPLIT_4P_SECOND_MIN, VERTICAL_SPLIT_4P_SECOND_MAX); m_playerCameras[2]->SetViewport(VERTICAL_SPLIT_4P_THIRD_MIN, VERTICAL_SPLIT_4P_THIRD_MAX); m_playerCameras[3]->SetViewport(VERTICAL_SPLIT_4P_FOURTH_MIN, VERTICAL_SPLIT_4P_FOURTH_MAX); } else { m_playerCameras[0]->SetViewport(HORIZONTAL_SPLIT_4P_FIRST_MIN, HORIZONTAL_SPLIT_4P_FIRST_MAX); m_playerCameras[1]->SetViewport(HORIZONTAL_SPLIT_4P_SECOND_MIN, HORIZONTAL_SPLIT_4P_SECOND_MAX); m_playerCameras[2]->SetViewport(HORIZONTAL_SPLIT_4P_THIRD_MIN, HORIZONTAL_SPLIT_4P_THIRD_MAX); m_playerCameras[3]->SetViewport(HORIZONTAL_SPLIT_4P_FOURTH_MIN, HORIZONTAL_SPLIT_4P_FOURTH_MAX); } } break; default: break; } } //------------------------------------------------------------------------------------------------------------------------------ const std::array<CarCamera*, 4>& SplitScreenSystem::GetAllCameras() const { return m_playerCameras; } //------------------------------------------------------------------------------------------------------------------------------ const CarCamera* SplitScreenSystem::GetCameraForPlayerID(int playerID) const { if (m_playerCameras[playerID] != nullptr) { return m_playerCameras[playerID]; } else { return nullptr; } } void SplitScreenSystem::SetColorTargets(ColorTargetView * colorTargetView) const { for (int cameraIndex = 0; cameraIndex < m_numPlayers; cameraIndex++) { m_playerCameras[cameraIndex]->SetColorTarget(colorTargetView); } }
This is the end.
Yaaay! You have made it to the end (well almost). Now you get to see a little clip of me trying to attempt a super long jump at top speed:
Okay now this is the end.
Retrospectives
What went well 🙂
- Completed a working demo of a racing game
- Implemented a fairly decent feeling vehicle tune
- Implemented a good track system and tools that allowed me to work rapidly
- Got over major programmer morale problems when encountering bugs
What went bad 🙁
- Had a very poorly structured project plan for most of the project duration
- Issues with morale problems over Covid-19 quarantine
- Sprint burn down charts were very inconsistent when reviewing time spent working
What I would do differently next time 🙂
- Make a personal plan that works for me using a medium I know best (text files)
- Don’t delay discussing road-blocks, keep stakeholders informed about problems
- Eliminate project risks early on and re-plan immediately if tasks are not viable