Untitled

Anonymous
plain_text
02/20/2026 5:12 PM
5.3 KB
19
Indexable
local ContextActionService = game:GetService("ContextActionService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local GuiService = game:GetService("GuiService")

local player = Players.LocalPlayer
local mouse = player:GetMouse()

-- Tuning
local MAX_SPEED, ACCEL, DRAG = 200, 200, 0.5
local TURN_RESP, TURN_180 = 20, true
local WALL_CHECK_DIST = 5.5
local STICK_RADIUS = 150
local DEADZONE = 10

local moving, vel = false, Vector3.zero

-- Mobile UI Setup
local screenGui = player:WaitForChild("PlayerGui"):WaitForChild("MobileControls")
local Stick = screenGui:WaitForChild("Stick") 

-- AUTO-HIDE UI FOR PC
local function updateUI()
	if UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled then
		screenGui.Enabled = true
	else
		screenGui.Enabled = false
	end
end
updateUI()

local function getChar()
	local char = player.Character
	return char, char and char:FindFirstChild("HumanoidRootPart")
end

local function canControl(char, hrp)
	return char and hrp and char.Parent == workspace and not char:GetAttribute("Dead")
end

-- PC Keybinds
ContextActionService:BindAction("Move", function(_, state)
	moving = (state == Enum.UserInputState.Begin)
end, false, Enum.KeyCode.W, Enum.UserInputType.MouseButton1)

-- Wall slide logic
local function applyWallSlide(hrp, currentVel)
	if currentVel.Magnitude < 0.1 then return Vector3.zero end
	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Exclude
	params.FilterDescendantsInstances = {hrp.Parent, workspace:FindFirstChild("Enemies"), workspace:FindFirstChild("Projectiles")}

	local result = workspace:Raycast(hrp.Position, currentVel.Unit * WALL_CHECK_DIST, params)
	if result and math.abs(result.Normal.Y) < 0.5 then
		local dot = currentVel:Dot(result.Normal)
		if dot < 0 then
			currentVel = currentVel - (result.Normal * dot)
			currentVel *= 0.9
		end
	end
	return Vector3.new(currentVel.X, 0, currentVel.Z)
end

RunService.RenderStepped:Connect(function(dt)
	local char, hrp = getChar()
	if not canControl(char, hrp) then return end

	local moveDirection = Vector3.zero
	local isTouch = UserInputService.TouchEnabled

	-- INPUT LOGIC
	if not isTouch or UserInputService.KeyboardEnabled then
		-- PC: Use Mouse for Rotation
		local target = Vector3.new(mouse.Hit.X, hrp.Position.Y, mouse.Hit.Z)
		local dir = (target - hrp.Position)
		if dir.Magnitude > 0.1 then
			local yRot = math.atan2(dir.X, dir.Z)
			local targetRot = CFrame.new(hrp.Position) * CFrame.Angles(0, yRot + (TURN_180 and math.pi or 0), 0)
			hrp.CFrame = hrp.CFrame:Lerp(targetRot, math.clamp(TURN_RESP * dt, 0, 1))
		end
		if moving then moveDirection = hrp.CFrame.LookVector end
	else
		-- MOBILE: Use Stick
		local inset = GuiService:GetGuiInset()
		local rawTouchPos = UserInputService:GetMouseLocation()
		local touchPos = rawTouchPos - inset

		local stickCenter = Stick.AbsolutePosition + (Stick.AbsoluteSize / 2)
		local diff = Vector2.new(touchPos.X - stickCenter.X, touchPos.Y - stickCenter.Y)
		local isPressing = UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1)

		-- Only move if pressing and outside deadzone
		if isPressing and diff.Magnitude > DEADZONE and diff.Magnitude < STICK_RADIUS then 
			-- Normalized 270-degree rotation logic
			moveDirection = Vector3.new(-diff.Y, 0, diff.X).Unit

			local yRot = math.atan2(-diff.Y, diff.X) 
			local targetRot = CFrame.new(hrp.Position) * CFrame.Angles(0, yRot + math.pi, 0)
			hrp.CFrame = hrp.CFrame:Lerp(targetRot, math.clamp(TURN_RESP * dt, 0, 1))
		end
	end

	-- Movement Physics
	if moveDirection.Magnitude > 0 then
		vel += moveDirection * (ACCEL * dt)
	end

	vel -= vel * math.clamp(DRAG * dt, 0, 1)

	if vel.Magnitude > MAX_SPEED then vel = vel.Unit * MAX_SPEED end

	vel = applyWallSlide(hrp, vel)
	hrp.AssemblyLinearVelocity = Vector3.new(vel.X, 0, vel.Z)
	hrp.AssemblyAngularVelocity = Vector3.zero 
end)
Editor is loading...
Leave a Comment