Files
FueraDeEscala/Assets/Game Kit Controller/Scripts/Player/Character Builder/characterHeightScaler.cs

479 lines
16 KiB
C#
Raw Normal View History

using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// Height scaling for GKC player characters.
/// NEW APPROACH: Instead of scaling COM/capsule/camera/IK down,
/// simply SCALES THE MODEL UP (or down) to match GKC's standard character height.
/// This way capsule, camera, IK, weapons, ground detection — everything stays at
/// GKC default values and just works. Only the model transform changes.
/// </summary>
public class characterHeightScaler : MonoBehaviour
{
[Header ("Auto-Detect Settings")]
[Space]
[Tooltip ("When enabled, the system automatically measures your FBX model height " +
"and scales it to match the GKC standard height. No manual slider needed.")]
public bool autoDetectHeight = true;
[Tooltip ("The standard/reference height in Unity units for the default GKC character. " +
"Typically 1.8 units (average humanoid).")]
public float gkcStandardHeight = 1.8f;
[Tooltip ("The detected height of the current FBX model in Unity units (before scaling).")]
public float detectedModelHeight;
[Tooltip ("The calculated model scale to match GKC standard height.\n" +
"= gkcStandardHeight / detectedModelHeight")]
public float modelScaleMultiplier = 1.0f;
[Space]
[Header ("Manual Scale (when Auto-Detect is OFF)")]
[Space]
[Range (0.3f, 3.0f)]
[Tooltip ("Manual scale multiplier for the model. Used only when autoDetectHeight is OFF.")]
public float characterHeightScale = 1.0f;
public float minHeightScale = 0.3f;
public float maxHeightScale = 3.0f;
[Space]
[Header ("State")]
[Space]
public bool heightBaseValuesCaptured;
public bool showDebugLog;
[Space]
[Header ("Captured Base Values")]
[Space]
public Vector3 baseModelLocalScale;
public Vector3 baseModelLocalPosition;
public Transform modelTransform;
[Tooltip ("Small vertical offset to fine-tune foot alignment (in Unity units). " +
"Positive = raise model, Negative = lower model.")]
public float footAlignmentOffset = 0.0f;
// Internal references
playerComponentsManager componentsManager;
playerController playerControllerManager;
IKSystem IKManager;
IKFootSystem IKFootManager;
Transform COMTransform;
// ==================== AUTO-DETECT HEIGHT ====================
/// <summary>
/// Measures the FBX model height using multiple methods.
/// Returns the height in Unity units.
/// </summary>
public float detectModelHeight (GameObject model)
{
if (model == null) {
Debug.LogWarning ("characterHeightScaler: No model provided for height detection.");
return gkcStandardHeight;
}
float measuredHeight = 0f;
string method = "none";
Animator animator = model.GetComponentInChildren<Animator> ();
// --- Method 1: Animator humanoid bones (head to foot) ---
if (animator != null && animator.isHuman && animator.avatar != null && animator.avatar.isHuman) {
Transform head = animator.GetBoneTransform (HumanBodyBones.Head);
Transform leftFoot = animator.GetBoneTransform (HumanBodyBones.LeftFoot);
Transform rightFoot = animator.GetBoneTransform (HumanBodyBones.RightFoot);
if (head != null && (leftFoot != null || rightFoot != null)) {
Transform foot = leftFoot != null ? leftFoot : rightFoot;
measuredHeight = head.position.y - foot.position.y;
// Head bone is at eye level, add ~10% for top of head
measuredHeight *= 1.1f;
method = "Animator bones (head-to-foot)";
}
}
// --- Method 2: Animator humanScale ---
if (measuredHeight <= 0.01f && animator != null && animator.isHuman) {
measuredHeight = animator.humanScale * 1.8f;
method = "Animator humanScale";
}
// --- Method 3: SkinnedMeshRenderer bounds ---
if (measuredHeight <= 0.01f) {
SkinnedMeshRenderer[] skinnedRenderers = model.GetComponentsInChildren<SkinnedMeshRenderer> ();
if (skinnedRenderers.Length > 0) {
Bounds combinedBounds = new Bounds (skinnedRenderers [0].bounds.center, Vector3.zero);
for (int i = 0; i < skinnedRenderers.Length; i++) {
combinedBounds.Encapsulate (skinnedRenderers [i].bounds);
}
measuredHeight = combinedBounds.size.y;
method = "SkinnedMeshRenderer bounds";
}
}
// --- Method 4: Regular Renderer bounds ---
if (measuredHeight <= 0.01f) {
Renderer[] renderers = model.GetComponentsInChildren<Renderer> ();
if (renderers.Length > 0) {
Bounds combinedBounds = new Bounds (renderers [0].bounds.center, Vector3.zero);
for (int i = 0; i < renderers.Length; i++) {
combinedBounds.Encapsulate (renderers [i].bounds);
}
measuredHeight = combinedBounds.size.y;
method = "Renderer bounds";
}
}
// --- Fallback ---
if (measuredHeight <= 0.01f) {
measuredHeight = gkcStandardHeight;
method = "fallback (assuming standard)";
}
if (showDebugLog) {
Debug.Log ("characterHeightScaler: DETECT '" + method + "'"
+ " | measured=" + measuredHeight.ToString ("F3")
+ " | standard=" + gkcStandardHeight.ToString ("F3"));
}
return measuredHeight;
}
/// <summary>
/// Auto-detects model height and calculates the scale multiplier
/// to make the model match GKC standard height.
/// scale = gkcStandardHeight / detectedModelHeight
/// </summary>
public float autoDetectAndSetScale ()
{
resolveModelTransform ();
if (modelTransform == null) {
Debug.LogWarning ("characterHeightScaler: Cannot auto-detect — no model transform found.");
return 1.0f;
}
detectedModelHeight = detectModelHeight (modelTransform.gameObject);
if (detectedModelHeight > 0.01f) {
modelScaleMultiplier = gkcStandardHeight / detectedModelHeight;
modelScaleMultiplier = Mathf.Clamp (modelScaleMultiplier, minHeightScale, maxHeightScale);
} else {
modelScaleMultiplier = 1.0f;
}
characterHeightScale = modelScaleMultiplier;
if (showDebugLog) {
Debug.Log ("characterHeightScaler: AUTO-DETECT RESULT"
+ " | modelHeight=" + detectedModelHeight.ToString ("F3")
+ " | standardHeight=" + gkcStandardHeight.ToString ("F3")
+ " | scaleMultiplier=" + modelScaleMultiplier.ToString ("F3")
+ " (model will be scaled UP by " + modelScaleMultiplier.ToString ("F2") + "x)");
}
return modelScaleMultiplier;
}
/// <summary>
/// Full automatic: capture base values, detect height, scale model, align feet.
/// </summary>
public void autoDetectAndApply ()
{
captureBaseValues ();
autoDetectAndSetScale ();
applyHeightScale ();
if (showDebugLog) {
Debug.Log ("characterHeightScaler: FULL AUTO complete."
+ " Model scaled " + characterHeightScale.ToString ("F3") + "x"
+ " (from " + detectedModelHeight.ToString ("F3")
+ "m to ~" + gkcStandardHeight.ToString ("F3") + "m)");
}
}
// ==================== CAPTURE / APPLY / RESET ====================
/// <summary>
/// Captures the model's current localScale and localPosition as the base (unscaled) values.
/// Call after building the character, before applying any scale.
/// </summary>
public void captureBaseValues ()
{
resolveReferences ();
resolveModelTransform ();
if (modelTransform != null) {
baseModelLocalScale = modelTransform.localScale;
baseModelLocalPosition = modelTransform.localPosition;
}
heightBaseValuesCaptured = true;
characterHeightScale = 1.0f;
modelScaleMultiplier = 1.0f;
if (showDebugLog) {
Debug.Log ("characterHeightScaler: CAPTURED"
+ " | model='" + (modelTransform != null ? modelTransform.name : "NULL") + "'"
+ " | baseScale=" + baseModelLocalScale
+ " | basePos=" + baseModelLocalPosition);
}
updateComponent ();
}
/// <summary>
/// Scales the model transform to match GKC standard height.
/// No COM, capsule, camera, IK, or weapon changes needed —
/// those all stay at GKC default values since we're fitting the model to the system,
/// not the system to the model.
/// </summary>
public void applyHeightScale ()
{
if (!heightBaseValuesCaptured) {
Debug.LogWarning ("characterHeightScaler: No base values captured. Click 'Capture Base Values' first.");
return;
}
resolveReferences ();
float scale = characterHeightScale;
if (showDebugLog) {
Debug.Log ("characterHeightScaler: ===== APPLYING MODEL SCALE = " + scale.ToString ("F3") + "x =====");
}
// 1. Scale the model
if (modelTransform != null) {
modelTransform.localScale = baseModelLocalScale * scale;
if (showDebugLog) {
Debug.Log (" [Model] '" + modelTransform.name + "' localScale = " + modelTransform.localScale);
}
}
// 2. Align feet to ground (model origin may not be at feet)
alignFeetToGround ();
// 3. Recalculate IK foot initial values (bone positions changed due to model scale)
if (IKFootManager != null) {
IKFootManager.calculateInitialFootValues ();
if (showDebugLog) {
Debug.Log (" [IKFoot] Recalculated initial foot values");
}
}
updateComponent ();
markComponentsDirty ();
if (showDebugLog) {
Debug.Log ("characterHeightScaler: ===== APPLY COMPLETE =====");
}
}
/// <summary>
/// After scaling the model, ensure feet are aligned with ground level.
/// Measures the lowest foot bone Y (or mesh bottom) and adjusts model localPosition.
/// </summary>
void alignFeetToGround ()
{
if (modelTransform == null) {
return;
}
Animator animator = modelTransform.GetComponentInChildren<Animator> ();
float footWorldY = float.MaxValue;
bool footFound = false;
// Try humanoid foot bones (LeftToes / RightToes first, then LeftFoot / RightFoot)
if (animator != null && animator.isHuman) {
Transform leftToes = animator.GetBoneTransform (HumanBodyBones.LeftToes);
Transform rightToes = animator.GetBoneTransform (HumanBodyBones.RightToes);
Transform leftFoot = animator.GetBoneTransform (HumanBodyBones.LeftFoot);
Transform rightFoot = animator.GetBoneTransform (HumanBodyBones.RightFoot);
// Prefer toes (closest to ground), fall back to foot bone
if (leftToes != null || rightToes != null) {
if (leftToes != null) footWorldY = Mathf.Min (footWorldY, leftToes.position.y);
if (rightToes != null) footWorldY = Mathf.Min (footWorldY, rightToes.position.y);
footFound = true;
} else if (leftFoot != null || rightFoot != null) {
if (leftFoot != null) footWorldY = Mathf.Min (footWorldY, leftFoot.position.y);
if (rightFoot != null) footWorldY = Mathf.Min (footWorldY, rightFoot.position.y);
footFound = true;
}
}
// Fallback: SkinnedMeshRenderer bounds bottom
if (!footFound) {
SkinnedMeshRenderer[] renderers = modelTransform.GetComponentsInChildren<SkinnedMeshRenderer> ();
if (renderers.Length > 0) {
Bounds combinedBounds = renderers [0].bounds;
for (int i = 1; i < renderers.Length; i++) {
combinedBounds.Encapsulate (renderers [i].bounds);
}
footWorldY = combinedBounds.min.y;
footFound = true;
}
}
if (!footFound) {
if (showDebugLog) {
Debug.LogWarning ("characterHeightScaler: Could not find foot bones or renderers for alignment.");
}
return;
}
// Ground level = Player Controller transform.position.y (capsule bottom)
float groundWorldY = transform.position.y;
float offsetY = footWorldY - groundWorldY;
if (showDebugLog) {
Debug.Log (" [FootAlign] footWorldY=" + footWorldY.ToString ("F4")
+ " groundY=" + groundWorldY.ToString ("F4")
+ " offset=" + offsetY.ToString ("F4")
+ " userAdjust=" + footAlignmentOffset.ToString ("F4"));
}
// Adjust model localPosition to put feet on ground + user fine-tune offset
if (Mathf.Abs (offsetY) > 0.005f || Mathf.Abs (footAlignmentOffset) > 0.001f) {
Vector3 modelPos = modelTransform.localPosition;
modelPos.y = baseModelLocalPosition.y - offsetY + footAlignmentOffset;
modelTransform.localPosition = modelPos;
if (showDebugLog) {
Debug.Log (" [FootAlign] model localPos.y = " + modelPos.y.ToString ("F4"));
}
}
}
/// <summary>
/// Resets model to original scale and position (1:1 with FBX import).
/// </summary>
public void resetHeightScale ()
{
if (modelTransform != null) {
modelTransform.localScale = baseModelLocalScale;
modelTransform.localPosition = baseModelLocalPosition;
}
characterHeightScale = 1.0f;
modelScaleMultiplier = 1.0f;
if (IKFootManager != null) {
IKFootManager.calculateInitialFootValues ();
}
updateComponent ();
markComponentsDirty ();
}
// ==================== INTERNAL ====================
void resolveReferences ()
{
if (componentsManager == null) {
GameObject playerObject = null;
buildPlayer builder = GetComponent<buildPlayer> ();
if (builder != null) {
playerObject = builder.player;
}
if (playerObject == null) {
playerObject = transform.root.gameObject;
}
componentsManager = playerObject.GetComponent<playerComponentsManager> ();
}
if (componentsManager != null) {
if (playerControllerManager == null) {
playerControllerManager = componentsManager.getPlayerController ();
}
if (IKManager == null) {
IKManager = componentsManager.getIKSystem ();
}
if (IKFootManager == null) {
IKFootManager = GetComponentInChildren<IKFootSystem> ();
}
if (COMTransform == null && IKManager != null) {
COMTransform = IKManager.getIKBodyCOM ();
}
}
resolveModelTransform ();
}
void resolveModelTransform ()
{
if (modelTransform == null) {
buildPlayer builder = GetComponent<buildPlayer> ();
if (builder != null && builder.currentCharacterModel != null) {
modelTransform = builder.currentCharacterModel.transform;
}
}
}
void markComponentsDirty ()
{
#if UNITY_EDITOR
if (modelTransform != null) {
UnityEditor.EditorUtility.SetDirty (modelTransform);
UnityEditor.EditorUtility.SetDirty (modelTransform.gameObject);
}
if (IKFootManager != null) {
UnityEditor.EditorUtility.SetDirty (IKFootManager);
}
UnityEditor.EditorUtility.SetDirty (this);
#endif
}
void updateComponent ()
{
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty (this);
#endif
}
}