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 38d3ee9..5cd47c1 100644 Binary files a/excel/ability.xlsx and b/excel/ability.xlsx differ 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