You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Roblox中制作复刻《阳光马里奥》物理与操控的角色控制器?

问题

我正在开发一款名为《Super Tropical Blox》的Roblox游戏,目标是复刻《Super Mario Sunshine》的物理效果与操控手感,但完全不知道如何制作对应的角色控制器。我了解Robot 64等游戏有类似马里奥风格的控制器,但它们的手感过于浮空、重力不足,我希望尽可能贴近《阳光马里奥》的体验,且不愿使用开源资源。

我曾尝试用VectorForce搭配LocalScript与服务器通信的方案,以及常规Humanoid方案,但均不满意;现有StarterCharacterScripts中的WIP输入与移动脚本如下:

现有LocalScript:

wait(2)
doact = script.Parent.Script.DoAction --I should put this in ReplicatedStorage :/
uis = game:GetService("UserInputService")
uis.InputBegan:Connect(function(key)
    if key.KeyCode == Enum.KeyCode.Z then
        j = script.Parent.Humanoid:LoadAnimation(script.Animation)
        j.Priority = Enum.AnimationPriority.Action
        j:Play()
        doact:FireServer("Dive")
        wait(0.7)
        repeat wait() until script.Parent.Humanoid.StateChanged
        j:Stop()
    end
    if key.KeyCode == Enum.KeyCode.E then
        doact:FireServer("FLUDD")
    end
    if key.KeyCode == Enum.KeyCode.Q then
        doact:FireServer("FLUDDswap")
    end
    if key.KeyCode == Enum.KeyCode.LeftControl then
        doact:FireServer("Crawl")
    end
end)

现有Script:

VectorForce = script.Parent.HumanoidRootPart.VectorForce
HRootPart = script.Parent.HumanoidRootPart
script.DoAction.OnServerEvent:Connect(function(player, action)
    print(action)
    if action == "Dive" then
        script.Parent.Humanoid.Sit = true
        VectorForce.Force = HRootPart.CFrame.LookVector * 1500 + Vector3.new(0,100,0)
        script.Disabled = true
        wait(0.7)
        script.Disabled = false
        script.Parent.Humanoid.Sit = false
        VectorForce.Force = Vector3.new()
    end
end)

我希望用ModuleScript优化这些脚本以避免代码冗余,现寻求符合需求的角色控制器实现方案。


解决方案

核心设计思路

  • 物理层面:放弃Humanoid默认移动逻辑,用自定义物理控制器直接操控HumanoidRootPart,精准调整重力、加速度、摩擦力,匹配《阳光马里奥》的扎实手感
  • 输入处理:客户端全权处理输入检测与动画播放,仅将必要动作授权同步到服务器,降低延迟
  • 模块化拆分:用ModuleScript分离输入管理、物理控制、服务器动作处理三个核心模块,消除冗余代码

第一步:重构为ModuleScript架构

1. 客户端输入模块(ReplicatedStorage/ClientInputModule)

local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemoteEvent = ReplicatedStorage:WaitForChild("CharacterActions")

local ClientInput = {}
ClientInput.ActionBindings = {
    Dive = Enum.KeyCode.Z,
    FLUDD = Enum.KeyCode.E,
    FLUDDswap = Enum.KeyCode.Q,
    Crawl = Enum.KeyCode.LeftControl
}

local AnimationCache = {}

function ClientInput:Init(character)
    local humanoid = character:WaitForChild("Humanoid")
    
    -- 预加载动画
    AnimationCache.Dive = humanoid:LoadAnimation(character:WaitForChild("DiveAnimation"))
    AnimationCache.Dive.Priority = Enum.AnimationPriority.Action
    
    -- 输入监听
    UserInputService.InputBegan:Connect(function(input, gameProcessed)
        if gameProcessed then return end
        
        for actionName, keycode in pairs(self.ActionBindings) do
            if input.KeyCode == keycode then
                self:TriggerAction(actionName, character)
                break
            end
        end
    end)
end

function ClientInput:TriggerAction(actionName, character)
    local humanoid = character:WaitForChild("Humanoid")
    
    if actionName == "Dive" then
        AnimationCache.Dive:Play()
        RemoteEvent:FireServer("Dive")
        
        -- 监听状态变化终止动画
        local stateConn = humanoid.StateChanged:Connect(function(_, newState)
            if newState ~= Enum.HumanoidStateType.Seated then
                AnimationCache.Dive:Stop()
                stateConn:Disconnect()
            end
        end)
        task.wait(0.7)
        stateConn:Disconnect()
        AnimationCache.Dive:Stop()
    else
        RemoteEvent:FireServer(actionName)
    end
end

return ClientInput

2. 客户端角色控制器(StarterCharacterScripts/CharacterController)

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local ClientInput = require(ReplicatedStorage:WaitForChild("ClientInputModule"))

local character = script.Parent
local humanoid = character:WaitForChild("Humanoid")
local rootPart = character:WaitForChild("HumanoidRootPart")

-- 禁用Humanoid默认移动,启用自定义物理
humanoid:SetStateEnabled(Enum.HumanoidStateType.Walking, false)
humanoid:SetStateEnabled(Enum.HumanoidStateType.Running, false)
humanoid.GravityScale = 1.2 -- 增加重力,减少浮空感

-- 初始化输入模块
ClientInput:Init(character)

-- 自定义地面移动逻辑
local runSpeed = 16
local acceleration = 50
local currentVelocity = Vector3.new()

RunService.Heartbeat:Connect(function(deltaTime)
    local moveDir = humanoid.MoveDirection
    if moveDir.Magnitude > 0 then
        local targetVelocity = moveDir * runSpeed
        currentVelocity = currentVelocity:Lerp(targetVelocity, acceleration * deltaTime)
    else
        currentVelocity = currentVelocity * 0.7 -- 地面摩擦力,模拟即时停步
    end
    
    -- 仅在接地时应用移动速度
    if humanoid.FloorMaterial ~= Enum.Material.Air then
        rootPart.AssemblyLinearVelocity = Vector3.new(currentVelocity.X, rootPart.AssemblyLinearVelocity.Y, currentVelocity.Z)
    end
end)

3. 服务器动作处理模块(ServerScriptService/CharacterActionHandler)

local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- 创建全局动作同步事件
local RemoteEvent = Instance.new("RemoteEvent")
RemoteEvent.Name = "CharacterActions"
RemoteEvent.Parent = ReplicatedStorage

local ServerActionHandler = {}

function ServerActionHandler:HandleDive(player, character)
    local humanoid = character:WaitForChild("Humanoid")
    local rootPart = character:WaitForChild("HumanoidRootPart")
    
    -- 创建临时VectorForce,避免复用导致状态残留
    local diveForce = Instance.new("VectorForce")
    diveForce.Force = rootPart.CFrame.LookVector * 1600 + Vector3.new(0, 80, 0) -- 调整参数增强贴地感
    diveForce.Attachment0 = rootPart:WaitForChild("RootAttachment")
    diveForce.Parent = rootPart
    
    humanoid.Sit = true
    task.wait(0.7)
    humanoid.Sit = false
    diveForce:Destroy()
end

-- 绑定事件处理
RemoteEvent.OnServerEvent:Connect(function(player, actionName)
    local character = player.Character
    if not character then return end
    
    if actionName == "Dive" then
        ServerActionHandler:HandleDive(player, character)
    elseif actionName == "FLUDD" then
        -- 实现FLUDD喷射逻辑
    elseif actionName == "FLUDDswap" then
        -- 实现FLUDD切换逻辑
    elseif actionName == "Crawl" then
        -- 实现爬行状态切换逻辑
    end
end)

return ServerActionHandler

第二步:优化《阳光马里奥》风格手感

  • 重力调整:将Humanoid.GravityScale设为1.2,比默认值稍高,减少浮空感
  • 落地反馈:在角色落地时添加微小向上力,模拟马里奥的弹性落地效果
  • 动作状态互斥:在输入模块中添加状态检查(如潜水时无法触发爬行、FLUDD动作),避免动作冲突
  • 潜水物理优化:减少潜水时的向上力参数,让动作更贴地,符合原作的俯冲感

内容的提问来源于stack exchange,提问作者DoTheDarkoSwing

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 00:14:54