你如何发射沿弯曲角度跟随的射弹

Posted

技术标签:

【中文标题】你如何发射沿弯曲角度跟随的射弹【英文标题】:How do you fire a projectile that follows along a curved angle 【发布时间】:2021-10-25 18:27:27 【问题描述】:

我希望我的一个射弹在击中目标之前遵循一个拱形路径。不管我怎么想,我就是无法绕开它。任何帮助将不胜感激

【问题讨论】:

【参考方案1】:

Bezier Curves 是制作有趣且平滑的弧线和路径的好方法。

贝塞尔曲线计算本质上只是花哨的加权平均函数:传入 0 时得到起点,传入 1 时得到终点。介于两者之间的所有值都是您的弹丸将要经过的路径。

因此,知道了弹丸的速度,我们就可以测量它在一段时间内行进了多远。如果我们将该距离与贝塞尔曲线的长度进行比较,我们可以得出一个介于 0 和 1 之间的比率。使用该比率,我们可以将其代入贝塞尔曲线计算并获得弹丸的位置。

但是三次贝塞尔曲线需要额外的信息。您需要起点和终点,还需要选择两个额外的控制点来定义曲线。起点和终点很容易,可以是玩家的位置以及他们瞄准的任何地方。并且使用CFrame 数学,您可以将控制点设置为相对于起点和终点的直角。

作为一个小的工作示例,我制作了一个简单的工具,只要你点击它就会产生一个射弹。

在工具的 LocalScript 中:

script.Parent.Activated:Connect(function()
    -- choose a spot in front of the player
    local player = game.Players.LocalPlayer
    local playerCFrame = player.Character:GetPrimaryPartCFrame()
    playerCFrame += playerCFrame.LookVector * 2.0

    -- choose the target based on where they clicked
    local target = player:GetMouse().Hit
    
    -- choose how much to rotate the control points around the player and target.
    -- set this to zero to always fire to the right
    local rotation = math.random(0, 180)
    
    -- tell the server to spawn a projectile
    game.ReplicatedStorage.RemoteEvent:FireServer(playerCFrame.Position, target.Position, rotation) 
end)

然后,在 ServerScriptService 的脚本中,监听 RemoteEvent 的触发:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local BULLET_VELOCITY = 50.0 -- studs / second
local BULLET_MAX_LIFETIME = 60.0 -- seconds
local BZ_NUM_SAMPLE_POINTS = 100
local BZ_ARC_RADIUS = 10

-- define some helper functions...
local function spawnBullet(startingPos)
    local bullet = Instance.new("Part")
    bullet.Position = startingPos
    bullet.Shape = Enum.PartType.Ball
    bullet.Size = Vector3.new(0.5, 0.5, 0.5)
    bullet.BrickColor = BrickColor.Blue()
    bullet.CanCollide = true
    bullet.Anchored = true
    bullet.Parent = game.Workspace
    
    return bullet
end

-- Bezier Curve functions adapted from :
-- https://developer.roblox.com/en-us/articles/Bezier-curves
local function lerp(a, b, c)
    return a + (b - a) * c
end

function cubicBezier(t, p0, p1, p2, p3)
    -- t = % complete between [0, 1]
    -- p0 = starting point
    -- p1 = first control point
    -- p2 = second control point
    -- p3 = ending point
    local l1 = lerp(p0, p1, t)
    local l2 = lerp(p1, p2, t)
    local l3 = lerp(p2, p3, t)
    local a = lerp(l1, l2, t)
    local b = lerp(l2, l3, t)
    local cubic = lerp(a, b, t)
    return cubic
end

function cubicBzLength(p0, p1, p2, p3)
    local calcLength = function(n, func, ...)
        local sum, ranges, sums = 0, , 
        for i = 0, n-1 do
            -- calculate the current point and the next point
            local p1, p2 = func(i/n, ...), func((i+1)/n, ...)
            -- get the distance between them
            local dist = (p2 - p1).magnitude
            -- store the information we gathered in a table that's indexed by the current distance
            ranges[sum] = dist, p1, p2
            -- store the current sum so we can easily sort through it later
            table.insert(sums, sum)
            -- update the sum
            sum = sum + dist
        end
        -- return values
        return sum, ranges, sums
    end
    
    local sum = calcLength(BZ_NUM_SAMPLE_POINTS, cubicBezier, p0, p1, p2, p3)
    return sum
end


ReplicatedStorage.RemoteEvent.OnServerEvent:Connect(function(player, spawnPos, targetPos, rotation)
    local bullet = spawnBullet(spawnPos)
    
    -- calculate the path
    local startingCFrame = CFrame.new(spawnPos, targetPos)
    local targetCFrame = CFrame.new(targetPos, spawnPos)
    
    -- calculate the control points as Vector3s
    -- p1 and p2 will be right angles to the starting and ending positions, but rotated based on input
    local p0 = (startingCFrame).Position
    local p1 = (startingCFrame + (startingCFrame:ToWorldSpace(CFrame.Angles(0, 0, math.rad(rotation))).RightVector * BZ_ARC_RADIUS)).Position
    local p2 = (targetCFrame + (targetCFrame:ToWorldSpace(CFrame.Angles(0, 0, math.rad(-rotation))).RightVector * -BZ_ARC_RADIUS)).Position
    local p3 = (targetCFrame).Position
    
    -- calculate the length of the curve
    local distance = cubicBzLength(p0, p1, p2, p3) -- studs
    
    -- calculate the time to travel the entire length
    local totalTime = distance / BULLET_VELOCITY -- seconds
    local startingTime = tick()
    
    -- start moving it towards the target
    local connection
    connection = RunService.Heartbeat:Connect(function(step)
        -- calculate the percentage complete based on how much time has passed
        local passedTime = tick() - startingTime
        local alpha = passedTime / totalTime
        
        -- move the bullet
        local updatedPos = cubicBezier(alpha, p0, p1, p2, p3)
        bullet.Position = updatedPos
        
        -- once we've arrived, disconnect the event and clean up the bullet
        if alpha > 1 or totalTime > BULLET_MAX_LIFETIME then
            bullet:Destroy()
            connection:Disconnect()
        end
    end)
end)

这使您可以发射沿有趣路径弯曲的射弹。

【讨论】:

以上是关于你如何发射沿弯曲角度跟随的射弹的主要内容,如果未能解决你的问题,请参考以下文章

在 Unity3D 中击中来袭导弹(简单的射弹导弹)

KlipC跟随日本Ispace公司探索我们的星球

canvas学习作业,模仿做一个祖玛的小游戏

Unity C# 向围绕移动轴旋转的目标发射弹丸

Cocos2D - 粒子跟随发射器而不是停留在它们被释放的位置

如何旋转模型以跟随路径