Handling Input & Core Mechanics & Character States separately

For Context

My current project is a Hajime no Ippo inspired boxing simulation game, where the player can choose one of the few boxing gyms across the map, train their stats through mini-games, develop their own boxing style and eventually, become either a professional boxer and aim for the belts or become a coach for other players.

An ambitious project that’s clearly way more than I could hope to chew, yet I still believe I could learn from diving head-first into this.

Since the character will have multiple states (walking, jogging, running, dashing, boxing, training) along with a few states that are only allowed under other states (such as bobbing/weaving, guarding the body or guarding the head while under the boxing state), along with the possibility of specific state sharing the same keybinds/input, I figured the best approach would be having a single script/module script that handles Input while each specific mechanic/feature has a script of its own.
image

However, given that I’m not at all a scripter yet, my scripts quickly became overwhelming and confusing, and I’m not particularly aware of the best practices for even planning a big, ambitious project.
I AM going to delete things and start all over again.

I’d love being pushed into the right direction so I can start such a project in a way that makes it easier for me to manage, and expand upon. Any advice is appreciated.
I’ll leave the three first scripts below, as the problem most likely lies there.

-- Input Handler [LocalScript]
local Players = game:GetService("Players");
local UserInputService = game:GetService("UserInputService");

local player = Players.LocalPlayer;
local character = player.Character or player.CharacterAdded:Wait();
local humanoid = character:WaitForChild("Humanoid");

local DefaultMovement = require(script.DefaultMovement);
local isWalking = false;
local isJogging = false;
local isRunning = false;
local isDashing = false;
local currentTick = 0;
local lastTick = currentTick;
local tickWindow = 0.8;

local BoxingState = require(script.BoxingState);
local isBoxing = false;

UserInputService.InputBegan:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.W and tick() - lastTick <= tickWindow then
		-- Check for double-tapping FIRST in order to avoid going back and forth
		isWalking = false;
		isJogging = false;
		isDashing = false;
		isRunning = true;
		warn("Is " .. player.Name .. " running?");
		DefaultMovement.StartRunning();
	elseif isWalking == false and input.KeyCode == Enum.KeyCode.W then
		lastTick = tick();
		isWalking = true;
		warn("Is " .. player.Name .. " walking?");
		DefaultMovement.StartWalking();
		--
		lastTick = tick();
	elseif isWalking and input.KeyCode == Enum.KeyCode.LeftShift then
		isWalking = false;
		isJogging = true;
		warn("Is " .. player.Name .. " jogging?");
		DefaultMovement.StopWalking();
		DefaultMovement.StartJogging();
		--
	elseif isRunning and input.KeyCode == Enum.KeyCode.LeftShift then
		isWalking = false;
		isJogging = false;
		isRunning = false;
		isDashing = true;
		warn("Is " .. player.Name .. " dashing?");
		DefaultMovement.StartDashing();
	elseif not isBoxing and input.KeyCode == Enum.KeyCode.B then
		isWalking = false;
		isJogging = false;
		isRunning = false;
		isDashing = false;
		isBoxing = true;
		warn("Is " .. player.Name .. " boxing?");
		BoxingState.StartBoxing();
	elseif isBoxing and input.KeyCode == Enum.KeyCode.B then
		isBoxing = false;
		warn(player.Name .. " stopped boxing.");
		BoxingState.StopBoxing();
	end 
end)

UserInputService.InputEnded:Connect(function(input)
	if isWalking and input.KeyCode == Enum.KeyCode.W then
		isWalking = false;
		warn(player.Name .. " will stop walking.");
		DefaultMovement.StopWalking();
	elseif isJogging and input.KeyCode == Enum.KeyCode.LeftShift then
		isWalking = true;
		isJogging = false;
		warn("——" .. player.Name .. " will stop jogging.");
		DefaultMovement.StopJogging();
	elseif not isJogging and input.KeyCode == Enum.KeyCode.W then
		warn("———" .. player.Name .. " will stop running.");
		DefaultMovement.StopRunning();
	elseif isRunning and input.KeyCode == Enum.KeyCode.W then
		isWalking = false;
		isJogging = false;
		isRunning = true;
		isDashing = false;
		warn("———" .. player.Name .. " will stop dashing.");
		DefaultMovement.StopDashing();
	end
end)
-- DefaultMovement [ModuleScript]
local DefaultMovement = {}

local Players = game:GetService("Players");
local player = Players.LocalPlayer;
local character = player.Character or player.CharacterAdded:Wait();
local humanoid = character:WaitForChild("Humanoid");

local walkingSpeed = 12;
local joggingSpeed = 18
local runningSpeed = 24;
local dashingSpeed = 30;

humanoid.WalkSpeed = walkingSpeed;

local walkingState = false;
local joggingState = false;
local runningState = false;
local dashingState = false;

function DefaultMovement.StartWalking()
	walkingState = true;
	print(player.Name .. " is walking.");
	humanoid.WalkSpeed = walkingSpeed;
end

function DefaultMovement.StopWalking()
	walkingState = false;
	print(player.Name .. " stopped walking.");
	humanoid.WalkSpeed = walkingSpeed;
end


function DefaultMovement.StartJogging()
	joggingState = true;
	print(player.Name .. " is jogging.");
	humanoid.WalkSpeed = joggingSpeed;
end

function DefaultMovement.StopJogging()
	joggingState = false;
	print(player.Name .. " stopped jogging.");
	humanoid.WalkSpeed = walkingSpeed;
end

function DefaultMovement.StartRunning()
	runningState = true;
	print(player.Name .. " is running.");
	humanoid.WalkSpeed = runningSpeed;
end

function DefaultMovement.StopRunning()
	runningState = false;
	print(player.Name .. " stopped running.");
	humanoid.WalkSpeed = joggingSpeed;
end

function DefaultMovement.StartDashing()
	if runningState then
		dashingState = true;
		print(player.Name .. " is Dashing.");
		humanoid.WalkSpeed = runningSpeed;
	end
end

function DefaultMovement.StopDashing()
	dashingState = false;
	print(player.Name .. " stopped dashing.");
	humanoid.WalkSpeed = walkingSpeed;
end

return DefaultMovement
-- BoxingState [ModuleScript]
local BoxingState = {}

local Players = game:GetService("Players");
local RunService = game:GetService("RunService");

local player = Players.LocalPlayer;
local character = player.Character or player.CharacterAdded:Wait();
local humanoid = character:WaitForChild("Humanoid");
local humanoidRootPart = humanoid.RootPart or humanoid:WaitForChild("HumanoidRootPart");

local DefaultMovement = require(script.Parent.DefaultMovement);

local inBoxingState = false;

local function instantiateStepPart()
	stepPart = Instance.new("Part");
	stepPart.Size = Vector3.new(1,1,3);
	stepPart.BrickColor = BrickColor.new("Br. yellowish green");
	stepPart.Material = Enum.Material.Neon;
	stepPart.Parent = workspace;
	stepPart.CanCollide = false;
	stepPart.Anchored = true;
	RunService.RenderStepped:Connect(function()
		stepPart.CFrame = humanoidRootPart.CFrame;
		stepPart.Orientation = humanoidRootPart.Orientation;
	end)
end

function BoxingState.BoxingMovement()
	if inBoxingState then
		instantiateStepPart();
	end
end

function BoxingState.StartBoxing()
	inBoxingState = true;
	DefaultMovement.StopWalking();
	DefaultMovement.StopJogging();
	DefaultMovement.StopRunning();
	DefaultMovement.StopDashing();
	humanoid.WalkSpeed = 0;
	print(player.Name .. " is currently boxing!");
	BoxingState.BoxingMovement();
end

function BoxingState.StopBoxing()
	inBoxingState = false;
	print(player.Name .. " is not boxing!");
	stepPart:Destroy();
end

return BoxingState

1 Like