Wall stick/Gravity Controller

Where can I find the latest version that has the ability to be enable and disabled, that I can apply these changes to, to get it working?

Thanks

EgoMoose hasn’t accepted the pull request yet. If you want the code, my proposed quick fix is here:

My main branch also gets rid of the need to use the CameraInjector and setfenv, which is probably a better fix in the long run.

I’ve been working on a refactor that I think will be less likely to break going forward, that create a separate UpVectorCamera module that doesn’t require the CameraModule injector and wraps the BaseCamera instead. I used that as an excuse to update the code some, add in --!strict luau Script Analysis, and just make sure everything has kept up with the last couple camera controller api changes.

I’ve been wanting to publish that and it got away from me a little. Script Analysis opened a can of worms. Hopefully I can share the refactor/rework version soon and see what EgoMoose thinks of it

Sorry to ask this, as I feel everyone gets the whole 'GitHub, branches, submissions, pulls, etc… But I just don’t really understand all of that.

So, dumb question… if I have the Gravity Controller from here…

where do I get the code that needs to be changed to get it to work?

I see you posted two links, but isn’t that for the ‘Wall Stick’ controller, which uses a de-rotated copy of the world?

I was needing the one where gravity is changed.

Thanks.

OK, I’ll show you exactly what you need to change, but I’ll start with how to find the change.

The code change for Gravity Controller and Wallstick is basically the same:

  1. I added a workaround so we can obtain the local player’s CameraModule using lua trickery, then inject code to adjust the camera angle for our desired UpVector
  2. I fixed a missing parameter caused by Roblox changing TransparencyController:Update() to need dt that has been making camera control weird for a while now

If you look at the EgoMoose project:

Click on Pull Requests
image

Click on my pull request
image

Click on Files changed:

you see exactly what code you need to change:

If you just want to copy/paste overwrite the files instead of editing them, you can get a direct link to the modified CameraInjector lua code

And PlayerScriptsLoader

3 Likes

I am having an issue with the gravity controller, where, when active, I can’t step up on small parts (parts small enough that you can’t walk on their ‘walls’, and in normal walking you would be able to step up on them)
Anyone have any issues like this and know of a fix?

1 Like

I haven’t forgotten about this. I promise I’ll take a look soon! I was traveling for RDC so I’ve been very busy as of late.

1 Like

Can you set up a demo place that has the issue? I would take a look

If you have any feedback on the fix, I’d be happy to make some adjustments.

I’m curious if you have feedback about what it should do or where it should go in the future.

The pull request I sent does the minimum - fixes CameraInjector in all 3 repos. Should just work.

Gratuitous Pull Request Summary

(setfenv’s a setmetatable() in CameraModule .new and causes require(PlayerModule.CameraModule) to return the properly instantiated CameraModule singleton)
with that patch

  1. anyone using the old pattern should be able to continue overriding or calling methods of CameraModule
  2. it no longer needs FakeUserSettings and doesn’t rely on FFlag settings that can change and break things in the future.
  3. for Wallstick and Gravity Controller repos, also minor fix to camera glitch from missing dt parameter

I don’t think I tested it on mobile yet, but I did test on console.

That said, I won’t be relying on CameraInjector or overriding CameraModule in my own projects.

I’m warming up to the idea that Roblox yanking CameraModule out of our hands wasn’t such a bad move. Trying to replace CameraModule.Update has proven painful. I’ve been working up to posting on the CameraInjector thread about it. (their suggestion of forking is just as bad. anything near an FFlag is a landmine)

The follow up commit to my repo does not override CameraModule.Update. I tested an improved custom camera method I think will be safer and more reliable going forward by wrapping BaseCamera methods.

BaseCameraExtender method details

I have a BaseCameraExtender puts a proxy .Update() on any camera controller that extends BaseCamera.

  1. CameraModule.Update() calls activeCameraController.Update
    ( this will always be the injected BaseCamera Update method)
  2. The injected method calls the official roblox corecript CameraController.Update, so futre changes to the implementation are unlikely to be a problem
  3. the (newCameraCFrame, newCameraFocus) returned from the active camera controller Update() are then adjusted for the current UpVector before being returned to the caller (CameraModule.Update())

What I would actually suggest is refactoring the custom camera overrides into a separate module. I created an UpVectorCamera module that can be used by both wallstick and gravity controllers that uses the BaseCameraExtender method.

image

UpVectorCamera and BaseCameraExtender module

I would like to release these as a standalone module, and credit you @EgoMoose for the base implementation, unless you have any other ideas.

As I moved the Egomoose camera override methods into UpVectorCamera, I compared them with diffs of the last few versions of roblox player scripts to make sure they were using properties and functions that are still valid.

The BaseCameraExtender lets you put any number of proxy method overrides on camera controller classes. Whenever a camera controller is enabled, the extender makes sure the overrides are on that controller.
The proxy method can modify the arguments, pass them to the camera controller method, then alter the results before returning them.

It doesn’t actually use CameraInjector or FakeUserSettings at this time, so I should probably them out. It’s better I don’t have to use them, but I’m just worried they might be needed in the future.
Since I’m not using them for now, there is no setfenv() happening, so the luau vm doesn’t have to disable optimizations due to 'dirty" environment.

UpVectorCamera exposes methods needed by wallstick or gravity controllers


function UpVectorCamera:GetUpVector(oldUpVector:Vector3)
	return oldUpVector	
end

function UpVectorCamera.SetUpVector(newUpVector:Vector3)
	upVector = newUpVector
end

function UpVectorCamera:SetSpinPart(part)
	spinPart = part
end

function UpVectorCamera:SetTransitionRate(rate)
	transitionRate = rate
end

function UpVectorCamera:GetTransitionRate()
	return transitionRate
end

function UpVectorCamera:IsCamRelative() : boolean
	if activeCameraController then
		return activeCameraController.inFirstPerson or activeCameraController.inMouseLockedMode
		--return activeCameraController:GetIsMouseLocked() or activeCameraController:IsInFirstPerson()
	end
	return false
end

Internally, it passes camera controller proxy methods to BaseCameraExtender

local CameraExtensions = BaseCameraExtender:GetExtensionFunctions()

-- mgmTodo: Hook MouseLockController.new
local MouseLockController:types.MouseLockController = nil

function CameraExtensions:Update(dt:number, ...)
	local cameraControllerMeta:BaseCamera = getmetatable(self) -- this is the underlying camera controller __index and metatable
	local newCameraCFrame, newCameraFocus 
		=  cameraControllerMeta.Update(self, dt, ...) -- get cframe and focus from underlying camera controller update
	
	--return self:UpdateWithUpVector(dt, newCameraCFrame, newCameraFocus)
	newCameraFocus = CFrame.new(newCameraFocus.p) -- vehicle camera fix

	calculateUpCFrame(dt)
	calculateSpinCFrame()

	local lockOffset = Vector3.new(0, 0, 0)
	if MouseLockController and MouseLockController:GetIsMouseLocked() then
		lockOffset = MouseLockController:GetMouseLockOffset()
	end

	local offset = newCameraFocus:ToObjectSpace(newCameraCFrame)
	local camRotation = upCFrame * twistCFrame * offset
	newCameraFocus = newCameraFocus - newCameraCFrame:VectorToWorldSpace(lockOffset) + camRotation:VectorToWorldSpace(lockOffset)
	newCameraCFrame = newCameraFocus * camRotation
	
	

	self.lastCameraTransform = newCameraCFrame
	self.lastCameraFocus = newCameraFocus
	
	return newCameraCFrame, newCameraFocus, ...
end

function CameraExtensions:CalculateNewLookCFrameFromArg(suppliedLookVector: Vector3?, rotateInput:Vector2, ...)
	local currLookVector = suppliedLookVector or self:GetCameraLookVector()
	currLookVector = upCFrame:VectorToObjectSpace(currLookVector, ...)
	
	-- get underlying camera controller __index and metatable
	local cameraControllerMeta:BaseCamera = getmetatable(self)
	return cameraControllerMeta.CalculateNewLookCFrameFromArg(self, currLookVector, rotateInput, ...)
end

lol I just remembered I forgot to test mouse lock. Clearly that’s not yet working
Those camera controller proxy functions pass varargs through for a bit of future proofing that will probably work unless Roblox completely changes the parameters.

There are still places in the UpVectorCamera that depend more on the CameraModule child script implementation than I’d prefer:

local CameraUtils:CameraUtils = require(CameraModuleScript:WaitForChild("CameraUtils"))

-- todo: see if this is still needed
function CameraExtensions:UpdateMouseBehavior()
	-- get underlying camera controller __index and metatable
	local cameraControllerMeta:BaseCamera = getmetatable(self)
	cameraControllerMeta.UpdateMouseBehavior(self)
	CameraUtils.setRotationTypeOverride(Enum.RotationType.MovementRelative)
	--[[
	if UserGameSettings.RotationType == Enum.RotationType.CameraRelative then
		UserGameSettings.RotationType = Enum.RotationType.MovementRelative
	end
	]]
end

Roblox moved that RotationType logic into CameraUtils.setRotationTypeOverride, but I don’t trust that function is going to 100% stay there, and it would be better to have a fallback if they remove/rename it.

Some places there isn’t a clear way to use a proxy method, and so basically the original code is still there

local Poppercam = require(CameraModuleScript:WaitForChild("Poppercam"))
local ZoomController = require(CameraModuleScript:WaitForChild("ZoomController"))

function Poppercam:Update(renderDt, desiredCameraCFrame, desiredCameraFocus, cameraController)
	local rotatedFocus = desiredCameraFocus * (desiredCameraCFrame - desiredCameraCFrame.p)
	local extrapolation = self.focusExtrapolator:Step(renderDt, rotatedFocus)
	local zoom = ZoomController.Update(renderDt, rotatedFocus, extrapolation)
	return rotatedFocus*CFrame.new(0, 0, zoom), desiredCameraFocus
end

I wanted to post about it last week, after I spiked a basic wallstick refactor, but I got lost in the weeds in a deeper refactor due to fights with luau, script analysis, and the difference between BasePart.Velocity and BasePart.AssemblyLinearVelocity causing drift on spinning parts.
That is now working and looks like this:
image

The UpVectorCamera refactor is nearly done, I just need to test in VR and with mouselock.
I will probably follow up with that in a day or two, with a common UpVectorCamera for wallstick and gravity controller, and submit another pull request to those repos if you’re interested in removing the CameraController dependency.

Tomorrow I will set up a demo place with the code I have, and also if anyone has any advice on how to make the thing a bit … lighter… I guess?

In my game I have my own sound code, and my own animation code, so I really don’t need all the animation and sound stuff associated with the module, just a simple way to determine if I am ‘walking’ or ‘idle’ or ‘jumping’ when the module is active. I have tried 3x to remove the animation code and sound code but each time, I really mess it up, it seems to be rather integrated.

Hello all. I am trying to make a gravity runner game with the new camera controller. But what I want is to automatically move the player like in a runner game. I also need the player to jump and move side to side as well. If you can please help because it will be heavily appreciated. Thanks!

Gravity Controller Roblox Working.rbxl (96.2 KB)

Here is the place file with ‘what I think’ is the most updated / patched gravity controller.
When you spawn in, if you step off of the spawn pad, you can not step back up onto the spawn pad.

2 Likes

I can confirm that you included all the fixes from my patch in that file and didn’t introduce any breaking changes.

Also, yes, when I try to step back onto the spawn pad, I hit it like a wall and do not step up on it.

In some ways, the gravity controller is more simple than the wallstick controller. The wallstick controller forces you to change any code that isn’t aware of the replication of the character between the copied physics world and the visible world.

Assuming that would continue causing more unexpected issues and be harder to maintain in the long run, that’s why I switched from the Wallstick controller to the Gravity Controller a couple years ago.

The Wallstick controller gets around hard-coded behavior in the roblox client
(like detecting there is a ladder or step in front of the player and changing to a climbing state, based on an assumed down vector of (0,-1,0) )
by making a copy of the world around the character rotated to the correct custom UpVector cframe relative to the player, so when you bump into a ladder, roblox reacts normally.

I had to eventually go back and switch the project back to the Wallstick controller because it works the way I need it to more often than the simpler gravity controller.

Do you know how long the small step up issue has been a problem? I got used to ignoring those movement interaction glitches with it, so I’m not sure if that worked better at some point.
Any reason you don’t switch to Wallstick?

There are people who can answer this question better than me, but I’ll take a crack at it.

I can tell you that if I were going to do that, I would probably use the Egomoose wallstick controller and rebind and override the standard character movement controls, so players aren’t finessing the controls (assuming you’re doing the standard runner thing where the player presses a direction but the character doesn’t start moving that way until it reaches the next grid point or lane or whatever)

Then I’d use Humanoid.WalkToPart on the WallStick.Physics.Humanoid

I don’t think you can use Humanoid.MoveTo or set the Humanoid.WalkToPoint or WalkToPart to move up a wall normally. The wallstick controller duplicates the objects in the workspace to a proxy world, rotated so the Wallstick.Physics copy of the player character can move around normally.

You can place a part for the character to walk to in the normal workspace world, but you’ll have to find it’s copy in the physics world and tell the physics world humanoid to go to that one.

Can you somehow make this for NPCs?

I’ve been doing a refactor of wall stick for my own project, in part to test out some luau script analysis workflow hacks, but mainly to make sure It degrades gracefully when it breaks after Roblox updates. And VR support.

For what wall stick does, it seems like singletons would do the job and it’s using class instances maybe just out of habit. It’s not too far from being able to run multiple wall stick instances, so I left it that way instead of taking some singleton shortcuts with the refactor.

Having it work for NPCs might be trivial in some cases, but I’m not sure that would hold up well if you need your NPCs to do pathfinding, because the rotated physics world will need to include the entire area that needs to be navigated, so you would either need a larger area, or you would need to have NPC waypoints fairly close together.
Also with the physics world constantly changing, I’m not sure when or even if a navigation mesh for it would be generated.

What do you want your NPCs to do? If they aren’t walking around, you don’t need most of wall stick

they need to be able to walk on walls and roofs

Did you ever find a fix for the small step up == wall issue?

is there any way to make it so it pulls the player to a certain center of mass like a planet made of terrain?
and if there is a cave in that planet you can’t walk on the walls but you do get pulled towards the center of the planet? In this case the planet being a sfere made of grass

Heya I have a little issue, whenever I use the gravity controller and my character is over 60 degrees the floor material is just blank, is there a way to fix that?

if you scroll up there should be a couple posts that could help you