更新
This commit is contained in:
parent
9900516b10
commit
fc39c8c3b3
BIN
excel/enemy.xlsx
BIN
excel/enemy.xlsx
Binary file not shown.
@ -1,5 +1,5 @@
|
||||
[
|
||||
{"id":1,"type":1,"name":1,"atk":10,"hp":100,"model":"Thief"},
|
||||
{"id":2,"type":1,"name":2,"atk":30,"hp":300,"model":"Thief"},
|
||||
{"id":1000,"type":2,"name":1000,"atk":50,"hp":1000,"model":"Thief"}
|
||||
{"id":1,"type":1,"name":1,"atk":10,"hp":100,"walkSpeed":10,"atkSpeed":2,"model":"Thief"},
|
||||
{"id":2,"type":1,"name":2,"atk":30,"hp":300,"walkSpeed":10,"atkSpeed":1,"model":"Thief"},
|
||||
{"id":1000,"type":2,"name":1000,"atk":50,"hp":1000,"walkSpeed":20,"atkSpeed":1,"model":"Thief"}
|
||||
]
|
@ -1,8 +1,6 @@
|
||||
-- 数据存储代理
|
||||
local ArchiveProxy = {}
|
||||
|
||||
print("进入")
|
||||
|
||||
--> Services
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
36
src/ServerStorage/Proxy/DamageProxy.luau
Normal file
36
src/ServerStorage/Proxy/DamageProxy.luau
Normal file
@ -0,0 +1,36 @@
|
||||
-- 伤害代理
|
||||
local DamageProxy = {}
|
||||
|
||||
-- 伤害类型枚举
|
||||
local DamageType = {
|
||||
NORMAL = "Normal", -- 普攻
|
||||
SKILL = "Skill", -- 技能
|
||||
}
|
||||
local DamageTag = {
|
||||
NORMAL = "Normal", -- 普攻
|
||||
CRIT = "Crit", -- 暴击
|
||||
}
|
||||
|
||||
export type DamageTag = "Normal" | "Critical"
|
||||
export type DamageType = "Normal" | "Skill"
|
||||
|
||||
|
||||
export type DamageInfo = {
|
||||
Damage: number,
|
||||
Type: DamageType,
|
||||
Tag: DamageTag,
|
||||
}
|
||||
|
||||
function DamageProxy:TakeDamage(Caster: Model, Victim: Model, DamageInfos: {DamageInfo})
|
||||
for _, DamageInfo in DamageInfos do
|
||||
local Damage = DamageInfo.Damage
|
||||
local DamageType = DamageInfo.DamageType
|
||||
local DamageTag = DamageInfo.DamageTag
|
||||
|
||||
-- 伤害计算
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
return DamageProxy
|
@ -73,7 +73,6 @@ function LevelProxy:InitPlayer(Player: Player)
|
||||
|
||||
-- 关卡目录下生成玩家目录
|
||||
local spawnFloder = Utils:CreateFolder(Player.UserId, game.Workspace:FindFirstChild(STORE_NAME))
|
||||
print("spawnFloder: ", spawnFloder.Name)
|
||||
|
||||
-- 新玩家数据初始化
|
||||
if not ArchiveProxy.pData[Player.UserId][STORE_NAME] then
|
||||
|
@ -1,4 +0,0 @@
|
||||
-- 怪物AI代理
|
||||
local MobAIProxy = {}
|
||||
|
||||
return MobAIProxy
|
112
src/ServerStorage/Proxy/MobsProxy/AI.luau
Normal file
112
src/ServerStorage/Proxy/MobsProxy/AI.luau
Normal file
@ -0,0 +1,112 @@
|
||||
--[[
|
||||
Evercyan @ March 2023
|
||||
AI
|
||||
|
||||
This script handles the physical behavior of mobs, notably Following and Jumping.
|
||||
|
||||
Mobs automatically follow any player within range, but once attacked, they will no longer follow the closest
|
||||
player within range for the follow session, and will instead start attacking the player which recently attacked it.
|
||||
]]
|
||||
|
||||
--> Services
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
--> Dependencies
|
||||
local MobList = require(script.Parent.MobList)
|
||||
|
||||
--> Variables
|
||||
local ActiveMobs = {}
|
||||
local AI = {}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- 获取两个单位之间的距离
|
||||
function AI:GetModelDistance(Unit1: Model, Unit2: Model): number
|
||||
return (Unit1:GetPivot().Position - Unit2:GetPivot().Position).Magnitude
|
||||
end
|
||||
|
||||
function AI:GetClosestPlayer(Mob: any): (Player?, number?)
|
||||
local Closest = {Player = nil, Magnitude = math.huge}
|
||||
|
||||
local ActivePlayer -- We retain a reference to this, so they have to get further away from the mob to stop following, instead of the usual distance.
|
||||
if Mob.Humanoid.WalkToPart then
|
||||
local Player = Players:GetPlayerFromCharacter(Mob.Humanoid.WalkToPart.Parent)
|
||||
if Player then
|
||||
ActivePlayer = Player
|
||||
end
|
||||
end
|
||||
|
||||
-- 临时的
|
||||
for _, Player in Players:GetPlayers() do
|
||||
if Mob.TargetPlayerUserID == Player.UserId then
|
||||
local Character = Player.Character
|
||||
if not Character then continue end
|
||||
|
||||
local Magnitude = (Character:GetPivot().Position - Mob.Instance:GetPivot().Position).Magnitude
|
||||
local MaxDistance = (ActivePlayer == Player and (Mob.Config.FollowDistance or 32) * 2) or Mob.Config.FollowDistance or 32
|
||||
|
||||
if Magnitude <= MaxDistance and Magnitude < Closest.Magnitude then
|
||||
Closest.Player = Player
|
||||
Closest.Magnitude = Magnitude
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Closest.Player, Closest.Player and Closest.Magnitude
|
||||
end
|
||||
|
||||
-- Adds the mob to the ActiveMobs table. This table runs a few times per second and updates follow & jump code for active mobs.
|
||||
-- Active mobs are unanchored, and anchored back when they're not active (not within follow range of any player)
|
||||
function AI:StartTracking(Mob)
|
||||
if not Mob.Destroyed then
|
||||
ActiveMobs[Mob.Instance] = Mob
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- Code to continue tracking on Active Mobs
|
||||
task.defer(function()
|
||||
while true do
|
||||
task.wait(1/4)
|
||||
for MobInstance, Mob in ActiveMobs do
|
||||
task.spawn(function()
|
||||
if not Mob.Stats.Died then
|
||||
local Player, distance = AI:GetClosestPlayer(Mob)
|
||||
local Enemy = Mob.Humanoid
|
||||
|
||||
-- Simulation
|
||||
if Mob.Root.Anchored then
|
||||
Mob.Root.Anchored = false
|
||||
end
|
||||
if not Mob.Root:GetNetworkOwner() then
|
||||
Mob.Root:SetNetworkOwner(Player)
|
||||
task.wait(0.05) -- Give physics more time so it doesn't appear as choppy
|
||||
end
|
||||
|
||||
-- Tracking
|
||||
if Player and Enemy then
|
||||
if distance > 5 then
|
||||
Enemy:MoveTo(Player.Character:GetPivot().Position, Player.Character.PrimaryPart)
|
||||
else
|
||||
-- 停止移动
|
||||
Enemy:MoveTo(MobInstance:GetPivot().Position)
|
||||
-- 触发攻击逻辑
|
||||
task.wait(Mob.Config.attackSpeed)
|
||||
if Mob.Stats.Died then return end
|
||||
if not Player then return end
|
||||
if AI:GetModelDistance(MobInstance, Player.Character) <= 5 then
|
||||
-- 调用伤害模块
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- ActiveMobs[MobInstance] = nil
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
return AI
|
219
src/ServerStorage/Proxy/MobsProxy/init.luau
Normal file
219
src/ServerStorage/Proxy/MobsProxy/init.luau
Normal file
@ -0,0 +1,219 @@
|
||||
-- 怪物AI代理
|
||||
local MobsProxy = {}
|
||||
|
||||
--> Services
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local ServerStorage = game:GetService("ServerStorage")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
--> Variables
|
||||
local Utils = require(ReplicatedStorage.Tools.Utils)
|
||||
local PlayerInfoProxy = require(ServerStorage.Proxy.PlayerInfoProxy)
|
||||
local AI = require(script.AI)
|
||||
|
||||
--> Json
|
||||
local JsonMob = require(ReplicatedStorage.Json.Mob)
|
||||
|
||||
--> Constants
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- 玩家怪物信息存储表
|
||||
MobsProxy.pData = {}
|
||||
-- 初始化生成怪物目录
|
||||
local MobsFolder = Utils:CreateFolder("Mobs", game.Workspace)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local function GetPlayerMobsFolder(Player: Player)
|
||||
local pData = Utils:GetPlayerDataFolder(Player)
|
||||
if not pData then return end
|
||||
local MobsFolder = pData:FindFirstChild("Mobs"):FindFirstChild(Player.UserId)
|
||||
return MobsFolder
|
||||
end
|
||||
|
||||
local function FindMobPrefab(MobModelName: string)
|
||||
return ReplicatedStorage.Mobs:FindFirstChild(MobModelName)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local Mob = {}
|
||||
Mob.__index = Mob
|
||||
|
||||
local LIMIT_ATTRIBUTE = {
|
||||
"health"
|
||||
}
|
||||
|
||||
function Mob.new(Player: Player, MobId: number)
|
||||
-- 怪物实例目录判断
|
||||
local playerMobsFolder = GetPlayerMobsFolder(Player)
|
||||
if not playerMobsFolder then return end
|
||||
-- 怪物数据获取
|
||||
local MobData = Utils:GetJsonData(JsonMob, MobId)
|
||||
if not MobData then warn("Mob Data not found", MobId) return end
|
||||
-- 怪物模型获取
|
||||
local MobInstance = FindMobPrefab(MobData.Model)
|
||||
if not MobInstance then warn("Mob Prefab not found", MobData.Model) return end
|
||||
-- 生成对应实例
|
||||
local newMobModel = MobInstance:Clone()
|
||||
local HumanoidRootPart = newMobModel:FindFirstChild("HumanoidRootPart") :: BasePart
|
||||
local mobHumanoid = newMobModel:FindFirstChild("Humanoid") :: Humanoid
|
||||
|
||||
-- 生成表格数据
|
||||
local newMob = setmetatable({}, Mob)
|
||||
newMob.Instance = newMobModel
|
||||
newMob.Config = MobData
|
||||
newMob.Root = HumanoidRootPart
|
||||
newMob.Humanoid = mobHumanoid
|
||||
newMob.Origin = HumanoidRootPart:GetPivot()
|
||||
newMob.TargetPlayerUserID = Player.UserId
|
||||
newMob.Connections = {}
|
||||
newMob.Stats = {}
|
||||
|
||||
-- 生成实例身上的配置数据
|
||||
local Attributes = Instance.new("Configuration")
|
||||
Attributes.Name = "Attributes"
|
||||
Attributes.Parent = newMob
|
||||
for attributeKey, attributeValue in newMob.Config do
|
||||
Attributes:SetAttribute(attributeKey, attributeValue)
|
||||
-- 设置限制值
|
||||
if table.find(LIMIT_ATTRIBUTE, attributeKey) then
|
||||
newMob.Config["max" .. attributeKey] = attributeValue
|
||||
Attributes:SetAttribute("max" .. attributeKey, attributeValue)
|
||||
end
|
||||
|
||||
local conAttribute = newMob.AttributeChanged:Connect(function(attributeKey: string, attributeValue: number)
|
||||
newMob:ChangeAttribute(attributeKey, attributeValue)
|
||||
end)
|
||||
table.insert(newMob.Connections, conAttribute)
|
||||
end
|
||||
|
||||
-- 配置角色状态数据
|
||||
local statsData = {
|
||||
Died = false
|
||||
}
|
||||
local Stats = Instance.new("Configuration")
|
||||
Stats.Name = "Stats"
|
||||
Stats.Parent = newMob
|
||||
for statKey, statValue in statsData do
|
||||
newMob.Stats[statKey] = statValue
|
||||
Stats:SetAttribute(statKey, statValue)
|
||||
end
|
||||
|
||||
-- 初始化原有功能数值
|
||||
mobHumanoid.WalkSpeed = MobData.walkSpeed
|
||||
-- 放入关卡中
|
||||
newMobModel.Parent = playerMobsFolder
|
||||
-- Set humanoid states (helps prevent falling down & useless calculations - you're unlikely to have an enemy climbing without pathfinding)
|
||||
for _, EnumName in {"FallingDown", "Seated", "Flying", "Swimming", "Climbing"} do
|
||||
local HumanoidStateType = Enum.HumanoidStateType[EnumName]
|
||||
mobHumanoid:SetStateEnabled(HumanoidStateType, false)
|
||||
if mobHumanoid:GetState() == HumanoidStateType then
|
||||
mobHumanoid:ChangeState(Enum.HumanoidStateType.Running)
|
||||
end
|
||||
end
|
||||
|
||||
-- 接入统一AI
|
||||
-- Following has finished. Anchor assembly to optimize.
|
||||
mobHumanoid.MoveToFinished:Connect(function()
|
||||
if not AI:GetClosestPlayer(Mob) and not Mob.isDead then
|
||||
HumanoidRootPart.Anchored = true
|
||||
else
|
||||
AI:StartTracking(Mob)
|
||||
end
|
||||
end)
|
||||
return newMob
|
||||
end
|
||||
|
||||
function Mob:GetAttribute(attributeKey: string)
|
||||
return self.Config[attributeKey], self.Instance.Attributes:GetAttribute(attributeKey)
|
||||
end
|
||||
|
||||
function Mob:ChangeAttribute(attributeKey: string, value: any)
|
||||
local newValue = value
|
||||
-- 限制最大值
|
||||
if table.find(LIMIT_ATTRIBUTE, attributeKey) then
|
||||
if newValue > self.Config["max" .. attributeKey] then
|
||||
newValue = self.Config["max" .. attributeKey]
|
||||
end
|
||||
end
|
||||
-- 改变值
|
||||
self.Config[attributeKey] = newValue
|
||||
self.Instance.Attributes:SetAttribute(attributeKey, newValue)
|
||||
|
||||
-- 死亡判断
|
||||
if attributeKey == "health" and self.Stats.Died == false then
|
||||
if self.Config[attributeKey] <= 0 then
|
||||
self:Died()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Mob:GetState(state: string)
|
||||
return self.Stats[state], self.Instance.Stats:GetAttribute(state)
|
||||
end
|
||||
|
||||
function Mob:ChangeState(state: string, value: any)
|
||||
self.Stats[state] = value
|
||||
self.Instance.Stats:SetAttribute(state, value)
|
||||
end
|
||||
|
||||
function Mob:Died()
|
||||
self:ChangeState("Died", true)
|
||||
MobsProxy:RemoveMob(self.Player, self.Instance)
|
||||
for _, connection in self.Connections do
|
||||
connection:Disconnect()
|
||||
end
|
||||
self.Connections = nil
|
||||
self.Instance:Destroy()
|
||||
self = nil
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- 给玩家创建怪物
|
||||
function MobsProxy:CreateMob(Player: Player, MobId: number)
|
||||
local Mob = Mob.new(Player, MobId)
|
||||
MobsProxy.pData[Player.UserId][Mob.Instance] = Mob
|
||||
return Mob
|
||||
end
|
||||
|
||||
-- 清除玩家单个怪物
|
||||
function MobsProxy:RemoveMob(Player: Player, MobInstance: Instance)
|
||||
local playerMobsFolder = GetPlayerMobsFolder(Player)
|
||||
if not playerMobsFolder then return end
|
||||
if not MobsProxy.pData[Player.UserId][MobInstance] then warn("Mob not found", MobInstance) return end
|
||||
|
||||
MobsProxy.pData[Player.UserId][MobInstance] = nil
|
||||
end
|
||||
|
||||
-- 清除玩家所有的怪物
|
||||
function MobsProxy:CleanPlayerMobs(Player: Player)
|
||||
if not Player then return end
|
||||
for key, mobInstance in MobsProxy.pData[Player.UserId] do
|
||||
mobInstance:Clean()
|
||||
MobsProxy.pData[Player.UserId][key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function MobsProxy:InitPlayer(Player: Player)
|
||||
local pData = Utils:GetPlayerDataFolder(Player)
|
||||
if not pData then return end
|
||||
Utils:CreateFolder(Player.UserId, MobsFolder)
|
||||
|
||||
MobsProxy.pData[Player.UserId] = {}
|
||||
end
|
||||
|
||||
function MobsProxy:OnPlayerRemoving(Player: Player)
|
||||
local pData = Utils:GetPlayerDataFolder(Player)
|
||||
if not pData then return end
|
||||
local MobsFolder = pData:FindFirstChild("Mobs"):FindFirstChild(Player.UserId)
|
||||
if MobsFolder then MobsFolder:Destroy() end
|
||||
end
|
||||
|
||||
Players.PlayerRemoving:Connect(function(Player: Player)
|
||||
MobsProxy:OnPlayerRemoving(Player)
|
||||
end)
|
||||
|
||||
return MobsProxy
|
Loading…
x
Reference in New Issue
Block a user