diff --git a/Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack1.FBX.meta b/Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack1.FBX.meta index c6b8e5c5b..b4b9f3b85 100644 --- a/Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack1.FBX.meta +++ b/Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack1.FBX.meta @@ -1,7 +1,14 @@ fileFormatVersion: 2 guid: d664857a783ffb645b4f451b91f99021 +AssetOrigin: + serializedVersion: 1 + productId: 39519 + packageName: Mage Warrior Mecanim Animation Pack + packageVersion: 2.4.1 + assetPath: Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack1.FBX + uploadId: 754788 ModelImporter: - serializedVersion: 25 + serializedVersion: 24300 internalIDToNameTable: - first: 1: 100000 @@ -470,18 +477,18 @@ ModelImporter: second: ImportLogs externalObjects: {} materials: - importMaterials: 0 + materialImportMode: 0 materialName: 0 materialSearch: 1 materialLocation: 0 + searchTexturesGlobally: 1 animations: legacyGenerateAnimations: 4 bakeSimulation: 0 resampleCurves: 1 optimizeGameObjects: 0 + removeConstantScaleCurves: 0 motionNodeName: Motion - rigImportErrors: - rigImportWarnings: animationImportErrors: animationImportWarnings: "\nClip 'Take 001' has import animation warnings that might lower retargeting quality:\nNote: Activate translation DOF on avatar @@ -643,6 +650,49 @@ ModelImporter: maskType: 0 maskSource: {instanceID: 0} additiveReferencePoseFrame: 0 + - serializedVersion: 16 + name: StaffCast3_S_10302 + takeName: Take 001 + internalID: 1827226128182048838 + firstFrame: 12 + lastFrame: 44 + wrapMode: 0 + orientationOffsetY: 0 + level: 0 + cycleOffset: 0 + loop: 0 + hasAdditiveReferencePose: 0 + loopTime: 0 + loopBlend: 0 + loopBlendOrientation: 1 + loopBlendPositionY: 0 + loopBlendPositionXZ: 0 + keepOriginalOrientation: 1 + keepOriginalPositionY: 1 + keepOriginalPositionXZ: 1 + heightFromFeet: 0 + mirror: 0 + bodyMask: 00000000010000000100000000000000000000000100000001000000010000000100000000000000000000000100000001000000 + curves: [] + events: + - time: 0 + functionName: LookAtTarget + data: + objectReferenceParameter: {instanceID: 0} + floatParameter: 0 + intParameter: 0 + messageOptions: 0 + - time: 0.5 + functionName: Projectile + data: Projectile_10302 + objectReferenceParameter: {instanceID: 0} + floatParameter: 0 + intParameter: 0 + messageOptions: 0 + transformMask: [] + maskType: 1 + maskSource: {fileID: 31900000, guid: 8568a603ff2c2854190b73771710444a, type: 2} + additiveReferencePoseFrame: 0 isReadable: 1 meshes: lODScreenPercentages: [] @@ -650,26 +700,39 @@ ModelImporter: meshCompression: 0 addColliders: 0 useSRGBMaterialColor: 1 + sortHierarchyByName: 1 + importPhysicalCameras: 1 importVisibility: 0 importBlendShapes: 0 importCameras: 0 importLights: 0 + nodeNameCollisionStrategy: 0 + fileIdsGeneration: 1 swapUVChannels: 0 generateSecondaryUV: 0 useFileUnits: 1 keepQuads: 0 weldVertices: 0 + bakeAxisConversion: 0 preserveHierarchy: 0 skinWeightsMode: 0 maxBonesPerVertex: 4 minBoneWeight: 0.001 + optimizeBones: 1 + generateMeshLods: 0 + meshLodGenerationFlags: 0 + maximumMeshLod: -1 meshOptimizationFlags: 0 indexFormat: 0 secondaryUVAngleDistortion: 8 secondaryUVAreaDistortion: 15.000001 secondaryUVHardAngle: 88 + secondaryUVMarginMethod: 0 + secondaryUVMinLightmapResolution: 40 + secondaryUVMinObjectScale: 1 secondaryUVPackMargin: 4 useFileScale: 0 + strictVertexDataChecks: 0 tangentSpace: normalSmoothAngle: 60 normalImportMode: 2 @@ -680,7 +743,6 @@ ModelImporter: normalSmoothingSource: 0 referencedClips: [] importAnimation: 1 - copyAvatar: 1 humanDescription: serializedVersion: 3 human: @@ -1389,16 +1451,14 @@ ModelImporter: skeletonHasParents: 0 lastHumanDescriptionAvatarSource: {fileID: 9000000, guid: 7c72ed3f90575084c9f3b5b585887182, type: 3} + autoGenerateAvatarMappingIfUnspecified: 1 animationType: 3 humanoidOversampling: 1 + avatarSetup: 2 + addHumanoidExtraRootOnlyWhenUsingAvatar: 0 + importBlendShapeDeformPercent: 0 + remapMaterialsIfMaterialImportModeIsNone: 1 additionalBone: 0 userData: assetBundleName: assetBundleVariant: -AssetOrigin: - serializedVersion: 1 - productId: 39519 - packageName: Mage Warrior Mecanim Animation Pack - packageVersion: 2.4.1 - assetPath: Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack1.FBX - uploadId: 754788 diff --git a/Assets/Res_Addr/Animations/pcanim_sorceress.controller b/Assets/Res_Addr/Animations/pcanim_sorceress.controller index 6aed368c7..ad8769d7f 100644 --- a/Assets/Res_Addr/Animations/pcanim_sorceress.controller +++ b/Assets/Res_Addr/Animations/pcanim_sorceress.controller @@ -2316,7 +2316,7 @@ AnimatorState: m_MirrorParameterActive: 0 m_CycleOffsetParameterActive: 0 m_TimeParameterActive: 0 - m_Motion: {fileID: -4485281783395832467, guid: a9b647a73aef2fb4a83c157be5e4867c, + m_Motion: {fileID: 1827226128182048838, guid: d664857a783ffb645b4f451b91f99021, type: 3} m_Tag: m_SpeedParameter: diff --git a/Assets/WL/Combat/CombatAnimMapping.asset b/Assets/WL/Combat/CombatAnimMapping.asset index c53f26d57..dd55a6568 100644 --- a/Assets/WL/Combat/CombatAnimMapping.asset +++ b/Assets/WL/Combat/CombatAnimMapping.asset @@ -984,13 +984,13 @@ MonoBehaviour: intParameter: 0 floatParameter: 0 - stateName: attack3 - sourceFbxPath: Assets/ResWork/Suriyun/Animations/AnimationsWing/Anim@CastspellC_wing.fbx - subClipName: StaffJumpCast3_S_{CID} - sourceClipName: CastspellC_wing + sourceFbxPath: Assets/ExplosiveLLC/Mage Warrior Mecanim Animation Pack/Animations/Mage@RangeAttack1.FBX + subClipName: StaffCast3_S_{CID} + sourceClipName: incomingExitTime: 0.85 createIfMissing: 1 - firstFrame: 431 - lastFrame: 487 + firstFrame: 12 + lastFrame: 44 speed: 2 maskPath: Assets/WL/Combat/Masks/WL_UpperBody.mask bakeRootY: 0 @@ -999,13 +999,13 @@ MonoBehaviour: time: 0 useNormalized: 1 normalizedTime: 0 - stringParameter: + stringParameter: intParameter: 0 floatParameter: 0 - functionName: Projectile time: 0 useNormalized: 1 - normalizedTime: 0.43 + normalizedTime: 0.5 stringParameter: Projectile_{CID} intParameter: 0 floatParameter: 0 diff --git a/Assets/WL/Combat/Weapon/Resources/WL/WLProjectileVisual.json b/Assets/WL/Combat/Weapon/Resources/WL/WLProjectileVisual.json index 772276158..d7967db5f 100644 --- a/Assets/WL/Combat/Weapon/Resources/WL/WLProjectileVisual.json +++ b/Assets/WL/Combat/Weapon/Resources/WL/WLProjectileVisual.json @@ -17,13 +17,15 @@ "FX_Shoot_06_projectile/trail", "FX_Shoot_06_projectile/back_fire", "FX_Shoot_06_projectile/particle", - "~WL_Visual/Pos/trail" + "~WL_Visual/Pos/trail", + "~WL_Visual/Pos/bullet/SubEmitter2" ], + "_note_948b": "#948 PD 「명중 직전 길쭉한 타격 이펙트」 — 후보 2개 모두 줄임: 탄 끝에서 나오는 길쭉한 줄기 입자(bullet/SubEmitter2 · 경계 1.66×0.31 m) 숨김 + 명중 별 섬광(WL_Hit_Staff · 세로 스파이크) 0.37 → 0.28. 명중 자체는 판정 반경(공격표 aoe 1.2 m) 때문에 접촉 전에 뜬다(판정 값은 PD 결정)", "stageScales": [ { "stage": 3, "scale": 0.0, "hitScale": 0.0 } ], "hitFx": "WL/Impact/WL_Hit_Staff", - "hitScale": 0.37, + "hitScale": 0.28, "hitLife": 1.5 }, { diff --git a/Assets/WL/Combat/Weapon/Resources/WL/WLWeaponTipGlow.json b/Assets/WL/Combat/Weapon/Resources/WL/WLWeaponTipGlow.json index b44392a19..53081a724 100644 --- a/Assets/WL/Combat/Weapon/Resources/WL/WLWeaponTipGlow.json +++ b/Assets/WL/Combat/Weapon/Resources/WL/WLWeaponTipGlow.json @@ -1,20 +1,19 @@ { "enabled": 1, - "note": "#948 PD 「마법검의 지팡이 끝은 은은하게 빛나도록(마법 무기 느낌)」 — 직업 10302(마법검 1071) 무기 끝에 가산 사각형(WL_Mat_ChargeHalo 사본 · 주황빛) + 점광원 1개 · 사인 맥동. 끄기 = enabled 0 · 점광원만 끄기 = light 0", + "note": "#948 PD 「마법검의 지팡이 끝은 은은하게 빛나도록(마법 무기 느낌)」 — 직업 10302(마법검 1071). 2차: 끝 = 무기 축 위 끝점(둥근 면 중심) · 겉모습 = 팩 이펙트 WL_Fx_BowCharge(시전 모으기와 같은 것)를 루프로 작게 + 약한 점광원 · 끄기 = enabled 0 · 점광원만 끄기 = light 0", "rules": [ { "classIds": [ 10302 ], "weaponNames": [], - "color": [ 1.0, 0.78, 0.42, 0.7 ], - "haloScale": 0.16, - "pulse": 0.25, + "prefab": "WL/Fx/WL_Fx_BowCharge", + "scale": 0.06, + "pulse": 0.2, "pulseSeconds": 1.6, "light": 1, + "lightColor": [ 1.0, 0.78, 0.42, 1.0 ], "lightRange": 0.8, "lightIntensity": 0.3, - "_note": "점광원 1.4 m/1.0 은 발밑에 큰 연두색 번짐이 생겨(캡처 948_tip) 0.8 m/0.3 으로 — 「은은하게」", - "tipUp": 0.0, - "material": "WL/Fx/WL_Mat_ChargeHalo" + "tipPull": 0.0 } ] } diff --git a/Assets/WL/Combat/Weapon/WLWeaponTipGlow.cs b/Assets/WL/Combat/Weapon/WLWeaponTipGlow.cs index 01d327841..a88952553 100644 --- a/Assets/WL/Combat/Weapon/WLWeaponTipGlow.cs +++ b/Assets/WL/Combat/Weapon/WLWeaponTipGlow.cs @@ -1,11 +1,12 @@ // ───────────────────────────────────────────────────────────────────── -// WLWeaponTipGlow.cs — 무기 끝 은은한 빛 (#948 · 2026-10-20) +// WLWeaponTipGlow.cs — 무기 끝 은은한 빛 (#948 · 2026-10-20 · 2차: 쿼드 → 팩 이펙트 · 끝 = 축 위 끝점) // // PD 원문: 「마법검의 지팡이 끝은 은은하게 빛나도록 해줘. (마법 무기라는 느낌이 들도록)」 -// ■ 붙는 자리 = WLWeaponFitter 손 맞춤 뒤 훅 1줄(WLOrbFloat 과 같은 자리 · 소켓 아래 무기 인스턴스마다 1회) -// ■ 무기 끝 = 소켓에서 가장 먼 렌더러 경계 모서리(WLStaffEarth.Tip 과 같은 생각 · 세로가 아닌 어떤 방향이든) → 무기 로컬 좌표로 기억해 따라간다 -// ■ 빛 = 카메라를 보는 가산 사각형(기존 WL_Mat_ChargeHalo 사본 · 색·크기 JSON) + 선택 점광원(range · intensity JSON · 모바일 예산 고려해 1개 · 끌 수 있음) · 사인 맥동 -// ■ 값 = Resources/WL/WLWeaponTipGlow.json (C45) · enabled 0 = 아무것도 안 함 · 원본 수정 0줄(훅은 WL 파일 WLWeaponFitter) +// 「지팡이 끝에 알 수 없는 흰색 선이 생겨났고 광원의 위치가 지팡이의 둥근 면이 아니라 엉뚱한 위치」 +// ■ 1차 실패: 무기 자식 쿼드가 무기의 비균등 스케일에 눌려 흰 선이 됐고, 끝 = 경계 상자 모서리라 축에서 벗어났다. +// ■ 2차: 끝 = 소켓 → 렌더러 경계 중심 방향(= 무기 축) 위에서 가장 먼 모서리의 투영점(둥근 끝면 중심) · 빛 = 씬 루트의 홀더가 매 프레임 그 점을 따라감 · +// 겉모습 = 기존 팩 이펙트 프리팹(기본 WL_Fx_BowCharge · 시전 모으기와 같은 것)을 루프로 켜 둔다 + 약한 점광원 · 사인 맥동(홀더 스케일) +// ■ 붙는 자리 = WLWeaponFitter 손 맞춤 뒤 훅 1줄(WLOrbFloat 과 같은 자리) · 값 = Resources/WL/WLWeaponTipGlow.json (C45) · enabled 0 = 아무것도 안 함 // 🔴 Debug.LogError 금지(원본 ErrorLogHook 팝업) — 예외는 LastLog 로만 // ───────────────────────────────────────────────────────────────────── using UnityEngine; @@ -17,15 +18,15 @@ namespace WL.Combat.Weapon { public int[] classIds = new int[0]; // 이 직업의 무기에 붙인다(PCActor.Get_ID) public string[] weaponNames = new string[0]; // 또는 무기 인스턴스 이름 앞부분 - public float[] color = { 1f, 0.8f, 0.45f, 1f }; - public float haloScale = 0.18f; // 사각형 한 변(m) - public float pulse = 0.25f; // 맥동 진폭(크기·밝기 ±비율) - public float pulseSeconds = 1.6f; // 맥동 주기(초) + public string prefab = "WL/Fx/WL_Fx_BowCharge"; // Resources 경로 · 파티클을 전부 루프로 + public float scale = 0.06f; + public float pulse = 0.2f; // 맥동 진폭(크기 ±비율) + public float pulseSeconds = 1.6f; public int light = 1; // 1 = 점광원도 - public float lightRange = 1.4f; - public float lightIntensity = 1.0f; - public float tipUp = 0f; // 끝에서 더 올릴 높이(m) - public string material = "WL/Fx/WL_Mat_ChargeHalo"; // Resources 경로 + public float[] lightColor = { 1f, 0.8f, 0.45f, 1f }; + public float lightRange = 0.8f; + public float lightIntensity = 0.3f; + public float tipPull = 0f; // 끝에서 손 쪽으로 당길 거리(m · 둥근 면 안쪽에 두고 싶을 때) } [System.Serializable] @@ -54,7 +55,7 @@ namespace WL.Combat.Weapon [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void ResetStatics() { s_loaded = false; s_cfg = null; } // 도메인 리로드 끔 대비(JSON 재읽기) - Transform _weapon, _socket, _halo; Light _light; Material _mat; WLWeaponTipGlowRule _r; float _t0; Vector3 _localTip; Color _col; + Transform _weapon, _socket, _holder; Light _light; WLWeaponTipGlowRule _r; float _t0; Vector3 _localTip; public static bool TryAttach(Transform weapon) { @@ -76,67 +77,66 @@ namespace WL.Combat.Weapon var g = weapon.gameObject.AddComponent(); g._weapon = weapon; g._socket = weapon.parent != null ? weapon.parent : weapon; g._r = rule; g._t0 = Time.time; g.Build(); - Attached++; LastLog = "무기 끝 빛 " + weapon.name + " 직업 " + cls + " 끝(로컬) " + g._localTip.ToString("F2"); + Attached++; LastLog = "무기 끝 빛 " + weapon.name + " 직업 " + cls + " 끝(로컬) " + g._localTip.ToString("F2") + (g._holder != null ? " · " + rule.prefab : " · 프리팹 없음"); return true; } catch (System.Exception ex) { LastLog = "무기 끝 빛 실패 — " + ex.Message; return false; } } + /// 무기 끝 = 소켓에서 렌더러 경계 중심을 향한 축 위, 가장 멀리 투영되는 모서리의 투영점(둥근 끝면 중심 · 상자 모서리가 아님). void Build() { var r = _r; - // 무기 끝 = 소켓에서 가장 먼 렌더러 경계 모서리 - Vector3 tip = _weapon.position; float best = -1f; + bool any = false; Bounds b = new Bounds(_weapon.position, Vector3.zero); foreach (var rd in _weapon.GetComponentsInChildren(false)) { if (rd is ParticleSystemRenderer || !rd.enabled) continue; - var b = rd.bounds; + if (!any) { b = rd.bounds; any = true; } else b.Encapsulate(rd.bounds); + } + Vector3 tip; + if (!any) tip = _weapon.position; + else + { + Vector3 o = _socket.position; Vector3 axis = b.center - o; if (axis.sqrMagnitude < 1e-6f) axis = _weapon.up; axis.Normalize(); + float far = 0f; for (int i = 0; i < 8; i++) { var corner = new Vector3((i & 1) == 0 ? b.min.x : b.max.x, (i & 2) == 0 ? b.min.y : b.max.y, (i & 4) == 0 ? b.min.z : b.max.z); - float d = (corner - _socket.position).sqrMagnitude; if (d > best) { best = d; tip = corner; } + float d = Vector3.Dot(corner - o, axis); if (d > far) far = d; } + tip = o + axis * Mathf.Max(0f, far - r.tipPull); } - _localTip = _weapon.InverseTransformPoint(tip + Vector3.up * r.tipUp); - _col = r.color != null && r.color.Length >= 4 ? new Color(r.color[0], r.color[1], r.color[2], r.color[3]) : new Color(1f, 0.8f, 0.45f, 1f); + _localTip = _weapon.InverseTransformPoint(tip); - var q = GameObject.CreatePrimitive(PrimitiveType.Quad); q.name = "~WL_TipGlow"; - var col = q.GetComponent(); if (col != null) Destroy(col); - q.transform.SetParent(_weapon, false); q.transform.localScale = Vector3.one * r.haloScale; - var src = Resources.Load(r.material); - var mr = q.GetComponent(); - if (src != null) { _mat = new Material(src); mr.sharedMaterial = _mat; SetColor(_col); } - mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; mr.receiveShadows = false; - _halo = q.transform; + var holder = new GameObject("~WL_TipGlow"); _holder = holder.transform; // 씬 루트(무기 스케일 영향 0) + _holder.position = tip; + var p = Resources.Load(r.prefab); + if (p != null) + { + var fx = Instantiate(p, _holder); fx.transform.localPosition = Vector3.zero; fx.transform.localRotation = Quaternion.identity; fx.transform.localScale = Vector3.one * r.scale; + foreach (var ps in fx.GetComponentsInChildren(true)) { var m = ps.main; m.loop = true; m.playOnAwake = true; ps.Play(true); } + var fade = fx.GetComponent(); // 사본 프리팹에 수명 스크립트(TurnOff_GO · WLFxFade)가 있으면 끈다 — 계속 켜 둬야 한다 + foreach (var mb in fx.GetComponents()) if (mb != null && !(mb is ParticleSystem)) mb.enabled = false; + } if (r.light != 0) { - var lg = new GameObject("~WL_TipLight"); lg.transform.SetParent(_halo, false); - _light = lg.AddComponent(); _light.type = LightType.Point; _light.range = r.lightRange; _light.intensity = r.lightIntensity; _light.color = _col; _light.shadows = LightShadows.None; + var lg = new GameObject("~WL_TipLight"); lg.transform.SetParent(_holder, false); + _light = lg.AddComponent(); _light.type = LightType.Point; _light.range = r.lightRange; _light.intensity = r.lightIntensity; _light.shadows = LightShadows.None; + _light.color = r.lightColor != null && r.lightColor.Length >= 3 ? new Color(r.lightColor[0], r.lightColor[1], r.lightColor[2]) : new Color(1f, 0.8f, 0.45f); } } - void SetColor(Color c) - { - if (_mat == null) return; - if (_mat.HasProperty("_TintColor")) _mat.SetColor("_TintColor", c); - if (_mat.HasProperty("_BaseColor")) _mat.SetColor("_BaseColor", c); - if (_mat.HasProperty("_Color")) _mat.SetColor("_Color", c); - if (_mat.HasProperty("_EmissionColor")) _mat.SetColor("_EmissionColor", c); - } - void LateUpdate() { - if (_halo == null || _weapon == null) return; + if (_holder == null || _weapon == null) return; float k = 1f + (_r.pulse > 0f && _r.pulseSeconds > 0f ? _r.pulse * Mathf.Sin((Time.time - _t0) / _r.pulseSeconds * Mathf.PI * 2f) : 0f); - var pos = _weapon.TransformPoint(_localTip); - var cam = Camera.main; - _halo.position = pos; - if (cam != null) _halo.rotation = Quaternion.LookRotation(pos - cam.transform.position); // 쿼드 앞면이 카메라를 보게 - _halo.localScale = Vector3.one * (_r.haloScale * k / Mathf.Max(0.0001f, _weapon.lossyScale.x)) ; - if (_mat != null) SetColor(new Color(_col.r, _col.g, _col.b, _col.a * Mathf.Clamp01(0.6f + 0.4f * k))); + _holder.position = _weapon.TransformPoint(_localTip); + _holder.localScale = Vector3.one * k; if (_light != null) _light.intensity = _r.lightIntensity * k; } - void OnDestroy() { if (_mat != null) Destroy(_mat); } + void OnDisable() { if (_holder != null) _holder.gameObject.SetActive(false); } + void OnEnable() { if (_holder != null) _holder.gameObject.SetActive(true); } + void OnDestroy() { if (_holder != null) Destroy(_holder.gameObject); } } }