From b319dfeb27ddeda244f071881193f02160b8a49e Mon Sep 17 00:00:00 2001 From: Ggafrik <906823881@qq.com> Date: Fri, 18 Jul 2025 01:11:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- default.project.json | 3 + excel/ability.xlsx | Bin 17131 -> 17163 bytes src/ReplicatedStorage/Base/TypeList.luau | 0 src/ReplicatedStorage/Base/UIEnums.luau | 7 + src/ReplicatedStorage/Base/UIList.luau | 124 ++++++++++++++ .../Base/UIWindow.luau | 94 ++++++----- src/ReplicatedStorage/Data/SignalEnum.luau | 7 + src/ReplicatedStorage/Json/Ability.json | 2 +- src/ReplicatedStorage/Modules/Signal.luau | 2 + src/ReplicatedStorage/Tools/Localization.luau | 12 +- src/ReplicatedStorage/Tools/Signal.luau | 151 ++++++++++++++++++ src/ReplicatedStorage/Tools/Utils.luau | 46 ++++++ src/ServerStorage/Base/Behaviour.luau | 20 +++ src/ServerStorage/Base/Character.luau | 5 +- .../Modules/Behaviours/Move.luau | 7 +- .../Modules/Behaviours/SwordWave.luau | 8 +- src/ServerStorage/Proxy/DamageProxy.luau | 7 +- .../Proxy/PlayerFightProxy/PlayerAI.luau | 23 ++- .../Proxy/PlayerFightProxy/init.luau | 4 + .../ClientMain/Camera.client.luau | 98 ++++++++++++ .../PerformanceClient/DamageBoard.luau | 21 +-- .../ClientMain/PerformanceClient/init.luau | 11 +- src/StarterPlayerScripts/UI/UIManager.luau | 31 ++++ .../AbilityStateWindow/AbilityShow.luau | 76 +++++++++ .../UI/Windows/AbilityStateWindow/init.luau | 49 ++++++ 25 files changed, 736 insertions(+), 72 deletions(-) create mode 100644 src/ReplicatedStorage/Base/TypeList.luau create mode 100644 src/ReplicatedStorage/Base/UIEnums.luau create mode 100644 src/ReplicatedStorage/Base/UIList.luau rename src/{StarterPlayerScripts => ReplicatedStorage}/Base/UIWindow.luau (51%) create mode 100644 src/ReplicatedStorage/Data/SignalEnum.luau create mode 100644 src/ReplicatedStorage/Tools/Signal.luau create mode 100644 src/StarterPlayerScripts/ClientMain/Camera.client.luau create mode 100644 src/StarterPlayerScripts/UI/Windows/AbilityStateWindow/AbilityShow.luau create mode 100644 src/StarterPlayerScripts/UI/Windows/AbilityStateWindow/init.luau diff --git a/default.project.json b/default.project.json index 23bee34..ff4a9ac 100644 --- a/default.project.json +++ b/default.project.json @@ -21,6 +21,9 @@ }, "ClientMain": { "$path": "src/StarterPlayerScripts/ClientMain" + }, + "UI": { + "$path": "src/StarterPlayerScripts/UI" } } }, diff --git a/excel/ability.xlsx b/excel/ability.xlsx index 38d3ee97933cbb9965c3cad6bd9a91f39a10cf38..5cd47c131717f5f60fc0619eeeec1d3b26deae84 100644 GIT binary patch delta 3887 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 diff --git a/src/ReplicatedStorage/Base/TypeList.luau b/src/ReplicatedStorage/Base/TypeList.luau new file mode 100644 index 0000000..e69de29 diff --git a/src/ReplicatedStorage/Base/UIEnums.luau b/src/ReplicatedStorage/Base/UIEnums.luau new file mode 100644 index 0000000..3033ac1 --- /dev/null +++ b/src/ReplicatedStorage/Base/UIEnums.luau @@ -0,0 +1,7 @@ +local UIEnums = {} + +UIEnums.UIParent = { + UIRoot = "UIRoot", +} + +return UIEnums \ No newline at end of file diff --git a/src/ReplicatedStorage/Base/UIList.luau b/src/ReplicatedStorage/Base/UIList.luau new file mode 100644 index 0000000..a0c9f22 --- /dev/null +++ b/src/ReplicatedStorage/Base/UIList.luau @@ -0,0 +1,124 @@ +local UIList = {} +UIList.__index = UIList + +--> Services +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +--> Dependencies +local Utils = require(ReplicatedStorage.Tools.Utils) + +-- 子脚本自动注入 +local function AutoInjectVariables(self) + for varName, _ in pairs(self.Variables) do + if typeof(varName) == "string" then + local firstChar = string.sub(varName, 1, 1) + local sixChar = string.sub(varName, 1, 6) + local target + + if firstChar == "_" then + if sixChar == "__list" then + local prefab = Utils:FindInDescendantsUI(self.UIRoot, varName) + target = UIList:Init(prefab) + else + -- _开头,递归查找 + target = Utils:FindInDescendantsUI(self.UIRoot, varName) + end + + if not target then + error("自动注入失败:未找到UI节点 " .. varName) + end + + self.Variables[varName] = target + end + end + end +end + +function UIList:Init(Prefab: Instance) + local self = {} + setmetatable(self, UIList) + self.Data = {} + self.Instances = {} + self.Connections = {} + self.Component = nil + self.UIRoot = Prefab + self.Org = Utils:FindInDescendantsUI(Prefab, "__org") + self.Org.Visible = false + + return self +end + +function UIList:AddComponent(Component: table) + self.Component = Component +end + +function UIList:SetData(Data: table) + self.Data = Data + self:Refresh() +end + +function UIList:AddData(data: table) + self.Data[#self.Data + 1] = data + self:SetSingleInstance(#self.Data, data) +end + +function UIList:SetSingleInstance(index: number, data: table) + local child = self.Component:Init(data) + + local uiInstance = self.Org:Clone() + child.UIRoot = uiInstance + uiInstance.Name = index + uiInstance.Parent = self.Org.Parent + self.Instances[index] = child + + AutoInjectVariables(child) + child:Refresh() + uiInstance.Visible = true +end + +function UIList:Refresh() + for _, ui in pairs(self.Instances) do ui:Destroy() end + self.Instances = {} + + if not self.Component then warn("UIList:Refresh() Component未设置") return end + + if self.Data then + for k, v in pairs(self.Data) do + self:SetSingleInstance(k, v) + end + end +end + +function UIList:Clean() + -- 清除自己的内容 + for _, connection in pairs(self.Connections) do + connection:Disconnect() + end + self.Connections = {} + + -- 清除下面组件的内容 + for _, ui in pairs(self.Instances) do + for _, connection in pairs(ui.SignalConnections) do + connection:DisconnectAll() + end + self.SignalConnections = nil + + for _, connection in pairs(ui.Connections) do + connection:Disconnect() + end + self.Connections = nil + + ui:Destroy() + end + self.Instances = {} +end + +function UIList:Destroy() + self:Clean() + self.SignalConnections = nil + self.Connections = nil + self.Org:Destroy() + self = nil +end + +return UIList \ No newline at end of file diff --git a/src/StarterPlayerScripts/Base/UIWindow.luau b/src/ReplicatedStorage/Base/UIWindow.luau similarity index 51% rename from src/StarterPlayerScripts/Base/UIWindow.luau rename to src/ReplicatedStorage/Base/UIWindow.luau index da73f46..adf31e1 100644 --- a/src/StarterPlayerScripts/Base/UIWindow.luau +++ b/src/ReplicatedStorage/Base/UIWindow.luau @@ -4,43 +4,31 @@ UIWindow.__index = UIWindow --> Services local ReplicatedStorage = game:GetService("ReplicatedStorage") +--> Dependencies +local UIList = require(ReplicatedStorage.Base.UIList) + --> Variables local FolderWindows = ReplicatedStorage:WaitForChild("UI"):WaitForChild("Windows") +local Utils = require(ReplicatedStorage.Tools.Utils) local LocalPlayer = game.Players.LocalPlayer local FolderPlayerGui = LocalPlayer:WaitForChild("PlayerGui") -------------------------------------------------------------------------------- --- 递归查找 -local function FindInDescendants(root, name) - if root:FindFirstChild(name) then - return root:FindFirstChild(name) - end - for _, child in ipairs(root:GetChildren()) do - local found = FindInDescendants(child, name) - if found then - return found - end - end - return nil -end - --- 非递归查找 -local function FindInRoot(root, name) - return root:FindFirstChild(name) -end - --------------------------------------------------------------------------------- - -function UIWindow:Init(Data: table?) +function UIWindow:Init(UIManager: table, Data: table?) local self = {} + self.UIManager = UIManager self.Data = Data self.Variables = {} + self.Connections = {} + self.SignalConnections = {} self.UIRootName = nil self.UIParentName = nil + self.CloseDestroy = true + -- 运行时操作变量 self.UIRoot = nil self.AutoInject = false @@ -52,36 +40,29 @@ function UIWindow:SetData(Data: table?) self:OnDataChanged() end -function UIWindow:OnOpenWindow() - - if not self.UIRoot then - -- 创建UI根节点 - self.UIRoot = FolderWindows:FindFirstChild(self.UIRootName):Clone() - self.UIRoot.Parent = FolderPlayerGui:FindFirstChild(self.UIParentName) - end - - -- 自动注入 +-- 自动注入 +function UIWindow:AutoInjectVariables() if not self.AutoInject then for varName, _ in pairs(self.Variables) do if typeof(varName) == "string" then local firstChar = string.sub(varName, 1, 1) - local secondChar = string.sub(varName, 2, 2) + local sixChar = string.sub(varName, 1, 6) local target if firstChar == "_" then - if secondChar == "_" then - -- __开头,只查根目录 - target = FindInRoot(self.UIRoot, varName) + if sixChar == "__list" then + local prefab = Utils:FindInDescendantsUI(self.UIRoot, varName) + target = UIList:Init(prefab) else -- _开头,递归查找 - target = FindInDescendants(self.UIRoot, varName) + target = Utils:FindInDescendantsUI(self.UIRoot, varName) end if not target then error("自动注入失败:未找到UI节点 " .. varName) end - self[varName] = target + self.Variables[varName] = target end end end @@ -89,8 +70,28 @@ function UIWindow:OnOpenWindow() end end -function UIWindow:OnCloseWindow() +function UIWindow:OnOpenWindow() + if not self.UIRoot then + -- 创建UI根节点 + self.UIRoot = FolderWindows:FindFirstChild(self.UIRootName):Clone() + self.UIRoot.Parent = FolderPlayerGui:WaitForChild(self.UIParentName) + print("节点", self.UIRoot, self.UIParentName, FolderPlayerGui, FolderPlayerGui:FindFirstChild(self.UIParentName)) + else + self.UIRoot.Visible = true + self:Refresh() + end + self:AutoInjectVariables() + +end + +function UIWindow:OnCloseWindow() + if self.CloseDestroy then + self.UIRoot:Destroy() + self.UIRoot = nil + else + self.UIRoot.Visible = false + end end --> 覆盖用 @@ -98,4 +99,21 @@ function UIWindow:OnDataChanged() end function UIWindow:Refresh() end +function UIWindow:Destroy() + for _, connection in pairs(self.SignalConnections) do + connection:DisconnectAll() + end + self.SignalConnections = nil + + for _, connection in pairs(self.Connections) do + connection:Disconnect() + end + self.Connections = nil + + for k, v in pairs(self) do + self[k] = nil + end + self = nil +end + return UIWindow \ No newline at end of file diff --git a/src/ReplicatedStorage/Data/SignalEnum.luau b/src/ReplicatedStorage/Data/SignalEnum.luau new file mode 100644 index 0000000..0e793e0 --- /dev/null +++ b/src/ReplicatedStorage/Data/SignalEnum.luau @@ -0,0 +1,7 @@ +local SignalEnum = {} + +SignalEnum = { + SHOW_ABILITY = "SHOW_ABILITY", +} + +return SignalEnum \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Ability.json b/src/ReplicatedStorage/Json/Ability.json index 0297ccb..60dfbfc 100644 --- a/src/ReplicatedStorage/Json/Ability.json +++ b/src/ReplicatedStorage/Json/Ability.json @@ -1,3 +1,3 @@ [ -{"id":20000,"type":1,"behaviourName":"swordWave","upgradeCost":[30000,5,10],"upgradeValue":[10,200],"recycle":[30000,100]} +{"id":20000,"type":1,"icon":1,"behaviourName":"SwordWave","upgradeCost":[30000,5,10],"upgradeValue":[10,200],"recycle":[30000,100]} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Modules/Signal.luau b/src/ReplicatedStorage/Modules/Signal.luau index 760b8a7..9fb75bd 100644 --- a/src/ReplicatedStorage/Modules/Signal.luau +++ b/src/ReplicatedStorage/Modules/Signal.luau @@ -7,6 +7,7 @@ Difference here compared to a BindableEvent is that it's way faster without the need of creating tons of instances using metatable magic! ]] + local freeRunnerThread = nil local function acquireRunnerThreadAndCallEventHandler(fn, ...) @@ -72,6 +73,7 @@ local Signals = {} local Signal = {} Signal.__index = Signal Signal.ClassName = "Signal" +Signal.ENUM = SignalEnum export type Signal = typeof(Signal) diff --git a/src/ReplicatedStorage/Tools/Localization.luau b/src/ReplicatedStorage/Tools/Localization.luau index a04cb63..5950cbc 100644 --- a/src/ReplicatedStorage/Tools/Localization.luau +++ b/src/ReplicatedStorage/Tools/Localization.luau @@ -1,7 +1,4 @@ --- 客户端调用的内容 -local RunService = game:GetService("RunService") -if RunService:IsClient() then return end - +-- 本地化读取工具 local Localization = {} --> Services @@ -19,10 +16,7 @@ local JsonImage_Zh_CN = require(ReplicatedStorage.Json.Image_Zh_CN) --> Variables local LocalPlayer = game.Players.LocalPlayer -local SystemLocaleId = LocalizationService:GetSystemLocaleId() -local JsonLanguage, JsonImage = Localization:GetLocalizationJson() - - +local SystemLocaleId = LocalizationService.SystemLocaleId -- 获取本地Json文件 function Localization:GetLocalizationJson() @@ -33,6 +27,8 @@ function Localization:GetLocalizationJson() end end +local JsonLanguage, JsonImage = Localization:GetLocalizationJson() + -- 获取文本Id数据 function Localization:GetLanguageData(Id: number) if not Id then return end diff --git a/src/ReplicatedStorage/Tools/Signal.luau b/src/ReplicatedStorage/Tools/Signal.luau new file mode 100644 index 0000000..a7903bb --- /dev/null +++ b/src/ReplicatedStorage/Tools/Signal.luau @@ -0,0 +1,151 @@ +--[[ + GoodSignal Class + stravant @ July 2021 + Edited slightly by Evercyan to add globals & 'Once' method + + Used for creating custom Signals - basically the signals like RBXScriptSignal is (workspace.ChildAdded, Players.PlayerAdded are both signals, because you can connect to them!) + Difference here compared to a BindableEvent is that it's way faster without the need of creating tons of instances using metatable magic! +]] + + +local SignalEnum = require(game:GetService("ReplicatedStorage").Data.SignalEnum) + +local freeRunnerThread = nil + +local function acquireRunnerThreadAndCallEventHandler(fn, ...) + local acquiredRunnerThread = freeRunnerThread + freeRunnerThread = nil + fn(...) + freeRunnerThread = acquiredRunnerThread +end + +local function runEventHandlerInFreeThread(...) + acquireRunnerThreadAndCallEventHandler(...) + while true do + acquireRunnerThreadAndCallEventHandler(coroutine.yield()) + end +end + +---- CONNECTION CLASS ---------------------------------------------------------- + +local Connection = {} +Connection.__index = Connection + +function Connection.new(signal, fn) + return setmetatable({ + _connected = true, + _signal = signal, + _fn = fn, + _next = false, + }, Connection) +end + +function Connection:Disconnect() + if not self._connected then + return + end + self._connected = false + + if self._signal._handlerListHead == self then + self._signal._handlerListHead = self._next + else + local prev = self._signal._handlerListHead + while prev and prev._next ~= self do + prev = prev._next + end + if prev then + prev._next = self._next + end + end +end + +setmetatable(Connection, { + __index = function(tb, key) + error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2) + end, + __newindex = function(tb, key, value) + error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2) + end +}) + +---- SIGNAL CLASS -------------------------------------------------------------- + +local Signals = {} + +local Signal = {} +Signal.__index = Signal +Signal.ClassName = "Signal" +Signal.ENUM = SignalEnum + +export type Signal = typeof(Signal) + +function Signal.new(Name: string?): Signal + if Name and Signals[Name] then + return Signals[Name] + else + local signal = setmetatable({ + _handlerListHead = false + }, Signal) + + if Name then + signal.Name = Name + Signals[Name] = signal + end + + return signal + end +end + +function Signal:Connect(fn) + local connection = Connection.new(self, fn) + if self._handlerListHead then + connection._next = self._handlerListHead + self._handlerListHead = connection + else + self._handlerListHead = connection + end + return connection +end + +function Signal:Once(fn) + local cn; cn = self:Connect(function(...) + cn:Disconnect() + fn(...) + end) + + return cn +end + +function Signal:DisconnectAll() + if rawget(self, "Name") then + Signals[self.Name] = nil + end + self._handlerListHead = false +end + +Signal.Destroy = Signal.DisconnectAll + +function Signal:Fire(...) + local item = self._handlerListHead + while item do + if item._connected then + if not freeRunnerThread then + freeRunnerThread = coroutine.create(runEventHandlerInFreeThread) + end + task.spawn(freeRunnerThread, item._fn, ...) + end + item = item._next + end +end + +function Signal:Wait() + local waitingCoroutine = coroutine.running() + local cn; + cn = self:Connect(function(...) + cn:Disconnect() + task.spawn(waitingCoroutine, ...) + end) + return coroutine.yield() +end + +return Signal \ No newline at end of file diff --git a/src/ReplicatedStorage/Tools/Utils.luau b/src/ReplicatedStorage/Tools/Utils.luau index 388561e..c943b2a 100644 --- a/src/ReplicatedStorage/Tools/Utils.luau +++ b/src/ReplicatedStorage/Tools/Utils.luau @@ -70,6 +70,16 @@ function Utils:GetIdDataFromJson(JsonData: table, id: number) return nil -- 没有找到对应id end +function Utils:GetSpecialKeyDataFromJson(JsonData: table, Key: string, Value: string) + -- 遍历JsonData,查找id字段等于目标id的项 + for _, item in ipairs(JsonData) do + if item[Key] == Value then + return item + end + end + return nil -- 没有找到对应id +end + -- 获取随机id,ExceptIdList为可选参数,如果传入则排除ExceptIdList中的id function Utils:GetRandomIdFromJson(JsonData: table, ExceptIdList: table?) local rng = Random.new() @@ -218,6 +228,42 @@ function Utils:TableSafeAddTableValue(AttributesData: table, AddTableValue: tabl end end +-- 递归查找 +function Utils:FindInDescendants(root, name) + if root:FindFirstChild(name) then + return root:FindFirstChild(name) + end + for _, child in ipairs(root:GetChildren()) do + local found = Utils:FindInDescendants(child, name) + if found then + return found + end + end + return nil +end + +-- 非递归查找 +function Utils:FindInRoot(root, name) + return root:FindFirstChild(name) +end + + +-- UI递归查找 +function Utils:FindInDescendantsUI(root, name) + if root:FindFirstChild(name) then + return root:FindFirstChild(name) + end + for _, child in ipairs(root:GetChildren()) do + local firstTwo = string.sub(child.Name, 1, 2) + if firstTwo ~= "__" then + local found = Utils:FindInDescendantsUI(child, name) + if found then + return found + end + end + end + return nil +end -------------------------------------------------------------------------------- diff --git a/src/ServerStorage/Base/Behaviour.luau b/src/ServerStorage/Base/Behaviour.luau index aec57cc..a75d6bb 100644 --- a/src/ServerStorage/Base/Behaviour.luau +++ b/src/ServerStorage/Base/Behaviour.luau @@ -52,6 +52,23 @@ function Behaviour:Execute() end +-- 检查行为前先清理之前遗留的引用数据 +function Behaviour:CheckClean() + + if self.Mobs then + for _, Mob in self.Mobs do + self.Mobs[Mob] = nil + end + self.Mobs = nil + end + if self.CheckData then + for key, cha in self.CheckData do + self.CheckData[key] = nil + end + self.CheckData = nil + end +end + -- 启动冷却时间清除计时 function Behaviour:StartCooldownTask() self.Cooldown = self.OrgCooldown @@ -115,6 +132,9 @@ function Behaviour:Destroy() self.PlayerAI:RemoveBehaviourUniqueId(UniqueId) end self.UniqueIdList = {} + for k, v in pairs(self) do + self[k] = nil + end self = nil end diff --git a/src/ServerStorage/Base/Character.luau b/src/ServerStorage/Base/Character.luau index d90dbd2..8668884 100644 --- a/src/ServerStorage/Base/Character.luau +++ b/src/ServerStorage/Base/Character.luau @@ -93,10 +93,11 @@ function Character:ChangeAttributeValue(attributeKey: string, value: any) self.Instance.Attributes:SetAttribute(attributeKey, newValue) -- 死亡判断 + local isDied = false if attributeKey == "hp" and self.Stats.Died == false then - - if self.Config[attributeKey] <= 0 then self:Died() end + if self.Config[attributeKey] <= 0 then self:Died() isDied = true end end + return newValue, isDied end function Character:GetState(state: string) diff --git a/src/ServerStorage/Modules/Behaviours/Move.luau b/src/ServerStorage/Modules/Behaviours/Move.luau index 44578e7..a8989d0 100644 --- a/src/ServerStorage/Modules/Behaviours/Move.luau +++ b/src/ServerStorage/Modules/Behaviours/Move.luau @@ -17,18 +17,19 @@ setmetatable(Move, {__index = Behaviour}) function Move:Init(PlayerAI, Character: TypeList.Character, Player: Player) local self = Behaviour:Init(PlayerAI, Character, script.Name) self.Player = Player + self.Mobs = nil setmetatable(self, Move) return self end function Move:Check(CheckInfo: table) if Behaviour.CheckStat(self) then return -1, self.CheckData end + self:CheckClean() - local PlayerMobs = MobsProxy:GetPlayerMobs(self.Player) - if not PlayerMobs then return end + self.Mobs = MobsProxy:GetPlayerMobs(self.Player) local closestMob, minDistance = nil, math.huge - for _, Mob in PlayerMobs do + for _, Mob in self.Mobs do if Mob.Instance and Mob.Instance.PrimaryPart then local dist = (Mob.Instance.PrimaryPart.Position - self.Character.Instance.PrimaryPart.Position).Magnitude if dist < minDistance then diff --git a/src/ServerStorage/Modules/Behaviours/SwordWave.luau b/src/ServerStorage/Modules/Behaviours/SwordWave.luau index f3244b7..e8b9921 100644 --- a/src/ServerStorage/Modules/Behaviours/SwordWave.luau +++ b/src/ServerStorage/Modules/Behaviours/SwordWave.luau @@ -25,6 +25,7 @@ local COOLDOWN = 1 function SwordWave:Init(PlayerAI, Character: TypeList.Character, Player: Player) local self = Behaviour:Init(PlayerAI, Character, script.Name) self.Player = Player + self.Mobs = nil setmetatable(self, SwordWave) self.OrgCooldown = COOLDOWN self:StartCooldownTask() @@ -36,12 +37,13 @@ end function SwordWave:Check(CheckInfo: table) if Behaviour.CheckStat(self) then return -1, self.CheckData end + self:CheckClean() - local PlayerMobs = MobsProxy:GetPlayerMobs(self.Player) - if not PlayerMobs then return end + self.Mobs = MobsProxy:GetPlayerMobs(self.Player) + if not self.Mobs then return end local closestMob, minDistance = nil, CAST_DISTANCE - for _, Mob in PlayerMobs do + for _, Mob in self.Mobs do if Mob.Instance and Mob.Instance.PrimaryPart then local dist = (Mob.Instance.PrimaryPart.Position - self.Character.Instance.PrimaryPart.Position).Magnitude if dist < minDistance then diff --git a/src/ServerStorage/Proxy/DamageProxy.luau b/src/ServerStorage/Proxy/DamageProxy.luau index c8030a6..0198d27 100644 --- a/src/ServerStorage/Proxy/DamageProxy.luau +++ b/src/ServerStorage/Proxy/DamageProxy.luau @@ -136,7 +136,7 @@ function DamageProxy:TakeDamage(Caster: TypeList.Character, Victim: TypeList.Cha for _, DamageInfo in DamageInfos do if not Victim then continue end - if Victim:GetState("Died") then continue end + -- if not Victim.Parent then continue end local Damage = DamageInfo.Damage local DamageType = DamageInfo.DamageType @@ -145,8 +145,9 @@ function DamageProxy:TakeDamage(Caster: TypeList.Character, Victim: TypeList.Cha -- 伤害计算 local VictimHealth = Victim:GetAttributeValue("hp") - Victim:ChangeAttributeValue("hp", math.max(0, VictimHealth - Damage)) - print("伤害数据打印", Damage, VictimHealth) + local resultValue, isDied = Victim:ChangeAttributeValue("hp", math.max(0, VictimHealth - Damage)) + print("伤害数据打印", Damage, VictimHealth, resultValue, isDied) + if isDied then break end end -- 实际发送数据 Communicate:SendToClient(RE_DamagePerformance, "Damage", Caster.Player, clientDamageInfos) diff --git a/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau b/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau index 410ac52..a0fd818 100644 --- a/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau +++ b/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau @@ -88,9 +88,10 @@ function PlayerAI:Update() end -- 动态添加行为 -function PlayerAI:AddBehaviour(BehaviourName: string) +function PlayerAI:AddBehaviour(BehaviourName: string, WearingSlot: number?) if not Behaviours[BehaviourName] then warn("Behaviour not found") return end local newBehaviour = Behaviours[BehaviourName]:Init(self, self.Character, self.Player) + newBehaviour.WearingSlot = WearingSlot or 4 self.BehaviourList[BehaviourName] = newBehaviour end @@ -101,9 +102,22 @@ end -- 获取客户端行为列表 function PlayerAI:GetClientBehaviourList() - local clientBehaviourList = {} + local clientBehaviourList = { + Ability = {}, + WearAbility = {}, + } for _, behaviour in self.BehaviourList do - clientBehaviourList[behaviour.ScriptName] = behaviour.Cooldown + if behaviour.ScriptName == "Move" then continue end + local wearTable + if behaviour.WearingSlot == 1 then + wearTable = clientBehaviourList.Ability + else + wearTable = clientBehaviourList.WearAbility + end + table.insert(wearTable, { + AbilityName = behaviour.ScriptName, + Cooldown = behaviour.Cooldown, + }) end return clientBehaviourList end @@ -130,6 +144,9 @@ function PlayerAI:Destroy() task.cancel(self.LoopTask) self.LoopTask = nil end + for k, v in pairs(self) do + self[k] = nil + end self = nil end diff --git a/src/ServerStorage/Proxy/PlayerFightProxy/init.luau b/src/ServerStorage/Proxy/PlayerFightProxy/init.luau index ec098a8..5a8bee1 100644 --- a/src/ServerStorage/Proxy/PlayerFightProxy/init.luau +++ b/src/ServerStorage/Proxy/PlayerFightProxy/init.luau @@ -198,6 +198,10 @@ function PlayerFightProxy:OnPlayerRemoving(Player: Player) Communicate:SendToClientFree(RE_CleanPlayerPerformance, Player) -- 正常清除玩家该模块下数据 if not PlayerFightProxy.pData[Player.UserId] then warn("PlayerFight Remove Data not found", Player.Name) return end + + PlayerFightProxy.pData[Player.UserId].PlayerAI:Destroy() + PlayerFightProxy.pData[Player.UserId].PlayerAI = nil + PlayerFightProxy.pData[Player.UserId] = nil end diff --git a/src/StarterPlayerScripts/ClientMain/Camera.client.luau b/src/StarterPlayerScripts/ClientMain/Camera.client.luau new file mode 100644 index 0000000..86bebcf --- /dev/null +++ b/src/StarterPlayerScripts/ClientMain/Camera.client.luau @@ -0,0 +1,98 @@ +-- 45度俯视角相机脚本 +local Players = game:GetService("Players") +local RunService = game:GetService("RunService") + +local player = Players.LocalPlayer +local camera = workspace.CurrentCamera + +-- local CAMERA_DISTANCE = 20 +-- local CAMERA_HEIGHT = 20 +-- local CAMERA_ANGLE = math.rad(-45) +-- local SCREEN_OFFSET = 5 -- 让角色在画面下方,数值越大越靠下 + +-- local function getCharacterRoot() +-- local character = player.Character +-- if character then +-- return character:FindFirstChild("HumanoidRootPart") or character:FindFirstChildWhichIsA("BasePart") +-- end +-- return nil +-- end + +-- RunService.RenderStepped:Connect(function() +-- local root = getCharacterRoot() +-- if root then +-- -- 让lookAt点在角色身后一点(以角色朝向为基准,反方向偏移) +-- local lookAt = root.Position - Vector3.new(-1, 0, 0) * SCREEN_OFFSET + +-- -- 相机位置(俯视角,Y高度固定) +-- local offset = Vector3.new( +-- -CAMERA_DISTANCE * math.cos(CAMERA_ANGLE), +-- CAMERA_HEIGHT, +-- 0 +-- ) +-- local cameraPos = lookAt + offset + +-- camera.CameraType = Enum.CameraType.Scriptable +-- camera.CFrame = CFrame.new(cameraPos, lookAt) +-- end +-- end) + +local CAMERA_DISTANCE = 20 +local CAMERA_HEIGHT = 20 +local CAMERA_ANGLE = math.rad(-45) +local SCREEN_OFFSET = 5 + +local DEADZONE_SIZE = Vector2.new(10, 15) -- 死区宽高(世界坐标) +local cameraCenter -- 当前相机中心点 + +local function getCharacterRoot() + local character = player.Character + if character then + return character:FindFirstChild("HumanoidRootPart") or character:FindFirstChildWhichIsA("BasePart") + end + return nil +end + +player.CharacterAdded:Connect(function(character) + local root = getCharacterRoot() + if root then + cameraCenter = Vector3.new(root.Position.X, 0, root.Position.Z) + end +end) + +RunService.RenderStepped:Connect(function() + local root = getCharacterRoot() + if root then + if not cameraCenter then + cameraCenter = Vector3.new(root.Position.X, 0, root.Position.Z) + end + + -- 只考虑X/Z平面 + local delta = Vector2.new(root.Position.X - cameraCenter.X, root.Position.Z - cameraCenter.Z) + local halfSize = DEADZONE_SIZE / 2 + + -- 检查是否超出死区 + local moveX, moveZ = 0, 0 + if math.abs(delta.X) > halfSize.X then + moveX = delta.X - math.sign(delta.X) * halfSize.X + end + if math.abs(delta.Y) > halfSize.Y then + moveZ = delta.Y - math.sign(delta.Y) * halfSize.Y + end + + -- 更新相机中心点 + cameraCenter = cameraCenter + Vector3.new(moveX, 0, moveZ) + + -- 让lookAt点在相机中心点后方 + local lookAt = cameraCenter - Vector3.new(-1, 0, 0) * SCREEN_OFFSET + local offset = Vector3.new( + -CAMERA_DISTANCE * math.cos(CAMERA_ANGLE), + CAMERA_HEIGHT, + 0 + ) + local cameraPos = lookAt + offset + + camera.CameraType = Enum.CameraType.Scriptable + camera.CFrame = CFrame.new(cameraPos, lookAt) + end +end) \ No newline at end of file diff --git a/src/StarterPlayerScripts/ClientMain/PerformanceClient/DamageBoard.luau b/src/StarterPlayerScripts/ClientMain/PerformanceClient/DamageBoard.luau index b411820..acf16c2 100644 --- a/src/StarterPlayerScripts/ClientMain/PerformanceClient/DamageBoard.luau +++ b/src/StarterPlayerScripts/ClientMain/PerformanceClient/DamageBoard.luau @@ -35,8 +35,8 @@ end function DamageBoard:CreateNormalDamageBoard(DamageDetail: table) local DamageInitPos = DamageDetail.DamagePosition - local BiasPos = Vector3.new(0, 3, 0) - local MultPos = Vector3.new(0, 1, 0) + local BiasPos = Vector3.new(0, 4, 0) + local MultPos = Vector3.new(0, 1.5, 0) -- 根据元素类型排序 local DamageInfos = DamageDetail["DamageInfos"] @@ -48,21 +48,22 @@ function DamageBoard:CreateNormalDamageBoard(DamageDetail: table) local index = 0 local BoardInstance = Boards["Board1"]:Clone() + local BoardCanvas = BoardInstance:FindFirstChild("BillboardGui"):FindFirstChild("Canvas") + local DamageInstance = BoardCanvas:FindFirstChild("Damage") for _, DamageInfo in DamageInfos do - local BoardCanvas = BoardInstance:FindFirstChild("BillboardGui"):FindFirstChild("Canvas") - local DamageInstance = BoardCanvas:FindFirstChild("Damage") - - local ElementInstance = BoardCanvas:FindFirstChild("Element") - ElementInstance.ImageColor3 = element_color[DamageInfo.ElementType] - DamageInstance.Text = DamageInfo.Damage - DamageInstance.TextColor3 = element_color[DamageInfo.ElementType] + local NewDamageInstance = DamageInstance:Clone() + NewDamageInstance.Text = DamageInfo.Damage + NewDamageInstance.TextColor3 = element_color[DamageInfo.ElementType] + NewDamageInstance.Visible = true + NewDamageInstance.Parent = BoardCanvas + NewDamageInstance.LayoutOrder = 10 - index BoardInstance.Position = DamageInitPos + BiasPos + MultPos * index BoardInstance.Parent = game.Workspace - game.Debris:AddItem(BoardInstance, 1) index = index + 1 end + game.Debris:AddItem(BoardInstance, 1) end diff --git a/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau b/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau index 3f158de..1e294e6 100644 --- a/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau +++ b/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau @@ -13,9 +13,14 @@ local RE_CleanPlayerPerformance = EventsFolder:FindFirstChild("RE_CleanPlayerPer local RE_DamagePerformance = EventsFolder:FindFirstChild("RE_DamagePerformance") local RE_AbilityPerformance = EventsFolder:FindFirstChild("RE_AbilityPerformance") +--> Dependencies +local UIManager = require(script.Parent.Parent.UI.UIManager) +local Signal = require(ReplicatedStorage.Tools.Signal) + --> Variables local LocalPlayer = game.Players.LocalPlayer local DamageBoard = require(script.DamageBoard) +local showAbilitySignal = Signal.new(Signal.ENUM.SHOW_ABILITY) -------------------------------------------------------------------------------- -- 生成本地化表现目录 @@ -64,6 +69,9 @@ RE_PerformanceEvent.OnClientEvent:Connect(function(ServerTime: number, CastTag: local BehaviourTable = PerformanceClient.pData[UserId][BehaviourName] BehaviourTable[CastInfo.UniqueId] = Behaviours[BehaviourName]:Init(CasterPlayer, CastInfo, delayTime, CastState) BehaviourTable[CastInfo.UniqueId]:Show(CasterPlayer, CastInfo, delayTime, CastState) + + -- 发送更新信号 + showAbilitySignal:Fire(BehaviourName, CastInfo) end elseif CastTag == "Destroy" then if not PerformanceClient.pData[UserId][BehaviourName] then return end @@ -76,6 +84,7 @@ RE_PerformanceEvent.OnClientEvent:Connect(function(ServerTime: number, CastTag: end end) + -- 监听伤害表现事件调用 RE_DamagePerformance.OnClientEvent:Connect(function(ServerTime: number, CastTag: string, CastPlayer: Player, DamageDetail: table) -- print("DamagePerformance", ServerTime, CastTag, CastPlayer, DamageDetail) @@ -85,11 +94,11 @@ end) -- 这里主要是初始化 RE_AbilityPerformance.OnClientEvent:Connect(function(ServerTime: number, CastTag: string, CastPlayer: Player, AbilityDetail: table) print("AbilityPerformance", ServerTime, CastTag, CastPlayer, AbilityDetail) + UIManager:OpenWindow("AbilityStateWindow", AbilityDetail) end) -- 清理玩家所有表现数据 RE_CleanPlayerPerformance.OnClientEvent:Connect(function(CleanedPlayer: Player) - -- print("CleanPlayerPerformance", CleanedPlayer) local UserId = CleanedPlayer.UserId if not PerformanceClient.pData[UserId] then return end for _, BehaviourList in pairs(PerformanceClient.pData[UserId]) do diff --git a/src/StarterPlayerScripts/UI/UIManager.luau b/src/StarterPlayerScripts/UI/UIManager.luau index e69de29..eb11217 100644 --- a/src/StarterPlayerScripts/UI/UIManager.luau +++ b/src/StarterPlayerScripts/UI/UIManager.luau @@ -0,0 +1,31 @@ +local UIManager = {} + +local FolderWindows = script.Parent.Windows + +UIManager.Instances = {} +UIManager.Windows = {} +for _, child in FolderWindows:GetChildren() do + UIManager.Windows[child.Name] = require(child) +end + +function UIManager:OpenWindow(WindowName: string, Data: table?) + if not UIManager.Windows[WindowName] then + warn("UIManager:OpenWindow() 窗口不存在:" .. WindowName) + return + end + + UIManager.Instances[WindowName] = UIManager.Windows[WindowName]:Init(self, Data) + UIManager.Instances[WindowName]:OnOpenWindow() +end + +function UIManager:CloseWindow(WindowName: string) + if not UIManager.Instances[WindowName] then + warn("UIManager:CloseWindow() 窗口不存在:" .. WindowName) + return + end + + UIManager.Instances[WindowName]:OnCloseWindow() +end + + +return UIManager \ No newline at end of file diff --git a/src/StarterPlayerScripts/UI/Windows/AbilityStateWindow/AbilityShow.luau b/src/StarterPlayerScripts/UI/Windows/AbilityStateWindow/AbilityShow.luau new file mode 100644 index 0000000..7b41eee --- /dev/null +++ b/src/StarterPlayerScripts/UI/Windows/AbilityStateWindow/AbilityShow.luau @@ -0,0 +1,76 @@ +local AbilityShow = {} +AbilityShow.__index = AbilityShow + +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Utils = require(ReplicatedStorage.Tools.Utils) +local Localization = require(ReplicatedStorage.Tools.Localization) +local JsonAbility = require(ReplicatedStorage.Json.Ability) +local Signal = require(ReplicatedStorage.Tools.Signal) + +local showAbilitySignal = Signal.new(Signal.ENUM.SHOW_ABILITY) + +function AbilityShow:Init(data: table) + local self = {} + self.Data = data + self.Variables = { + ["_imgIcon"] = 0, + ["_imgMask"] = 0, + ["_tmpCd"] = 0, + } + self.Task = nil + self.SignalConnections = {} + + local con = showAbilitySignal:Connect(function(BehaviourName, CastInfo) + if BehaviourName == self.Data.AbilityName then + self:Refresh() + end + end) + table.insert(self.SignalConnections, con) + + setmetatable(self, AbilityShow) + return self +end + +function AbilityShow:Refresh() + if self.Task then + task.cancel(self.Task) + self.Task = nil + end + + if self.Data.AbilityName ~= nil then + self.Variables._imgIcon.Image = Localization:GetImageData(Utils:GetSpecialKeyDataFromJson(JsonAbility, "behaviourName", self.Data.AbilityName).icon) + end + + self.Variables._imgIcon.Visible = true + self.Variables._imgMask.Visible = false + self.Variables._tmpCd.Text = "" + + if self.Data.Cooldown > 0 then + self.Variables._imgMask.Visible = true + self.Variables._tmpCd.Text = self.Data.Cooldown + self.Task = task.spawn(function() + local cd = self.Data.Cooldown + while cd > 0 do + task.wait(0.2) + cd -= 0.2 + self.Variables._tmpCd.Text = string.format("%.1f", cd) + end + self.Variables._imgMask.Visible = false + self.Variables._tmpCd.Text = "" + end) + end +end + +function AbilityShow:Destroy() + if self.Task then + task.cancel(self.Task) + self.Task = nil + end + for k, v in pairs(self) do + self[k] = nil + end + self = nil +end + +return AbilityShow \ No newline at end of file diff --git a/src/StarterPlayerScripts/UI/Windows/AbilityStateWindow/init.luau b/src/StarterPlayerScripts/UI/Windows/AbilityStateWindow/init.luau new file mode 100644 index 0000000..d97908e --- /dev/null +++ b/src/StarterPlayerScripts/UI/Windows/AbilityStateWindow/init.luau @@ -0,0 +1,49 @@ +--> Services +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +--> Dependencies +local UIWindow = require(ReplicatedStorage.Base.UIWindow) +local UIEnums = require(ReplicatedStorage.Base.UIEnums) + +--> Components +local AbilityShow = require(script.AbilityShow) + +-------------------------------------------------------------------------------- + +local AbilityStateWindows = {} +AbilityStateWindows.__index = AbilityStateWindows +setmetatable(AbilityStateWindows, {__index = UIWindow}) + +function AbilityStateWindows:Init(UIManager: table, Data: table?) + local self = UIWindow:Init(UIManager, Data) + setmetatable(self, AbilityStateWindows) + self.Variables = { + ["__listAbility"] = 0, + ["__listWearAbility"] = 0, + } + self.UIRootName = "ui_w_abilityState" + self.UIParentName = UIEnums.UIParent.UIRoot + + + return self +end + +function AbilityStateWindows:OnOpenWindow() + UIWindow.OnOpenWindow(self) + + self.Variables["__listAbility"]:AddComponent(AbilityShow) + self.Variables["__listWearAbility"]:AddComponent(AbilityShow) + self.Variables["__listAbility"]:SetData(self.Data.Ability) + self.Variables["__listWearAbility"]:SetData(self.Data.WearAbility) +end + +function AbilityStateWindows:OnCloseWindow() + UIWindow.OnCloseWindow(self) + + self.Variables["__listAbility"]:Clean() + self.Variables["__listWearAbility"]:Clean() +end + + + +return AbilityStateWindows \ No newline at end of file