This commit is contained in:
Ggafrik 2025-07-04 01:08:08 +08:00
parent 9900516b10
commit fc39c8c3b3
8 changed files with 370 additions and 10 deletions

Binary file not shown.

View File

@ -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"}
]

View File

@ -1,8 +1,6 @@
-- 数据存储代理
local ArchiveProxy = {}
print("进入")
--> Services
local CollectionService = game:GetService("CollectionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

View 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

View File

@ -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

View File

@ -1,4 +0,0 @@
-- 怪物AI代理
local MobAIProxy = {}
return MobAIProxy

View 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

View 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