Files
FueraDeEscala/Assets/Game Kit Controller/Scripts/Player/Character Builder/characterHeightScaler.cs
Robii Aragon 0a3413a7be Add character height scaler and assets
Add a new characterHeightScaler runtime component and characterHeightScalerEditor for Unity to auto-detect and apply a model scale to match the GKC standard height (capture/apply/reset, foot alignment, debug UI). Update DudeAvatar materials to assign main textures, clear legacy shader keywords and enable XR motion vectors. Modify launchObject material to use transparent surface settings, correct render queue/blend/ZWrite and disabled shadow/depth passes for proper particle rendering. Update ParticlesLight prefab to include UniversalAdditionalLightData and URP-related light fields (cookie size, shadow & rendering layers, etc.). Add new Flaco alien folder with prefabs/meshes and new house materials; remove some legacy alien FBX files. Also include Scene and ProjectSettings updates to accommodate the changes.
2026-03-06 05:37:37 -08:00

479 lines
16 KiB
C#

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
}
}