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.
This commit is contained in:
Robii Aragon
2026-03-06 05:37:37 -08:00
parent 0fccefb104
commit bb7c0173e6
35 changed files with 1421 additions and 870 deletions

View File

@@ -0,0 +1,259 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
[CanEditMultipleObjects]
[CustomEditor (typeof (characterHeightScaler))]
public class characterHeightScalerEditor : Editor
{
characterHeightScaler manager;
GUIStyle headerStyle;
GUIStyle boxStyle;
bool showDebugValues;
void OnEnable ()
{
manager = (characterHeightScaler)target;
}
public override void OnInspectorGUI ()
{
serializedObject.Update ();
setupStyles ();
GUILayout.BeginVertical ();
EditorGUILayout.Space (5);
// ==================== MAIN SECTION ====================
GUILayout.BeginVertical (boxStyle);
EditorGUILayout.LabelField ("CHARACTER HEIGHT SCALER", headerStyle);
EditorGUILayout.LabelField ("Scales model UP to match GKC standard size", EditorStyles.centeredGreyMiniLabel);
EditorGUILayout.Space (5);
manager.autoDetectHeight = EditorGUILayout.Toggle (
new GUIContent ("Auto-Detect Height",
"Automatically measures your FBX model and scales it to match GKC's standard height."),
manager.autoDetectHeight);
manager.gkcStandardHeight = EditorGUILayout.FloatField (
new GUIContent ("GKC Standard Height",
"Reference height for the default GKC character (usually 1.8 units)."),
manager.gkcStandardHeight);
EditorGUILayout.Space (3);
// Show detected info
if (manager.detectedModelHeight > 0.01f) {
string scaleDir = manager.modelScaleMultiplier > 1.01f ? "UP" :
(manager.modelScaleMultiplier < 0.99f ? "DOWN" : "—");
EditorGUILayout.HelpBox (
"Model Height: " + manager.detectedModelHeight.ToString ("F3") + " units\n" +
"GKC Standard: " + manager.gkcStandardHeight.ToString ("F3") + " units\n" +
"Scale Factor: " + manager.modelScaleMultiplier.ToString ("F3") + "x (model scaled " + scaleDir + ")",
MessageType.Info);
}
EditorGUILayout.Space (3);
// Main action buttons
EditorGUILayout.BeginHorizontal ();
Color defaultBgColor = GUI.backgroundColor;
GUI.backgroundColor = new Color (0.3f, 0.8f, 1.0f);
if (GUILayout.Button ("Detect Height", GUILayout.Height (28))) {
if (!Application.isPlaying) {
Undo.RecordObject (manager, "Detect Height");
manager.autoDetectAndSetScale ();
EditorUtility.SetDirty (manager);
}
}
GUI.backgroundColor = new Color (0.4f, 1.0f, 0.6f);
GUIStyle boldButton = new GUIStyle (GUI.skin.button);
boldButton.fontStyle = FontStyle.Bold;
if (GUILayout.Button ("DETECT + APPLY", boldButton, GUILayout.Height (28))) {
if (!Application.isPlaying) {
Undo.RecordObject (manager, "Detect and Apply Height");
manager.autoDetectAndApply ();
EditorUtility.SetDirty (manager);
}
}
GUI.backgroundColor = defaultBgColor;
EditorGUILayout.EndHorizontal ();
GUILayout.EndVertical ();
EditorGUILayout.Space (8);
// ==================== MANUAL / FINE-TUNE ====================
GUILayout.BeginVertical ("Manual Scale", "window");
if (!manager.heightBaseValuesCaptured) {
EditorGUILayout.HelpBox (
"Build your character first, then use 'DETECT + APPLY' above.\n" +
"The manual slider below is for fine-tuning after auto-detect.",
MessageType.Warning);
GUI.enabled = false;
}
EditorGUILayout.Space ();
// Scale slider
EditorGUILayout.BeginHorizontal ();
float newScale = EditorGUILayout.Slider (
new GUIContent ("Model Scale", "Multiplier for the model transform. Auto-detect sets this automatically."),
manager.characterHeightScale,
manager.minHeightScale,
manager.maxHeightScale);
if (newScale != manager.characterHeightScale) {
Undo.RecordObject (manager, "Change Model Scale");
manager.characterHeightScale = newScale;
}
GUI.backgroundColor = Color.cyan;
if (GUILayout.Button ("1x", GUILayout.Width (35))) {
Undo.RecordObject (manager, "Reset Scale to 1x");
manager.characterHeightScale = 1.0f;
}
GUI.backgroundColor = defaultBgColor;
EditorGUILayout.EndHorizontal ();
// Foot alignment offset
EditorGUILayout.Space (2);
manager.footAlignmentOffset = EditorGUILayout.Slider (
new GUIContent ("Foot Offset", "Fine-tune vertical position. Positive = raise, Negative = lower."),
manager.footAlignmentOffset,
-0.2f, 0.2f);
EditorGUILayout.Space ();
// Apply / Reset row
EditorGUILayout.BeginHorizontal ();
GUI.backgroundColor = new Color (0.4f, 1.0f, 0.4f);
GUIStyle applyStyle = new GUIStyle (GUI.skin.button);
applyStyle.fontStyle = FontStyle.Bold;
applyStyle.fontSize = 13;
if (GUILayout.Button (" APPLY ", applyStyle, GUILayout.Height (30))) {
if (!Application.isPlaying) {
Undo.RecordObject (manager, "Apply Scale");
manager.applyHeightScale ();
EditorUtility.SetDirty (manager);
}
}
GUI.backgroundColor = new Color (1.0f, 0.5f, 0.5f);
if (GUILayout.Button (" RESET ", applyStyle, GUILayout.Height (30))) {
if (!Application.isPlaying) {
Undo.RecordObject (manager, "Reset Scale");
manager.resetHeightScale ();
EditorUtility.SetDirty (manager);
}
}
GUI.backgroundColor = defaultBgColor;
EditorGUILayout.EndHorizontal ();
GUI.enabled = true;
GUILayout.EndVertical ();
EditorGUILayout.Space ();
// ==================== DEBUG ====================
GUILayout.BeginVertical ("Debug", "window");
manager.showDebugLog = EditorGUILayout.Toggle ("Show Debug Log", manager.showDebugLog);
EditorGUILayout.Space ();
if (manager.heightBaseValuesCaptured) {
showDebugValues = EditorGUILayout.Foldout (showDebugValues, "Captured Base Values", true);
if (showDebugValues) {
GUI.enabled = false;
EditorGUILayout.ObjectField ("Model Transform", manager.modelTransform, typeof (Transform), true);
EditorGUILayout.Vector3Field ("Base Scale", manager.baseModelLocalScale);
EditorGUILayout.Vector3Field ("Base Position", manager.baseModelLocalPosition);
EditorGUILayout.FloatField ("Detected Height", manager.detectedModelHeight);
EditorGUILayout.FloatField ("Scale Multiplier", manager.modelScaleMultiplier);
EditorGUILayout.FloatField ("Foot Offset", manager.footAlignmentOffset);
GUI.enabled = true;
}
}
// Capture button
EditorGUILayout.Space ();
GUI.backgroundColor = manager.heightBaseValuesCaptured ? Color.yellow : Color.green;
if (GUILayout.Button (manager.heightBaseValuesCaptured ? "Re-Capture Base Values" : "Capture Base Values")) {
if (!Application.isPlaying) {
Undo.RecordObject (manager, "Capture Base Values");
manager.captureBaseValues ();
serializedObject.Update ();
}
}
GUI.backgroundColor = defaultBgColor;
GUILayout.EndVertical ();
EditorGUILayout.Space ();
GUILayout.EndVertical ();
if (GUI.changed) {
EditorUtility.SetDirty (manager);
serializedObject.ApplyModifiedProperties ();
}
}
void setupStyles ()
{
if (headerStyle == null) {
headerStyle = new GUIStyle (EditorStyles.boldLabel);
headerStyle.fontSize = 14;
headerStyle.alignment = TextAnchor.MiddleCenter;
}
if (boxStyle == null) {
boxStyle = new GUIStyle ("box");
boxStyle.padding = new RectOffset (10, 10, 8, 8);
}
}
}
#endif

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 960fa73d6185eba41bf04dd9f8ae1aca

View File

@@ -0,0 +1,478 @@
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
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 483841965e178d243bb0504793ad7395