This commit is contained in:
gechangfu 2025-08-20 19:29:16 +08:00
parent fdc6ee6a6e
commit 2c43aba4bf
20 changed files with 867 additions and 41 deletions

147
RUNE_STATE_WINDOW_README.md Normal file
View File

@ -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** - 初始版本,支持基础符文显示和小丑牌动画
- 支持符文执行记录接收和播放
- 支持测试脚本快速验证功能

Binary file not shown.

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
@ -20,24 +26,51 @@ function RuneCalculation:GetRuneAttributes(Player: Player, PlayerAI: table, Attr
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

View File

@ -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
-- 获取对应装备槽位上的技能

View File

@ -123,5 +123,6 @@ end)
-- 打开默认界面
UIManager:OpenWindow("MainWindow")
UIManager:OpenWindow("TipsWindow")
UIManager:OpenWindow("RuneStateWindow")
return PerformanceClient

View File

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

View File

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

View File

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

View File

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