diff --git a/RUNE_STATE_WINDOW_README.md b/RUNE_STATE_WINDOW_README.md new file mode 100644 index 0000000..a7bbc01 --- /dev/null +++ b/RUNE_STATE_WINDOW_README.md @@ -0,0 +1,147 @@ +# RuneStateWindow 符文状态窗口 + +## 📋 功能概述 + +RuneStateWindow是一个用于显示当前穿戴符文状态的UI窗口,主要功能包括: + +1. **符文栏显示** - 根据当前穿戴装备显示符文列表 +2. **小丑牌动画** - 接收服务端符文执行记录后播放类似小丑牌的动画效果 + +## 🏗️ 文件结构 + +``` +src/StarterPlayerScripts/UI/Windows/RuneStateWindow/ +├── init.luau # 主窗口文件 +└── RuneShow.luau # 符文显示组件 + +src/StarterPlayerScripts/ClientMain/ +├── RuneExecutionClient.luau # 客户端接收符文执行记录 +└── TestRuneStateWindow.luau # 测试脚本 +``` + +## 🎮 使用方法 + +### 1. 打开符文状态窗口 + +```lua +-- 通过UIManager打开窗口 +UIManager:OpenWindow("RuneStateWindow", { + runeData = { + { + runeName = "RuneFireDamage", + runeUniqueId = 1, + level = 5, + icon = "fire_icon" + }, + { + runeName = "RuneIceCoffin", + runeUniqueId = 2, + level = 3, + icon = "ice_icon" + } + } +}) +``` + +### 2. 接收符文执行记录 + +窗口会自动接收服务端发送的符文执行记录,并播放动画: + +```lua +-- 服务端发送的数据格式 +local executionRecords = { + { + runeName = "RuneFireDamage", + runeUniqueId = 1, + index = 1, + timestamp = tick(), + action = "Execute", + attributesBefore = {attack = 100, hp = 1000}, + attributesAfter = {attack = 150, hp = 1000}, + behaviorListBefore = {"Attack"}, + behaviorListAfter = {"Attack", "FireDamage"}, + nextIndex = 2 + } +} +``` + +### 3. 测试功能 + +使用测试脚本可以快速验证功能: + +- **按 R 键** - 打开符文状态窗口 +- **按 T 键** - 模拟接收符文执行记录 +- **按 C 键** - 关闭符文状态窗口 + +## 🎨 动画效果 + +### 小丑牌动画特点 + +1. **金色光效** - 符文激活时显示金色发光效果 +2. **缩放动画** - 符文图标会放大并旋转 +3. **粒子效果** - 中心产生黄色粒子扩散效果 +4. **顺序播放** - 多个符文按执行顺序依次播放动画 + +### 动画参数 + +- **动画时长**: 0.3秒 +- **缩放比例**: 1.5倍 +- **旋转角度**: 360度 +- **颜色**: 金色(#FFD700)和黄色(#FFFF00) + +## 🔧 技术实现 + +### 核心组件 + +1. **RuneStateWindow** - 主窗口管理 + - 管理符文执行记录队列 + - 控制动画播放顺序 + - 处理窗口生命周期 + +2. **RuneShow** - 符文显示组件 + - 显示符文图标、名称、等级 + - 处理符文激活动画 + - 管理组件生命周期 + +3. **RuneExecutionClient** - 客户端接收器 + - 监听服务端符文执行记录 + - 转发数据到窗口组件 + +### 数据流程 + +``` +服务端符文执行 → RemoteEvent发送 → 客户端接收 → 窗口处理 → 动画播放 +``` + +## 🎯 扩展功能 + +### 可扩展的动画效果 + +1. **音效支持** - 添加符文激活音效 +2. **特效增强** - 添加更多视觉特效 +3. **交互功能** - 点击符文查看详情 +4. **状态显示** - 显示符文冷却时间 + +### 自定义动画 + +可以通过修改`CreateJokerCardEffect`方法来自定义动画效果: + +```lua +function RuneStateWindow:CreateJokerCardEffect(runeItem, record) + -- 自定义动画逻辑 + -- 可以添加更多特效、音效等 +end +``` + +## 🐛 注意事项 + +1. **UI预制体** - 需要创建对应的UI预制体`ui_w_rune_state` +2. **符文数据** - 需要正确配置符文JSON数据 +3. **图标资源** - 需要上传对应的符文图标资源 +4. **性能优化** - 大量符文时注意动画性能 + +## 📝 更新日志 + +- **v1.0.0** - 初始版本,支持基础符文显示和小丑牌动画 +- 支持符文执行记录接收和播放 +- 支持测试脚本快速验证功能 diff --git a/excel/Rune.xlsx b/excel/Rune.xlsx index 7198895..7bed530 100644 Binary files a/excel/Rune.xlsx and b/excel/Rune.xlsx differ diff --git a/excel/ability.xlsx b/excel/ability.xlsx index adf63a1..8e6459d 100644 Binary files a/excel/ability.xlsx and b/excel/ability.xlsx differ diff --git a/src/ReplicatedStorage/Json/Ability.json b/src/ReplicatedStorage/Json/Ability.json index 8eef569..c24fb01 100644 --- a/src/ReplicatedStorage/Json/Ability.json +++ b/src/ReplicatedStorage/Json/Ability.json @@ -1,4 +1,5 @@ [ {"id":20000,"type":1,"icon":1,"nameId":20000,"behaviourName":"Attack","upgradeCost":[30000,5,0],"upgradeValue":[10,0],"recycle":[30000,0],"isInPool":null}, -{"id":20001,"type":1,"icon":1,"nameId":20001,"behaviourName":"SwordWave","upgradeCost":[30000,5,10],"upgradeValue":[10,200],"recycle":[30000,100],"isInPool":1} +{"id":20001,"type":1,"icon":1,"nameId":20001,"behaviourName":"SwordWave","upgradeCost":[30000,5,10],"upgradeValue":[10,200],"recycle":[30000,100],"isInPool":1}, +{"id":61000,"type":1,"icon":1,"nameId":61000,"behaviourName":"IceCoffine","upgradeCost":[30000,5,0],"upgradeValue":[10,0],"recycle":[30000,0],"isInPool":null} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Attributes.json b/src/ReplicatedStorage/Json/Attributes.json index 1786872..c9efaa9 100644 --- a/src/ReplicatedStorage/Json/Attributes.json +++ b/src/ReplicatedStorage/Json/Attributes.json @@ -30,5 +30,5 @@ {"id":53,"type":1,"specialType":null,"effectAttribute":"elementNumber","battleValue":[1,10],"iconId":29,"nameId":253}, {"id":54,"type":1,"specialType":null,"effectAttribute":"elementDefNumber","battleValue":[1,10],"iconId":30,"nameId":254}, {"id":55,"type":1,"specialType":null,"effectAttribute":"gemNumber","battleValue":[1,10],"iconId":31,"nameId":255}, -{"id":56,"type":1,"specialType":null,"effectAttribute":"runeNumber","battleValue":[1,10],"iconId":31,"nameId":256} +{"id":56,"type":1,"specialType":null,"effectAttribute":"runeNumber","battleValue":[1,10],"iconId":32,"nameId":256} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Rune.json b/src/ReplicatedStorage/Json/Rune.json index 320bce9..1b012ae 100644 --- a/src/ReplicatedStorage/Json/Rune.json +++ b/src/ReplicatedStorage/Json/Rune.json @@ -1,5 +1,5 @@ [ {"id":60000,"quality":1,"type":1,"icon":1,"nameId":60000,"runeName":"RuneFireDamage","behaviorName":null,"recycle":[],"isInPool":1}, -{"id":61000,"quality":2,"type":1,"icon":1,"nameId":61000,"runeName":"RuneIceCoffin","behaviorName":"IceCoffin","recycle":[],"isInPool":1}, +{"id":61000,"quality":2,"type":1,"icon":1,"nameId":61000,"runeName":"RuneIceCoffin","behaviorName":"IceCoffine","recycle":[],"isInPool":1}, {"id":62000,"quality":3,"type":1,"icon":1,"nameId":62000,"runeName":null,"behaviorName":null,"recycle":[],"isInPool":1} ] \ No newline at end of file diff --git a/src/Server/ServerMain/init.server.luau b/src/Server/ServerMain/init.server.luau index 578f158..4fc9f83 100644 --- a/src/Server/ServerMain/init.server.luau +++ b/src/Server/ServerMain/init.server.luau @@ -60,6 +60,14 @@ local Temporary = CreateFolder("Temporary", workspace) local ProjectileCache = CreateFolder("ProjectileCache", Temporary) local Characters = CreateFolder("Characters", workspace) +-- 初始化ReplicatedStorage目录 +local EventsFolder = CreateFolder("Events", ReplicatedStorage) + +-- 创建符文执行记录RemoteEvent +local RE_RuneExecutionRecord = Instance.new("RemoteEvent") +RE_RuneExecutionRecord.Name = "RE_RuneExecutionRecord" +RE_RuneExecutionRecord.Parent = EventsFolder + -- Initially require all server-sided & shared modules -- 加载模块 ReplicatedStorage.Modules, ServerStorage.Modules for _, Location in {ReplicatedStorage.Modules, ServerStorage.Modules} do diff --git a/src/ServerStorage/Base/Rune.luau b/src/ServerStorage/Base/Rune.luau index 53de154..4a029ed 100644 --- a/src/ServerStorage/Base/Rune.luau +++ b/src/ServerStorage/Base/Rune.luau @@ -7,20 +7,55 @@ local ServerStorage = game:GetService("ServerStorage") --> Dependencies local TypeList = require(ServerStorage.Base.TypeList) -local Communicate = require(ServerStorage.Modules.Tools.Communicate) local Utils = require(ReplicatedStorage.Tools.Utils) - - function Rune:Init(PlayerAI: Player, Character: TypeList.Character, ScriptName: string) local self = {} setmetatable(self, Rune) self.PlayerAI = PlayerAI self.Character = Character self.ScriptName = ScriptName + self.Player = self.Character.Player self.TriggerSlot = 0 self.WearingSlot = 0 self.TriggerTime = 0 + self.executionRecords = {} + self.isRecording = false -- 记录开关 + return self +end + +-- 开始记录 +function Rune:StartRecording() + self.isRecording = true + self.executionRecords = {} +end + +-- 停止记录 +function Rune:StopRecording() + self.isRecording = false +end + +-- 记录执行信息 +function Rune:RecordExecution(index: number, attributesBefore: table?, attributesAfter: table?, behaviorListBefore: table?, behaviorListAfter: table?, nextIndex: number?) + if not self.isRecording then return end + + table.insert(self.executionRecords, { + runeName = self.ScriptName, + runeUniqueId = self.TriggerSlot, + index = index, + timestamp = tick(), + action = "Execute", + attributesBefore = attributesBefore or {}, + attributesAfter = attributesAfter or {}, + behaviorListBefore = behaviorListBefore or {}, + behaviorListAfter = behaviorListAfter or {}, + nextIndex = nextIndex + }) +end + +-- 获取执行记录 +function Rune:GetExecutionRecords() + return self.executionRecords end -- 触发最开始事件 @@ -62,14 +97,25 @@ function Rune:Check(index: number, AttributesData: table?, BehaviorNameList: tab -- index: 当前符文在列表中的位置 -- AttributesData: 属性数据,符文可以修改 -- BehaviorNameList: 行为名称列表,符文可以修改 - return false -- 默认返回false,表示不可以执行 + -- 返回值: true表示可以执行,false表示跳过 + return false end -- 执行事件 function Rune:Execute(index: number, AttributesData: table?, BehaviorNameList: table?) - -- 使用PlayerAI的专用方法,性能更好 + -- index: 当前符文在列表中的位置 + -- AttributesData: 属性数据,符文可以修改 + -- BehaviorNameList: 行为名称列表,符文可以修改 + -- 返回值: 下一个要执行的index,或者nil表示继续下一个 + + -- 记录执行前的状态 + local attributesBefore = Utils:DeepCopyTable(AttributesData or {}) + local behaviorListBefore = Utils:DeepCopyTable(BehaviorNameList or {}) + + -- 使用PlayerAI的专用方法 local beginNextIndex = self.PlayerAI:TriggerAllRunesExcept(self, "OnTriggerBeginEvent", index, AttributesData, BehaviorNameList) if type(beginNextIndex) == "number" then + self:RecordExecution(index, attributesBefore, AttributesData, behaviorListBefore, BehaviorNameList, beginNextIndex) return beginNextIndex end @@ -77,32 +123,35 @@ function Rune:Execute(index: number, AttributesData: table?, BehaviorNameList: t local nextIndex = self:OnExecute(index, AttributesData, BehaviorNameList) if type(nextIndex) == "number" then self.PlayerAI:TriggerAllRunesExcept(self, "OnTriggerEndEvent", index, AttributesData, BehaviorNameList) + self:RecordExecution(index, attributesBefore, AttributesData, behaviorListBefore, BehaviorNameList, nextIndex) return nextIndex end local endNextIndex = self.PlayerAI:TriggerAllRunesExcept(self, "OnTriggerEndEvent", index, AttributesData, BehaviorNameList) if type(endNextIndex) == "number" then + self:RecordExecution(index, attributesBefore, AttributesData, behaviorListBefore, BehaviorNameList, endNextIndex) return endNextIndex end + self.TriggerTime = self.TriggerTime + 1 - return nextIndex -- 返回下一个要执行的index + self:RecordExecution(index, attributesBefore, AttributesData, behaviorListBefore, BehaviorNameList, nextIndex) + + return nextIndex end -- 子类可以重写这个方法 function Rune:OnExecute(index: number, AttributesData: table?, BehaviorNameList: table?) - -- 默认实现,子类可以重写 -- index: 当前符文在列表中的位置 -- AttributesData: 属性数据,符文可以修改 -- BehaviorNameList: 行为名称列表,符文可以修改 -- 返回值: 下一个要执行的index,或者nil表示继续下一个 - return nil -- 默认继续下一个符文 + return nil end -- 销毁 function Rune:OnDestroy() + self.executionRecords = nil self = nil end - - return Rune \ No newline at end of file diff --git a/src/ServerStorage/Modules/Behaviours/IceCoffine.luau b/src/ServerStorage/Modules/Behaviours/IceCoffine.luau index d3911c3..5dccc76 100644 --- a/src/ServerStorage/Modules/Behaviours/IceCoffine.luau +++ b/src/ServerStorage/Modules/Behaviours/IceCoffine.luau @@ -28,7 +28,7 @@ function IceCoffine:Init(PlayerAI, Character: TypeList.Character, Player: Player self.Mobs = nil setmetatable(self, IceCoffine) self.OrgCooldown = COOLDOWN - self:StartCooldownTask() + -- self:StartCooldownTask() -- 客户端表现 self:SendPerformanceEvent("Init", self.Player, script.Name, true, {}) @@ -40,8 +40,8 @@ function IceCoffine:Check(CheckInfo: table) self:CheckClean() -- 当前血量<=20%时触发技能 - local maxHp = self.Character.Stats.MaxHp - local currentHp = self.Character.Stats.Hp + local maxHp = self.Character.Config.maxhp + local currentHp = self.Character.Config.hp local recoverHp = maxHp * 0.2 if currentHp <= recoverHp then self.CheckData = {} @@ -58,7 +58,7 @@ function IceCoffine:Execute() -- cd放前面之后发送事件才能正常记录cd self:StartCooldownTask() - local maxHp = self.Character.Stats.MaxHp + local maxHp = self.Character.Config.MaxHp local recoverHp = math.floor(maxHp * (self.PlayerAI:GetSharedData("IceCoffin_MaxRecover") / 100)) DamageProxy:Heal(self.Character, self.Character, recoverHp) task.wait(0.5) diff --git a/src/ServerStorage/Modules/Runes/RuneFireDamage.luau b/src/ServerStorage/Modules/Runes/RuneFireDamage.luau index 3e5d225..a19216f 100644 --- a/src/ServerStorage/Modules/Runes/RuneFireDamage.luau +++ b/src/ServerStorage/Modules/Runes/RuneFireDamage.luau @@ -1,17 +1,19 @@ --> Services local ReplicatedStorage = game:GetService("ReplicatedStorage") +local ServerStorage = game:GetService("ServerStorage") --> Dependencies local Utils = require(ReplicatedStorage.Tools.Utils) +local TypeList = require(ServerStorage.Base.TypeList) +local Rune = require(ServerStorage.Base.Rune) local RuneFireDamage = {} RuneFireDamage.__index = RuneFireDamage setmetatable(RuneFireDamage, {__index = Rune}) -function RuneFireDamage:Init(PlayerAI, Character: TypeList.Character, Player: Player) +function RuneFireDamage:Init(PlayerAI, Character: TypeList.Character) local self = Rune:Init(PlayerAI, Character, script.Name) - self.Player = Player setmetatable(self, RuneFireDamage) return self diff --git a/src/ServerStorage/Modules/Runes/RuneIceCoffin.luau b/src/ServerStorage/Modules/Runes/RuneIceCoffin.luau index 040e688..4a40c2e 100644 --- a/src/ServerStorage/Modules/Runes/RuneIceCoffin.luau +++ b/src/ServerStorage/Modules/Runes/RuneIceCoffin.luau @@ -1,17 +1,19 @@ --> Services local ReplicatedStorage = game:GetService("ReplicatedStorage") +local ServerStorage = game:GetService("ServerStorage") --> Dependencies local Utils = require(ReplicatedStorage.Tools.Utils) +local TypeList = require(ServerStorage.Base.TypeList) +local Rune = require(ServerStorage.Base.Rune) local RuneIceCoffin = {} RuneIceCoffin.__index = RuneIceCoffin setmetatable(RuneIceCoffin, {__index = Rune}) -function RuneIceCoffin:Init(PlayerAI, Character: TypeList.Character, Player: Player) +function RuneIceCoffin:Init(PlayerAI, Character: TypeList.Character) local self = Rune:Init(PlayerAI, Character, script.Name) - self.Player = Player setmetatable(self, RuneIceCoffin) return self diff --git a/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau b/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau index 685b939..5e315c0 100644 --- a/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau +++ b/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau @@ -95,7 +95,7 @@ end -- 动态添加行为 function PlayerAI:AddBehaviour(BehaviourName: string, WearingSlot: number?) - if not Behaviours[BehaviourName] then warn("Behaviour not found") return end + if not Behaviours[BehaviourName] then warn("Behaviour not found", BehaviourName) return end local newBehaviour = Behaviours[BehaviourName]:Init(self, self.Character, self.Player) newBehaviour.WearingSlot = WearingSlot or 4 self.BehaviourList[BehaviourName] = newBehaviour diff --git a/src/ServerStorage/Proxy/PlayerFightProxy/init.luau b/src/ServerStorage/Proxy/PlayerFightProxy/init.luau index 305a5a4..d4898ad 100644 --- a/src/ServerStorage/Proxy/PlayerFightProxy/init.luau +++ b/src/ServerStorage/Proxy/PlayerFightProxy/init.luau @@ -185,7 +185,6 @@ function PlayerFightProxy:UpdatePlayerFightData(Player: Player) end -- 更新玩家属性 - -- print(AttributesData) for AttributeName, AttributeValue in AttributesData do -- TODO:这里可能涉及到战斗时更换装备的属性处理,还需要再函数内部再根据剩余百分比数值变化 PlayerRole:ChangeAttributeValue(AttributeName, AttributeValue) @@ -205,7 +204,6 @@ function PlayerFightProxy:UpdatePlayerFightData(Player: Player) playerAI:AddBehaviour(behaviorName) end playerAI:AddBehaviour("Move") - -- playerAI:AddBehaviour("SwordWave") playerAI:AddBehaviour("Attack") diff --git a/src/ServerStorage/Proxy/RuneProxy/RuneCalculation.luau b/src/ServerStorage/Proxy/RuneProxy/RuneCalculation.luau index 8ad3573..c1d64b2 100644 --- a/src/ServerStorage/Proxy/RuneProxy/RuneCalculation.luau +++ b/src/ServerStorage/Proxy/RuneProxy/RuneCalculation.luau @@ -1,5 +1,11 @@ local RuneCalculation = {} +--> Services +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +--> Events +local RE_RuneExecutionRecord = ReplicatedStorage.Events:WaitForChild("RE_RuneExecutionRecord") + -- 处理符文机制主函数 function RuneCalculation:GetRuneAttributes(Player: Player, PlayerAI: table, AttributesData: table, BehaviorNameList: table) if not Player and not PlayerAI then warn("Player or PlayerAI not found") return end @@ -19,25 +25,52 @@ function RuneCalculation:GetRuneAttributes(Player: Player, PlayerAI: table, Attr PlayerAI:ClearSharedData() PlayerAI:ClearAllRune() for wearingSlot, runeName in wearingRuneName do PlayerAI:AddRune(runeName, wearingSlot) end + + -- 开始记录 + for runeName, rune in pairs(PlayerAI.RuneList) do + rune:StartRecording() + end -- 触发开始事件,并检查是否有符文要控制流程 local startNextIndex = PlayerAI:ForEachRune(function(runeName, triggerSlot, rune) local result = rune:OnStartEvent(runeName, triggerSlot, AttributesData, BehaviorNameList) if type(result) == "number" then - return result -- 返回要跳转的index + return result end end) local index = 1 - local maxSteps = 1000 -- 防止无限循环 + local maxSteps = 1000 - -- 如果开始事件指定了起始位置,使用它 if type(startNextIndex) == "number" then index = startNextIndex end -- 执行符文循环 - self:ExecuteRuneLoop(PlayerAI, wearingRuneName, wearingRuneUniqueId, index, maxSteps) + self:ExecuteRuneLoop(PlayerAI, wearingRuneName, wearingRuneUniqueId, index, maxSteps, AttributesData, BehaviorNameList) + + -- 收集所有符文的执行记录 + local allExecutionRecords = {} + for runeName, rune in pairs(PlayerAI.RuneList) do + local records = rune:GetExecutionRecords() + if records and #records > 0 then + for _, record in ipairs(records) do + table.insert(allExecutionRecords, record) + end + end + rune:StopRecording() + end + + -- 统一发送执行记录到前端 + if #allExecutionRecords > 0 then + local success, error = pcall(function() + RE_RuneExecutionRecord:FireClient(Player, allExecutionRecords) + end) + + if not success then + warn("RuneCalculation: 发送符文执行记录失败:", error) + end + end end -- 执行符文循环的辅助函数 @@ -49,31 +82,28 @@ function RuneCalculation:ExecuteRuneLoop(PlayerAI: table, wearingRuneName: table if result then local nextIndex = PlayerAI:TriggerRune(wearingRuneName[index], "Execute", index, AttributesData, BehaviorNameList) - -- 根据返回值决定下一个index if type(nextIndex) == "number" then - index = nextIndex -- 符文指定了下一个位置 + index = nextIndex else - index += 1 -- 默认继续下一个 + index += 1 end else index += 1 end - maxSteps -= 1 -- 防止无限循环 + maxSteps -= 1 end -- 触发结束事件,并检查是否有符文要控制流程 local endNextIndex = PlayerAI:ForEachRune(function(runeName, triggerSlot, rune) local result = rune:OnEndEvent(runeName, triggerSlot, AttributesData, BehaviorNameList) if type(result) == "number" then - return result -- 返回要跳转的index + return result end end) - -- 如果结束事件指定了要跳转的位置,重新开始循环 if type(endNextIndex) == "number" and endNextIndex <= #wearingRuneUniqueId and maxSteps > 0 then self:ExecuteRuneLoop(PlayerAI, wearingRuneName, wearingRuneUniqueId, endNextIndex, maxSteps, AttributesData, BehaviorNameList) end end - return RuneCalculation \ No newline at end of file diff --git a/src/ServerStorage/Proxy/RuneProxy/init.luau b/src/ServerStorage/Proxy/RuneProxy/init.luau index e302b27..a7b2302 100644 --- a/src/ServerStorage/Proxy/RuneProxy/init.luau +++ b/src/ServerStorage/Proxy/RuneProxy/init.luau @@ -258,16 +258,16 @@ function RuneProxy:GetPlayerWearingRuneData(Player: Player) for _, RuneData in ArchiveProxy.pData[Player.UserId][STORE_NAME] do if tonumber(RuneData.wearing) > 0 and table.find(wearingEquipments, RuneData.wearing) then table.insert(wearingRuneUniqueId, RuneData.id) - local RuneData = Utils:GetIdDataFromJson(JsonRune, RuneData.orgId) - if RuneData.runeName then - wearingRuneName[RuneData.wearingSlot] = RuneData.runeName + local JsonRuneData = Utils:GetIdDataFromJson(JsonRune, RuneData.orgId) + if JsonRuneData and JsonRuneData.runeName then + wearingRuneName[RuneData.wearingSlot] = JsonRuneData.runeName end - if RuneData.behaviorName then - table.insert(wearingRuneBehaviorName, RuneData.behaviorName) + if JsonRuneData and JsonRuneData.behaviorName then + table.insert(wearingRuneBehaviorName, JsonRuneData.behaviorName) end end end - return wearingRuneUniqueId, wearingRuneName + return wearingRuneUniqueId, wearingRuneName, wearingRuneBehaviorName end -- 获取对应装备槽位上的技能 diff --git a/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau b/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau index f62a98c..cf5c3a7 100644 --- a/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau +++ b/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau @@ -123,5 +123,6 @@ end) -- 打开默认界面 UIManager:OpenWindow("MainWindow") UIManager:OpenWindow("TipsWindow") +UIManager:OpenWindow("RuneStateWindow") return PerformanceClient \ No newline at end of file diff --git a/src/StarterPlayerScripts/ClientMain/TestRuneStateWindow.luau b/src/StarterPlayerScripts/ClientMain/TestRuneStateWindow.luau new file mode 100644 index 0000000..ca20a23 --- /dev/null +++ b/src/StarterPlayerScripts/ClientMain/TestRuneStateWindow.luau @@ -0,0 +1,70 @@ +--> Services +local Players = game:GetService("Players") +local UserInputService = game:GetService("UserInputService") + +--> Dependencies +local UIManager = require(script.Parent.Parent.UI.UIManager) + +-------------------------------------------------------------------------------- + +local LocalPlayer = Players.LocalPlayer + +-- 测试执行记录 +local testExecutionRecords = { + { + runeName = "RuneFireDamage", + runeUniqueId = 1, + index = 1, + timestamp = tick(), + action = "Execute", + attributesBefore = {attack = 100, hp = 1000, atkSpeed = 100}, + attributesAfter = {attack = 150, hp = 1000, atkSpeed = 120}, + behaviorListBefore = {"Attack"}, + behaviorListAfter = {"Attack", "FireDamage"}, + nextIndex = 2 + }, + { + runeName = "RuneIceCoffin", + runeUniqueId = 2, + index = 2, + timestamp = tick(), + action = "Execute", + attributesBefore = {attack = 150, hp = 1000, atkSpeed = 120}, + attributesAfter = {attack = 150, hp = 1200, atkSpeed = 120, fireAtk = 50}, + behaviorListBefore = {"Attack", "FireDamage"}, + behaviorListAfter = {"Attack", "FireDamage", "IceCoffin"}, + nextIndex = nil + } +} + +-- 键盘输入测试 +UserInputService.InputBegan:Connect(function(input, gameProcessed) + if gameProcessed then return end + + if input.KeyCode == Enum.KeyCode.R then + -- 打开符文状态窗口 + print("打开符文状态窗口") + UIManager:OpenWindow("RuneStateWindow") + + elseif input.KeyCode == Enum.KeyCode.T then + -- 模拟接收符文执行记录 + print("模拟符文执行记录") + local runeStateWindow = UIManager:GetWindow("RuneStateWindow") + if runeStateWindow then + runeStateWindow:OnRuneExecutionRecord(testExecutionRecords) + end + + elseif input.KeyCode == Enum.KeyCode.C then + -- 关闭符文状态窗口 + print("关闭符文状态窗口") + UIManager:CloseWindow("RuneStateWindow") + end +end) + +print("符文状态窗口测试脚本已启动") +print("按 R 键打开符文状态窗口") +print("按 T 键模拟符文执行记录") +print("按 C 键关闭符文状态窗口") + +-- 返回一个空表作为模块返回值 +return {} diff --git a/src/StarterPlayerScripts/UI/UIManager.luau b/src/StarterPlayerScripts/UI/UIManager.luau index 19365f2..f206713 100644 --- a/src/StarterPlayerScripts/UI/UIManager.luau +++ b/src/StarterPlayerScripts/UI/UIManager.luau @@ -9,6 +9,7 @@ UIManager.ExcludeFromStack = { -- 不受堆栈影响的窗口列表 "TipsWindow", -- 主窗口 "AbilityStateWindow", "LevelStageWindow", + "RuneStateWindow", -- 符文状态窗口 -- 可以继续添加其他窗口名称 } @@ -129,6 +130,11 @@ function UIManager:IsOpened(WindowName: string) return UIManager.Instances[WindowName] ~= nil end +-- 获取窗口实例 +function UIManager:GetWindow(WindowName: string) + return UIManager.Instances[WindowName] +end + function UIManager:SetData(WindowName: string, Data: table) if not UIManager.Instances[WindowName] then warn("UIManager:SetData() 窗口不存在:" .. WindowName) diff --git a/src/StarterPlayerScripts/UI/Windows/RuneStateWindow/RuneShow.luau b/src/StarterPlayerScripts/UI/Windows/RuneStateWindow/RuneShow.luau new file mode 100644 index 0000000..e870f7a --- /dev/null +++ b/src/StarterPlayerScripts/UI/Windows/RuneStateWindow/RuneShow.luau @@ -0,0 +1,151 @@ +local RuneShow = {} +RuneShow.__index = RuneShow + +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Utils = require(ReplicatedStorage.Tools.Utils) +local Localization = require(ReplicatedStorage.Tools.Localization) +local JsonRune = require(ReplicatedStorage.Json.Rune) +local Signal = require(ReplicatedStorage.Tools.Signal) + +local showRuneSignal = Signal.new(Signal.ENUM.SHOW_RUNE) + +function RuneShow:Init(data: table) + local self = {} + self.Data = data + self.Variables = { + ["_imgIcon"] = 0, + ["_imgMask"] = 0, + ["_tmpName"] = 0, + ["_tmpLevel"] = 0, + } + self.Task = nil + self.SignalConnections = {} + + local con = showRuneSignal:Connect(function(RuneName, RuneUniqueId) + if RuneName == self.Data.runeName and RuneUniqueId == self.Data.runeUniqueId then + self:Refresh() + end + end) + table.insert(self.SignalConnections, con) + + setmetatable(self, RuneShow) + return self +end + +-- 初始化完成后的回调 +function RuneShow:OnInitFinish() +end + +function RuneShow:Refresh() + if self.Task then + task.cancel(self.Task) + self.Task = nil + end + + if self.Data.runeName ~= nil then + -- 获取符文数据 + local runeData = Utils:GetSpecialKeyDataFromJson(JsonRune, "runeName", self.Data.runeName) + + -- 设置符文图标 + if runeData and runeData.icon then + self.Variables._imgIcon.Image = Localization:GetImageData(runeData.icon) + else + -- 使用默认图标 + self.Variables._imgIcon.Image = "rbxassetid://0" + end + + -- 设置符文名称 + if runeData and runeData.nameId then + -- 通过nameId获取本地化文本 + self.Variables._tmpName.Text = Localization:GetLanguageData(runeData.nameId) or "未知符文" + else + -- 使用符文名称作为显示文本 + self.Variables._tmpName.Text = self.Data.runeName or "未知符文" + end + + -- 设置符文等级 + if self.Data.level then + self.Variables._tmpLevel.Text = "Lv." .. tostring(self.Data.level) + else + self.Variables._tmpLevel.Text = "Lv.1" + end + + -- 设置符文背景颜色 + if runeData and runeData.quality then + local bgColor = Localization:GetRuneQualityBgColor(runeData.quality) + if bgColor and self.Variables._imgIcon then + self.Variables._imgIcon.BackgroundColor3 = bgColor + end + end + end + + self.Variables._imgIcon.Visible = true + self.Variables._imgMask.Visible = false +end + +-- 播放激活动画 +function RuneShow:PlayActivateAnimation() + -- 创建激活效果 + local effect = Instance.new("Frame") + effect.Size = UDim2.new(1, 0, 1, 0) + effect.Position = UDim2.new(0, 0, 0, 0) + effect.BackgroundColor3 = Color3.fromRGB(255, 215, 0) -- 金色 + effect.BackgroundTransparency = 0.3 + effect.BorderSizePixel = 0 + effect.Parent = self.UIRoot + + -- 创建粒子效果 + local particle = Instance.new("Frame") + particle.Size = UDim2.new(0.1, 0, 0.1, 0) + particle.Position = UDim2.new(0.45, 0, 0.45, 0) + particle.BackgroundColor3 = Color3.fromRGB(255, 255, 0) -- 黄色 + particle.BackgroundTransparency = 0 + particle.BorderSizePixel = 0 + particle.Parent = effect + + -- 动画效果 + local tweenInfo = TweenInfo.new(0.5, Enum.EasingStyle.Quad, Enum.EasingDirection.Out) + + -- 主效果动画 + local mainTween = game:GetService("TweenService"):Create(effect, tweenInfo, { + Size = UDim2.new(1.5, 0, 1.5, 0), + BackgroundTransparency = 1 + }) + + -- 粒子动画 + local particleTween = game:GetService("TweenService"):Create(particle, tweenInfo, { + Size = UDim2.new(0.5, 0, 0.5, 0), + BackgroundTransparency = 1 + }) + + -- 播放动画 + mainTween:Play() + particleTween:Play() + + -- 动画结束后清理 + mainTween.Completed:Connect(function() + effect:Destroy() + end) +end + +function RuneShow:Destroy() + if self.Task then + task.cancel(self.Task) + self.Task = nil + end + + -- 清理信号连接 + for _, connection in pairs(self.SignalConnections) do + if connection then + connection:Disconnect() + end + end + + for k, v in pairs(self) do + self[k] = nil + end + self = nil +end + +return RuneShow diff --git a/src/StarterPlayerScripts/UI/Windows/RuneStateWindow/init.luau b/src/StarterPlayerScripts/UI/Windows/RuneStateWindow/init.luau new file mode 100644 index 0000000..5909d86 --- /dev/null +++ b/src/StarterPlayerScripts/UI/Windows/RuneStateWindow/init.luau @@ -0,0 +1,361 @@ +--> Services +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +--> Dependencies +local UIWindow = require(ReplicatedStorage.Base.UIWindow) +local UIEnums = require(ReplicatedStorage.Base.UIEnums) +local Localization = require(ReplicatedStorage.Tools.Localization) +local Utils = require(ReplicatedStorage.Tools.Utils) + +--> Json +local JsonAttributes = require(ReplicatedStorage.Json.Attributes) + +--> Components +local RuneShow = require(script.RuneShow) + +-------------------------------------------------------------------------------- + +local RuneStateWindow = {} +RuneStateWindow.__index = RuneStateWindow +setmetatable(RuneStateWindow, {__index = UIWindow}) + +function RuneStateWindow:Init(UIManager: table, Data: table?) + local self = UIWindow:Init(UIManager, Data) + setmetatable(self, RuneStateWindow) + self.Variables = { + ["__listRune"] = 0, + } + self.UIRootName = "ui_w_rune_state" + self.UIParentName = UIEnums.UIParent.UIRoot + + -- 符文执行记录队列 + self.runeExecutionQueue = {} + self.isPlayingAnimation = false + + -- 事件连接 + self.runeExecutionConnection = nil + + return self +end + +function RuneStateWindow:OnOpenWindow() + UIWindow.OnOpenWindow(self) + + self.Variables["__listRune"]:AddComponent(RuneShow) + + -- 连接符文执行记录事件 + self:ConnectRuneExecutionEvent() + + -- 初始化符文列表 + self:RefreshRuneList() +end + +function RuneStateWindow:OnCloseWindow() + UIWindow.OnCloseWindow(self) + + self.Variables["__listRune"]:Clean() + + -- 断开符文执行记录事件连接 + self:DisconnectRuneExecutionEvent() +end + +-- 刷新符文列表 +function RuneStateWindow:RefreshRuneList() + -- 这里需要从服务端获取当前穿戴的符文数据 + -- 暂时使用模拟数据 + local runeData = self:GetWearingRuneData() + self.Variables["__listRune"]:SetData(runeData) + print("刷新符文列表", runeData) +end + + + +-- 获取穿戴的符文数据 +function RuneStateWindow:GetWearingRuneData() + -- 从传入的数据中获取符文信息 + if self.Data and self.Data.runeData then + return self.Data.runeData + end + + -- 从ReplicatedStorage获取当前穿戴的符文 + local runeData = {} + local Players = game:GetService("Players") + local LocalPlayer = Players.LocalPlayer + + -- 获取玩家数据文件夹 + local PlayerDataFolder = ReplicatedStorage:WaitForChild("PlayerData") + local PlayerFolder = PlayerDataFolder:FindFirstChild(LocalPlayer.UserId) + if not PlayerFolder then return runeData end + + -- 获取符文文件夹 + local RuneFolder = PlayerFolder:FindFirstChild("Rune") + if not RuneFolder then return runeData end + + -- 遍历所有符文,找到穿戴中的 + for _, runeInstance in pairs(RuneFolder:GetChildren()) do + local wearing = runeInstance:GetAttribute("wearing") + if wearing and tonumber(wearing) > 0 then + local runeInfo = { + runeName = runeInstance:GetAttribute("runeName"), + runeUniqueId = tonumber(runeInstance.Name), + level = runeInstance:GetAttribute("level") or 1, + wearingSlot = runeInstance:GetAttribute("wearingSlot") or 1 + } + table.insert(runeData, runeInfo) + end + end + + -- 按穿戴槽位排序 + table.sort(runeData, function(a, b) + return a.wearingSlot < b.wearingSlot + end) + + return runeData +end + +-- 接收符文执行记录 +function RuneStateWindow:OnRuneExecutionRecord(executionRecords: table) + -- 自动刷新符文列表(获取最新的穿戴符文数据) + self:RefreshRuneList() + + -- 将执行记录添加到队列 + for _, record in ipairs(executionRecords) do + table.insert(self.runeExecutionQueue, record) + end + + -- 开始播放动画 + if not self.isPlayingAnimation then + self:PlayRuneAnimation() + end +end + +-- 播放符文动画 +function RuneStateWindow:PlayRuneAnimation() + if #self.runeExecutionQueue == 0 then + self.isPlayingAnimation = false + return + end + + self.isPlayingAnimation = true + local record = table.remove(self.runeExecutionQueue, 1) + + -- 播放小丑牌动画 + self:PlayJokerCardAnimation(record) +end + +-- 播放小丑牌动画 +function RuneStateWindow:PlayJokerCardAnimation(record: table) + -- 找到对应的符文UI元素 + local runeItem = self:FindRuneItem(record.runeName, record.runeUniqueId) + if not runeItem then + -- 如果找不到符文,直接播放下一个 + task.wait(0.5) -- 短暂延迟 + self:PlayRuneAnimation() + return + end + + -- 创建动画效果 + self:CreateJokerCardEffect(runeItem, record) +end + +-- 查找符文UI元素 +function RuneStateWindow:FindRuneItem(runeName: string, runeUniqueId: number) + -- 在符文列表中查找对应的符文 + local runeList = self.Variables["__listRune"] + if runeList and runeList.Instances then + for _, component in pairs(runeList.Instances) do + if component.Data and component.Data.runeName == runeName and component.Data.runeUniqueId == runeUniqueId then + return component + end + end + end + return nil +end + +-- 创建小丑牌动画效果 +function RuneStateWindow:CreateJokerCardEffect(runeItem: table, record: table) + -- 获取符文UI根节点 + local runeUI = runeItem.UIRoot + if not runeUI then + return + end + + -- 创建动画效果 + local effect = Instance.new("Frame") + effect.Size = UDim2.new(1, 0, 1, 0) + effect.Position = UDim2.new(0, 0, 0, 0) + effect.BackgroundColor3 = Color3.fromRGB(255, 215, 0) -- 金色 + effect.BackgroundTransparency = 0.5 + effect.BorderSizePixel = 0 + effect.Parent = runeUI + + -- 创建发光效果 + local glow = Instance.new("Frame") + glow.Size = UDim2.new(1.2, 0, 1.2, 0) + glow.Position = UDim2.new(-0.1, 0, -0.1, 0) + glow.BackgroundColor3 = Color3.fromRGB(255, 255, 0) -- 黄色 + glow.BackgroundTransparency = 0.8 + glow.BorderSizePixel = 0 + glow.Parent = effect + + -- 创建属性变化显示 + self:CreateAttributeChangeDisplay(runeUI, record) + + -- 动画效果 + local tweenInfo = TweenInfo.new(0.3, Enum.EasingStyle.Quad, Enum.EasingDirection.Out) + + -- 缩放动画 + local scaleTween = game:GetService("TweenService"):Create(effect, tweenInfo, { + Size = UDim2.new(1.5, 0, 1.5, 0), + BackgroundTransparency = 0.9 + }) + + -- 旋转动画 + local rotationTween = game:GetService("TweenService"):Create(effect, tweenInfo, { + Rotation = 360 + }) + + -- 播放动画 + scaleTween:Play() + rotationTween:Play() + + -- 动画结束后清理 + scaleTween.Completed:Connect(function() + effect:Destroy() + -- 播放下一个动画 + task.wait(0.2) + self:PlayRuneAnimation() + end) +end + +-- 创建属性变化显示 +function RuneStateWindow:CreateAttributeChangeDisplay(runeUI: Instance, record: table) + -- 收集所有属性变化 + local attributeChanges = {} + + -- 检查属性变化 + if record.attributesBefore and record.attributesAfter then + for attrName, afterValue in pairs(record.attributesAfter) do + local beforeValue = record.attributesBefore[attrName] or 0 + local change = afterValue - beforeValue + if change ~= 0 then + -- 通过effectAttribute查找对应的nameId + local attributeData = Utils:GetSpecialKeyDataFromJson(JsonAttributes, "effectAttribute", attrName) + local attributeName = attrName -- 默认使用原始名称 + + if attributeData and attributeData.nameId then + -- 通过nameId获取本地化文本 + attributeName = Localization:GetLanguageData(attributeData.nameId) or attrName + end + + table.insert(attributeChanges, { + name = attributeName, + change = change + }) + end + end + end + + -- 如果没有属性变化,直接返回 + if #attributeChanges == 0 then + return + end + + -- 创建统一的属性变化显示 + self:CreateAttributeChangeContainer(runeUI, attributeChanges) +end + +-- 创建属性变化容器 +function RuneStateWindow:CreateAttributeChangeContainer(runeUI: Instance, attributeChanges: table) + -- 计算容器高度 + local containerHeight = #attributeChanges * 25 + 10 + + -- 创建属性变化显示容器 + local changeContainer = Instance.new("Frame") + changeContainer.Size = UDim2.new(0, 160, 0, containerHeight) + changeContainer.Position = UDim2.new(0.5, -80, 0, -30) + changeContainer.BackgroundColor3 = Color3.fromRGB(20, 20, 20) + changeContainer.BackgroundTransparency = 0.2 + changeContainer.BorderSizePixel = 0 + changeContainer.Parent = runeUI + + -- 创建圆角 + local corner = Instance.new("UICorner") + corner.CornerRadius = UDim.new(0, 8) + corner.Parent = changeContainer + + -- 创建边框 + local stroke = Instance.new("UIStroke") + stroke.Color = Color3.fromRGB(255, 215, 0) + stroke.Thickness = 1.5 + stroke.Parent = changeContainer + + -- 创建属性变化文本 + for i, change in ipairs(attributeChanges) do + local changeText = Instance.new("TextLabel") + changeText.Size = UDim2.new(1, -10, 0, 20) + changeText.Position = UDim2.new(0, 5, 0, 5 + (i-1) * 25) + changeText.Text = string.format("%s +%d", change.name, change.change) + changeText.TextColor3 = change.change > 0 and Color3.fromRGB(0, 255, 100) or Color3.fromRGB(255, 100, 100) + changeText.TextScaled = true + changeText.Font = Enum.Font.SourceSansBold + changeText.BackgroundTransparency = 1 + changeText.Parent = changeContainer + end + + -- 创建向上移动动画 + local moveTween = game:GetService("TweenService"):Create(changeContainer, TweenInfo.new(2, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), { + Position = UDim2.new(0.5, -80, 0, -150), + BackgroundTransparency = 1 + }) + + -- 播放动画 + moveTween:Play() + + -- 动画结束后清理 + moveTween.Completed:Connect(function() + changeContainer:Destroy() + end) +end + +-- 连接符文执行记录事件 +function RuneStateWindow:ConnectRuneExecutionEvent() + -- 确保只连接一次 + if self.runeExecutionConnection then + return + end + + -- 等待RemoteEvent + local RE_RuneExecutionRecord = ReplicatedStorage.Events:WaitForChild("RE_RuneExecutionRecord") + + -- 连接事件 + self.runeExecutionConnection = RE_RuneExecutionRecord.OnClientEvent:Connect(function(executionRecords: table) + print("收到符文执行记录:", executionRecords) + + -- 发送执行记录到窗口 + self:OnRuneExecutionRecord(executionRecords) + end) +end + +-- 断开符文执行记录事件 +function RuneStateWindow:DisconnectRuneExecutionEvent() + if self.runeExecutionConnection then + self.runeExecutionConnection:Disconnect() + self.runeExecutionConnection = nil + end +end + +-- 销毁窗口 +function RuneStateWindow:Destroy() + -- 清理动画队列 + self.runeExecutionQueue = {} + self.isPlayingAnimation = false + + -- 断开事件连接 + self:DisconnectRuneExecutionEvent() + + -- 调用父类销毁方法 + UIWindow.Destroy(self) +end + +return RuneStateWindow