From 4f262b3b8e38e65dca56f1819be7b99544886b76 Mon Sep 17 00:00:00 2001 From: swrring Date: Tue, 20 Oct 2026 17:45:21 +0900 Subject: [PATCH] =?UTF-8?q?WL=20#949=20=EB=A7=88=EB=B2=95=EC=B1=85(9009)?= =?UTF-8?q?=20PD=20=EC=A7=80=EC=8B=9C=204=EA=B1=B4=20=E2=80=94=20=E2=91=A0?= =?UTF-8?q?=20=EC=B1=85=EC=9D=B4=20=EB=96=A0=20=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EB=8F=99=EC=95=88=20=EC=86=90=20=EB=AC=B4=EA=B8=B0(=EC=85=89?= =?UTF-8?q?=ED=84=B0)=20=EB=A0=8C=EB=8D=94=EB=9F=AC=20=EC=88=A8=EA=B9=80(W?= =?UTF-8?q?LFloatingBook=20hideHandWeapon=20=C2=B7=20=ED=8F=B4=EB=A7=81?= =?UTF-8?q?=EB=A7=88=EB=8B=A4=20=EC=9E=AC=EC=A0=81=EC=9A=A9=20=C2=B7=20?= =?UTF-8?q?=EC=A7=81=EC=97=85=20=ED=91=9C=20=EC=88=98=EC=A0=95=20=ED=96=89?= =?UTF-8?q?=20=EB=B0=A9=EC=8B=9D=EC=9D=80=20Set=5FObj=20=EC=9E=AC=EB=B6=80?= =?UTF-8?q?=EC=B0=A9=EC=9D=B4=20=EB=A1=9C=EB=B9=84=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=EB=A5=BC=20=EB=81=BC=EC=9B=8C=20=ED=8F=90?= =?UTF-8?q?=EA=B8=B0)=20=E2=91=A1=20=EB=A7=88=EC=A7=80=EB=A7=89=20?= =?UTF-8?q?=ED=83=80(attack2)=20=3D=20Mage@RangeAttack2=20f16~46(=EC=98=A4?= =?UTF-8?q?=EB=A5=B8=EC=86=90=20=EB=82=B4=EC=A7=80=EB=A5=B8=20=EC=B1=84=20?= =?UTF-8?q?=EC=95=BD=200.5=20s=20=EA=B3=A0=EC=A0=95=20=C2=B7=20=EB=B0=9C?= =?UTF-8?q?=EC=82=AC=200.35=20=C2=B7=20=EC=9E=AC=EA=B5=BD=EA=B8=B0=20?= =?UTF-8?q?=E2=86=92=20pcanim=5Fsummoner)=20=E2=91=A2=20=EC=B1=85=20?= =?UTF-8?q?=EC=B6=94=EC=A0=81:=20=ED=8F=89=EC=86=8C=20=3D=20=EC=B9=B4?= =?UTF-8?q?=EB=A9=94=EB=9D=BC=20=EA=B8=B0=EC=A4=80=20=EC=BA=90=EB=A6=AD?= =?UTF-8?q?=ED=84=B0=20=EB=92=A4(=ED=99=94=EB=A9=B4=20=EC=9C=84=20=C2=B7?= =?UTF-8?q?=20=EB=A8=BC=EC=AA=BD=200.45=20=C2=B7=20=EC=98=86=200.35=20?= =?UTF-8?q?=C2=B7=20=EB=86=92=EC=9D=B4=200.72)=20=C2=B7=20=EC=8B=9C?= =?UTF-8?q?=EC=A0=84=20=EC=A4=91(=ED=81=B4=EB=A6=BD=20Cast1/Cast2/BookChar?= =?UTF-8?q?ge/BookCast)=20=3D=20PC=20=EC=A0=95=EB=A9=B4=200.5=20m(cameraSp?= =?UTF-8?q?ace=20=C2=B7=20castStates=20=C2=B7=20cast*=20=EC=8B=A0=EC=84=A4?= =?UTF-8?q?)=20=E2=91=A3=20=EC=8A=A4=ED=82=AC=20=EC=8B=9C=EC=A0=84=20NRE(A?= =?UTF-8?q?ctor.Use=5FSkill:756=20ServerInfo=20=EC=97=86=EC=9D=8C=20=C2=B7?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=94=AC)=20=EA=B0=80?= =?UTF-8?q?=EB=93=9C=201=EC=A4=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- .../Animations/Mage@RangeAttack2.FBX.meta | 43 ++++++++++++++++ .../Animations/pcanim_summoner.controller | 2 +- Assets/Script/Character/Actor.cs | 2 +- Assets/WL/Combat/CombatAnimMapping.asset | 17 ++++--- .../Weapon/Resources/WL/WLFloatingBook.json | 13 +++-- Assets/WL/Combat/Weapon/WLFloatingBook.cs | 49 ++++++++++++++++++- 6 files changed, 113 insertions(+), 13 deletions(-) diff --git a/Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack2.FBX.meta b/Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack2.FBX.meta index 6820d642c..df11b6bad 100644 --- a/Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack2.FBX.meta +++ b/Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack2.FBX.meta @@ -694,6 +694,49 @@ ModelImporter: maskType: 0 maskSource: {instanceID: 0} additiveReferencePoseFrame: 0 + - serializedVersion: 16 + name: Cast2_S_10303 + takeName: Take 001 + internalID: -998496362607748373 + firstFrame: 16 + lastFrame: 46 + wrapMode: 0 + orientationOffsetY: 0 + level: 0 + cycleOffset: 0 + loop: 0 + hasAdditiveReferencePose: 0 + loopTime: 0 + loopBlend: 0 + loopBlendOrientation: 1 + loopBlendPositionY: 1 + loopBlendPositionXZ: 0 + keepOriginalOrientation: 1 + keepOriginalPositionY: 1 + keepOriginalPositionXZ: 1 + heightFromFeet: 0 + mirror: 0 + bodyMask: 01000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000 + curves: [] + events: + - time: 0 + functionName: LookAtTarget + data: + objectReferenceParameter: {instanceID: 0} + floatParameter: 0 + intParameter: 0 + messageOptions: 0 + - time: 0.35 + functionName: Projectile + data: Projectile_10303 + objectReferenceParameter: {instanceID: 0} + floatParameter: 0 + intParameter: 0 + messageOptions: 0 + transformMask: [] + maskType: 0 + maskSource: {instanceID: 0} + additiveReferencePoseFrame: 0 isReadable: 1 meshes: lODScreenPercentages: [] diff --git a/Assets/Res_Addr/Animations/pcanim_summoner.controller b/Assets/Res_Addr/Animations/pcanim_summoner.controller index 1b41f347e..a25795226 100644 --- a/Assets/Res_Addr/Animations/pcanim_summoner.controller +++ b/Assets/Res_Addr/Animations/pcanim_summoner.controller @@ -2349,7 +2349,7 @@ AnimatorState: m_MirrorParameterActive: 0 m_CycleOffsetParameterActive: 0 m_TimeParameterActive: 0 - m_Motion: {fileID: 6641085478485924818, guid: e0919932c9c179142ad06cf4ee11f204, + m_Motion: {fileID: -998496362607748373, guid: 7a1612ec6fd9fbf4b917575b7c1ecdcb, type: 3} m_Tag: m_SpeedParameter: diff --git a/Assets/Script/Character/Actor.cs b/Assets/Script/Character/Actor.cs index 53b090974..2cf4e8420 100644 --- a/Assets/Script/Character/Actor.cs +++ b/Assets/Script/Character/Actor.cs @@ -753,7 +753,7 @@ public class Actor : MyCoroutine if (!testskill) m_Stat.Set_Stat(false, eStat.MP, skilldata.n_MP); else m_SkillData.SetSkillExtraType = true; Set_MagicCoolTime(skilldata.Get_CoolTime(m_Stat.Get_Stat(eStat.COOLTIME_Multiplier)), index); - if (IsMainPC() && !testskill) ServerInfo.Ins.m_ServerData.Add_MissionCount(ePurpose.USE_SKILL_ALL); + if (IsMainPC() && !testskill && !DSUtil.CheckNull(ServerInfo.Ins) && ServerInfo.Ins.m_ServerData != null) ServerInfo.Ins.m_ServerData.Add_MissionCount(ePurpose.USE_SKILL_ALL); // WL #949 — 스킬 테스트 씬(서버 객체 없음)에서 NRE 로 스킬이 안 나가던 것(실측 Actor.cs:756) · 서버 있으면 원본 그대로 return Play_Skill(skilldata.s_Animation); } public virtual void Shoot_Skill() diff --git a/Assets/WL/Combat/CombatAnimMapping.asset b/Assets/WL/Combat/CombatAnimMapping.asset index 404d1b222..2589be23d 100644 --- a/Assets/WL/Combat/CombatAnimMapping.asset +++ b/Assets/WL/Combat/CombatAnimMapping.asset @@ -1153,26 +1153,29 @@ MonoBehaviour: intParameter: 0 floatParameter: 0 - stateName: attack2 - sourceFbxPath: Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@Attack3_S.FBX + sourceFbxPath: Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack2.FBX subClipName: Cast2_S_{CID} - sourceClipName: + sourceClipName: incomingExitTime: 0.854 createIfMissing: 0 - firstFrame: -1 - lastFrame: -1 - speed: 0 + firstFrame: 16 + lastFrame: 46 + speed: 1 + maskPath: + bakeRootY: -1 + mirror: 0 events: - functionName: LookAtTarget time: 0 useNormalized: 1 normalizedTime: 0 - stringParameter: + stringParameter: intParameter: 0 floatParameter: 0 - functionName: Projectile time: 0 useNormalized: 1 - normalizedTime: 0.407 + normalizedTime: 0.35 stringParameter: Projectile_{CID} intParameter: 0 floatParameter: 0 diff --git a/Assets/WL/Combat/Weapon/Resources/WL/WLFloatingBook.json b/Assets/WL/Combat/Weapon/Resources/WL/WLFloatingBook.json index 5a4a3ca90..af625005d 100644 --- a/Assets/WL/Combat/Weapon/Resources/WL/WLFloatingBook.json +++ b/Assets/WL/Combat/Weapon/Resources/WL/WLFloatingBook.json @@ -7,9 +7,16 @@ "prefab": "WL/Book/WL_Book_Magic", "count": 1, "scale": 0.9, - "side": 0.55, - "height": 0.68, - "forward": 0.05, + "side": 0.35, + "height": 0.72, + "forward": 0.45, + "cameraSpace": 1, + "castStates": [ "Cast1_S", "Cast2_S", "BookCharge", "BookCast" ], + "castSide": 0.25, + "castHeight": 0.62, + "castForward": 0.5, + "hideHandWeapon": 1, + "_note_949": "#949 PD 「책이 캐릭터를 가리지 않게 가능한 캐릭터보다 뒤쪽에(추적 로직 수정) · 시전 동작 중엔 책이 캐릭터 모션 앞쪽으로 유지」 — 평소 자리 = 카메라 기준(cameraSpace 1 · forward + = 카메라에서 먼 쪽 = 화면 위 = 캐릭터 뒤 0.45 m · 옆 0.35 · 높이 0.72) · 시전 중(클립 이름 Cast1/Cast2/BookCharge/BookCast) = PC 정면 0.5 m · 옆 0.25 · 높이 0.62 · followLerp 로 오간다. 이전 = PC 오른쪽 0.55 · 앞 0.05(카메라에선 몸 앞을 가림)", "euler": [ 40.0, 0.0, diff --git a/Assets/WL/Combat/Weapon/WLFloatingBook.cs b/Assets/WL/Combat/Weapon/WLFloatingBook.cs index 6ce57e472..c78c3afad 100644 --- a/Assets/WL/Combat/Weapon/WLFloatingBook.cs +++ b/Assets/WL/Combat/Weapon/WLFloatingBook.cs @@ -36,6 +36,11 @@ namespace WL.Combat.Weapon public float animLoopStartSec, animLoopEndSec;// 책 클립 반복 구간(초 · 끝 ≤ 시작 = 반복 안 함) public int matchPcLayer; // 1 = 책 레이어를 PC 와 같게(같은 카메라 경로로 그려지게) public float pollSec = 0.25f; + // #949 PD 「책이 캐릭터를 가리지 않게 가능한 캐릭터보다 뒤쪽에 · 시전 동작 중엔 캐릭터 모션 앞쪽으로 유지」 + public int cameraSpace; // 1 = 평소 자리(side · forward)를 카메라 yaw 기준으로 — forward + = 카메라에서 먼 쪽(화면 위 · 캐릭터 뒤) · 0 = PC 정면 기준 + public string[] castStates = new string[0]; // PC 현재 클립 이름에 이 조각이 들면 「시전 중」 → castSide · castHeight · castForward(PC 정면 기준) 로 + public float castSide, castHeight, castForward; + public int hideHandWeapon; // #949 1 = 책이 떠 있는 동안 PC 손 소켓(w_*) 아래 무기 렌더러를 끈다(지팡이·셉터 숨김 · 책이 사라지면 되돌림) — 직업 표(수정 행)로 비우면 Set_Obj 재부착이 로비 컨트롤러를 끼워 실패(실측) public string note = ""; } @@ -98,6 +103,28 @@ namespace WL.Combat.Weapon for (int i = 0; i < _books.Count; i++) if (_books[i] == null) { missing = true; break; } if (_books.Count > 0 && (!want || missing || !ReferenceEquals(pc, _pc))) Clear(!want ? "조건 해제" : missing ? "씬 정리됨" : "PC 교체"); if (want && _books.Count == 0) Spawn(c, pc); + if (want && c.hideHandWeapon != 0 && _books.Count > 0) HideHandWeapon(pc); // 매 폴링 — 원본이 무기 모델을 다시 붙여도(Set_Obj) 다시 끈다 + } + + // #949 손 소켓(w_*) 아래 무기 렌더러 끄기 · 되돌리기 + readonly List _hiddenWeapon = new List(); + public static int WeaponHidden; + void HideHandWeapon(PCActor pc) + { + foreach (var t in pc.GetComponentsInChildren(true)) + { + if (!t.name.StartsWith("w_")) continue; + foreach (var r in t.GetComponentsInChildren(true)) + { + if (r == null || !r.enabled || r is ParticleSystemRenderer) continue; + r.enabled = false; _hiddenWeapon.Add(r); WeaponHidden++; + } + } + } + void RestoreHandWeapon() + { + for (int i = 0; i < _hiddenWeapon.Count; i++) if (_hiddenWeapon[i] != null) _hiddenWeapon[i].enabled = true; + _hiddenWeapon.Clear(); } /// 장착 무기(FR GroupNo)가 weaponIds 에 들거나, 그 무기의 공격표 class_id 가 classIds 에 드는가. @@ -139,6 +166,7 @@ namespace WL.Combat.Weapon if (_books.Count == 0) return; for (int i = 0; i < _books.Count; i++) if (_books[i] != null) Destroy(_books[i].gameObject); _books.Clear(); _anims.Clear(); _pc = null; + RestoreHandWeapon(); // #949 Cleared++; LastLog = "책 정리 — " + why; } @@ -166,6 +194,12 @@ namespace WL.Combat.Weapon Quaternion faceYaw = pcYaw; if (c.faceCamera != 0) { var cam = Camera.main; if (cam != null) { var f = cam.transform.forward; f.y = 0f; if (f.sqrMagnitude > 1e-4f) faceYaw = Quaternion.LookRotation(f.normalized, Vector3.up); } } var e = c.euler != null && c.euler.Length >= 3 ? Quaternion.Euler(c.euler[0], c.euler[1], c.euler[2]) : Quaternion.identity; + // #949 시전 중(현재 클립 이름에 castStates 조각) = PC 정면 기준 cast 자리 · 평소 = cameraSpace 면 카메라 yaw 기준(캐릭터 뒤 = 화면 위) + bool casting = IsCasting(c); + Casting = casting; + float side = casting ? c.castSide : c.side, height = casting ? c.castHeight : c.height, forward = casting ? c.castForward : c.forward; + Quaternion basis = casting || c.cameraSpace == 0 ? pcYaw : (c.faceCamera != 0 ? faceYaw : pcYaw); + if (!casting && c.cameraSpace != 0 && c.faceCamera == 0) { var cam = Camera.main; if (cam != null) { var f = cam.transform.forward; f.y = 0f; if (f.sqrMagnitude > 1e-4f) basis = Quaternion.LookRotation(f.normalized, Vector3.up); } } int n = _books.Count; float kp = snap || c.followLerp <= 0f ? 1f : 1f - Mathf.Exp(-c.followLerp * Time.deltaTime); float kr = snap || c.turnLerp <= 0f ? 1f : 1f - Mathf.Exp(-c.turnLerp * Time.deltaTime); @@ -176,13 +210,26 @@ namespace WL.Combat.Weapon var ring = Quaternion.Euler(0f, _orbit + 360f * share, 0f); float bob = Mathf.Sin((t * c.bobHz + share) * Mathf.PI * 2f) * c.bobAmp; float sw = Mathf.Sin((t * c.swayHz + share) * Mathf.PI * 2f); - var pos = pt.position + pcYaw * (ring * new Vector3(c.side, 0f, c.forward)) + Vector3.up * (c.height + bob); + var pos = pt.position + basis * (ring * new Vector3(side, 0f, forward)) + Vector3.up * (height + bob); var rot = faceYaw * ring * e * Quaternion.Euler(0f, sw * c.swayYawDeg, sw * c.swayRollDeg); if (kp >= 1f && kr >= 1f) b.SetPositionAndRotation(pos, rot); else b.SetPositionAndRotation(Vector3.Lerp(b.position, pos, kp), Quaternion.Slerp(b.rotation, rot, kr)); } } + public static bool Casting; // 프로브용 — 마지막 배치가 시전 자리였는가 + + /// #949 PC 애니메이터 0층 현재 클립 이름에 castStates 조각이 들면 시전 중. + bool IsCasting(WLFloatingBookJson c) + { + if (c.castStates == null || c.castStates.Length == 0 || _pc == null) return false; + var an = _pc.m_animation; if (an == null) return false; + var ci = an.GetCurrentAnimatorClipInfo(0); string name = ci.Length > 0 && ci[0].clip != null ? ci[0].clip.name : ""; + if (name.Length == 0) return false; + for (int i = 0; i < c.castStates.Length; i++) if (!string.IsNullOrEmpty(c.castStates[i]) && name.Contains(c.castStates[i])) return true; + return false; + } + /// 펼친 뒤 넘김 구간 반복 — 상태 시간이 끝 초를 넘으면 시작 초로 되돌린다. void LoopAnim(WLFloatingBookJson c) {