天然灵凡 发表于 2021-7-14 09:54:10

【编程】【从小白到大佬的游戏制作教程3】RPG攻击伤害判定

大家好,我又回来了!最近忙于新版本的更新同时还有两个新游戏在制作中,所以更新稍微慢了一点请见谅。幻想乡传说国际服收获大量好评的同时,国服这边也刚刚完成了审核,应该再过一段时间就能和大家见面了!


【本篇简介】
我将尽量用通俗易懂的语言和简洁明了的代码让大家看懂RPG游戏的攻击判定是如何制作的。我会尽可能使用性能最好的方法,并让各位避免踩雷。
【正文】
常见攻击判定类型
1.武器碰撞类
在Roblox2016年前后诞生的大量RPG作品中,经常能见到利用武器的物理碰撞实现攻击判定的游戏。方法是运用".Touched",然而这是目前最耗费性能的判定方式,当怪物组成比较复杂时碰撞判定往往会在一帧内运行上百次,就算人为增加判定间隔也很难进行优化处理。因此碰撞类判定已经基本被摒弃,原因是现今的RPG游戏大多需要更加复杂的地图更加多样的兵种,后期处理同屏三角面数量会占用大量运算资源,我们需尽可能避免过多的资源浪费。
2.零件碰撞类
这是至今许多游戏仍在使用的判定方式,对于需要进行攻击判定的区域生成一个透明的判定零件(往往是方形),在该零件内检测与零件存在碰撞关系的物体。


这是这种方法常用的API,但该方法存在由于服务器延迟,判定区块的大小而导致的种种问题。因此在追求更高精度和更高性能二者平衡之中我们也尽量要避免使用这种判定方式。
3.区域类
该方法的核心是Region3。区域类攻击判定包括区域零件判定,以及区域范围判定。区域零件判定比较类似于第2种判定方式,但不同的是,这次判定的区域是零件的Region3。关于Region3,通俗易懂的来说就算一个WorkSpace中存在的3D空间,拥有x、y、z三个维度。在空间内任意创造一个零件,获取该零件两端的点可以获取到零件在空间中相对于坐标系的区域(Region3)。


例如图上这个零件,获取Region3时可以这么写,


再说一遍,Region3是这个方法的核心!
区域范围判定就是在获取Region3的基础上将零件去掉,转而变成对一块空区域的判定。说人话就是,当玩家执行一次攻击时对玩家身前的这一片Region3进行一次判定。
客户端脚本建议放在玩家初始化或角色初始化下,


另外,玩家攻击时需要一个动作,因此我们先创建一个动作并放在角色初始化下(关于动作的教程我会在下一期中讲解)。


攻击判定客户端脚本如下:
local ClientStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")

local player = Players.LocalPlayer

local normalAttack = ClientStorage:WaitForChild("RemoteEvents"):WaitForChild("NormalAttack")

local animationTrackAttack = nil

local function CastAttack()
        local character = player.Character
        local humanoid = character:FindFirstChild("Humanoid")
       
        normalAttack:FireServer(character:FindFirstChild("HumanoidRootPart").Position,
                character:FindFirstChild("HumanoidRootPart").Orientation)
       
        if animationTrackAttack == nil then
                local animationAttack = character:FindFirstChild("AttackAnim")
                animationTrackAttack = humanoid:LoadAnimation(animationAttack)
        end
        animationTrackAttack:Play()
end

UserInputService.InputBegan:Connect(function(inputObject,gpe)
        if inputObject.UserInputType == Enum.UserInputType.MouseButton1 then
                CastAttack()
        end
end)
这里稍微解释下,首先我需要在ReplicatedStorage中的RemoteEvents(我创建的)文件夹下建立RemoteEvent,取名为NormalAttack。关于远程事件的运用请详见我上一期的教程:https://forum.robloxdev.cn/t/topic/1078。
攻击判定服务器脚本如下
local ClientStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local normalAttack = ClientStorage:WaitForChild("RemoteEvents"):WaitForChild("NormalAttack")

normalAttack.OnServerEvent:Connect(function(player,attackPos,attackRot)
        local character = player.Character
       
        local humanoid = character:FindFirstChild("Humanoid")
       
        local startPos = Vector3.new(attackPos.x-5,attackPos.y-5,attackPos.z-5)
        local endPos = Vector3.new(attackPos.x+5,attackPos.y+5,attackPos.z+5)
       
        local region = Region3.new(startPos,endPos)
       
        local innerTable = workspace:FindPartsInRegion3(region,nil,150)
        for k,v in pairs(innerTable) do

                local norVec = Vector3.new(-math.cos(math.rad(attackRot.x))*math.sin(math.rad(attackRot.y)),0,-math.cos(math.rad(attackRot.x))*math.cos(math.rad(attackRot.y)))
                local temVec = (v.Position - attackPos).Unit
                local angle = math.acos(norVec:Dot(temVec))*180/math.pi

                if angle <= 120 then
                        if v.Name == "HumanoidRootPart" then
                                local humanoid0 = v.Parent:FindFirstChild("Humanoid")
                                if humanoid0 ~= humanoid then
                                        humanoid0:TakeDamage(1)
                                end
                        end
                end       
        end
end)
这种方法的好处在于可以通过改变判定角(angle)判定身前任意角度扇形区域的单位。通过修改判定区域内检测零件的个数可以改变判定对象的总量。同时,由于每次只在攻击时进行一次判定,可以极大减少判定次数,进而达到性能优化的目的。
页: [1]
查看完整版本: 【编程】【从小白到大佬的游戏制作教程3】RPG攻击伤害判定