Six Months With Cyclone
Back in the summer of 2017, I started experimenting with the idea of a puzzle game by placing launch pads on surfaces. The first build was very sloppy, and there wasn't any wall launching at this time. This build however was the first step of the experimenting process. Only one map was ever made and I just reused old Vectronic assets.
I shelved this concept until Winter of 2020 asI developed an itch to work on something and I wanted to get familiar with GIMP. This build again only had one main map along with other sample ideas but it was just another demo.
Cyclone Prototype - Work in Progress - Ultra Engine Community - Game Engine for VR
Although I really liked the direction of the project, I didn't develop further on that iteration after that March, but I did show it to people to get thoughts which were mostly positive. At this time, I wanted to wait until the Ultra Engine shipped to revisit the concept yet again, but eventually I got bored of waiting and figured I'd have to spend a lot of time learning the new systems Instead of using the almost seven years of working with Leadwerks. The plan was to get a small game out within a year. I set my scope to be a simple Portal clone with 10 maps. Anything else I probably would have got overwhelmed and bailed out like I did many times before with other projects.
I first started working on the foundation code base. I came up with the idea of a Stage Class with it handling all the Actors. The Stage class handles time, map loading, transitions, temp decals, and is responsible for attaching actors to the entitles in the editor via a Lua script. It's also is in-charge of ensuring that a Camera is present at all times. After every map load, a camera is created in which something like a Player actor would point to as it's Camera. It all works very nicely.
//========= Copyright Reep Softworks, All rights reserved. ============// // // Purpose: // //=====================================================================// #ifndef STAGE_H #define STAGE_H #if defined( _WIN32 ) #pragma once #endif #include "pch.h" enum StageEvent { /// <summary> // NULL; Use this for the main menu. This is the default. /// </summary> STAGE_EVENTNULL = 0, /// <summary> // Intermission; the event(s) between plays. /// </summary> STAGE_EVENTINTERMISSION, /// <summary> // Transition; Intermission between scenes. /// </summary> STAGE_EVENTTRANSITION, /// <summary> // Play; In-Game /// </summary> STAGE_EVENTPLAY }; struct TransitionData { std::string szMapPath; std::string szLandmarkName; Leadwerks::Entity* pLandmarkEntity; Leadwerks::Vec3 vLandmarkDistance; Leadwerks::Vec3 vCamRot; Leadwerks::Quat qCamQuat; Leadwerks::Vec3 vVelo; void Clear() { szMapPath = ""; szLandmarkName = ""; vLandmarkDistance = Leadwerks::Vec3(); vCamRot = Leadwerks::Vec3(); qCamQuat = Leadwerks::Quat(); vVelo = Leadwerks::Vec3(); pLandmarkEntity = NULL; } }; class StageActor; class GameMenu; class Stage : public Leadwerks::Object { protected: Leadwerks::World* m_pWorld; Leadwerks::Camera* m_pCamera; Leadwerks::Context* m_pFramebuffer; GameMenu* m_pMenu; StageEvent m_hEvent; bool m_bVSync; int m_intFrameLimit; std::string m_szCurrentSceneName; std::string m_szPreviousSceneName; //std::string m_szNextSceneName; bool m_bShowStats; Leadwerks::Vec3 m_vGravity; uint8_t m_intPhysSteps; TransitionData m_hTransitionData; std::vector<StageActor*> m_vActors; void ClearScene(const bool bForce = true); bool LoadSceneFile(const std::string& pszFilePath, const bool bForce = true); void SetStageEvent(const StageEvent& hEvent, const bool bFireFunction = true); void PreTransition(); void PostTransition(); GameMenu* GetGameMenu(); public: Stage() {}; Stage(Leadwerks::Window* pWindow); virtual ~Stage(); // Time: void Pause(const bool bFireOutput = true); void Resume(const bool bFireOutput = true); const bool Paused(); uint64_t GetTime(); const bool TogglePause(const bool bHandleMouse = false); void Update(); void Clear(); bool SafeLoadSceneFile(const std::string& pszFilePath); StageEvent GetCurrentEvent(); Leadwerks::Entity* FindEntity(const std::string& pszName); StageActor* FindActor(const std::string& pszName); Leadwerks::Decal* CreateTempDecal(Leadwerks::Material* pMaterial); //std::string GetCurrentScene(); const bool InPlay(); void Reload(); void DrawStats(const bool bMode); void ToggleStats(); const bool ConsoleShowing(); void SetPhysSteps(const uint8_t iSteps); void SetVSync(const bool bState); Leadwerks::World* GetWorld(); Leadwerks::Camera* GetCamera(); Leadwerks::Context* GetFrameBuffer(); Leadwerks::Widget* GetGameMenuHUD(); void Transition(const std::string& pszFilePath, Leadwerks::Entity* pLandmark); void ShowHUD(); void HideHUD(); //Virtual functions: virtual void Start() {}; virtual void OnUpdate() {}; virtual void OnTick() {}; virtual void PostRender(Leadwerks::Context* pFrameBuffer) {}; virtual void OnNoPlay() {}; virtual void OnPostLoad(Leadwerks::Entity* pEntity) {}; virtual void OnIntermission() {}; virtual void OnTransition() {}; virtual void OnPlay() {}; virtual void OnProcessEvent(const Leadwerks::Event iEvent) {}; virtual void OnPaused() {}; virtual void OnResume() {}; virtual void ScheduleRestart() {}; static Stage* Create(Leadwerks::Window* pWindow); friend class StageActor; }; extern Stage* ActiveStage; extern Stage* CreateStage(Leadwerks::Window* pWindow); #endif // STAGE_H
The first few months of development of this system still crashed randomly as I was hard loading the map instead of checking if a string was not empty every frame like how it's set up in the MyGame example. I went back to that approach and the crashing stopped. This did cause very hard to find bug which caused random restarts in then Release build. The issue ended up being that the string I was checking wasn't declared as empty.
With a great foundation by February 2021 , I went to getting the Cyclone stuff online. I mostly copied from the 2020 build but it was good enough to get started. A quick model later, and I had my item dropper in game.
https://cdn.discordapp.com/attachments/226834351982247936/815726095198322698/unknown.png
The item dropper was the first item I had to rethink how physics objects would work. The original idea was to have the boxes be an actor that made the impact sounds and other effects when I got to it. However, the entity's actor doesn't get copied when it's instanced.
The solution was to have the item dropper create a the box model, and after it instances the model, it also assigns a collision hook to that model. I also apply modifications to the box friction and dampening.
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- uint64_t BoxImpactNoiseLastSoundTime; void PuzzleBoxCollisionHook(Entity* entity, Entity* entity2, const Vec3& position, const Vec3& normal, const float speed) { auto self = entity; auto target = entity2; if (self->GetWorld()->GetWaterMode()) { if (self->GetPosition().y < self->GetWorld()->GetWaterHeight()) { DMsg("Puzzlebox below water level. Deleting."); ReleaseGamePlayObject(self); return; } } float fixedSpeed = __FixSpeed(speed); float flSoftThreshhold = 1.5f; float flHardThreshhold = 4.0f; long intMaxFrequency = 300; if (fixedSpeed > flSoftThreshhold) { int collisiontype = target->GetCollisionType(); if (collisiontype == COLLISION_PROP || collisiontype == COLLISION_SCENE) { long t = Leadwerks::Time::GetCurrent(); if (t - BoxImpactNoiseLastSoundTime > intMaxFrequency) { BoxImpactNoiseLastSoundTime = t; if (fixedSpeed > flHardThreshhold) { // HARD self->EmitSound(GetImpactHardSound(), 20.0f, 0.5f, 1.0f, false); } else { // SOFT self->EmitSound(GetImpactSoftSound(), 20.0f, 0.5f, 1.0f, false); } } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Leadwerks::Model* CreatePuzzleBox(const bool bAttactHook) { auto mdl = Model::Load(PATH_PUZZLEBOX_MDL); ApplyBoxSettings(mdl); if (bAttactHook) mdl->AddHook(Entity::CollisionHook, (void*)PuzzleBoxCollisionHook); return mdl; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Leadwerks::Model* SpawnPuzzleBox(Leadwerks::Model* ModelReference, const Vec3& vPosition) { if (ModelReference == NULL) { ModelReference = CreatePuzzleBox(); } auto newitem = (Model*)ModelReference->Instance(); newitem->SetPosition(vPosition); ApplyBoxSettings(newitem); newitem->AddHook(Entity::CollisionHook, (void*)PuzzleBoxCollisionHook); newitem->Show(); return newitem; }
Although adding hooks to entities isn't really considered official, this is only one case out of many that I do this. It's really nice to write a quick function without creating and assigning an actor. The item dropper got improvements down the line to fix some physics issues. (I made the dropper very slippery so there was no friction and it stopped acting weird.)
The months rolled on and I kept chipping away at the project. My maps count was increasing every month but there something really bothering me. It was the fly movement.
In Cyclone, I wanted a way for the player to launch themselves across rooms and deadly pits. I originally just applied velocity to the player character but the results felt terrible. I noticed that the wall launch was smoother if you held the button of the direction you were flying down. This gave me a nice arch instead of a sharp line. I ended up completely rewriting my player code from the ground up. Doing so allowed me to break apart what my Player actually was.
First. there's the base Player class. This class alone will give you a spectator camera with input controls. Everything regarding the camera is defined in this base class. I also spent the time to work how HUD hints would work. The HUD uses the Leadwerks GUI system but for the HUD, I kept it to simple image drawing on the framebuffer.
The base class also controls ambient sound, although it's still a work in progress.
Base Player Improvements - Work in Progress - Ultra Engine Community - Game Engine for VR
//========= Copyright Reep Softworks, All rights reserved. ============// // // Purpose: // //=====================================================================// #ifndef PLAYERACTOR_H #define PLAYERACTOR_H #if defined( _WIN32 ) #pragma once #endif #include "pch.h" #include "Input.h" #include "../Classes/StageActor.h" enum CharacterMoveState { CHARACTERSTATE_IDLE = 0, CHARACTERSTATE_WALKING, CHARACTERSTATE_JUMPING, CHARACTERSTATE_FALLING, CHARACTERSTATE_FLYING }; enum ScreenEffect { SCREENEFFECT_NONE = 0, SCREENEFFECT_FADEIN, SCREENEFFECT_FADEOUT, SCREENEFFECT_FLASH, SCREENEFFECT_BLIND, }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class CommentaryData { public: std::string devname = ""; uint8_t index = 0; uint8_t maxindex = 0; Source* speaker = NULL; ~CommentaryData() { if (speaker) { speaker->Release(); speaker = NULL; } } }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class PlayerActor : public StageActor { PickInfo* m_pLookTrace; // HUD: bool m_bShowHint; bool m_bShowCommentary; bool m_bShowGameOver; uint64_t m_u64GameOverTime; CharacterMoveState m_hState; std::weak_ptr<CommentaryData> m_pCommentaryData; protected: Listener* m_pListener; bool m_bIsAlive; bool m_bAllowCrosshair; bool m_bSuspendMovement; bool m_bSuspendLook; bool m_bCrouched; bool m_bCrouching; bool m_bWantsToCrouch; Vec3 m_vPushForce; Vec3 m_vCamRotation; Vec3 m_vCameraOffset; float m_flEyeHeight; bool m_bFreeLook; bool m_bFreeMove; Pivot* m_pRotator; uint64_t m_u64QuickSpinInitTime; bool m_bSpinLockX; double m_dCamTopAngle; double m_dCamBtmAngle; float m_flEyeTraceDistance; float m_flInteractDistance; float m_flPickRadius; Action ACTION_MOVEFORWARD; Action ACTION_MOVEBACKWARD; Action ACTION_MOVERIGHT; Action ACTION_MOVELEFT; Action ACTION_INTERACTION; // Effects: ScreenEffect m_hEffectType; Vec4 m_vCurtianColor; float m_flCurtianRate; bool m_bZoomed; bool m_bZoomedIn; Vec3 m_vCamRotationOffset; Vec3 m_vSmoothedCamRotationOffset; const bool ActionHit(const Action actionname); const bool ActionDown(const Action actionname); void SetVelocity(const Vec3 vVelo); void AddForce(const Vec3 vForce); void ChangeMovementState(const CharacterMoveState hState); public: PlayerActor(); virtual ~PlayerActor(); virtual void Start(); virtual void UpdateWorld(); virtual void UpdateMatrix(); virtual bool IsPlayer() { return true; }; virtual bool IsAlive(); virtual void Kill(); virtual void Respawn() {}; void Kick(); virtual void Spawn() {}; virtual void UpdateKeyBindings(); // Quick functions for locking movement/look: virtual void SuspendMovement(const bool bValue) { m_bSuspendMovement = bValue; }; virtual void SuspendLook(const bool bValue) { m_bSuspendLook = bValue; }; // Camera functions: Camera* GetCamera(); Vec3 GetEyePosition(); Vec3 GetEyeAngles(); Quat GetEyeQAngles(); const float GetEyeHeight(); const float GetCrouchedEyeHeight(); virtual const bool CanUnCrouch() { return true; }; virtual void SetEyeHeight(const float flHeight); virtual void SetCameraAngles(Leadwerks::Vec3 vRot); virtual void Teleport(const Vec3& pNewPos, const Vec3& pNewRot, const Vec3& pNewVelocity); const bool FireUseOnEntity(Entity* pEntity); void SetFreeLookMode(const bool bMode); void SetFreeMoveMode(const bool bMode); void UpdateFreeLookMode(); void UpdateFreeMoveMode(); void QuickSpin(); virtual void UpdateCameraHeight(const bool bCrouchState); const float GetEyeTraceDistance() { return m_flEyeTraceDistance; }; Entity* GetEyeLookingAt(); const Vec3 GetEyeLookPositon(); PickInfo* GetEyeTrace() { return m_pLookTrace; }; const float GetPickRadius(); void ForceLookAtPoint(const Vec3& vPos, const bool bLockX = false); const Line3D GetLine(); // Movement + Physics virtual void SetPhysicsMode(const int iMode); const int GetPhysicsMode(); void SetWalkSpeed(const float iSpeed); const int GetWalkSpeed(); virtual void HandleMovement(); virtual const bool IsCrouched(); const bool IsAirbone(); virtual void HandleInteraction(); virtual void SetNoClipMode(const bool bState) {}; CharacterMoveState GetMovementState() { return m_hState; }; Vec3 GetVelocity(); virtual void Push(const int x, const int y, const int z); void ForceJump(const float fJump); // Interaction virtual void OnSuccessfullUse(Entity* pHitEntity) {}; virtual void OnUnSuccessfullUse() {}; virtual bool ShouldSkipUseTest() { return false; }; // Post drawing: void ShowCrosshair(const bool bShow); virtual void PostRender(Context* context); virtual void DrawHUD(Context* pContext); virtual void DrawCurtian(Context* pContext); Widget* GetHUD(); // Effects: virtual void HandleScreenEffects(); void SetCurtianColor(const Vec3& pColor); void PlayScreenEffect(const ScreenEffect iScreenEffect, const Vec3 vColor = Vec3(0), const float flRate = 0.1f); void PlayQuickFlash(const Vec3 vColor = Vec3(1)); void ClearScreenEffect(); void ZoomIn(const float fNewFov, const float flRate); void ZoomOut(const float flRate); // HUD Hint: void ShowHUDHint(const Action actionname, const std::string& pszMessage); void HideHUDHint(); void ShowCommentaryPanel(std::shared_ptr<CommentaryData> pData); void HideCommentaryPanel(); void ShowGameOverScreen(const std::string& pszMessage); }; extern const bool IsPlayerActor(Leadwerks::Entity* pEntity); extern PlayerActor* ToPlayerActor(Leadwerks::Entity* pEntity); extern PlayerActor* GetPlayerActor(); extern const bool IsPlayerLookingAtEntity(Leadwerks::Entity* pEntity); //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class PlayerProxy : public StageActor { uint64_t m_u64TriggerTime; uint64_t m_u64DelayTime; protected: PlayerActor* GetPlayer(); const bool IsPlayerLookingAtMe(); public: PlayerProxy(); virtual void Start(); virtual void UpdatePhysics(); virtual void ReceiveSignal(const std::string& inputname, Entity* sender); virtual void Trigger() {}; }; class CommentaryNode : public PlayerProxy { bool m_bActive; std::shared_ptr<CommentaryData> m_hData; public: CommentaryNode(); virtual ~CommentaryNode(); virtual void Start(); virtual void UpdateWorld(); virtual bool Use(StageActor* pActor); virtual void Detach(); friend class CommentaryNode; }; class ProxyEndGame : public PlayerProxy { public: virtual void Trigger(); }; class ProxyCurtain : public PlayerProxy { ScreenEffect m_hType; uint64_t m_u64ShowTime; uint64_t m_u64TimeOut; public: // This needs to be PostStart as the player may be placed/loaded after this actor! virtual void PostStart(); virtual void UpdateWorld(); virtual void Trigger(); }; class ProxyHint : public PlayerProxy { uint64_t m_u64ShowTime; uint64_t m_u64TimeOut; public: virtual void Start(); virtual void UpdateWorld(); virtual void Trigger(); }; class ProxyAmbientSound : public PlayerProxy { Source* m_pSpeaker; float m_flMaxVolume; bool m_bActive; public: virtual void Start(); virtual void UpdatePhysics(); void Activate(); virtual void Detach(); friend class ProxyAmbientSound; }; class ProxyLookAtMe : public PlayerProxy { Model* m_pBox; public: virtual void Start(); Model* GetTargetBox(); }; #endif // PLAYERACTOR_H
Next, I made an FPSPlayer which is built on top on the PlayerActor class. This transfroms the base class into a FPS character controller with interaction and physics pickup. It also has base code for a weapon. There is also a CyclonePlayer but it really is just a Start function that controls if it should give the player the weapon or not.
//========= Copyright Reep Softworks, All rights reserved. ============// // // Purpose: // //=====================================================================// #ifndef FPSPLAYER_H #define FPSPLAYER_H #if defined( _WIN32 ) #pragma once #endif #include "pch.h" #include "PlayerActor.h" struct InteractPickupStoredData { float mass; int collisiontype; Vec2 dampinging; Vec2 friction; }; class FPSPlayer; class FPSWeapon : public StageActor { protected: Pivot* m_pSwayPivot; bool m_bHolstered; Model* m_pViewModel; Vec3 m_vModelOffset; Vec3 m_vBaseRotation; FPSPlayer* m_pOwner; public: FPSWeapon(); virtual void Holster() {}; virtual void Unholster() {}; virtual void UpdateHolster() {}; virtual void Fire(const int iMode = 0) {}; virtual void TestPickupState() {}; virtual void Reload() {}; virtual void BeginJump() {}; virtual void ReAdjustViewModel(const float flPlayerFOV); friend class FPSPlayer; }; class FPSWeaponPickup : public StageActor { void GiveWeapon(Entity* entity); public: virtual void Collision(Entity* entity, const Vec3& position, const Vec3& normal, float speed); virtual bool Use(StageActor* pActor); }; class FPSPlayer : public PlayerActor { // Movement + Physics bool m_bCrouchedOldState; bool m_bWalking; float m_flUpdateTick; uint64_t m_intLastStepTime; bool m_bLeftStep; std::string m_pszFootStepSounds[2]; Model* m_pCorpse; uint64_t m_u64PushLaunchTime; Vec3 m_flLastImpactSpeed; // Interaction Vec3 m_vCarryPosition; Quat m_vCarryQuat; Vec3 m_flThrowVelocity; float m_flPickDistance; float m_flCarryDistance; float m_flHoldThreshold; Pivot* m_pRotationCorrection; Joint* m_pEffector; Entity* m_pCarryEntity; protected: Action ACTION_JUMP; Action ACTION_CROUCH; Action ACTION_ZOOM; Action ACTION_FIREPRIMARY; Action ACTION_FIRESECONDARY; Action ACTION_RELOAD; // Weapon Pivot* m_pWeaponTag; //FPSWeapon* m_pActiveWeapon; virtual void PickupObject(Entity* pEntity); virtual void UpdateHeldObject(); virtual void ThrowObject(); void SetThrowVelocity(Vec3 vVelocity); public: FPSPlayer(); virtual ~FPSPlayer(); virtual void Spawn(); virtual void Kill(); virtual void Detach(); virtual void UpdateWorld(); virtual void UpdatePhysics(); virtual void Collision(Entity* entity, const Vec3& position, const Vec3& normal, float speed); virtual void UpdateKeyBindings(); virtual void Respawn(); // Movement + Physics virtual const bool IsCrouched(); virtual const bool CanUnCrouch(); virtual void HandleCrouching(); virtual void HandleCharacterController(); virtual void SetNoClipMode(const bool bState); virtual void UpdateMovement(Vec2 vMovement); virtual void OnUpdateMovement() {} virtual void OnPerStep(); virtual void OnStopMovement() {}; virtual void OnJump(); virtual void OnLand(); virtual bool GetCrouched() { return false; }; const bool IsFlying(); virtual void Push(const int x, const int y, const int z); const float GetFallSpeed(); // Interaction virtual void OnSuccessfullUse(Entity* pHitEntity); virtual void OnUnSuccessfullUse(); virtual bool ShouldSkipUseTest(); virtual void ForceDropObject(); const bool HoldingObject(); // Weapon void GiveWeapon(); FPSWeapon* GetWeapon(); virtual void BuildWeapon(FPSWeapon* pWeapon); void AdjustViewModel(); }; extern const bool IsFPSPlayer(Leadwerks::Entity* pEntity); extern FPSPlayer* ToFPSPlayer(Leadwerks::Entity* pEntity); #endif // FPSPLAYER_H
The magic of getting the fly code to work correctly is in this bit. I also force the player to crouch in a ball to make it feel way less awkward. It was really tricky, but it all worked out.
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void FPSPlayer::UpdatePhysics() { HandleCharacterController(); UpdateHeldObject(); // This fixes a crash when dying. if (GetEntity()->GetPhysicsMode() == Entity::CharacterPhysics) { // Stop moving me if we're landed and it's been a bit since we were last pushed. if (m_u64PushLaunchTime > 0) { if (GetStage()->GetTime() > m_u64PushLaunchTime + 100) { if (m_vPushForce != Vec3(0) && !IsAirbone()) { DMsg("PlayerActor: Resetting push launch timer."); //GetEntity()->charactercontroller->stepheight = 0.51; m_vPushForce = Vec3(0); SetVelocity(m_vPushForce); m_bWantsToCrouch = false; m_u64PushLaunchTime = 0; } } } } if (GetMovementState() != CHARACTERSTATE_FALLING && GetEntity()->GetVelocity().y < -4.5f) { ChangeMovementState(CHARACTERSTATE_FALLING); } else if (GetMovementState() == CHARACTERSTATE_FALLING && GetEntity()->GetVelocity().y >= 0.0f) { ChangeMovementState(CHARACTERSTATE_IDLE); OnLand(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void FPSPlayer::HandleCharacterController() { if (GetStage()->ConsoleShowing() || m_bSuspendMovement) return; if (GetEntity()->GetPhysicsMode() == Entity::RigidBodyPhysics) return; float jumpinput = m_vPushForce.y; Vec2 movement = Input::GetActionAxis(Input::ToActionAxis(ACTION_MOVEFORWARD, ACTION_MOVEBACKWARD, ACTION_MOVELEFT, ACTION_MOVERIGHT, GAMEPADAXIS_LSTICK)); float speed = g_player_walkspeed; // Handle Crouching HandleCrouching(); if (IsCrouched()) speed = g_player_walkspeed / 2; auto l = movement.Length(); if (l > 0.0) { movement.x = (movement.x / l) * speed; movement.y = (movement.y / l) * speed; } // Handle Jumping if (ActionHit(ACTION_JUMP)) { if (!IsAirbone()) { jumpinput = g_player_jumpforce; ChangeMovementState(CHARACTERSTATE_JUMPING); OnJump(); if (movement.y != 0) movement.y = movement.y * 1.6; if (movement.x == 0 || movement.y == 0) OnPerStep(); } } if (m_u64PushLaunchTime > 0) { if (m_vPushForce.x != 0 || m_vPushForce.z != 0) { movement = Vec2(0); movement += GetEntity()->GetVelocity(true).xz(); //DMsg(movement.ToString()); // Hackory to make the player crouch properly while flying. m_bWantsToCrouch = false; m_bCrouched = true; m_vCameraOffset.y = GetCrouchedEyeHeight(); GetEntity()->SetInput(0, movement.y, movement.x, m_vPushForce.y, m_bCrouched, g_player_maxdecel, g_player_mindecel, true); } } else { UpdateMovement(movement); GetEntity()->SetInput(GetEyeAngles().y, movement.y, movement.x, jumpinput, m_bCrouched, g_player_maxdecel, g_player_mindecel, true); } m_vPushForce.y = 0; }
Along with some adjustments to the Cyclone code, Everything was working much better. I continued to make tweaks and changes as I continued along. A nice bonus was this fixed Cyclones on Ramps which didn't work with my old code which helped me to get another map done.
Revamped Cyclone Behavior - Work in Progress - Ultra Engine Community - Game Engine for VR
After 6 months of development, I'm very happy to say that today I hit my first milestone. I have 10 playable maps with everything feeling acceptable. Most gameplay functions are online AND today I squeezed an Addon System to allow custom maps. The game runs with the Lua Sandbox enabled so custom maps can code new content in Lua!
However, the entire game looks like this.
I think it's safe to say that it's time to close Visual Studio for a bit and open Blender and Gimp on a daily basis. I'll have to open Visual Studio again to code the indicator strips and other effects but I'm really excited to just zone out and work models and textures. (although I don't see myself as the best in that area.) If you're actually interested in helping, my DM is open.
Right now the plan is to get some maps art up for some screenshots and by late September/October set up Cyclone for Steamworks for an Early Access release Q1 2022. I can beat the game within a few minutes but with the project having zero story it can get more content in the future by me or others thanks to the addon system.
First thing I need is a logo....
- 6
0 Comments
Recommended Comments
There are no comments to display.