From 9d7726e7d6ce7b36a9c182171e32ec4717393d52 Mon Sep 17 00:00:00 2001 From: gechangfu Date: Tue, 5 Aug 2025 15:19:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- excel/global.xlsx | Bin 22392 -> 22519 bytes excel/item.xlsx | Bin 22653 -> 22655 bytes excel/level.xlsx | Bin 10412 -> 10386 bytes src/ReplicatedStorage/Data/SignalEnum.luau | 1 + src/ReplicatedStorage/Json/Level.json | 100 ++++++------ src/ReplicatedStorage/Json/Param.json | 4 +- src/ServerStorage/Proxy/BookProxy.luau | 12 ++ src/ServerStorage/Proxy/DamageProxy.luau | 153 ++++++++++++++++-- src/ServerStorage/Proxy/LevelProxy.luau | 19 ++- src/ServerStorage/Proxy/MobsProxy/init.luau | 7 +- .../Proxy/PlayerFightProxy/LevelLoop.luau | 15 +- .../Proxy/PlayerFightProxy/PlayerAI.luau | 5 + .../Proxy/PlayerFightProxy/init.luau | 8 +- src/ServerStorage/Proxy/PlayerInfoProxy.luau | 6 + src/StarterPlayerScripts/UI/UIManager.luau | 1 + .../UI/Windows/LevelStageWindow/init.luau | 121 ++++++++++++++ .../UI/Windows/MainWindow/init.luau | 97 +++-------- 17 files changed, 393 insertions(+), 156 deletions(-) create mode 100644 src/StarterPlayerScripts/UI/Windows/LevelStageWindow/init.luau diff --git a/excel/global.xlsx b/excel/global.xlsx index 56ed5e1a6b8f373b6b018d399299c37b6d31677f..98f592d97bfa97ce94e2a1f343a3e6a101cdb108 100644 GIT binary patch delta 4351 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 NLzXuvj7XrZi diff --git a/excel/item.xlsx b/excel/item.xlsx index 8e4432d36800d8fe350213380a85474b85f070de..f5c902d7dfdc82c970a70e60381c476b8bed5d9d 100644 GIT binary patch delta 2043 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 Dj6mvs diff --git a/excel/level.xlsx b/excel/level.xlsx index eb0604aff110fb1732e61e528f253cc7ba8a8c21..80dfa16059a97bf0883560da3359a1a7690294b0 100644 GIT binary patch delta 3804 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 delta 3820 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 Json local JsonItem = require(ReplicatedStorage.Json.ItemProp) local JsonGem = require(ReplicatedStorage.Json.Gem) +local JsonParam = require(ReplicatedStorage.Json.Param) --> Events local RE_PlayerTip = ReplicatedStorage.Events.RE_PlayerTip @@ -39,15 +40,26 @@ function BookProxy:InitPlayer(Player: Player) local BookFolder = Utils:CreateFolder("Book", pData) -- 新玩家数据初始化 + local isNew = false if not ArchiveProxy.pData[Player.UserId][STORE_NAME] then ArchiveProxy.pData[Player.UserId][STORE_NAME] = {} ArchiveProxy.pData[Player.UserId][STORE_NAME].Books = {} + + isNew = true end -- 创建玩家信息实例 for BookId, BookData in ArchiveProxy.pData[Player.UserId][STORE_NAME].Books do Utils:CreateDataInstance(Player, BookId, BookData, BookFolder) end + + if isNew then + local ItemProxy = require(ServerStorage.Proxy.ItemProxy) + local newData = Utils:GetIdDataFromJson(JsonParam, 6) + for i = 1, #newData.intArray, 2 do + ItemProxy:AddItem(Player, newData.intArray[i], newData.intArray[i + 1]) + end + end end -------------------------------------------------------------------------------- diff --git a/src/ServerStorage/Proxy/DamageProxy.luau b/src/ServerStorage/Proxy/DamageProxy.luau index 6c70367..069887a 100644 --- a/src/ServerStorage/Proxy/DamageProxy.luau +++ b/src/ServerStorage/Proxy/DamageProxy.luau @@ -48,6 +48,61 @@ DamageProxy.ElementType = ElementType -------------------------------------------------------------------------------- +-- 对象生命周期管理器 +local LifecycleManager = {} +LifecycleManager.__index = LifecycleManager + +function LifecycleManager.new() + local self = setmetatable({}, LifecycleManager) + self.activeObjects = {} -- 使用弱引用表 + self.objectCallbacks = {} -- 对象销毁时的回调 + return self +end + +function LifecycleManager:RegisterObject(obj, onDestroy) + if not obj then return end + + -- 使用弱引用存储对象 + self.activeObjects[obj] = true + self.objectCallbacks[obj] = onDestroy + + -- 监听对象销毁 + if obj.Instance then + obj.Instance.AncestryChanged:Connect(function(_, parent) + if not parent then + self:OnObjectDestroyed(obj) + end + end) + end +end + +function LifecycleManager:OnObjectDestroyed(obj) + if self.objectCallbacks[obj] then + self.objectCallbacks[obj](obj) + self.objectCallbacks[obj] = nil + end + self.activeObjects[obj] = nil +end + +function LifecycleManager:IsObjectValid(obj) + return self.activeObjects[obj] and obj.Instance and obj.Instance.Parent +end + +function LifecycleManager:GetValidObjects(objects) + local validObjects = {} + for _, obj in pairs(objects) do + if self:IsObjectValid(obj) then + table.insert(validObjects, obj) + end + end + return validObjects +end + +-- 创建全局生命周期管理器 +local globalLifecycleManager = LifecycleManager.new() + +-------------------------------------------------------------------------------- + function DamageProxy:GetHumanoid(Target: Model) if not Target:IsA("Model") then warn("Target is not a Model") @@ -66,12 +121,31 @@ function DamageProxy:IsDied(Target: Model) return Humanoid:GetAttribute("hp") <= 0 end --- 获取范围内敌人 +-- 获取范围内敌人(改进版本) function DamageProxy:GetAoeEnemies(Caster: TypeList.Character, Position: Vector3, Radius: number) + -- 基础参数验证 + if not Caster or not Position or not Radius or Radius <= 0 then + return {} + end + local Enemies = {} local MobsProxy = require(ServerStorage.Proxy.MobsProxy) - for _, enemy in pairs(MobsProxy:GetPlayerMobs(Caster.Player)) do - if enemy ~= Caster and enemy.Instance then + + -- 获取所有敌人并注册到生命周期管理器 + local allEnemies = MobsProxy:GetPlayerMobs(Caster.Player) or {} + for _, enemy in pairs(allEnemies) do + if enemy and enemy ~= Caster then + globalLifecycleManager:RegisterObject(enemy, function(destroyedObj) + -- 对象销毁时的回调 + print("敌人对象被销毁:", destroyedObj) + end) + end + end + + -- 只处理有效的敌人 + local validEnemies = globalLifecycleManager:GetValidObjects(allEnemies) + for _, enemy in pairs(validEnemies) do + if enemy ~= Caster then local enemy_pos = enemy.Instance:GetPivot().Position if (enemy_pos - Position).Magnitude <= Radius then table.insert(Enemies, enemy) @@ -81,8 +155,14 @@ function DamageProxy:GetAoeEnemies(Caster: TypeList.Character, Position: Vector3 return Enemies end --- 弹道伤害 +-- 弹道伤害(改进版本) function DamageProxy:CastFreeProjectile(Caster: TypeList.Character, StartPos: Vector3, EndPos: Vector3, Duration: number, Range: number, OnHit: (Target: TypeList.Character) -> (boolean?)) + -- 参数验证 + if not Caster or not StartPos or not EndPos or not Duration or Duration <= 0 or not Range or Range <= 0 then + warn("CastFreeProjectile: 参数无效") + return + end + local projectileTask = nil local step_time = 0.05 -- 每帧检测间隔 local elapsed = 0 @@ -100,20 +180,37 @@ function DamageProxy:CastFreeProjectile(Caster: TypeList.Character, StartPos: Ve end end + -- 注册施法者到生命周期管理器 + globalLifecycleManager:RegisterObject(Caster, function() + cancelTask() -- 施法者被销毁时取消弹道 + end) + projectileTask = task.spawn(function() while elapsed < Duration and not cancelled do + -- 检查施法者是否仍然有效 + if not globalLifecycleManager:IsObjectValid(Caster) then + break + end + -- 计算当前位置 current_pos = StartPos + direction * speed * elapsed + -- 检测范围内敌人 local MobsProxy = require(ServerStorage.Proxy.MobsProxy) - for _, enemy in pairs(MobsProxy:GetPlayerMobs(Caster.Player)) do - if enemy ~= Caster and enemy.Instance then + local allEnemies = MobsProxy:GetPlayerMobs(Caster.Player) or {} + local validEnemies = globalLifecycleManager:GetValidObjects(allEnemies) + + for _, enemy in pairs(validEnemies) do + if enemy ~= Caster then local enemy_pos = enemy.Instance:GetPivot().Position if (enemy_pos - current_pos).Magnitude <= Range then if not hit_targets[enemy] then hit_targets[enemy] = true local stop = OnHit(enemy) - if stop then cancelTask() break end + if stop then + cancelTask() + break + end end end end @@ -124,8 +221,24 @@ function DamageProxy:CastFreeProjectile(Caster: TypeList.Character, StartPos: Ve end) end - +-- 伤害处理(改进版本) function DamageProxy:TakeDamage(Caster: TypeList.Character, Victim: TypeList.Character, DamageInfos: {DamageInfo}) + -- 基础参数验证 + if not Caster or not Victim or not DamageInfos or #DamageInfos == 0 then + warn("TakeDamage: 基础参数无效") + return + end + + -- 注册对象到生命周期管理器 + globalLifecycleManager:RegisterObject(Caster) + globalLifecycleManager:RegisterObject(Victim) + + -- 检查对象是否仍然有效 + if not globalLifecycleManager:IsObjectValid(Caster) or not globalLifecycleManager:IsObjectValid(Victim) then + warn("TakeDamage: 施法者或受害者已被销毁") + return + end + -- 给前端发送伤害信息 local clientDamageInfos = {} clientDamageInfos.Caster = Caster.Instance @@ -135,23 +248,31 @@ function DamageProxy:TakeDamage(Caster: TypeList.Character, Victim: TypeList.Cha clientDamageInfos.DamageInfos = Utils:DeepCopyTable(DamageInfos) for _, DamageInfo in DamageInfos do - if not Victim then continue end - -- if not Victim.Parent then continue end + -- 检查受害者是否仍然有效 + if not globalLifecycleManager:IsObjectValid(Victim) then + warn("TakeDamage: 受害者在处理过程中被销毁") + break + end - local Damage = DamageInfo.Damage + local Damage = DamageInfo.Damage or 0 local DamageType = DamageInfo.DamageType local DamageTag = DamageInfo.DamageTag local ElementType = DamageInfo.ElementType -- 伤害计算 - local VictimHealth = Victim:GetAttributeValue("hp") + local VictimHealth = Victim:GetAttributeValue("hp") or 0 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) + + -- 发送数据到客户端 + local success, error = pcall(function() + Communicate:SendToClient(RE_DamagePerformance, "Damage", Caster.Player, clientDamageInfos) + end) + + if not success then + warn("TakeDamage: 发送伤害数据失败:", error) + end end - return DamageProxy \ No newline at end of file diff --git a/src/ServerStorage/Proxy/LevelProxy.luau b/src/ServerStorage/Proxy/LevelProxy.luau index ef6e15b..d753fa3 100644 --- a/src/ServerStorage/Proxy/LevelProxy.luau +++ b/src/ServerStorage/Proxy/LevelProxy.luau @@ -36,6 +36,7 @@ local ENUM_LEVEL_TYPE = { -- 初始化生成关卡目录 local LevelFolder = Utils:CreateFolder(STORE_NAME, game.Workspace) +local SpawnLocation = game.Workspace:WaitForChild("SpawnLocation") -------------------------------------------------------------------------------- @@ -66,9 +67,12 @@ local function CreateLevelInstance(Player: Player, Folder: Instance, LevelKey: s InstanceType = "BoolValue" elseif type(LevelValue) == "string" then InstanceType = "StringValue" + elseif type(LevelValue) == "vector" then + InstanceType = "Vector3Value" else InstanceType = "NumberValue" end + local LevelInstance = Instance.new(InstanceType) LevelInstance.Name = LevelKey LevelInstance.Parent = Folder @@ -174,6 +178,7 @@ function LevelProxy:InitPlayer(Player: Player) LevelProxy.pData[Player.UserId].ShouldWave = 0 LevelProxy.pData[Player.UserId].SpawnWaveFinish = false LevelProxy.pData[Player.UserId].Mobs = {} + LevelProxy.pData[Player.UserId].LevelPosition = Vector3.new(0, 0, 30) -- 关卡挑战信息前端 for key, value in LevelProxy.pData[Player.UserId] do @@ -218,7 +223,7 @@ function LevelProxy:ChallengeLevel(Player: Player, LevelId: number) local mobCount = waveData[i + 2] for _ = 1, mobCount do print("怪物增益", LevelData.atkBonus, LevelData.hpBonus) - local mob = MobsProxy:CreateMob(Player, mobId, LevelData.atkBonus, LevelData.hpBonus, OnMobDied) + local mob = MobsProxy:CreateMob(Player, mobId, LevelProxy.pData[Player.UserId].LevelPosition, LevelData.atkBonus, LevelData.hpBonus, OnMobDied) table.insert(LevelProxy.pData[Player.UserId].Mobs, mob) end end @@ -300,6 +305,18 @@ function LevelProxy:GetLevelGetBonus(Player: Player) return level_get_bonus["intArray"][1] + (level - 1) * level_get_bonus["intArray"][2] end +-- 传送到关卡 +function LevelProxy:TeleportToLevel(Player: Player) + local LevelPosition = LevelProxy.pData[Player.UserId].LevelPosition + Vector3.new(0, 3, 0) + Player.Character.HumanoidRootPart.CFrame = CFrame.new(LevelPosition) +end + +-- 传送到城镇 +function LevelProxy:TeleportToTown(Player: Player) + local TownPosition = SpawnLocation.Position + Vector3.new(0, 3, 0) + Player.Character.HumanoidRootPart.CFrame = CFrame.new(TownPosition) +end + function LevelProxy:OnPlayerRemoving(Player: Player) -- 关卡文件夹清除 local PlayerLevelFolder = GetPlayerLevelWorkspaceFolder(Player.UserId) diff --git a/src/ServerStorage/Proxy/MobsProxy/init.luau b/src/ServerStorage/Proxy/MobsProxy/init.luau index 458ff6b..77021e2 100644 --- a/src/ServerStorage/Proxy/MobsProxy/init.luau +++ b/src/ServerStorage/Proxy/MobsProxy/init.luau @@ -41,7 +41,7 @@ local Mob = {} Mob.__index = Mob setmetatable(Mob, {__index = Character}) -function Mob.new(Player: Player, MobId: number, OnMobDied: ((Player: Player, Mob: TypeList.Character) -> ())?) +function Mob.new(Player: Player, MobId: number, Position: Vector3, OnMobDied: ((Player: Player, Mob: TypeList.Character) -> ())?) -- 获取玩家怪物目录 local playerMobsFolder = GetPlayerMobsFolder(Player) if not playerMobsFolder then return end @@ -56,6 +56,7 @@ function Mob.new(Player: Player, MobId: number, OnMobDied: ((Player: Player, Mob -- 克隆怪物模型 local newMobModel = MobInstance:Clone() + newMobModel.PrimaryPart.CFrame = CFrame.new(Position) -- 调用父类Character的new方法,初始化通用属性 local self = Character.new(Player, newMobModel, MobData) @@ -93,8 +94,8 @@ end -------------------------------------------------------------------------------- -- 给玩家创建怪物 -function MobsProxy:CreateMob(Player: Player, MobId: number, AtkBonus: number?, HpBonus: number?, OnMobDied: ((Player: Player, Mob: TypeList.Character) -> ())?) - local Mob = Mob.new(Player, MobId, OnMobDied) +function MobsProxy:CreateMob(Player: Player, MobId: number, Position: Vector3, AtkBonus: number?, HpBonus: number?, OnMobDied: ((Player: Player, Mob: TypeList.Character) -> ())?) + local Mob = Mob.new(Player, MobId, Position, OnMobDied) AI:StartTracking(Mob) -- 关卡系数 if AtkBonus then Mob:ChangeAttributeValue("attack", math.floor(Mob.Config.attack * (AtkBonus / 1000))) end diff --git a/src/ServerStorage/Proxy/PlayerFightProxy/LevelLoop.luau b/src/ServerStorage/Proxy/PlayerFightProxy/LevelLoop.luau index fd2c4bd..f7deabd 100644 --- a/src/ServerStorage/Proxy/PlayerFightProxy/LevelLoop.luau +++ b/src/ServerStorage/Proxy/PlayerFightProxy/LevelLoop.luau @@ -18,7 +18,6 @@ local Utils = require(ReplicatedStorage.Tools.Utils) local JsonLevel = require(ReplicatedStorage.Json.Level) --> Constants -local SpawnLocation = game.Workspace:WaitForChild("SpawnLocation") local ChallengeLocation -------------------------------------------------------------------------------- @@ -32,7 +31,7 @@ function LevelLoop.new(Player: Player, PlayerRole: TypeList.Character) self.Player = Player self.PlayerRole = PlayerRole self.TaskAutoChallenge = nil - self.AutoChallenge = false + self.IsAutoChallenge = false self.ConChallengeEnd = BD_ChallengeEnd.Event:Connect(function(Player: Player, LevelId: number) if Player ~= self.Player then return end @@ -53,21 +52,21 @@ end -- 手动挑战关卡 function LevelLoop:OnChallengeLevel(isStart: boolean) - self.AutoChallenge = isStart + self.IsAutoChallenge = isStart if isStart then self:AutoChallenge() else - -- TODO: 传送回城镇 - + LevelProxy:TeleportToTown(self.Player) LevelProxy:ChallengeEnd(self.Player, false) + self.PlayerRole:Recover() end end function LevelLoop:AutoChallenge(ignoreBoss: boolean?) - if not self.AutoChallenge then return end + if not self.IsAutoChallenge then return end print("AutoChallenge") - -- TODO: 传送到关卡起点 + LevelProxy:TeleportToLevel(self.Player) -- TODO: 回退有bug,不能一关一关回退 @@ -87,7 +86,7 @@ function LevelLoop:AutoChallenge(ignoreBoss: boolean?) end function LevelLoop:OnChallengeEnd(Player: Player, LevelId: number, result: boolean) - if not self.AutoChallenge then return end + if not self.IsAutoChallenge then return end if self.ChallengeBoss then self.ChallengeBoss = nil return end self.TaskAutoChallenge = task.spawn(function() task.wait(3) diff --git a/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau b/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau index 8304845..4544910 100644 --- a/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau +++ b/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau @@ -71,6 +71,11 @@ function PlayerAI.new(Player: Player, PlayerRole: TypeList.Character) self.ExecutingState = false end end) + -- task.spawn(function() + -- while task.wait(0.25) do + -- print(self) + -- end + -- end) return self end diff --git a/src/ServerStorage/Proxy/PlayerFightProxy/init.luau b/src/ServerStorage/Proxy/PlayerFightProxy/init.luau index 2c016b3..89d95bc 100644 --- a/src/ServerStorage/Proxy/PlayerFightProxy/init.luau +++ b/src/ServerStorage/Proxy/PlayerFightProxy/init.luau @@ -76,11 +76,17 @@ function PlayerRole:Respawn() self:ChangeState("Died", false) self.Humanoid.WalkSpeed = self.Config.walkSpeed self:ChangeAttributeValue("hp", self.Config.maxhp) - -- TODO: 重置玩家位置 + -- 重置玩家关卡位置 + LevelProxy:TeleportToLevel(self.Player) PlayerFightProxy:UpdatePlayerFightData(self.Player) end +function PlayerRole:Recover() + self:ChangeState("Died", false) + self:ChangeAttributeValue("hp", self.Config.maxhp) +end + -------------------------------------------------------------------------------- function PlayerFightProxy:InitPlayer(Player: Player) diff --git a/src/ServerStorage/Proxy/PlayerInfoProxy.luau b/src/ServerStorage/Proxy/PlayerInfoProxy.luau index f8a402e..7bcda75 100644 --- a/src/ServerStorage/Proxy/PlayerInfoProxy.luau +++ b/src/ServerStorage/Proxy/PlayerInfoProxy.luau @@ -15,6 +15,7 @@ local JsonLvUpgrade = require(ReplicatedStorage.Json.LvUpgrade) local JsonItem = require(ReplicatedStorage.Json.ItemProp) local JsonAttributesUpgrade = require(ReplicatedStorage.Json.AttributesUpgrade) local JsonForge = require(ReplicatedStorage.Json.Forge) +local JsonParam = require(ReplicatedStorage.Json.Param) --> Events local RE_PlayerTip = ReplicatedStorage.Events.RE_PlayerTip @@ -99,6 +100,11 @@ function PlayerInfoProxy:InitPlayer(Player: Player) ArchiveProxy.pData[Player.UserId][STORE_NAME].Stats = {} ArchiveProxy.pData[Player.UserId][STORE_NAME].Items = {} ArchiveProxy.pData[Player.UserId][STORE_NAME].AttributesUpgrade = {} + + local newData = Utils:GetIdDataFromJson(JsonParam, 7) + for i = 1, #newData.intArray, 2 do + ArchiveProxy.pData[Player.UserId][STORE_NAME].Items[newData.intArray[i]] = newData.intArray[i + 1] + end end -- 放在外面是为了以后系统新增内容方便(同时不用在初始化数据是做写入了) diff --git a/src/StarterPlayerScripts/UI/UIManager.luau b/src/StarterPlayerScripts/UI/UIManager.luau index 708b3f0..19365f2 100644 --- a/src/StarterPlayerScripts/UI/UIManager.luau +++ b/src/StarterPlayerScripts/UI/UIManager.luau @@ -8,6 +8,7 @@ UIManager.WindowStack = {} -- 窗口堆栈 UIManager.ExcludeFromStack = { -- 不受堆栈影响的窗口列表 "TipsWindow", -- 主窗口 "AbilityStateWindow", + "LevelStageWindow", -- 可以继续添加其他窗口名称 } diff --git a/src/StarterPlayerScripts/UI/Windows/LevelStageWindow/init.luau b/src/StarterPlayerScripts/UI/Windows/LevelStageWindow/init.luau new file mode 100644 index 0000000..4b61188 --- /dev/null +++ b/src/StarterPlayerScripts/UI/Windows/LevelStageWindow/init.luau @@ -0,0 +1,121 @@ +--> Services +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +--> Dependencies +local UIWindow = require(ReplicatedStorage.Base.UIWindow) +local UIEnums = require(ReplicatedStorage.Base.UIEnums) +local Signal = require(ReplicatedStorage.Tools.Signal) + +--> Json +local JsonLevel = require(ReplicatedStorage.Json.Level) +local JsonForge = require(ReplicatedStorage.Json.Forge) + +local Utils = require(ReplicatedStorage.Tools.Utils) + +--> Events +local RE_ChallengeBoss = ReplicatedStorage.Events.RE_ChallengeBoss +local RE_ChallengeLevel = ReplicatedStorage.Events.RE_ChallengeLevel + +local LocalPlayer = game:GetService("Players").LocalPlayer +local challengeLevelEndSignal = Signal.new(Signal.ENUM.CHALLENGE_LEVEL_END) + +-------------------------------------------------------------------------------- + +local LevelStageWindow = {} +LevelStageWindow.__index = LevelStageWindow +setmetatable(LevelStageWindow, {__index = UIWindow}) + +function LevelStageWindow:Init(UIManager: table, Data: table?) + local self = UIWindow:Init(UIManager, Data) + setmetatable(self, LevelStageWindow) + self.Variables = { + ["_btnStopChallenge"] = 0, + + -- 关卡 + ["_tmpNowLevel"] = 0, + ["_imgBoss"] = 0, + ["_btnChallengeBoss"] = 0, + } + self.UIRootName = "ui_w_level_stage" + self.UIParentName = UIEnums.UIParent.UIRoot + + return self +end + +-- 设置关卡显示 +function LevelStageWindow:SetShowLevel(level: number) + self.Variables["_tmpNowLevel"].Text = string.format("第%d关", level) + local levelData = Utils:GetIdDataFromJson(JsonLevel, level) + if levelData.type == 2 then + self.Variables["_imgBoss"].Visible = true + else + self.Variables["_imgBoss"].Visible = false + end +end + +-- 设置boss挑战按钮显示 +function LevelStageWindow:SetShowBossChallenge(newValue: number) + local playerDataFolder = game.Workspace:WaitForChild("Level"):WaitForChild(LocalPlayer.UserId) + local ProgressFolder = playerDataFolder:WaitForChild("Progress") + local mainValue = ProgressFolder:FindFirstChild("Main").Value + local levelIdValue = playerDataFolder:WaitForChild("Challenge"):WaitForChild("LevelId").Value + + if levelIdValue == mainValue then + self.Variables["_btnChallengeBoss"].Visible = false + else + self.Variables["_btnChallengeBoss"].Visible = (newValue == mainValue) + end +end + +-- 手动点击按钮,挑战boss +function LevelStageWindow:OnClickChallengeBoss() + RE_ChallengeBoss:FireServer() + self.Variables["_btnChallengeBoss"].Visible = false +end + +function LevelStageWindow:OnClickChallengeButton() + challengeLevelEndSignal:Fire(false) + -- TODO: 之后这里做临时传送特效,可以打断,要不就独立个脚本,用signal触发对应特效 + -- task.wait(2) + RE_ChallengeLevel:FireServer(false) + self.UIManager:CloseWindow(script.Name) +end + +function LevelStageWindow:OnOpenWindow() + UIWindow.OnOpenWindow(self) + + local challengeBossCon = self.Variables["_btnChallengeBoss"].Activated:Connect(function() + self:OnClickChallengeBoss() + end) + local stopChallengeCon = self.Variables["_btnStopChallenge"].Activated:Connect(function() + self:OnClickChallengeButton(false) + end) + + table.insert(self.Connections, challengeBossCon) + table.insert(self.Connections, stopChallengeCon) + + -- TODO: 暂时用主关卡数显示,我记得之前这里主要是记录的,Challenge中才是正在挑战的内容 + -- TODO: 之后LevelProxy也应该挪到ReplicatedStorage下,之前可能是因为觉得Challenge是临时的内容所以放在workspace下,但是逻辑做的不统一 + local playerDataFolder = game.Workspace:WaitForChild("Level"):WaitForChild(LocalPlayer.UserId) + local StatsFolder = playerDataFolder:WaitForChild("Challenge") + local ProgressFolder = playerDataFolder:WaitForChild("Progress") + + local levelCon = StatsFolder.LevelId.Changed:Connect(function(newValue) + self:SetShowLevel(newValue) + self:SetShowBossChallenge(ProgressFolder.BossFail.Value) + end) + local bossChallengeCon = ProgressFolder.BossFail.Changed:Connect(function(newValue) + self:SetShowBossChallenge(newValue) + end) + + -- 初始值设置 + if StatsFolder.LevelId.Value ~= 0 then + self:SetShowLevel(StatsFolder.LevelId.Value) + end + self:SetShowBossChallenge(ProgressFolder.BossFail.Value) + + table.insert(self.Connections, levelCon) + table.insert(self.Connections, bossChallengeCon) +end + +return LevelStageWindow \ No newline at end of file diff --git a/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau b/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau index 7219843..3978378 100644 --- a/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau +++ b/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau @@ -4,6 +4,7 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage") --> Dependencies local UIWindow = require(ReplicatedStorage.Base.UIWindow) local UIEnums = require(ReplicatedStorage.Base.UIEnums) +local Signal = require(ReplicatedStorage.Tools.Signal) --> Json local JsonLevel = require(ReplicatedStorage.Json.Level) @@ -11,11 +12,15 @@ local JsonForge = require(ReplicatedStorage.Json.Forge) local Utils = require(ReplicatedStorage.Tools.Utils) + --> Events local RE_ChallengeBoss = ReplicatedStorage.Events.RE_ChallengeBoss local RE_ChallengeLevel = ReplicatedStorage.Events.RE_ChallengeLevel +--> Local local LocalPlayer = game:GetService("Players").LocalPlayer +local forgeRedPointSignal = Signal.new(Signal.ENUM.FORGE_RED_POINT) +local challengeLevelEndSignal = Signal.new(Signal.ENUM.CHALLENGE_LEVEL_END) -------------------------------------------------------------------------------- @@ -81,14 +86,8 @@ function MainWindow:Init(UIManager: table, Data: table?) ["_btnMainCreate"] = 0, ["_btnMainCha"] = 0, ["_btnMainAttributeUpgrade"] = 0, - ["_btnStopChallenge"] = 0, ["_btnStartChallenge"] = 0, - -- 关卡 - ["_tmpNowLevel"] = 0, - ["_imgBoss"] = 0, - ["_btnChallengeBoss"] = 0, - -- 锻造条 ["_goForgeBar"] = 0, ["_goForgeFill"] = 0, @@ -104,35 +103,12 @@ function MainWindow:Init(UIManager: table, Data: table?) return self end --- 设置关卡显示 -function MainWindow:SetShowLevel(level: number) - self.Variables["_tmpNowLevel"].Text = string.format("第%d关", level) - local levelData = Utils:GetIdDataFromJson(JsonLevel, level) - if levelData.type == 2 then - self.Variables["_imgBoss"].Visible = true - else - self.Variables["_imgBoss"].Visible = false - end -end - --- 设置boss挑战按钮显示 -function MainWindow:SetShowBossChallenge(newValue: number) - local playerDataFolder = game.Workspace:WaitForChild("Level"):WaitForChild(LocalPlayer.UserId) - local ProgressFolder = playerDataFolder:WaitForChild("Progress") - local mainValue = ProgressFolder:FindFirstChild("Main").Value - local levelIdValue = playerDataFolder:WaitForChild("Challenge"):WaitForChild("LevelId").Value - - if levelIdValue == mainValue then - self.Variables["_btnChallengeBoss"].Visible = false - else - self.Variables["_btnChallengeBoss"].Visible = (newValue == mainValue) - end -end - --- 手动点击按钮,挑战boss -function MainWindow:OnClickChallengeBoss() - RE_ChallengeBoss:FireServer() - self.Variables["_btnChallengeBoss"].Visible = false +function MainWindow:OnClickChallengeButton() + self.Variables["_btnStartChallenge"].Visible = false + -- TODO: 之后这里做临时传送特效,可以打断,要不就独立个脚本,用signal触发对应特效 + task.wait(2) + RE_ChallengeLevel:FireServer(true) + self.UIManager:OpenWindow("LevelStageWindow") end function MainWindow:SetShowForgeBar(nowForgeTime : number, moneyValue: number) @@ -160,18 +136,6 @@ function MainWindow:SetShowForgeBar(nowForgeTime : number, moneyValue: number) end end -function MainWindow:OnClickChallengeButton(isStart: boolean) - if isStart then - RE_ChallengeLevel:FireServer(true) - self.Variables["_btnStartChallenge"].Visible = false - self.Variables["_btnStopChallenge"].Visible = true - else - RE_ChallengeLevel:FireServer(false) - self.Variables["_btnStartChallenge"].Visible = true - self.Variables["_btnStopChallenge"].Visible = false - end -end - function MainWindow:OnOpenWindow() UIWindow.OnOpenWindow(self) @@ -184,47 +148,28 @@ function MainWindow:OnOpenWindow() local attributeUpgradeCon = self.Variables["_btnMainAttributeUpgrade"].Activated:Connect(function() self.UIManager:OpenWindow("AttributeLvupWindow") end) - local challengeBossCon = self.Variables["_btnChallengeBoss"].Activated:Connect(function() - self:OnClickChallengeBoss() - end) - local stopChallengeCon = self.Variables["_btnStopChallenge"].Activated:Connect(function() - self:OnClickChallengeButton(false) - end) local startChallengeCon = self.Variables["_btnStartChallenge"].Activated:Connect(function() - self:OnClickChallengeButton(true) + self:OnClickChallengeButton() end) table.insert(self.Connections, createCon) table.insert(self.Connections, chaCon) table.insert(self.Connections, attributeUpgradeCon) - table.insert(self.Connections, challengeBossCon) - table.insert(self.Connections, stopChallengeCon) table.insert(self.Connections, startChallengeCon) - - -- TODO: 暂时用主关卡数显示,我记得之前这里主要是记录的,Challenge中才是正在挑战的内容 - -- TODO: 之后LevelProxy也应该挪到ReplicatedStorage下,之前可能是因为觉得Challenge是临时的内容所以放在workspace下,但是逻辑做的不统一 - local playerDataFolder = game.Workspace:WaitForChild("Level"):WaitForChild(LocalPlayer.UserId) - local StatsFolder = playerDataFolder:WaitForChild("Challenge") - local ProgressFolder = playerDataFolder:WaitForChild("Progress") - local levelCon = StatsFolder.LevelId.Changed:Connect(function(newValue) - self:SetShowLevel(newValue) - self:SetShowBossChallenge(ProgressFolder.BossFail.Value) + local forgeRedPointCon = forgeRedPointSignal:Connect(function(show: boolean) + self.Variables["_tmpRedCreate"].Visible = show end) - local bossChallengeCon = ProgressFolder.BossFail.Changed:Connect(function(newValue) - self:SetShowBossChallenge(newValue) + table.insert(self.Connections, forgeRedPointCon) + + local challengeLevelEndCon = challengeLevelEndSignal:Connect(function(result: boolean) + self.Variables["_btnStartChallenge"].Visible = true end) - - -- 初始值设置 - self:SetShowLevel(StatsFolder.LevelId.Value) - self:SetShowBossChallenge(ProgressFolder.BossFail.Value) - - table.insert(self.Connections, levelCon) - table.insert(self.Connections, bossChallengeCon) - + table.insert(self.Connections, challengeLevelEndCon) + -- 货币进度条显示 - local rePlayerDataFolder = Utils:GetPlayerDataFolder(LocalPlayer) + local rePlayerDataFolder = Utils:WaitPlayerDataFolder(LocalPlayer) local playerInfoFolder = rePlayerDataFolder:WaitForChild("PlayerInfo") local forgeInstance = playerInfoFolder:WaitForChild("Stats"):WaitForChild("forge")