Hi,
I have a base class that should store states. For example, when the player is sprinting, isSprinting becomes true. Problem is when I was debugging the base class, base class was printing twice instead of once. Another problem is that the properties are not shared.
I changed the title from “Module printing twice instead of once” to “Base class running twice and properties aren’t shared.” to make it more clear.
Here’s how I was debugging:
function BaseMovement.new(): ClassType
local self = {
character = nil,
-- states?
isWalking = false,
isSprinting = false,
isWallrunning = false,
isFloating = false,
isSliding = false,
BaseConnections = Trove.new()
}
setmetatable(self, BaseMovement)
-- debugging
task.spawn(function()
while task.wait(3) do
print(self.isSprinting)
end
end)
self:_init()
return self
end
This printed out false 2x. And when the player was sprinting, it printed out true 1x and false 1x.
This is because I currently have two sub-classes that are accessing BaseMovement.
Float and sprint are accessing BaseMovement. Sprint is changing isSprinting to true, but float return isSprinting to false.
Sprint
--!strict
--[[
Sprint sub-class
--]]
--// roblox services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
--// variables
local CurrentCamera = workspace.CurrentCamera
local CameraTweenIn = TweenService:Create(CurrentCamera, TweenInfo.new(0.2), {FieldOfView = 75})
local CameraTweenOut = TweenService:Create(CurrentCamera, TweenInfo.new(0.2), {FieldOfView = 70})
--// Dependencies
local PlayerSettings = require(ReplicatedStorage.PlayerSettings)
local BaseMovement = require(script.Parent.BaseMovement)
local Trove = require(ReplicatedStorage.Packages._Index["sleitnick_trove@1.1.0"]["trove"])
--// class
local Sprint = setmetatable({}, {__index = BaseMovement})
Sprint.__index = Sprint
export type ClassType = typeof( setmetatable({} :: {
_connections: Trove.ClassType;
--Bind: (self: ClassType, actionName: string, inputState: Enum.UserInputState, _inputObject: any) -> ();
} , Sprint) ) & BaseMovement.ClassType
--// constructor
function Sprint.new(): ClassType
local self = setmetatable(BaseMovement.new() :: any, Sprint)
self._connections = Trove.new()
return self
end
function Sprint.Bind(self: ClassType, actionName, inputState, _inputObject): ()
if not self.character then
return
end
local Character = self.character
local Humanoid = Character:FindFirstChild("Humanoid") :: Humanoid
if inputState == Enum.UserInputState.Begin then
self.isSprinting = true
CameraTweenIn:Play()
Humanoid.WalkSpeed = PlayerSettings.CharacterSprintSpeed
end
if inputState == Enum.UserInputState.End then
CameraTweenOut:Play()
Humanoid.WalkSpeed = PlayerSettings.CharacterWalkSpeed
self.isSprinting = false
end
end
function Sprint.GetParameters()
return {
actionName = "Sprint", -- mandatory
createTouchButton = false, -- optional, will always be false on default
inputs = { -- mandatory
Enum.KeyCode.LeftShift,
Enum.KeyCode.RightShift
}
}
end
return Sprint.new()
Float
--!strict
--[[
Float sub-class
--]]
--// roblox services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
--// Dependencies
local BaseMovement = require(script.Parent.BaseMovement)
--// class
local Float = setmetatable({}, {__index = BaseMovement})
Float.__index = Float
export type ClassType = typeof( setmetatable({} :: {
} , Float) ) & BaseMovement.ClassType
--// constructor
function Float.new(): ClassType
local self = setmetatable(BaseMovement.new() :: any, Float)
return self
end
function Float.Bind(self: ClassType, actionName, inputState, _inputObject): ()
if not self.character then
return
end
local Character = self.character
local Humanoid = Character:FindFirstChild("Humanoid") :: Humanoid
if inputState == Enum.UserInputState.Begin then
print("Float start")
self.isFloating = true
end
if inputState == Enum.UserInputState.End then
print("Float end")
self.isFloating = false
end
end
function Float.GetParameters()
return {
actionName = "Float", -- mandatory
createTouchButton = false, -- optional, will always be false on default
inputs = { -- mandatory
Enum.KeyCode.F,
}
}
end
return Float.new()
REPRO PLACE FILE ( REMOVE THE JUMP SUBCLASS):
baseclass_repro.rbxl (187.0 KB)
An example of this working is in roblox’s control subclasses.
Robloxs Base Class:
--[[
BaseCharacterController - Abstract base class for character controllers, not intended to be
directly instantiated.
2018 PlayerScripts Update - AllYourBlox
--]]
local ZERO_VECTOR3: Vector3 = Vector3.new(0,0,0)
--[[ The Module ]]--
local BaseCharacterController = {}
BaseCharacterController.__index = BaseCharacterController
function BaseCharacterController.new()
local self = setmetatable({}, BaseCharacterController)
self.enabled = false
self.moveVector = ZERO_VECTOR3
self.moveVectorIsCameraRelative = true
self.isJumping = false
return self
end
function BaseCharacterController:OnRenderStepped(dt: number)
-- By default, nothing to do
end
function BaseCharacterController:GetMoveVector(): Vector3
return self.moveVector
end
function BaseCharacterController:IsMoveVectorCameraRelative(): boolean
return self.moveVectorIsCameraRelative
end
function BaseCharacterController:GetIsJumping(): boolean
return self.isJumping
end
-- Override in derived classes to set self.enabled and return boolean indicating
-- whether Enable/Disable was successful. Return true if controller is already in the requested state.
function BaseCharacterController:Enable(enable: boolean): boolean
error("BaseCharacterController:Enable must be overridden in derived classes and should not be called.")
return false
end
return BaseCharacterController
And for some reason BaseCharacterController is able to print once instead of the amount of subclasses that are using BaseCharacterController.
Roblox's Subclass - Keyboard (which uses its basecharactercontroller)
--!nonstrict
--[[
Keyboard Character Control - This module handles controlling your avatar from a keyboard
2018 PlayerScripts Update - AllYourBlox
--]]
--[[ Roblox Services ]]--
local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
--[[ Constants ]]--
local ZERO_VECTOR3 = Vector3.new(0,0,0)
--[[ The Module ]]--
local BaseCharacterController = require(script.Parent:WaitForChild("BaseCharacterController"))
local Keyboard = setmetatable({}, BaseCharacterController)
Keyboard.__index = Keyboard
function Keyboard.new(CONTROL_ACTION_PRIORITY)
local self = setmetatable(BaseCharacterController.new() :: any, Keyboard)
self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY
self.textFocusReleasedConn = nil
self.textFocusGainedConn = nil
self.windowFocusReleasedConn = nil
self.forwardValue = 0
self.backwardValue = 0
self.leftValue = 0
self.rightValue = 0
self.jumpEnabled = true
return self
end
function Keyboard:Enable(enable: boolean)
if not UserInputService.KeyboardEnabled then
return false
end
if enable == self.enabled then
-- Module is already in the state being requested. True is returned here since the module will be in the state
-- expected by the code that follows the Enable() call. This makes more sense than returning false to indicate
-- no action was necessary. False indicates failure to be in requested/expected state.
return true
end
self.forwardValue = 0
self.backwardValue = 0
self.leftValue = 0
self.rightValue = 0
self.moveVector = ZERO_VECTOR3
self.jumpRequested = false
self:UpdateJump()
if enable then
self:BindContextActions()
self:ConnectFocusEventListeners()
else
self:UnbindContextActions()
self:DisconnectFocusEventListeners()
end
self.enabled = enable
return true
end
function Keyboard:UpdateMovement(inputState)
if inputState == Enum.UserInputState.Cancel then
self.moveVector = ZERO_VECTOR3
else
self.moveVector = Vector3.new(self.leftValue + self.rightValue, 0, self.forwardValue + self.backwardValue)
end
end
function Keyboard:UpdateJump()
self.isJumping = self.jumpRequested
end
function Keyboard:BindContextActions()
-- Note: In the previous version of this code, the movement values were not zeroed-out on UserInputState. Cancel, now they are,
-- which fixes them from getting stuck on.
-- We return ContextActionResult.Pass here for legacy reasons.
-- Many games rely on gameProcessedEvent being false on UserInputService.InputBegan for these control actions.
local handleMoveForward = function(actionName, inputState, inputObject)
self.forwardValue = (inputState == Enum.UserInputState.Begin) and -1 or 0
self:UpdateMovement(inputState)
return Enum.ContextActionResult.Pass
end
local handleMoveBackward = function(actionName, inputState, inputObject)
self.backwardValue = (inputState == Enum.UserInputState.Begin) and 1 or 0
self:UpdateMovement(inputState)
return Enum.ContextActionResult.Pass
end
local handleMoveLeft = function(actionName, inputState, inputObject)
self.leftValue = (inputState == Enum.UserInputState.Begin) and -1 or 0
self:UpdateMovement(inputState)
return Enum.ContextActionResult.Pass
end
local handleMoveRight = function(actionName, inputState, inputObject)
self.rightValue = (inputState == Enum.UserInputState.Begin) and 1 or 0
self:UpdateMovement(inputState)
return Enum.ContextActionResult.Pass
end
local handleJumpAction = function(actionName, inputState, inputObject)
self.jumpRequested = self.jumpEnabled and (inputState == Enum.UserInputState.Begin)
self:UpdateJump()
return Enum.ContextActionResult.Pass
end
-- TODO: Revert to KeyCode bindings so that in the future the abstraction layer from actual keys to
-- movement direction is done in Lua
ContextActionService:BindActionAtPriority("moveForwardAction", handleMoveForward, false,
self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterForward)
ContextActionService:BindActionAtPriority("moveBackwardAction", handleMoveBackward, false,
self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterBackward)
ContextActionService:BindActionAtPriority("moveLeftAction", handleMoveLeft, false,
self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterLeft)
ContextActionService:BindActionAtPriority("moveRightAction", handleMoveRight, false,
self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterRight)
ContextActionService:BindActionAtPriority("jumpAction", handleJumpAction, false,
self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterJump)
end
function Keyboard:UnbindContextActions()
ContextActionService:UnbindAction("moveForwardAction")
ContextActionService:UnbindAction("moveBackwardAction")
ContextActionService:UnbindAction("moveLeftAction")
ContextActionService:UnbindAction("moveRightAction")
ContextActionService:UnbindAction("jumpAction")
end
function Keyboard:ConnectFocusEventListeners()
local function onFocusReleased()
self.moveVector = ZERO_VECTOR3
self.forwardValue = 0
self.backwardValue = 0
self.leftValue = 0
self.rightValue = 0
self.jumpRequested = false
self:UpdateJump()
end
local function onTextFocusGained(textboxFocused)
self.jumpRequested = false
self:UpdateJump()
end
self.textFocusReleasedConn = UserInputService.TextBoxFocusReleased:Connect(onFocusReleased)
self.textFocusGainedConn = UserInputService.TextBoxFocused:Connect(onTextFocusGained)
self.windowFocusReleasedConn = UserInputService.WindowFocused:Connect(onFocusReleased)
end
function Keyboard:DisconnectFocusEventListeners()
if self.textFocusReleasedConn then
self.textFocusReleasedConn:Disconnect()
self.textFocusReleasedConn = nil
end
if self.textFocusGainedConn then
self.textFocusGainedConn:Disconnect()
self.textFocusGainedConn = nil
end
if self.windowFocusReleasedConn then
self.windowFocusReleasedConn:Disconnect()
self.windowFocusReleasedConn = nil
end
end
return Keyboard
How do I make it print once and make the properties shared?