Weapon grip anchor: gizmo-authored WYSIWYG grip + operator's hand-tuned grip baked
WeaponGripAnchor under Hand_R in Player.prefab carries the actual axe mesh as an EditorOnly preview (baker skips it; play-verified single axe) so grip manipulation is the real model live in the prefab stage; AttachMeleeWeapon reads the anchor (consts = seed/fallback). Includes the operator's first gizmo-tuned grip (rebaked mesh matches expected radius exactly). PlayerRigTools also carries the heavy-lock clip re-speeds (1.0/0.85/0.88) consumed by the next commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -674,9 +674,9 @@ namespace ProjectM.EditorTools
|
||||
// window. Hit-start seconds measured off the pack takes (= the WindUp take length).
|
||||
var swaps = new (string state, string fbxSub, string clip, float speed)[]
|
||||
{
|
||||
("Swing1", "Attack/LightCombo01/A_Attack_LightCombo01A_Sword", "A_Attack_LightCombo01A_Sword", 1.25f), // hit @0.33s -> ~0.27s (07-19 retime: 26-tick window)
|
||||
("Swing2", "Attack/LightCombo01/A_Attack_LightCombo01B_Sword", "A_Attack_LightCombo01B_Sword", 1.0f), // hit @0.17s natural speed
|
||||
("Swing3", "Attack/LightCombo01/A_Attack_LightCombo01C_Sword", "A_Attack_LightCombo01C_Sword", 1.1f), // finisher: hit @0.33s, heaviest of the chain
|
||||
("Swing1", "Attack/LightCombo01/A_Attack_LightCombo01A_Sword", "A_Attack_LightCombo01A_Sword", 1.0f), // 07-21 heavy lock: TRUE contact 0.333s = 20t (matches MeleeContactTicks 20)
|
||||
("Swing2", "Attack/LightCombo01/A_Attack_LightCombo01B_Sword", "A_Attack_LightCombo01B_Sword", 0.85f), // TRUE contact 0.196s = 12t (knob step2 = round(20*0.625) = 12)
|
||||
("Swing3", "Attack/LightCombo01/A_Attack_LightCombo01C_Sword", "A_Attack_LightCombo01C_Sword", 0.88f), // finisher: TRUE contact 0.417s = 25t (knob step3 = 25; 1 tick under CastFacingTicks)
|
||||
("SpecialSlam", "Attack/HeavyCombo01/A_Attack_HeavyCombo01A_Sword", "A_Attack_HeavyCombo01A_Slam_Sword", 1.5f), // custom take (skips ~2/3 of the windup); hit ~0.35s
|
||||
("Death", "Death/A_Death_B_01_Sword", "A_Death_B_01_Sword", 1.0f), // backward collapse, holds last frame
|
||||
};
|
||||
@@ -784,7 +784,22 @@ namespace ProjectM.EditorTools
|
||||
// Rigid rest matrices (GraftSmr conventions: scale-stripped rest, feet-at-0 prefab space).
|
||||
var hm = hand.localToWorldMatrix;
|
||||
var handRigid = Matrix4x4.TRS((Vector3)hm.GetColumn(3), hm.rotation, Vector3.one);
|
||||
var grip = Matrix4x4.TRS(WeaponGripPos, Quaternion.Euler(WeaponGripEuler), Vector3.one * WeaponScale); // uniform scale: normals keep their directions
|
||||
// 07-20 designer flow: the WeaponGripAnchor child of Hand_R (EditorOnly, carries a live preview of
|
||||
// the weapon mesh) is the AUTHORING source when present -- move/rotate/scale it with normal gizmos
|
||||
// in the prefab stage, then re-run this tool to bake. The consts are the fallback/seed only.
|
||||
Vector3 gripPos = WeaponGripPos;
|
||||
Quaternion gripRot = Quaternion.Euler(WeaponGripEuler);
|
||||
float gripScale = WeaponScale;
|
||||
var gripAnchor = hand.Find("WeaponGripAnchor");
|
||||
if (gripAnchor != null)
|
||||
{
|
||||
gripPos = gripAnchor.localPosition;
|
||||
gripRot = gripAnchor.localRotation;
|
||||
gripScale = gripAnchor.localScale.x; // uniform: gizmo-scaled non-uniformly -> x wins
|
||||
if (Mathf.Abs(gripAnchor.localScale.y - gripScale) > 1e-3f || Mathf.Abs(gripAnchor.localScale.z - gripScale) > 1e-3f)
|
||||
Debug.LogWarning("[PlayerRigTools] WeaponGripAnchor scale is non-uniform; using X for the rigid bake.");
|
||||
}
|
||||
var grip = Matrix4x4.TRS(gripPos, gripRot, Vector3.one * gripScale); // uniform scale: normals keep their directions
|
||||
var toWorld = handRigid * grip;
|
||||
|
||||
var verts = srcMesh.vertices;
|
||||
@@ -838,8 +853,79 @@ namespace ProjectM.EditorTools
|
||||
}
|
||||
finally { PrefabUtility.UnloadPrefabContents(root); }
|
||||
AssetDatabase.SaveAssets();
|
||||
Debug.Log($"[PlayerRigTools] Melee weapon attached to Hand_R ({srcMesh.vertexCount}v, grip pos {WeaponGripPos} euler {WeaponGripEuler}).");
|
||||
Debug.Log($"[PlayerRigTools] Melee weapon attached to Hand_R ({srcMesh.vertexCount}v). Grip source: WeaponGripAnchor if present (gizmo-authored), else the consts. Re-run after moving the anchor.");
|
||||
}
|
||||
|
||||
/// <summary>07-20 designer grip flow: create (or refresh the preview on) the WeaponGripAnchor under
|
||||
/// Hand_R in Player.prefab. The anchor CARRIES the actual weapon mesh as an edit-time preview (plain
|
||||
/// MeshRenderer + a URP/Lit material with the pack atlas), so what you grab with the move/rotate/scale
|
||||
/// gizmos in the prefab stage IS the axe at true size, live. Tagged <b>EditorOnly</b> -- the Entities
|
||||
/// baker skips it, so it can never leak an un-animated duplicate into the game; the baked rigid-skinned
|
||||
/// SMR stays the only runtime weapon. Until you re-run Attach Melee Weapon you'll see BOTH (preview at
|
||||
/// the new grip, baked axe at the old) -- a free before/after ghost; when they coincide, you're baked.
|
||||
/// Seeds from the consts ONLY on first creation; re-runs never stomp a hand-tuned anchor.</summary>
|
||||
[MenuItem("ProjectM/Animation/Player - Create Weapon Grip Anchor (LANTERN)")]
|
||||
public static void CreateWeaponGripAnchor()
|
||||
{
|
||||
const string weaponFbx = "Assets/Synty/PolygonDungeonRealms/Models/SM_Wep_Axe_Large_01.fbx";
|
||||
const string atlasPath = "Assets/Synty/PolygonDungeonRealms/Textures/Dungeons_2_Texture_01_A.png";
|
||||
const string previewMatPath = "Assets/_Project/Materials/M_Weapon_Preview.mat";
|
||||
const string output = "Assets/_Project/Prefabs/Player.prefab";
|
||||
|
||||
var src = AssetDatabase.LoadAssetAtPath<GameObject>(weaponFbx);
|
||||
var srcMf = src != null ? src.GetComponentInChildren<MeshFilter>(true) : null;
|
||||
if (srcMf == null || srcMf.sharedMesh == null) { Debug.LogError($"[PlayerRigTools] Weapon fbx/mesh missing: {weaponFbx}"); return; }
|
||||
|
||||
// Plain URP/Lit preview material (the runtime deformation shader won't render on a static MeshRenderer).
|
||||
var mat = AssetDatabase.LoadAssetAtPath<Material>(previewMatPath);
|
||||
if (mat == null)
|
||||
{
|
||||
var lit = Shader.Find("Universal Render Pipeline/Lit");
|
||||
if (lit == null) { Debug.LogError("[PlayerRigTools] URP/Lit shader not found."); return; }
|
||||
mat = new Material(lit) { name = "M_Weapon_Preview" };
|
||||
var atlas = AssetDatabase.LoadAssetAtPath<Texture>(atlasPath);
|
||||
if (atlas != null && mat.HasProperty("_BaseMap")) mat.SetTexture("_BaseMap", atlas);
|
||||
AssetDatabase.CreateAsset(mat, previewMatPath);
|
||||
}
|
||||
|
||||
var root = PrefabUtility.LoadPrefabContents(output);
|
||||
try
|
||||
{
|
||||
Transform hand = null;
|
||||
foreach (var t in root.GetComponentsInChildren<Transform>(true)) if (t.name == "Hand_R") { hand = t; break; }
|
||||
if (hand == null) { Debug.LogError("[PlayerRigTools] Hand_R not found on Player.prefab."); return; }
|
||||
|
||||
var anchor = hand.Find("WeaponGripAnchor");
|
||||
bool fresh = anchor == null;
|
||||
if (fresh)
|
||||
{
|
||||
var go = new GameObject("WeaponGripAnchor");
|
||||
anchor = go.transform;
|
||||
anchor.SetParent(hand, false);
|
||||
// Seed exactly where the current bake sits, so the preview and the baked axe start coincident.
|
||||
anchor.localPosition = WeaponGripPos;
|
||||
anchor.localRotation = Quaternion.Euler(WeaponGripEuler);
|
||||
anchor.localScale = Vector3.one * WeaponScale;
|
||||
}
|
||||
anchor.gameObject.tag = "EditorOnly"; // the Entities baker SKIPS EditorOnly -- preview never reaches runtime
|
||||
|
||||
var mf = anchor.GetComponent<MeshFilter>();
|
||||
if (mf == null) mf = anchor.gameObject.AddComponent<MeshFilter>();
|
||||
mf.sharedMesh = srcMf.sharedMesh;
|
||||
var mr = anchor.GetComponent<MeshRenderer>();
|
||||
if (mr == null) mr = anchor.gameObject.AddComponent<MeshRenderer>();
|
||||
mr.sharedMaterial = mat;
|
||||
mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
||||
|
||||
PrefabUtility.SaveAsPrefabAsset(root, output);
|
||||
}
|
||||
finally { PrefabUtility.UnloadPrefabContents(root); }
|
||||
AssetDatabase.SaveAssets();
|
||||
Debug.Log("[PlayerRigTools] WeaponGripAnchor ready under Hand_R (EditorOnly, live axe preview). "
|
||||
+ "Open Player.prefab, search 'WeaponGripAnchor', move/rotate/scale it with normal gizmos, then run "
|
||||
+ "'Player - Attach Melee Weapon' to bake. Preview + baked axe overlap = you're in sync.");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user