From 11ddf9fe1fabe65f2f1c306d9842645a4dea795f Mon Sep 17 00:00:00 2001 From: gechangfu Date: Wed, 30 Jul 2025 19:46:01 +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/ability.xlsx | Bin 17163 -> 17195 bytes excel/anim.xlsx | Bin 16323 -> 16329 bytes excel/attribute.xlsx | Bin 12561 -> 12535 bytes excel/enemy.xlsx | Bin 9060 -> 9056 bytes excel/level.xlsx | Bin 10188 -> 10150 bytes src/ReplicatedStorage/Json/Ability.json | 3 +- src/ReplicatedStorage/Json/Animation.json | 3 +- src/ReplicatedStorage/Json/Attributes.json | 2 +- src/ReplicatedStorage/Json/Enemy.json | 6 +- src/ReplicatedStorage/Json/Level.json | 80 ++++----- .../Modules/BehavioursClient/Attack.luau | 48 ++++++ src/ServerStorage/Base/Behaviour.luau | 26 ++- .../Modules/Behaviours/Attack.luau | 121 +++++++++++++ .../Modules/Behaviours/Move.luau | 25 ++- src/ServerStorage/Proxy/ArchiveProxy.luau | 1 - src/ServerStorage/Proxy/MobsProxy/AI.luau | 12 +- src/ServerStorage/Proxy/MobsProxy/init.luau | 5 +- .../Proxy/PlayerFightProxy/init.luau | 7 +- .../ClientMain/Camera.client.luau | 2 +- .../ClientMain/MobAnim.luau | 159 ++++++++++++++++++ .../PerformanceClient/DamageBoard.luau | 4 +- .../ClientMain/PerformanceClient/init.luau | 1 - .../ClientMain/PlayerControl.luau | 55 ++++-- src/StarterPlayerScripts/UI/UIManager.luau | 8 + .../UI/Windows/MainWindow/init.luau | 10 +- 25 files changed, 495 insertions(+), 83 deletions(-) create mode 100644 src/ReplicatedStorage/Modules/BehavioursClient/Attack.luau create mode 100644 src/ServerStorage/Modules/Behaviours/Attack.luau create mode 100644 src/StarterPlayerScripts/ClientMain/MobAnim.luau diff --git a/excel/ability.xlsx b/excel/ability.xlsx index 5cd47c131717f5f60fc0619eeeec1d3b26deae84..032866438e68b9f1b2a74e9eb3b2d9b3d540220f 100644 GIT binary patch delta 3690 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 OV!jxhrC-xK9{L~Ab)y6T delta 3624 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 diff --git a/excel/anim.xlsx b/excel/anim.xlsx index 52ebc72280a03b014f146dc4d5df39291832e83b..e880a3c94d4ea7baee7bff0103e392db7d2eb64f 100644 GIT binary patch delta 2854 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 delta 4573 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 diff --git a/excel/enemy.xlsx b/excel/enemy.xlsx index 32f8ea287375ecc2e41b5b38ba001013942acbe7..acac0856dc2ff78309e9c80249b80c4065f131cc 100644 GIT binary patch delta 2147 zcmV-p2%PuiM&L%U$_9UkvA$8{0ssKZ1^@sL0001ZY%h0ja%*C5Z)+}iZEUQS(QcbC z6o&67?H!QsDVPwNppvR7M2oadRo68)l`03EUUu8litJRp@f`^~=RZ$kkkk zl?4J^MJU(03LQtKIW37g6BXqUSqNDY4MVwfR3$0NR8FZb3&(Taz$ppis8tB%do;vi z!BQHFbX`)eTM*{LWikeIWA>1`v5tTGL!OD-gt7m#+1 z`&KRC7r-C8(0G5{lOS~RsFI~pD6!CIAnmlKPx8mEgYnT4)Te9kJ|*9g^of}o?p?{? zgRX+TR&Wr*?x)o-pw+Mv!s2~Ut-tr?q0z;H74*JE*CJK*la!`X1+ogFbdoVmvk0BT zP;BVo0%RrE)p*Sch9yb*5ZtBo zS=<`!>s$cb)-CZmnezp*Vby^Lm;UbVf-f_yes|T0Fc$ zlAQAY4^WcSr0YPwt*7?EjlaX<{~}LN3MhECdsC;d*0}e`QyJpN zXWX0gt_FYOv7IE7iR}-@zC8@$2{hh$ZxRgL7zZzqj10n5khRWtRW{NQ@}Z?$e3^z6G?n+6K8n~frCI;RR_sk4h>+ec8J;24l9roR;b9d<5Zqn;Rd4`@n z@Oz$p?Z;lyKTqOh+&ASh8-Msew!en)u=}DImpPI8PLgy5-=SN&7!w6NwF!Y=z_D}f zbaw-d{sNPc0~E952{Qr-@3OuX)&l?l%#$DtA%9(O+aMH%?1> zkAJUr#awTJ*D**dmZ#|h(_D7+Kq%}j4AV4Hz_Ss9hRz=#78LiGziVahPBblU!D(*S zLq8Hm_lT&Zl71hdlxR^bSDmrEQdsvc8*@Q+7VjLNyOT_uzf#gtSmR9N6C|@k&e!En z*_3f{CSBKm6DAuo3mX{@@Pf)emQr?qW`E@u*xd(V4`*1|E?W$;F6rO&c#W{nLX%#a z)3<+gK8rs2ZV8$ZzHyCb@EYuvQ8u5|5>G!=Oe{1d{gM^SNtll@za(tkyD{;+dIbf- z1L|j#1nXADti!@3PNhLDE(k;}yxhQ7P^zRa!To=t1`bhxofY1XPil*3cFehkhJO#r z|1jGZkgL|C>^RBqaTkJ(ihBPHPN08LJIMoA7=;jQiI(^uPSAdQ7yGTOeT(sF@;E_2 z1Aogsu-`&GDE--L-8WynfVibpQIX?ic1V ztNX5R9X*{-%=&eMx;cOT@wRK)+vCEVoyX#+5$K0tFU(^WD#4e8yH}BHiG(VVYl^@=g$98tr3QhHs{ye|11TN7lBLaF1*>{>!GEifNR0$H z5}A?6jYMH2N(}++R!d}>0L8MeTE5a?ddJ<~FvsF8;hWCvHQ0#^=yx1!AI276;TU8X z%w)ZX74DC2iwOh5v;iAIHII!^N8+W*B!@Fo2J^7%k&6hGZ_aC+^f9D z)nh2MrAumLJUACNsOF1@u~?iUGkilt_!0rfl*knHq*|8C)Q5(%Ax050KY|GDh9xz! zE@Hkol~Y6<-Vkw+6XD>=C2|43`)fGw@c@1F`J#R<7w~J|*H<-_)X=(+`QiyC!=C;r z{{gd;5C01Z$)lFBN&x@>ShJfOA_0Fw=%OKp%OB#m?c?*Xs$a?eRvb{42u`^MW?P+=yh!k@?r&E! zW4G?iMFryaknzHMNbv8z!wf^IM-~Xr<)CBglkytf-RB#WEl3#qWaX1M53gx>L!;$; zKlbc>pKEM1$Kn4H7mj%4M3o${+v$008pY^>+XO delta 2138 zcmV-g2&MPnM&w4Y$_9VBz4qqq0ssKi1^@sL0001ZY%h0ja%*C5Z)+}iZEUQSVNatl z7{}jVa^HdUJwh2{8xf6em|NmqjOV#GCZ;Q$^ad^07Kbr@_j?M%se9w;k*%e){P{n9 zer*qro3e23nbKT}1mOc8IZRNQadDTRUsq@D7&+Pyk&!|QmY{zJrqS`y*Dr?~sn$y= z*A55>ouJ&9D)u~0b5;_4AS))IXC+lh3^di9rz^!srgO$jS$Ki(M_x&|K%GLY-lHK` zD^A%=(z;~AbRvos#DIOB^Gf$>C4G-vNz}ToTq?^7B$mA3=AjiuPD$hWosfzw3kbWx zL#I*ji{OuaD1v|fOVG9VsghGAwOpA4koG#$d+}r6!}!${H2ZV#KBdr8?4H{kKD$!U z2VF(aTG37n`=3_BfL6;&43qakwc)e30398!c)_kabRAMvKS*gaRUoGz+N2pbEKAS` znsUQ-40_I|!IKDGXDB7&eu{0D{|) zK8stcJ= z4QnVXZeFlS_7}oG&`GF`+k?P6bKU*hNdS*3?g_jw%7Od&>>RT2liWj2AoJOF$oO)hW@y zQx^;n_yrt$*IxhD(O&?QkpmR7+X*uQ37W$%#?}J>0MU~d3?YBpj@uv*hVLu&9T4|1 zK4cHE;;gE!qNW#B)$QRL<8h1v#s!>%s=oUUj+3mEn`kW?FlN4g27b?<_F9oGSSL+e z&?Jf}fmWDGw(Ela`njAuQsO*sE3Qln1wDYHPm7Nq<~w6It^x1_39Tz=<9(Mi=0pRU zyU29V;+NW3%{_m<+BI{X1zrt7TCp^a@0sSZrAI<;Z(*3amI9uQ=ry$d2(h5J$NXI* zJ9naKaSKj!yXm`$FuFrTDV6kx0ZNG$`D)!7%S(lI?~^GPWN-0K@ySjy!~B(!mcklm zY9Aq)9do`ef5v8vi!d)_6NU z=`9DlW7dBa)Vx>zhuM7rxo$iPPjM51i}LF53{IeZQ98;3R2YQ>Y>Afm8$O`>_m5HQQGe~a zfi^>vM34KA@ra2{jQ+d1_3Yc_OpIUKYud}#AMN_KyA3h1K3tMSEk{2&8xw~ZEbm$R zOsvR!CXv}rM7k#&naK8pClho}*uIS9dnAyFLQw?HDHRA*sT2rwT+fKfG%%%$S7zyL zubh9Cz1rYaOJrJtYKdG+6k4Lx5|x60cI!Q|PJm?Dl`UV{U^*ww6!SgYHoLKLtWy!N z&tY&)>W_~dhfj-7%o`XD%lJUmZNmnf@7#af^rs0MQEo02lxO00000000000000943iuT8j}wq zDghOfIwC*;%9DE{Ispokwjw7R&6VqsMF9W+R{{V45dZ)H0000000000004xO_#!?6 Q03MS#BO(UQAOHXW0O#fMSpWb4 diff --git a/excel/level.xlsx b/excel/level.xlsx index e9f07bce065ebd695c6545599b25ee74168e2578..bbbc993ac3fc1ff622de4c1810f27002626cc7ab 100644 GIT binary patch delta 3571 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 Services +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +--> Dependencies +local BehaviourClient = require(ReplicatedStorage.Base.BehaviourClient) + +--> Variables +local PrefabFolder = ReplicatedStorage:WaitForChild("Prefabs") +-- local Prefab_Attack = PrefabFolder:WaitForChild("Projectiles"):WaitForChild("Attack") + +-------------------------------------------------------------------------------- + +local Attack = {} +Attack.__index = Attack +setmetatable(Attack, {__index = BehaviourClient}) + +function Attack:Init(CasterPlayer: Player, CastInfo: table, DelayTime: number, CastState: boolean) + local self = BehaviourClient:Init(CasterPlayer, CastInfo, DelayTime, CastState) + setmetatable(self, Attack) + + -- 加载动画 + self:LoadAnimationByName("Attack") + return self +end + +function Attack:Show(CasterPlayer: Player, CastInfo: table, DelayTime: number, CastState: boolean) + self.EffectDispatcher:ShowAnimation(self.Player, DelayTime, self:GetAnimationByName("Attack")) +end + +function Attack:Destroy() + if self.ShowTask then + task.cancel(self.ShowTask) + self.ShowTask = nil + end + if self.Tween then + self.Tween:Cancel() + self.Tween = nil + end + if self.Projectile then + self.Projectile:Destroy() + self.Projectile = nil + end + BehaviourClient.Destroy(self) +end + +return Attack \ No newline at end of file diff --git a/src/ServerStorage/Base/Behaviour.luau b/src/ServerStorage/Base/Behaviour.luau index a75d6bb..c96cb84 100644 --- a/src/ServerStorage/Base/Behaviour.luau +++ b/src/ServerStorage/Base/Behaviour.luau @@ -12,6 +12,16 @@ local RE_CleanPlayerPerformance = ReplicatedStorage:FindFirstChild("Events"):Fin --> Dependencies local TypeList = require(ServerStorage.Base.TypeList) local Communicate = require(ServerStorage.Modules.Tools.Communicate) +local Utils = require(ReplicatedStorage.Tools.Utils) + +--> Json +local JsonAttributes = require(ReplicatedStorage.Json.Attributes) + +--> 临时维护一个属性数据表,用于记录属性类型 +local AttributesNameData = {} +for _, Attribute in JsonAttributes do + AttributesNameData[Attribute.effectAttribute] = Utils:DeepCopyTable(Attribute) +end -------------------------------------------------------------------------------- @@ -54,7 +64,6 @@ end -- 检查行为前先清理之前遗留的引用数据 function Behaviour:CheckClean() - if self.Mobs then for _, Mob in self.Mobs do self.Mobs[Mob] = nil @@ -69,6 +78,21 @@ function Behaviour:CheckClean() end end +function Behaviour:GetAttributeValue(AttributeName: string) + local AttributeValue = self.Character.Instance:FindFirstChild("Attributes"):GetAttribute(AttributeName) + if not AttributeValue then return nil end + -- 处理对应的值 + local AttributeData = AttributesNameData[AttributeName] + if not AttributeData then return nil end + + local typeValue = AttributeData.type + if typeValue == 1 then + return AttributeValue + elseif typeValue == 2 then + return AttributeValue / 100 + end +end + -- 启动冷却时间清除计时 function Behaviour:StartCooldownTask() self.Cooldown = self.OrgCooldown diff --git a/src/ServerStorage/Modules/Behaviours/Attack.luau b/src/ServerStorage/Modules/Behaviours/Attack.luau new file mode 100644 index 0000000..092c555 --- /dev/null +++ b/src/ServerStorage/Modules/Behaviours/Attack.luau @@ -0,0 +1,121 @@ +-- 移动行为 + +--> Services +local ServerStorage = game:GetService("ServerStorage") +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +--> Dependencies +local TypeList = require(ServerStorage.Base.TypeList) +local Behaviour = require(ServerStorage.Base.Behaviour) +local MobsProxy = require(ServerStorage.Proxy.MobsProxy) +local DamageProxy = require(ServerStorage.Proxy.DamageProxy) +local Utils = require(ReplicatedStorage.Tools.Utils) + +-------------------------------------------------------------------------------- + +local Attack = {} +Attack.__index = Attack +setmetatable(Attack, {__index = Behaviour}) + +local CAST_DISTANCE = 8 +local COOLDOWN = 1 +local ATTRIBUTE_LIST = { + {Name = "attack", ElementType = DamageProxy.ElementType.NONE}, + {Name = "fireAtk", ElementType = DamageProxy.ElementType.FIRE}, + {Name = "iceAtk", ElementType = DamageProxy.ElementType.ICE}, + {Name = "lightAtk", ElementType = DamageProxy.ElementType.LIGHT}, + {Name = "shadowAtk", ElementType = DamageProxy.ElementType.SHADOW}, + +} + +function Attack:Init(PlayerAI, Character: TypeList.Character, Player: Player) + local self = Behaviour:Init(PlayerAI, Character, script.Name) + self.Player = Player + self.Mobs = nil + setmetatable(self, Attack) + self.OrgCooldown = COOLDOWN + -- self:StartCooldownTask() + + -- 客户端表现 + self:SendPerformanceEvent("Init", self.Player, script.Name, true, {}) + return self +end + +function Attack:Check(CheckInfo: table) + if Behaviour.CheckStat(self) then return -1, self.CheckData end + self:CheckClean() + + self.Mobs = MobsProxy:GetPlayerMobs(self.Player) + if not self.Mobs then return end + + + local closestMob, minDistance = nil, CAST_DISTANCE + for _, Mob in self.Mobs do + if Mob.Instance and Mob.Instance.PrimaryPart then + local dist = (Mob.Instance.PrimaryPart.Position - self.Character.Instance.PrimaryPart.Position).Magnitude + if dist < minDistance then + minDistance = dist + closestMob = Mob + end + end + end + + self.CheckData = {} + if closestMob then + self.CheckData["ClosestCharacter"] = closestMob + return 50, self.CheckData + end + + -- 返回优先级,执行数据 + return -1, self.CheckData +end + +function Attack:Execute() + self.ExeTask = task.spawn(function () + self:ChangeExecutingState(true) + -- cd放前面之后发送事件才能正常记录cd + -- self:StartCooldownTask() + + -- 停止移动 + self.Character.Humanoid:MoveTo(self.Character.Root.Position) + -- 朝向目标 + local HumanoidRootPart = self.Character.Root + local TargetPosition = self.CheckData["ClosestCharacter"].Instance.PrimaryPart.Position + + if HumanoidRootPart then + HumanoidRootPart.CFrame = CFrame.lookAt(HumanoidRootPart.Position, TargetPosition) + end + + local atkSpeed = self:GetAttributeValue("atkSpeed") + if not atkSpeed then warn("atkSpeed not found") return end + + -- 表现部分 + self:SendPerformanceEvent("Show", self.Player, self.ScriptName, true, { + { UniqueId = self.PlayerAI:GetBehaviourUniqueId() } + }) + -- 攻击前摇 + task.wait(atkSpeed) + + -- TODO: 之后这里可以提前做暴击判定 + + -- 伤害逻辑计算部分 + local damageData = {} + for _, attribute in ATTRIBUTE_LIST do + local attributeValue = self:GetAttributeValue(attribute.Name) + if attributeValue then + table.insert(damageData, { + Damage = attributeValue, + Type = DamageProxy.DamageType.NORMAL, + Tag = DamageProxy.DamageTag.NORMAL, + ElementType = attribute.ElementType, + }) + end + end + DamageProxy:TakeDamage(self.Character, self.CheckData["ClosestCharacter"], damageData) + + -- task.wait(atkSpeed / 2) + self:ChangeExecutingState(false) + end) +end + +return Attack \ No newline at end of file diff --git a/src/ServerStorage/Modules/Behaviours/Move.luau b/src/ServerStorage/Modules/Behaviours/Move.luau index a8989d0..94d0f37 100644 --- a/src/ServerStorage/Modules/Behaviours/Move.luau +++ b/src/ServerStorage/Modules/Behaviours/Move.luau @@ -14,6 +14,8 @@ local Move = {} Move.__index = Move setmetatable(Move, {__index = Behaviour}) +local ATTACK_DISTANCE = 8 + function Move:Init(PlayerAI, Character: TypeList.Character, Player: Player) local self = Behaviour:Init(PlayerAI, Character, script.Name) self.Player = Player @@ -41,8 +43,11 @@ function Move:Check(CheckInfo: table) self.CheckData = {} if closestMob then - self.CheckData["ClosestCharacter"] = closestMob - return 10, self.CheckData + local distance = (closestMob.Instance.PrimaryPart.Position - self.Character.Instance.PrimaryPart.Position).Magnitude + if distance > ATTACK_DISTANCE then + self.CheckData["ClosestCharacter"] = closestMob + return 10, self.CheckData + end end -- 返回优先级,执行数据 @@ -50,10 +55,22 @@ function Move:Check(CheckInfo: table) end function Move:Execute() + if self.ExeTask then task.cancel(self.ExeTask) end self.ExeTask = task.spawn(function () self:ChangeExecutingState(true) - self.Character.Humanoid:MoveTo(self.CheckData["ClosestCharacter"].Instance:GetPivot().Position) - task.wait(0.5) + + -- 新移动朝向 + local TargetCharacter = self.CheckData["ClosestCharacter"].Instance + local TargetPosition = TargetCharacter:GetPivot().Position + local CharacterPosition = self.Character.Instance:GetPivot().Position + local Direction = (TargetPosition - CharacterPosition).Unit + local MoveToPosition = TargetPosition - (Direction * ATTACK_DISTANCE) + + self.Character.Humanoid:MoveTo(MoveToPosition) + + -- 旧内容 + -- self.Character.Humanoid:MoveTo(self.CheckData["ClosestCharacter"].Instance:GetPivot().Position) + task.wait(0.2) self:ChangeExecutingState(false) end) end diff --git a/src/ServerStorage/Proxy/ArchiveProxy.luau b/src/ServerStorage/Proxy/ArchiveProxy.luau index 9a89920..687dbb0 100644 --- a/src/ServerStorage/Proxy/ArchiveProxy.luau +++ b/src/ServerStorage/Proxy/ArchiveProxy.luau @@ -86,7 +86,6 @@ local function LoadData(Player: Player): (boolean, any) if Success and Response then print(("DataManager: User %s's data loaded into the game."):format(Player.Name)) - print(Response) else print(("DataManager: User %s had no data to load from."):format(Player.Name)) end diff --git a/src/ServerStorage/Proxy/MobsProxy/AI.luau b/src/ServerStorage/Proxy/MobsProxy/AI.luau index cc77bf9..04a760b 100644 --- a/src/ServerStorage/Proxy/MobsProxy/AI.luau +++ b/src/ServerStorage/Proxy/MobsProxy/AI.luau @@ -67,9 +67,7 @@ task.defer(function() local Enemy = Mob.Humanoid -- Simulation - if Mob.Root.Anchored then - Mob.Root.Anchored = false - end + if Mob.Root.Anchored then Mob.Root.Anchored = false end if not Mob.Root:GetNetworkOwner() then Mob.Root:SetNetworkOwner(Player) task.wait(0.05) -- Give physics more time so it doesn't appear as choppy @@ -78,9 +76,12 @@ task.defer(function() -- Tracking if Player and Enemy and PlayerRole then if PlayerRole.Stats.Died then return end - if distance > 5 then + if distance > 8 then + Enemy:SetAttribute("AttackState", "Idle") Enemy:MoveTo(Player.Character:GetPivot().Position, Player.Character.PrimaryPart) else + Enemy:SetAttribute("AttackState", "Hit") + Mob.Root.Anchored = true Mob.ExecutingState = true -- 停止移动 Enemy:MoveTo(MobInstance:GetPivot().Position) @@ -92,7 +93,7 @@ task.defer(function() if not Player then return end if PlayerRole.Stats.Died then return end - if AI:GetModelDistance(MobInstance, Player.Character) <= 5 then + if AI:GetModelDistance(MobInstance, Player.Character) <= 8 then -- 调用伤害模块 DamageProxy:TakeDamage(Mob, Mob.PlayerRole, { { @@ -104,6 +105,7 @@ task.defer(function() }) end Mob.ExecutingState = false + Enemy:SetAttribute("AttackState", "Idle") end end else diff --git a/src/ServerStorage/Proxy/MobsProxy/init.luau b/src/ServerStorage/Proxy/MobsProxy/init.luau index 94db091..0f3f4be 100644 --- a/src/ServerStorage/Proxy/MobsProxy/init.luau +++ b/src/ServerStorage/Proxy/MobsProxy/init.luau @@ -99,7 +99,10 @@ function MobsProxy:CreateMob(Player: Player, MobId: number, AtkBonus: number?, H AI:StartTracking(Mob) -- 关卡系数 if AtkBonus then Mob:ChangeAttributeValue("attack", math.floor(Mob.Config.attack * (AtkBonus / 1000))) end - if HpBonus then Mob:ChangeAttributeValue("hp", math.floor(Mob.Config.hp * (HpBonus / 1000))) end + if HpBonus then + Mob:ChangeAttributeValue("hp", math.floor(Mob.Config.hp * (HpBonus / 1000))) + Mob:ChangeAttributeValue("maxhp", math.floor(Mob.Config.maxhp * (HpBonus / 1000))) + end MobsProxy.pData[Player.UserId][Mob.Instance] = Mob return Mob end diff --git a/src/ServerStorage/Proxy/PlayerFightProxy/init.luau b/src/ServerStorage/Proxy/PlayerFightProxy/init.luau index c9b8af4..8bf3b1a 100644 --- a/src/ServerStorage/Proxy/PlayerFightProxy/init.luau +++ b/src/ServerStorage/Proxy/PlayerFightProxy/init.luau @@ -65,6 +65,7 @@ function PlayerRole.new(Player: Player, CharacterId: number) return self end + function PlayerRole:Died() self:ChangeState("Died", true) self.Humanoid.WalkSpeed = 0 @@ -119,7 +120,6 @@ function PlayerFightProxy:UpdatePlayerFightData(Player: Player) local AbilityProxy = require(ServerStorage.Proxy.AbilityProxy) local GemProxy = require(ServerStorage.Proxy.GemProxy) - local AttributesData = {} -- 计算角色基础属性值 + 装备属性值 + 宝石属性值,赋值属性 @@ -178,8 +178,9 @@ function PlayerFightProxy:UpdatePlayerFightData(Player: Player) for _, behaviourName in behaviourNameList do playerAI:AddBehaviour(behaviourName) end - -- playerAI:AddBehaviour("Move") - playerAI:AddBehaviour("SwordWave") + playerAI:AddBehaviour("Move") + -- playerAI:AddBehaviour("SwordWave") + playerAI:AddBehaviour("Attack") -- 给前端发送技能信息 diff --git a/src/StarterPlayerScripts/ClientMain/Camera.client.luau b/src/StarterPlayerScripts/ClientMain/Camera.client.luau index c9fea3a..fb9d04d 100644 --- a/src/StarterPlayerScripts/ClientMain/Camera.client.luau +++ b/src/StarterPlayerScripts/ClientMain/Camera.client.luau @@ -5,7 +5,7 @@ local RunService = game:GetService("RunService") local player = Players.LocalPlayer local camera = workspace.CurrentCamera -local CAMERA_DISTANCE = 20 +local CAMERA_DISTANCE = 30 local CAMERA_HEIGHT = 40 local CAMERA_ANGLE = math.rad(-45) local SCREEN_OFFSET = 5 diff --git a/src/StarterPlayerScripts/ClientMain/MobAnim.luau b/src/StarterPlayerScripts/ClientMain/MobAnim.luau new file mode 100644 index 0000000..d439f1c --- /dev/null +++ b/src/StarterPlayerScripts/ClientMain/MobAnim.luau @@ -0,0 +1,159 @@ +--[[ + Evercyan @ March 2023 + MobClient + + Unlike MobLib which handles server-sided code, and most of it in general, we run + some code on the client for things such as custom health overlays & overwriting humanoid state types. + + If you want to edit mob code to add new behavior or edit existing behavior, you likely want to refer + to the server-sided code under ServerStorage. +]] + +--> Services +local CollectionService = game:GetService("CollectionService") +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local Players = game:GetService("Players") + +--> Player +local Player = Players.LocalPlayer + +--> Dependencies +local Tween = require(ReplicatedStorage.Modules.Tween) +local Maid = require(ReplicatedStorage.Modules.Maid) + +--> Variables +local Mobs = {} + +-- Folder +local ClientMainPrefabs = Player.PlayerScripts.ClientMainPrefabs + +-------------------------------------------------------------------------------- + +-- WaitForChild keeps yielding, even if the Instance is removed. +-- Using this for safe yielding with StreamingEnabled! +local function safeWait(Item: Instance, Name: string): Instance? + if not Item then + return + elseif Item:FindFirstChild(Name) then + return Item:FindFirstChild(Name) + end + + local ItemAdded = Instance.new("BindableEvent") + local Maid = Maid.new() + + Maid:Add(Item.ChildAdded:Connect(function(Child) + if Child.Name == Name then + ItemAdded:Fire(Child) + ItemAdded:Destroy() + Maid:Destroy() + end + end)) + Maid:Add(Item.Destroying:Connect(function() + ItemAdded:Fire() + ItemAdded:Destroy() + Maid:Destroy() + end)) + + return ItemAdded.Event:Wait() +end + +-- Creates an "AnimationTrack" instance which is stored on the client for playing +local function LoadAnimationTrack(MobInstance: Model, Name: string, Priority: string) : AnimationTrack? + local Humanoid = MobInstance:FindFirstChild("Humanoid") :: Humanoid + local Animator = Humanoid and Humanoid:FindFirstChild("Animator") :: Animator + if not Animator then return nil end + + local Animation + Animation = ClientMainPrefabs.MobClient.DefaultAnimations[Name] + + local AnimationTrack = Animator:LoadAnimation(Animation) + AnimationTrack.Priority = Enum.AnimationPriority[Priority or "Core"] + + return AnimationTrack +end + +local function PerMob(MobInstance: Model) + if Mobs[MobInstance] then return end + + local Humanoid = safeWait(MobInstance, "Humanoid") :: Humanoid + local Root = safeWait(MobInstance, "HumanoidRootPart") :: BasePart + if not Humanoid or not Root then return end + + local Maid = Maid.new() + + -- Set humanoid states (helps prevent falling down & useless calculations - you're unlikely to have an enemy climbing without pathfinding) + for _, EnumName in {"FallingDown", "Seated", "Flying", "Swimming", "Climbing"} do + local HumanoidStateType = Enum.HumanoidStateType[EnumName] + Humanoid:SetStateEnabled(HumanoidStateType, false) + if Humanoid:GetState() == HumanoidStateType then + Humanoid:ChangeState(Enum.HumanoidStateType.Running) + end + end + + -- Animations / Behavior --------------------------------------------------- + + local AnimationTracks = { + Running = LoadAnimationTrack(MobInstance, "Running", "Core"), + Jumping = LoadAnimationTrack(MobInstance, "Jumping", "Movement"), + Hit = LoadAnimationTrack(MobInstance, "Hit", "Action") + } + + Maid:Add(Humanoid.Running:Connect(function(Speed) + if Speed > 0.01 then + local Percent = Speed/Humanoid.WalkSpeed + if not AnimationTracks.Running.IsPlaying then + AnimationTracks.Running:Play() + end + AnimationTracks.Running:AdjustSpeed(Percent) + else + AnimationTracks.Running:Stop() + end + end)) + + Maid:Add(Humanoid.Jumping:Connect(function() + AnimationTracks.Jumping.TimePosition = 0 + if not AnimationTracks.Jumping.IsPlaying then + AnimationTracks.Jumping:Play() + end + end)) + + -- Maid:Add(Humanoid.Died:Once(function() + -- Root:ApplyImpulse(-Root.CFrame.LookVector * Root.AssemblyMass*50) -- Ragdoll Impulse + -- end)) + + Maid:Add(Root:GetPropertyChangedSignal("Anchored"):Connect(function() + if Root.Anchored then + AnimationTracks.Running:Stop() + end + end)) + + -- TODO: 攻击动画之后要做延迟处理和攻速处理 + Maid:Add(Humanoid:GetAttributeChangedSignal("AttackState"):Connect(function() + if Humanoid:GetAttribute("AttackState") == "Hit" then + if AnimationTracks.Hit.IsPlaying then + AnimationTracks.Hit.TimePosition = 0 + else + AnimationTracks.Hit:Play() + end + else + AnimationTracks.Hit:Stop() + end + end)) + + local Mob = {} + Mob.Instance = MobInstance + Mob.AnimationTracks = AnimationTracks + Mobs[MobInstance] = Mob + + Maid:Add(MobInstance.Destroying:Connect(function() + Mobs[MobInstance] = nil + Maid:Destroy() + end)) +end + +CollectionService:GetInstanceAddedSignal("Mob"):Connect(PerMob) +for _, MobInstance in CollectionService:GetTagged("Mob") do + task.spawn(PerMob, MobInstance) +end + +return {} \ No newline at end of file diff --git a/src/StarterPlayerScripts/ClientMain/PerformanceClient/DamageBoard.luau b/src/StarterPlayerScripts/ClientMain/PerformanceClient/DamageBoard.luau index acf16c2..bff133f 100644 --- a/src/StarterPlayerScripts/ClientMain/PerformanceClient/DamageBoard.luau +++ b/src/StarterPlayerScripts/ClientMain/PerformanceClient/DamageBoard.luau @@ -35,8 +35,8 @@ end function DamageBoard:CreateNormalDamageBoard(DamageDetail: table) local DamageInitPos = DamageDetail.DamagePosition - local BiasPos = Vector3.new(0, 4, 0) - local MultPos = Vector3.new(0, 1.5, 0) + local BiasPos = Vector3.new(0, 6, 0) + local MultPos = Vector3.new(0, 1, 0) -- 根据元素类型排序 local DamageInfos = DamageDetail["DamageInfos"] diff --git a/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau b/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau index 7ddccf5..f62a98c 100644 --- a/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau +++ b/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau @@ -56,7 +56,6 @@ RE_PerformanceEvent.OnClientEvent:Connect(function(ServerTime: number, CastTag: local delayTime = ServerTime - tick() if CastTag == "Init" then - -- print("Init", BehaviourName) -- 暂时就新增一个表,不调用初始化,因为服务端不是多个new做的,而是多次调用 if not PerformanceClient.pData[UserId][BehaviourName] then PerformanceClient.pData[UserId][BehaviourName] = {} diff --git a/src/StarterPlayerScripts/ClientMain/PlayerControl.luau b/src/StarterPlayerScripts/ClientMain/PlayerControl.luau index 42588c4..464eb66 100644 --- a/src/StarterPlayerScripts/ClientMain/PlayerControl.luau +++ b/src/StarterPlayerScripts/ClientMain/PlayerControl.luau @@ -2,30 +2,57 @@ local UserInputService = game:GetService("UserInputService") local PlayerControl = {} --- 监听按键按下 -function PlayerControl.ListenMoveKeyDown(onKeyDown) - UserInputService.InputBegan:Connect(function(input, gameProcessed) - if gameProcessed then return end - if input.UserInputType == Enum.UserInputType.Keyboard then - local key = input.KeyCode - if key == Enum.KeyCode.W or key == Enum.KeyCode.A or key == Enum.KeyCode.S or key == Enum.KeyCode.D then - onKeyDown(key) - end - end - end) -end +-- 添加按键状态跟踪 +local pressedKeys = {} --- 监听按键松开 function PlayerControl.ListenMoveKeyUp(onKeyUp) UserInputService.InputEnded:Connect(function(input, gameProcessed) if gameProcessed then return end if input.UserInputType == Enum.UserInputType.Keyboard then local key = input.KeyCode if key == Enum.KeyCode.W or key == Enum.KeyCode.A or key == Enum.KeyCode.S or key == Enum.KeyCode.D then - onKeyUp(key) + pressedKeys[key] = nil -- 移除当前按键状态 + + -- 检查是否还有其他移动按键在按下 + local hasOtherKeysPressed = false + for k, v in pairs(pressedKeys) do + if k == Enum.KeyCode.W or k == Enum.KeyCode.A or k == Enum.KeyCode.S or k == Enum.KeyCode.D then + hasOtherKeysPressed = true + break + end + end + + -- 只有当没有其他移动按键按下时才调用onKeyUp + if not hasOtherKeysPressed then + onKeyUp(key) + end end end end) end +-- 添加按键按下监听 +function PlayerControl.ListenMoveKeyDown(onKeyDown) + UserInputService.InputBegan:Connect(function(input, gameProcessed) + if gameProcessed then return end + if input.UserInputType == Enum.UserInputType.Keyboard then + local key = input.KeyCode + if key == Enum.KeyCode.W or key == Enum.KeyCode.A or key == Enum.KeyCode.S or key == Enum.KeyCode.D then + pressedKeys[key] = true + onKeyDown(key) + end + end + end) +end + +-- 添加获取当前按下按键的函数 +function PlayerControl.GetPressedKeys() + return pressedKeys +end + +-- 添加检查特定按键是否按下的函数 +function PlayerControl.IsKeyPressed(keyCode) + return pressedKeys[keyCode] == true +end + return PlayerControl \ No newline at end of file diff --git a/src/StarterPlayerScripts/UI/UIManager.luau b/src/StarterPlayerScripts/UI/UIManager.luau index 0fb6a88..708b3f0 100644 --- a/src/StarterPlayerScripts/UI/UIManager.luau +++ b/src/StarterPlayerScripts/UI/UIManager.luau @@ -128,6 +128,14 @@ function UIManager:IsOpened(WindowName: string) return UIManager.Instances[WindowName] ~= nil end +function UIManager:SetData(WindowName: string, Data: table) + if not UIManager.Instances[WindowName] then + warn("UIManager:SetData() 窗口不存在:" .. WindowName) + return + end + UIManager.Instances[WindowName]:SetData(Data) +end + -- 获取当前窗口堆栈信息(用于调试) function UIManager:GetWindowStackInfo() local info = {} diff --git a/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau b/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau index 050451d..ddbb2fe 100644 --- a/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau +++ b/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau @@ -62,14 +62,16 @@ function MainWindow:OnOpenWindow() table.insert(self.Connections, chaCon) table.insert(self.Connections, attributeUpgradeCon) - local playerDataFolder = Utils:WaitPlayerDataFolder(LocalPlayer) - local StatsFolder = playerDataFolder:WaitForChild("PlayerInfo"):WaitForChild("Stats") - local levelCon = StatsFolder.level.Changed:Connect(function(newValue) + -- TODO: 暂时用主关卡数显示,我记得之前这里主要是记录的,Challenge中才是正在挑战的内容 + -- TODO: 之后LevelProxy也应该挪到ReplicatedStorage下,之前可能是因为觉得Challenge是临时的内容所以放在workspace下,但是逻辑做的不统一 + local playerDataFolder = game.Workspace:WaitForChild("Level"):WaitForChild(LocalPlayer.UserId) + local StatsFolder = playerDataFolder:WaitForChild("Progress") + local levelCon = StatsFolder.Main.Changed:Connect(function(newValue) self:SetShowLevel(newValue) end) -- 初始值设置 - self:SetShowLevel(StatsFolder.level.Value) + self:SetShowLevel(StatsFolder.Main.Value) table.insert(self.Connections, levelCon) end