【问题标题】:How does one go about making it so that you can grab other players by clicking on them whilst near them?如何做到这一点,以便您可以通过在附近点击其他玩家来抓住他们?
【发布时间】:2021-11-29 02:10:37
【问题描述】:

(从 DevForums 被禁止,在这里询问。)

基本上,当你成为“微笑者”时,一款名为 Infectious Smile 的游戏会有一个抓取系统。你持有它们的时间越长,它们转化的越多。当他们的生命值为 0 时,他们会变成一个微笑者,就像玩家转换他们一样,并转换其他人。

我将如何制作一个系统/抓取系统来做到这一点,但只是把它们变成布娃娃。 (显然,我想让玩家在一段时间后被丢弃!)

我也正在研究一个布娃娃系统,所以我没有被完全喂饱。

布偶脚本:完成!

抓取和布偶脚本:在点击时显示抓取动画,但不显示抓取。

抓取只播放动画的脚本(LocalScript)

local userInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")

local player = Players.LocalPlayer
local character = player.Character
if not character or not character.Parent then
    character = player.CharacterAdded:Wait()
end
local humanoid = character:WaitForChild("Humanoid")
local Animator = humanoid:WaitForChild("Animator")
local cooldown = false
local cooldowntime = 1

local grab = Instance.new("Animation")
grab.Name = "GrabAnim"
grab.AnimationId = "rbxassetid://7691396275"

game:GetService("ContentProvider"):PreloadAsync({grab})

local grabTrack = Animator:LoadAnimation(grab)
grabTrack.Priority = Enum.AnimationPriority.Core
grabTrack.Looped = false
grabTrack.Name = "GrabAnimTrack"

if(cooldown) then 
    wait(cooldowntime) 
    cooldown = false
    return
end


game:GetService("UserInputService").InputBegan:Connect(function(inputObject, gameProcessed)
    if inputObject.UserInputType == Enum.UserInputType.MouseButton1 then
        if(cooldown) then 
            return 
        end
        
        -- It'll return "Handle" half the time.
        local Head = player.Character.Head
        local D = Head.CFrame.LookVector * 10
        local RayCast = workspace:Raycast(Head.Position,D)
        if RayCast then
            script.Parent.Lock:FireServer(RayCast.Instance)
        end
        cooldown = true
        grabTrack:Play(0.09, 1, 1.2)
    end
    
    if(cooldown) then
        wait(cooldowntime)
        cooldown = false
        return
    end
end)

对于事件调用脚本(Script)

local event = script.Parent:WaitForChild('Lock')
local arrested = false
local weld

event.OnServerEvent:Connect(function(plr, Target)
    print(Target)
    if Target.Name == "UpperTorso" or Target.Name == "LowerTorso" or Target.Parent:IsA("Accessory") then
        arrested = true
        coroutine.wrap(function()
            while arrested == true do
                Target.Anchored = true
                Target.CFrame = plr.Character.HumanoidRootPart.CFrame * CFrame.new(0,0,-3)
                wait()
            end
        end)()
    elseif arrested then
        arrested = false
    end
end)

抓取似乎返回“Handle”而不是“UpperTorso”,所以我不知道如何摆脱手柄的东西。

【问题讨论】:

    标签: lua roblox


    【解决方案1】:

    您已经准备好许多必要的部件。整个问题主要是确保事件链正确发生,并观察到不同的事件。

    您缺少的主要部分是如何“抓住”另一个玩家。而要做到这一点,你所要做的就是:

    • 在抓取器和被抓取物之间创建焊缝或约束。
    • 将抓取的人形机器人的状态设置为 Ragdoll 或 Physics。
    • 设置抓取人物模型的网络所有权给服务器。

    这应该足以让你能够操纵其他玩家的角色。重要的是要知道这些事件需要在哪里处理。有些东西,比如动画,需要在客户端播放,而其他的东西,比如碰撞检测,需要由服务器来管理。

    以下是您问题中每个操作的细分:

    Event Action Client-side Server-side
    A player on the Smiler team clicks their mouse... spammed input is debounced x
    an animation plays x
    collisions with players on the other team are detected x
    a cooldown starts to debounce additional clicks x
    A player is touched... their controls are disabled x
    their character ragdolls x x
    a constraint between the grabber and the player is created x
    network ownership over their character is given to the grabber x
    their health decreases x
    they are released after a little while x
    A player's health decreases... if it drops too low, they switch teams x
    A player is released from the grab... their character no longer ragdolls x x
    the constraint connecting them to the grabber is deleted x
    network ownership is returned to them x
    they regain control of their character x

    流程中的每个步骤本身并不困难,但如果将它们放在一起,可能会有些混乱。因此,让我们深入研究一下它可能是什么样子。

    此示例的工作区如下所示:

    Workspace Notes
    - In ReplicatedStorage, there are two RemoteEvents for communicating between the server and client. There is an Animation instance holding onto the grab animation.

    - In ServerScriptService, there is a Script hosting the server-side logic.

    - In StarterPlayer > StarterCharacterScripts, there is a LocalScript hosting the client-side logic.

    - In Teams, there are two Team objects defined, one for the Smilers and one for the other team.

    在本地脚本中:

    -- Services
    local ContextActionService = game:GetService("ContextActionService")
    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local Teams = game:GetService("Teams")
    
    -- Remote Events
    local GrabPlayerEvent = ReplicatedStorage:FindFirstChild("GrabPlayerEvent")
    local TogglePlayerControlsEvent = ReplicatedStorage:FindFirstChild("TogglePlayerControlsEvent")
    
    -- Animations
    local GrabAnimation = ReplicatedStorage:FindFirstChild("GrabAnimation")
    
    -- define some variables
    local player = Players.LocalPlayer
    local playerControlModule = require(player.PlayerScripts.PlayerModule)
    local cooldown = false
    local COOLDOWN_TIMER = 2.5 --seconds
    -- TODO : implement a UI so that people can see this cooldown
    
    -- load the animation
    local humanoid = player.Character.Humanoid
    local animator = humanoid:FindFirstChildOfClass("Animator")
    if not animator then
        error("Could not find Animator on character model")
    end
    
    local animationTrack = animator:LoadAnimation(GrabAnimation)
    animationTrack.Looped = false
    animationTrack.Stopped:Connect(function()
        -- tell the server that we're done playing the animation
        GrabPlayerEvent:FireServer(false)
    end)
    
    
    function onAction(actionName, inputState, inputObject)
        -- escape if the character doesn't exist
        if player.Character == nil then
            return
        end
        
        -- escape if the player isn't on the Smiler team
        if player.TeamColor ~= Teams.Smilers.TeamColor then
            return
        end
        
        -- wait for the mouseUp event
        if inputState ~= Enum.UserInputState.End then
            return
        end
        
        -- escape if on cooldown
        if cooldown then
            return
        end
        
        -- Play the animation, and tell the server we're doing it   
        GrabPlayerEvent:FireServer(true)
        animationTrack:Play()
        
    
        -- start the cooldown
        cooldown = true
        wait(COOLDOWN_TIMER)
        cooldown = false
    end
    
    -- Listen for Mouse clicks to know when to play the grab animation
    game.ContextActionService:BindAction("Grab", onAction, false, Enum.UserInputType.MouseButton1)
    
    
    -- Listen for when the server demands that we enable/disable player controls 
    TogglePlayerControlsEvent.OnClientEvent:Connect(function(enabled)
        -- disable player controls
        local controls = playerControlModule:GetControls()
        if enabled then
            controls:Enable()
        else
            controls:Disable()
        end
        
        -- ragdoll the character
        local character = player.Character
        if not character then
            return
        end
        local humanoid = character.Humanoid
    
        if not enabled then
            humanoid:ChangeState(Enum.HumanoidStateType.Physics)
        else
            humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
        end
    end)
    

    并在服务器脚本中:

    -- Services
    local Players = game:GetService("Players")
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local Teams = game:GetService("Teams")
    
    -- script connections
    local animationConnections = {} -- array<playerId, array<RBXScriptConnection>>
    
    -- remote events
    local GrabPlayerEvent = ReplicatedStorage:FindFirstChild("GrabPlayerEvent")
    local TogglePlayerControlsEvent = ReplicatedStorage:FindFirstChild("TogglePlayerControlsEvent")
    
    -- helper functions
    
    local function constrainTwoParts(partA, partB, constraintType)
        local a0, a1 = Instance.new("Attachment"), Instance.new("Attachment")
        a0.Parent = partA
        a1.Parent = partB
    
        local b = Instance.new(constraintType)
        b.Attachment0 = a0
        b.Attachment1 = a1
        b.Parent = game.Workspace
        
        return b
    end
    
    local function destroyConstraintsAndAttachments(constraint)
        constraint.Enabled = false
        constraint.Attachment0:Destroy()
        constraint.Attachment1:Destroy()
        constraint:Destroy()
    end
    
    local function createPlayerKey(player)
        -- helper function to ensure dictionary key consistency
        return tostring(player.UserId)
    end
    
    local function getPlayerHands(characterModel)
        -- find the player's hands, regardless of R6, R15, or Rthro model type
        local hands = {}
        local expectedHandNames = { "LeftHand", "LeftArm", "RightHand", "RightArm" }
        for _, partName in ipairs(expectedHandNames) do
            local hand = characterModel:FindFirstChild(partName, false)
            if hand then
                table.insert(hands, hand)
            end
        end
        
        return hands
    end
    
    local function toggleNetworkOwnership(characterModel, isEnabled, owner)
        for _, child in ipairs(characterModel:GetDescendants()) do
            if child:IsA("BasePart") then
                if child.Anchored then
                    child.Anchored = false
                end
                
                if isEnabled then
                    child.Massless = false
                    child:SetNetworkOwner(owner)
                else
                    child.Massless = true
                    child:SetNetworkOwner(nil)
                end
            end
        end
    end
    
    local function enableRagdoll(characterModel, player, enabled)
        if enabled then
            if player then
                -- disable the opponent's controls temporarily and ragdoll them
                TogglePlayerControlsEvent:FireClient(player, false)
                
                -- set the network ownership to the server to allow better ragdolling
                toggleNetworkOwnership(characterModel, false, player)
            end
            
            -- create a bunch of temporary attachments and constraints so they flop around
            for _, v in pairs(characterModel:GetDescendants()) do  --ragdoll
                if v:IsA("Motor6D") then
                    local a0, a1 = Instance.new("Attachment"), Instance.new("Attachment")
                    a0.CFrame = v.C0
                    a1.CFrame = v.C1
                    a0.Parent = v.Part0
                    a1.Parent = v.Part1
    
                    local b = Instance.new("BallSocketConstraint")
                    b.Attachment0 = a0
                    b.Attachment1 = a1
                    b.Parent = v.Part0
    
                    -- disable the existing character motors
                    v.Enabled = false
                end
            end
        else
            -- remove the ragdoll constraints and connections
            for _,v in pairs(characterModel:GetDescendants()) do  --unragdoll
                if v:IsA('Motor6D') then
                    v.Enabled = true
                end
                if v.Name == 'BallSocketConstraint' then
                    v:Destroy()
                end
                if v.Name == 'Attachment' then
                    v:Destroy()
                end
            end
            
            if player then
                -- tell the player to stop ragdolling and restore controls
                TogglePlayerControlsEvent:FireClient(player, true)
                
                -- return network ownership to the original player
                toggleNetworkOwnership(characterModel, true, player)
            end
        end
    end
    
    local function stopObservingHands(player)
        -- clean up the listeners on the player's hands
        local playerKey = createPlayerKey(player)
        for _, connection in ipairs(animationConnections[playerKey]) do
            connection:Disconnect()
        end
    end
    
    local function createHandTouchListener(hand, player)
        return function(otherPart)
            local character = otherPart.Parent
            local humanoid = character:FindFirstChild("Humanoid")
            if humanoid then
                local otherPlayer = Players:GetPlayerFromCharacter(character)
                
                if otherPlayer then
                    -- check what team the other player is so you don't grab people on your own team
                    if otherPlayer.Team == Teams.Smilers then
                        return
                    end
                end
                
                -- disable the collision detectors on the hands
                stopObservingHands(player)
                
                -- ragdoll the other player
                enableRagdoll(character, otherPlayer, true)
                    
                -- add a temporary weld between the hand and the otherPart so we grab them
                local constraint = constrainTwoParts(hand, otherPart, "RodConstraint")
                constraint.Enabled = true
                constraint.Length = constraint.CurrentDistance
                constraint.Thickness = 2
                
                -- TODO : figure out how long to hold on here
                -- define some constants
                local TIME_TO_HOLD_PLAYER = 5.0 -- seconds
                local DAMAGE_PER_TICK = 1.0
                local TICKS_PER_SECOND = 5.0
                local TIME_TO_WAIT = 1.0 / TICKS_PER_SECOND
                local TEAM_SWITCH_THRESHOLD = 0 -- health
                
                -- start holding the other player and subtracting health
                local startingTime = tick()
                local timePassed = 0.0
                local otherPlayerDead = false
                while (timePassed < TIME_TO_HOLD_PLAYER and otherPlayerDead == false) do
                    timePassed = tick() - startingTime
                    
                    -- every tick subtract some health from the opponent
                    local humanoid = character.Humanoid
                    if humanoid then
                        -- if this tick will kill the player, release the weld so we also don't die
                        if humanoid.Health - DAMAGE_PER_TICK <= TEAM_SWITCH_THRESHOLD then
                            destroyConstraintsAndAttachments(constraint)
                            constraint = nil
                        end
    
                        humanoid:TakeDamage(DAMAGE_PER_TICK)
                        
                        -- check if the other player should switch teams
                        if (humanoid.Health <= TEAM_SWITCH_THRESHOLD) then
                            -- change the player's team to be the smilers
                            if otherPlayer then
                                otherPlayer.Team = Teams.Smilers
                            end
                            otherPlayerDead = true
                            break
                        end
                    else
                        otherPlayerDead = true
                        break
                    end
                    
                    wait(TIME_TO_WAIT)
                end
    
                -- let go of the other player
                if constraint then
                    destroyConstraintsAndAttachments(constraint)
                end
    
                -- tell the other player to stand up
                enableRagdoll(character, otherPlayer, false)
            end
        end
    end
    
    -- Listen for when the client starts to do the grab animation
    GrabPlayerEvent.OnServerEvent:Connect(function(player, isStart)
        local playerKey = createPlayerKey(player)
        local playerCharacterModel = player.Character
        if playerCharacterModel == nil then
            error(string.format("%s's character model should exist, but it doesn't", player.Name))
        end
        
        -- if this is the start of the animation...
        if isStart then
            -- set up listeners in case we touch the other players
            for _, hand in ipairs(getPlayerHands(playerCharacterModel)) do
                local touchListener = createHandTouchListener(hand, player)
                local connection = hand.Touched:Connect(touchListener)
                table.insert(animationConnections[playerKey], connection)
            end
        else
            stopObservingHands(player)
        end
    end)
    
    -- listen for when players join to initialize some variables
    Players.PlayerAdded:Connect(function(player)
        -- add an entry in the connections and animation tables for this player
        local key = createPlayerKey(player)
        animationConnections[key] = {}
        
        player.CharacterAdded:Connect(function(character)
            character.Humanoid.BreakJointsOnDeath = false
            character.Humanoid.RequiresNeck = false
        end)
    end)
    
    -- listen for when players leave to clean up some variables
    Players.PlayerRemoving:Connect(function(player)
        -- clean up any connections
        local key = createPlayerKey(player)
        
        for _, connection in ipairs(animationConnections[key]) do
            connection:Disconnect()
        end
    end)
    

    【讨论】:

    • 对不起,我下线了几个星期,我希望我能给你这个声誉。
    • 出于某种原因,当从这里解开布娃娃时,玩家的跳跃和所有动画看起来都非常混乱。有什么修复吗?
    • 在 Studio 中使用 2 个播放器进行本地测试时,我没有看到这一点。控制台有错误吗?
    • 不。没有错误。
    • 我现在将它设置为绳索约束,但是当玩家被抓住时,他们会飞到空中,我的坠落伤害会杀死他们和其他玩家。我再次输入了代码,它仍然可以。为什么?
    【解决方案2】:

    配件内部是一个把手。因此,如果您不想要手柄,只需删除 part.Parent:IsA("Accessory")。此外,如果您希望光线投射忽略或直接通过附件,则首先制作一个获取所有附件的表格,然后制作一个光线投射参数并将该表格添加到 FilterDescendantsInstances。确保 FilterType 是 Blacklist(它的默认值,所以不用担心)。或者,如果您想确定头部是否看着配件,然后选择躯干,然后首先测试是否有躯干。如果有,则将其设为local upperTorso = Target.Parent.Parent.UpperTorso

    【讨论】:

      【解决方案3】:

      我是这里的初学者,但我使用 love2d 并且注意到当我调用 mouse:IsDown() 时它会多次调用我的函数,因为它捕获了鼠标按钮被按下的帧数。

      于是我做了一个函数:

      Leftclick = 1
      
      function mouseClicker ()
      if mouse:IsDown() then
      
      Leftclick = Leftclick -1 
      else
      
       Leftclick =1 
      end 
      

      当我使用这个时:

      if Leftclick ==0 then
      
      end 
      

      它可以防止很多疯狂。也许你可以使用类似的东西

      if leftclick &lt; 0 then

      我不知道这是否有助于解决垃圾邮件问题 你提到了。

      【讨论】:

      • 这在 roblox 中可用吗?
      • 我知道 Love 中的 mousepressed 但是你必须在 mousepressed 函数中调用该函数,这可能很乏味。鼠标点击方式只是增加了轻松
      • 我之前没有使用过 roblox,但我确信有一些类似的功能可以跟踪鼠标点击
      猜你喜欢
      • 2018-08-28
      • 2022-01-18
      • 2020-05-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-14
      相关资源
      最近更新 更多