Actually, I may have found a better way. I don’t think the CameraInjector is likely to stop working this time, but most of the problems that came up in the last 2 years-3 years have been from changes in CameraModule.Update that we can’t predict.
I got the WallStick controller to work without modifying the CameraModule, and modifying BaseCamera instead. I had BaseCamera.new() wait to get called, then it wraps the CameraController.Update method instead. The unmodified CameraModule.Update calls my camera controller UpdateWrapper, which calls the controller .Update, then adjusts the cframe and focus to match the custom UpVector.
It also avoids using setfenv, so theoretically this avoids turning off the luau performance optimizations in CameraModule.
IDK how many people are still using this, but if people are interesting, I can create a branch using this technique that I’m pretty sure is less likely to break in the future.
demo code for clarity:
local cameraControllers = {}
local BaseCamera:BaseCamera = require(CameraModule:WaitForChild("BaseCamera"))
local BaseCamera_new = BaseCamera.new
function BaseCamera:UpdateWithUpVector(dt,newCameraCFrame, newCameraFocus)
return newCameraCFrame, newCameraFocus
end
function BaseCamera.new()
local env = getfenv(2)
local camScript:ModuleScript = env.script
local newSelfTable:BaseCamera = BaseCamera_new()
-- camera controller implementation (such as ClassicCamera) has called BaseCamera.new
-- newSelfTable will be returned to actual camera controller implementation to be initialized
-- actual controller module table will be set as the metatable of newSelfTable
-- we can wrap any calls to the actual controller methods before we return newSelfTable:
if typeof(camScript)=="Instance" and camScript:IsA("ModuleScript") then -- sanity check
print("BaseCamera.new called for camera controller: ", camScript:GetFullName())
local controllerInfo = {
Name = camScript.Name,
Script = camScript,
ControllerSelf = newSelfTable,
}
cameraControllers[controllerInfo.Name] = controllerInfo
function newSelfTable:Update(dt)
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)
end
-- sanity check:
function newSelfTable:Enable(enable: boolean)
if self~=newSelfTable then
print("Something is wrong. expected self==baseTable. There will be problems")
end
local cameraControllerMeta:BaseCamera = getmetatable(self)
print("Enabling camera controller:", controllerInfo.Name)
cameraControllerMeta.Enable(self, enable)
end
else
print("Can't find script calling BaseCamera.new", env)
end
return newSelfTable
end
local Camera = require(CameraModule)
After that, you can wrap the return values from whateverCameraController.Update(dt) by doing this
BaseCamera.UpdateWithUpVector = function(self, dt, newCameraCFrame, newCameraFocus)
newCameraFocus = CFrame.new(newCameraFocus.p) -- vehicle camera fix
calculateUpCFrame(Camera, dt)
calculateSpinCFrame(Camera)
local lockOffset = Vector3.new(0, 0, 0)
if Camera.activeMouseLockController and Camera.activeMouseLockController:GetIsMouseLocked() then
lockOffset = Camera.activeMouseLockController:GetMouseLockOffset()
end
local offset = newCameraFocus:ToObjectSpace(newCameraCFrame)
local camRotation = upCFrame * twistCFrame * offset
newCameraFocus = newCameraFocus - newCameraCFrame:VectorToWorldSpace(lockOffset) + camRotation:VectorToWorldSpace(lockOffset)
newCameraCFrame = newCameraFocus * camRotation
return newCameraCFrame, newCameraFocus
end