1187 lines
39 KiB
C#
1187 lines
39 KiB
C#
//This script is for demo purposes only and is not required for the animations to work.
|
|
//Human Basic Motions
|
|
//Kevin Iglesias
|
|
//www.keviniglesias.com
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using System.Collections.Generic;
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace KevinIglesias
|
|
{
|
|
[System.Serializable]
|
|
public class AnimationEntry
|
|
{
|
|
public string displayName;
|
|
public string folderName;
|
|
public string folderPath;
|
|
public int folderLevel;
|
|
public AnimationClip clip;
|
|
public GameObject[] prop;
|
|
public bool disableSpineProxy;
|
|
[HideInInspector] public Button animationButton;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class CharacterEntry
|
|
{
|
|
public GameObject characterObject;
|
|
public Transform characterTransform;
|
|
public Animator animator;
|
|
public Transform leftFoot;
|
|
public Transform rightFoot;
|
|
[HideInInspector] public Transform spineProxyBone;
|
|
[HideInInspector] public Button characterButton;
|
|
}
|
|
|
|
public class HumanBasicMotionsDemo : MonoBehaviour
|
|
{
|
|
[Header("Camera")]
|
|
[SerializeField] private Transform cameraPivot;
|
|
[SerializeField] private Camera targetCamera;
|
|
[SerializeField] private float zoomStep = 5f;
|
|
[SerializeField] private float minFOV = 35f;
|
|
[SerializeField] private float maxFOV = 75f;
|
|
[SerializeField] private float rotationSpeed = 120f;
|
|
private float rotationInput = 0f;
|
|
|
|
[Header("Base")]
|
|
[SerializeField] private Transform baseT;
|
|
[SerializeField] private Material baseMaterial;
|
|
[SerializeField] private float baseTreadmillMultiplier = 0.5f;
|
|
private float baseOffsetX;
|
|
private float baseOffsetY;
|
|
private float baseUVRotation;
|
|
|
|
[Header("Shadow")]
|
|
[SerializeField] private Transform shadowT;
|
|
[SerializeField] private SpriteRenderer shadowSprite;
|
|
[SerializeField] private float shadowFloorY = 0.001f;
|
|
[SerializeField] private float groundedScale = 0.6f;
|
|
[SerializeField] private float groundedAlpha = 0.78f;
|
|
[SerializeField] private float maxJumpHeight = 1f;
|
|
[SerializeField] private float airScale = 1f;
|
|
[SerializeField] private float airAlpha = 0.05f;
|
|
private Transform currentLeftFoot;
|
|
private Transform currentRightFoot;
|
|
[SerializeField] private float shadowFootPadding = 0.35f;
|
|
[SerializeField] private float shadowMinScale = 0.75f;
|
|
[SerializeField] private float shadowMaxScale = 2.25f;
|
|
|
|
[Header("Background")]
|
|
[SerializeField] private Material backgroundMaterial;
|
|
[SerializeField] private Vector3 backgroundLightDir = new Vector3(0f, -0.1f, 1f);
|
|
|
|
[Header("UI")]
|
|
[SerializeField] private Transform zoomIn;
|
|
[SerializeField] private Transform zoomOut;
|
|
[SerializeField] private Transform leftArrow;
|
|
[SerializeField] private Transform rightArrow;
|
|
[SerializeField] private Color normalButtonColor = Color.white;
|
|
[SerializeField] private Color selectedButtonColor = Color.black;
|
|
[SerializeField] private Color normalTextColor = Color.black;
|
|
[SerializeField] private Color selectedTextColor = Color.white;
|
|
[SerializeField] private Vector3 normalScale = Vector3.one;
|
|
[SerializeField] private Vector3 pressedScale = Vector3.one * 0.85f;
|
|
|
|
[Header("Characters")]
|
|
[SerializeField] private CharacterEntry[] availableCharacters;
|
|
[SerializeField] private int currentCharacterIndex = 0;
|
|
[SerializeField] private GameObject characterUIPrefab;
|
|
[SerializeField] private Transform charactersUIRoot;
|
|
[SerializeField] private Transform character;
|
|
[SerializeField] private Animator animator;
|
|
private Button currentSelectedCharacterButton;
|
|
private bool characterInitialized = false;
|
|
|
|
[Header("Animations")]
|
|
[SerializeField] private AnimationEntry[] availableAnimations;
|
|
[SerializeField] private Text currentAnimationText;
|
|
[SerializeField] private Text currentAnimationFolderText;
|
|
[SerializeField] private Transform animationsUIRoot;
|
|
[SerializeField] private GameObject folderButtonPrefab;
|
|
[SerializeField] private GameObject animationUIPrefab;
|
|
[SerializeField] private string currentAnimationName;
|
|
[SerializeField] private string currentUpperAnimationName;
|
|
private int baseAnimationToPlay = 0;
|
|
private int upperAnimationToPlay = -1;
|
|
private AnimatorOverrideController overrideController;
|
|
private string currentFolder = "";
|
|
private Button currentSelectedAnimationButton;
|
|
private GameObject[] currentBaseProps;
|
|
private GameObject[] currentUpperProps;
|
|
[SerializeField] private int animationType = 0;
|
|
[SerializeField] private Button baseTypeButton;
|
|
[SerializeField] private Button upperTypeButton;
|
|
|
|
///UNITY DEFAULT FUNCTIONS
|
|
private void Start()
|
|
{
|
|
ResetBase();
|
|
|
|
CreateAnimationButtons();
|
|
CreateCharacterButtons();
|
|
|
|
if(availableCharacters != null && availableCharacters.Length > 0)
|
|
{
|
|
overrideController = new AnimatorOverrideController(availableCharacters[0].animator.runtimeAnimatorController);
|
|
}
|
|
|
|
for(int i = 0; i < availableCharacters.Length; i++)
|
|
{
|
|
if(availableCharacters[i].characterObject != null)
|
|
{
|
|
availableCharacters[i].characterObject.SetActive(false);
|
|
|
|
Transform[] transforms = availableCharacters[i].characterObject.GetComponentsInChildren<Transform>(true);
|
|
|
|
foreach(Transform t in transforms)
|
|
{
|
|
if (t.name == "B-spineProxy")
|
|
{
|
|
availableCharacters[i].spineProxyBone = t;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectCharacter(currentCharacterIndex);
|
|
SelectCharacterButton(availableCharacters[currentCharacterIndex].characterButton);
|
|
|
|
int defaultIndex = FindAnimationIndex("HumanM@Idle01");
|
|
|
|
if(defaultIndex >= 0)
|
|
{
|
|
PlayAnimation(defaultIndex, 0);
|
|
|
|
if(defaultIndex < availableAnimations.Length)
|
|
{
|
|
SelectAnimationButton(availableAnimations[defaultIndex].animationButton);
|
|
}
|
|
}
|
|
|
|
SetButtonSelected(baseTypeButton, true);
|
|
}
|
|
private void Update()
|
|
{
|
|
baseT.position = new Vector3(character.position.x, 0, character.position.z);
|
|
UpdateBlobShadowFromFeet();
|
|
|
|
UpdateBaseMaterialOffset();
|
|
|
|
Vector3 correctedLightDir = cameraPivot.rotation * backgroundLightDir;
|
|
|
|
backgroundMaterial.SetVector("_LightDir", new Vector4(correctedLightDir.x, correctedLightDir.y, correctedLightDir.z, 0f));
|
|
|
|
if(rotationInput != 0f)
|
|
{
|
|
cameraPivot.Rotate(new Vector3(0f, rotationInput * rotationSpeed * Time.deltaTime, 0f), Space.World);
|
|
}
|
|
|
|
character.position = new Vector3(0, character.position.y, 0);
|
|
}
|
|
private void FixedUpdate()
|
|
{
|
|
Vector3 correctedLightDir = cameraPivot.rotation * backgroundLightDir;
|
|
backgroundMaterial.SetVector("_LightDir", new Vector4(correctedLightDir.x, correctedLightDir.y, correctedLightDir.z, 0f));
|
|
}
|
|
private void LateUpdate()
|
|
{
|
|
Vector3 correctedLightDir = cameraPivot.rotation * backgroundLightDir;
|
|
backgroundMaterial.SetVector("_LightDir", new Vector4(correctedLightDir.x, correctedLightDir.y, correctedLightDir.z, 0f));
|
|
}
|
|
private void OnDisable()
|
|
{
|
|
ResetBase();
|
|
}
|
|
///
|
|
|
|
///CHARACTER SELECTION
|
|
public void SelectCharacter(int index)
|
|
{
|
|
if(availableCharacters == null || index < 0 || index >= availableCharacters.Length)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(characterInitialized && index == currentCharacterIndex)
|
|
{
|
|
return;
|
|
}
|
|
|
|
characterInitialized = true;
|
|
currentCharacterIndex = index;
|
|
|
|
for(int i = 0; i < availableCharacters.Length; i++)
|
|
{
|
|
CharacterEntry entry = availableCharacters[i];
|
|
|
|
bool selected = i == index;
|
|
|
|
if(entry.animator != null)
|
|
{
|
|
entry.animator.runtimeAnimatorController = selected ? overrideController : null;
|
|
}
|
|
|
|
if(entry.characterObject != null)
|
|
{
|
|
entry.characterObject.SetActive(selected);
|
|
}
|
|
}
|
|
|
|
CharacterEntry selectedEntry = availableCharacters[index];
|
|
|
|
character = selectedEntry.characterTransform;
|
|
animator = selectedEntry.animator;
|
|
|
|
currentLeftFoot = selectedEntry.leftFoot;
|
|
currentRightFoot = selectedEntry.rightFoot;
|
|
|
|
if(animator != null)
|
|
{
|
|
animator.Rebind();
|
|
animator.Update(0f);
|
|
}
|
|
|
|
ResetProps();
|
|
|
|
ResetBase();
|
|
|
|
string equivalentAnimationName = currentAnimationName;
|
|
if(index == 1)
|
|
{
|
|
equivalentAnimationName = currentAnimationName.Replace("HumanM@", "HumanF@");
|
|
}else{
|
|
equivalentAnimationName = currentAnimationName.Replace("HumanF@", "HumanM@");
|
|
}
|
|
int equivalentAnimation = FindAnimationIndex(equivalentAnimationName);
|
|
|
|
if(equivalentAnimation >= 0)
|
|
{
|
|
PlayAnimation(equivalentAnimation, 0);
|
|
}else{
|
|
PlayAnimation(baseAnimationToPlay, 0);
|
|
}
|
|
|
|
string equivalentUpperAnimationName = currentUpperAnimationName;
|
|
if(index == 1)
|
|
{
|
|
equivalentUpperAnimationName = currentUpperAnimationName.Replace("HumanM@", "HumanF@");
|
|
}else{
|
|
equivalentUpperAnimationName = currentUpperAnimationName.Replace("HumanF@", "HumanM@");
|
|
}
|
|
int equivalentUpperAnimation = FindAnimationIndex(equivalentUpperAnimationName);
|
|
|
|
if(equivalentUpperAnimation >= 0)
|
|
{
|
|
PlayAnimation(equivalentUpperAnimation, 1);
|
|
}else{
|
|
if(upperAnimationToPlay >= 0)
|
|
{
|
|
PlayAnimation(upperAnimationToPlay, 1);
|
|
}else{
|
|
ClearUpperAnimation();
|
|
}
|
|
}
|
|
|
|
string newFolder = currentFolder;
|
|
if(index == 1)
|
|
{
|
|
newFolder = currentFolder.Replace("Male", "Female");
|
|
}else{
|
|
newFolder = currentFolder.Replace("Female", "Male");
|
|
}
|
|
ShowFolder(newFolder);
|
|
|
|
SelectCurrentAnimationButtonInOpenFolder();
|
|
}
|
|
private void CreateCharacterButtons()
|
|
{
|
|
if(characterUIPrefab == null || charactersUIRoot == null || availableCharacters == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for(int i = 0; i < availableCharacters.Length; i++)
|
|
{
|
|
int index = i;
|
|
|
|
GameObject buttonGO = Instantiate(characterUIPrefab, charactersUIRoot);
|
|
|
|
Button button = buttonGO.GetComponent<Button>();
|
|
availableCharacters[i].characterButton = button;
|
|
Image image = buttonGO.GetComponent<Image>();
|
|
Text text = buttonGO.GetComponentInChildren<Text>();
|
|
|
|
if(image != null)
|
|
{
|
|
image.color = normalButtonColor;
|
|
}
|
|
|
|
if(text != null)
|
|
{
|
|
text.color = normalTextColor;
|
|
text.text = availableCharacters[i].characterObject.name;
|
|
}
|
|
|
|
if(button != null)
|
|
{
|
|
button.onClick.AddListener(() =>
|
|
{
|
|
SelectCharacter(index);
|
|
SelectCharacterButton(button);
|
|
});
|
|
}
|
|
|
|
buttonGO.SetActive(true);
|
|
}
|
|
}
|
|
private void SelectCharacterButton(Button selectedButton)
|
|
{
|
|
if(currentSelectedCharacterButton != null)
|
|
{
|
|
SetButtonSelected(currentSelectedCharacterButton, false);
|
|
}
|
|
|
|
currentSelectedCharacterButton = selectedButton;
|
|
|
|
if(currentSelectedCharacterButton != null)
|
|
{
|
|
SetButtonSelected(currentSelectedCharacterButton, true);
|
|
}
|
|
}
|
|
///
|
|
|
|
///ANIMATION SELECTION
|
|
public void PlayAnimation(int index, int newAnimationType)
|
|
{
|
|
if(animator == null || overrideController == null)
|
|
return;
|
|
|
|
if(!animator.gameObject.activeInHierarchy)
|
|
return;
|
|
|
|
if(availableAnimations == null || index < 0 || index >= availableAnimations.Length)
|
|
return;
|
|
|
|
ResetBase();
|
|
|
|
if(newAnimationType == 0)
|
|
{
|
|
baseAnimationToPlay = index;
|
|
|
|
overrideController["HumanM@Idle01"] = availableAnimations[index].clip;
|
|
|
|
animator.Play("BaseAnimation", 0, 0f);
|
|
animator.Update(0f);
|
|
|
|
currentAnimationName = availableAnimations[index].clip.name;
|
|
currentAnimationText.text = currentAnimationName;
|
|
}else{
|
|
upperAnimationToPlay = index;
|
|
|
|
overrideController["HumanM@Idle02"] = availableAnimations[index].clip;
|
|
|
|
animator.Play("UpperAnimation", 1, 0f);
|
|
animator.Update(0f);
|
|
|
|
currentUpperAnimationName = availableAnimations[index].clip.name;
|
|
currentAnimationText.text = currentUpperAnimationName+"\n"+currentAnimationName;
|
|
}
|
|
|
|
if(currentUpperAnimationName != "")
|
|
{
|
|
currentAnimationText.text = currentUpperAnimationName+"\n"+currentAnimationName;
|
|
}
|
|
|
|
character.position = Vector3.zero;
|
|
|
|
if(availableCharacters[currentCharacterIndex].spineProxyBone)
|
|
{
|
|
if(availableAnimations[index].disableSpineProxy)
|
|
{
|
|
availableCharacters[currentCharacterIndex].spineProxyBone.gameObject.SetActive(false);
|
|
}else{
|
|
availableCharacters[currentCharacterIndex].spineProxyBone.gameObject.SetActive(true);
|
|
}
|
|
}
|
|
|
|
UpdateProps(index, newAnimationType);
|
|
}
|
|
private void ResetProps()
|
|
{
|
|
DisableProps(currentBaseProps, currentUpperProps);
|
|
DisableProps(currentUpperProps, currentBaseProps);
|
|
|
|
currentBaseProps = null;
|
|
currentUpperProps = null;
|
|
}
|
|
|
|
private void UpdateProps(int activeIndex, int animationLayerType)
|
|
{
|
|
if(availableAnimations == null)
|
|
return;
|
|
|
|
AnimationEntry entry = availableAnimations[activeIndex];
|
|
GameObject[] newProps = entry != null ? entry.prop : null;
|
|
|
|
if(animationLayerType == 0)
|
|
{
|
|
if(currentBaseProps == newProps)
|
|
return;
|
|
|
|
DisableProps(currentBaseProps, currentUpperProps);
|
|
currentBaseProps = newProps;
|
|
EnableProps(currentBaseProps);
|
|
}
|
|
else
|
|
{
|
|
if(currentUpperProps == newProps)
|
|
return;
|
|
|
|
DisableProps(currentUpperProps, currentBaseProps);
|
|
currentUpperProps = newProps;
|
|
EnableProps(currentUpperProps);
|
|
}
|
|
}
|
|
|
|
private void EnableProps(GameObject[] props)
|
|
{
|
|
if(props == null)
|
|
return;
|
|
|
|
foreach(GameObject prop in props)
|
|
{
|
|
if(prop != null)
|
|
prop.SetActive(true);
|
|
}
|
|
}
|
|
|
|
private void DisableProps(GameObject[] propsToDisable, GameObject[] propsStillUsed)
|
|
{
|
|
if(propsToDisable == null)
|
|
return;
|
|
|
|
foreach(GameObject prop in propsToDisable)
|
|
{
|
|
if(prop == null)
|
|
continue;
|
|
|
|
if(IsPropUsedBy(prop, propsStillUsed))
|
|
continue;
|
|
|
|
prop.SetActive(false);
|
|
}
|
|
}
|
|
|
|
private bool IsPropUsedBy(GameObject prop, GameObject[] props)
|
|
{
|
|
if(props == null)
|
|
return false;
|
|
|
|
foreach(GameObject p in props)
|
|
{
|
|
if(p == prop)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
private void CreateAnimationButtons()
|
|
{
|
|
ClearAnimationUI();
|
|
ShowFolder("");
|
|
}
|
|
private void ClearAnimationUI()
|
|
{
|
|
if(availableAnimations != null)
|
|
{
|
|
for(int i = 0; i < availableAnimations.Length; i++)
|
|
{
|
|
availableAnimations[i].animationButton = null;
|
|
}
|
|
}
|
|
|
|
for(int i = animationsUIRoot.childCount - 1; i >= 0; i--)
|
|
{
|
|
Destroy(animationsUIRoot.GetChild(i).gameObject);
|
|
}
|
|
|
|
clearUpperAnimationButton = null;
|
|
currentSelectedAnimationButton = null;
|
|
}
|
|
private void ShowFolder(string folderPath)
|
|
{
|
|
currentFolder = folderPath;
|
|
ClearAnimationUI();
|
|
|
|
if(!string.IsNullOrEmpty(currentFolder))
|
|
{
|
|
CreateBackButton();
|
|
}
|
|
|
|
CreateSubFolderButtons(currentFolder);
|
|
CreateAnimationButtonsInFolder(currentFolder);
|
|
SelectCurrentAnimationButtonInOpenFolder();
|
|
|
|
currentAnimationFolderText.text = "/"+currentFolder;
|
|
}
|
|
private void CreateBackButton()
|
|
{
|
|
GameObject backGO = Instantiate(folderButtonPrefab, animationsUIRoot);
|
|
|
|
Text text = backGO.GetComponentInChildren<Text>();
|
|
if(text != null)
|
|
{
|
|
text.text = "← Back";
|
|
}
|
|
|
|
Button button = backGO.GetComponent<Button>();
|
|
if(button != null)
|
|
{
|
|
button.onClick.AddListener(() => ShowFolder(GetParentFolder(currentFolder)));
|
|
}
|
|
|
|
backGO.SetActive(true);
|
|
}
|
|
private void CreateSubFolderButtons(string folderPath)
|
|
{
|
|
HashSet<string> folders = new HashSet<string>();
|
|
|
|
foreach (AnimationEntry entry in availableAnimations)
|
|
{
|
|
if(entry == null || entry.clip == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!IsDirectChildFolder(entry, folderPath, out string childPath, out string childName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
folders.Add(childPath);
|
|
}
|
|
|
|
foreach(string folder in folders)
|
|
{
|
|
GameObject folderGO = Instantiate(folderButtonPrefab, animationsUIRoot);
|
|
|
|
Text text = folderGO.GetComponentInChildren<Text>();
|
|
if(text != null)
|
|
{
|
|
text.text = GetLastFolderName(folder);
|
|
}
|
|
|
|
Button button = folderGO.GetComponent<Button>();
|
|
if(button != null)
|
|
{
|
|
button.onClick.AddListener(() => ShowFolder(folder));
|
|
}
|
|
|
|
folderGO.SetActive(true);
|
|
}
|
|
}
|
|
private void CreateAnimationButtonsInFolder(string folderPath)
|
|
{
|
|
folderPath = NormalizeFolder(folderPath);
|
|
|
|
for(int i = 0; i < availableAnimations.Length; i++)
|
|
{
|
|
AnimationEntry entry = availableAnimations[i];
|
|
|
|
if(entry == null || entry.clip == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string entryPath = NormalizeFolder(entry.folderPath);
|
|
|
|
if(entryPath != folderPath)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int index = i;
|
|
|
|
GameObject buttonGO = Instantiate(animationUIPrefab, animationsUIRoot);
|
|
|
|
Button button = buttonGO.GetComponent<Button>();
|
|
availableAnimations[i].animationButton = button;
|
|
Image image = buttonGO.GetComponent<Image>();
|
|
Text text = buttonGO.GetComponentInChildren<Text>();
|
|
|
|
if(image != null)
|
|
{
|
|
image.color = normalButtonColor;
|
|
}
|
|
|
|
if(text != null)
|
|
{
|
|
text.color = normalTextColor;
|
|
text.text = entry.displayName;
|
|
}
|
|
|
|
if(button != null)
|
|
{
|
|
button.onClick.AddListener(() =>
|
|
{
|
|
PlayAnimation(index, animationType);
|
|
SelectAnimationButton(button);
|
|
});
|
|
}
|
|
|
|
buttonGO.SetActive(true);
|
|
}
|
|
|
|
if(animationType == 1)
|
|
{
|
|
GenerateClearUpperAnimationButton();
|
|
}
|
|
}
|
|
private void SelectCurrentAnimationButtonInOpenFolder()
|
|
{
|
|
if(availableAnimations == null)
|
|
return;
|
|
|
|
int selectedIndex = animationType == 0 ? baseAnimationToPlay : upperAnimationToPlay;
|
|
|
|
if(selectedIndex < 0 || selectedIndex >= availableAnimations.Length)
|
|
return;
|
|
|
|
AnimationEntry currentEntry = availableAnimations[selectedIndex];
|
|
|
|
if(currentEntry == null)
|
|
return;
|
|
|
|
string entryFolder = NormalizeFolder(currentEntry.folderPath);
|
|
string openFolder = NormalizeFolder(currentFolder);
|
|
|
|
if(entryFolder != openFolder)
|
|
return;
|
|
|
|
SelectAnimationButton(currentEntry.animationButton);
|
|
}
|
|
private void SelectAnimationButton(Button selectedButton)
|
|
{
|
|
if(currentSelectedAnimationButton != null)
|
|
{
|
|
SetButtonSelected(currentSelectedAnimationButton, false);
|
|
}
|
|
|
|
currentSelectedAnimationButton = selectedButton;
|
|
|
|
if(currentSelectedAnimationButton != null)
|
|
{
|
|
SetButtonSelected(currentSelectedAnimationButton, true);
|
|
}
|
|
|
|
if(animationType == 1)
|
|
{
|
|
GenerateClearUpperAnimationButton();
|
|
}
|
|
}
|
|
private int FindAnimationIndex(string clipName)
|
|
{
|
|
if(availableAnimations == null)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
for(int i = 0; i < availableAnimations.Length; i++)
|
|
{
|
|
if(availableAnimations[i] != null && availableAnimations[i].clip != null && availableAnimations[i].clip.name == clipName)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
private GameObject clearUpperAnimationButton = null;
|
|
public void SelectAnimationType(int newType)
|
|
{
|
|
animationType = newType;
|
|
|
|
if(newType == 0)
|
|
{
|
|
SetButtonSelected(baseTypeButton, true);
|
|
SetButtonSelected(upperTypeButton, false);
|
|
|
|
if(clearUpperAnimationButton != null)
|
|
{
|
|
Destroy(clearUpperAnimationButton);
|
|
clearUpperAnimationButton = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetButtonSelected(baseTypeButton, false);
|
|
SetButtonSelected(upperTypeButton, true);
|
|
|
|
GenerateClearUpperAnimationButton();
|
|
}
|
|
|
|
SelectCurrentAnimationButtonInOpenFolder();
|
|
}
|
|
private void GenerateClearUpperAnimationButton()
|
|
{
|
|
if(currentUpperAnimationName == "")
|
|
{
|
|
return;
|
|
}
|
|
if(clearUpperAnimationButton == null)
|
|
{
|
|
clearUpperAnimationButton = Instantiate(animationUIPrefab, animationsUIRoot);
|
|
|
|
Button button = clearUpperAnimationButton.GetComponent<Button>();
|
|
Image image = clearUpperAnimationButton.GetComponent<Image>();
|
|
Text text = clearUpperAnimationButton.GetComponentInChildren<Text>();
|
|
|
|
if(image != null)
|
|
{
|
|
image.color = normalButtonColor;
|
|
}
|
|
|
|
if(text != null)
|
|
{
|
|
text.color = normalTextColor;
|
|
text.text = "[X] Clear upper animation";
|
|
}
|
|
|
|
if(button != null)
|
|
{
|
|
button.onClick.AddListener(() =>
|
|
{
|
|
ClearUpperAnimation();
|
|
SelectAnimationButton(button);
|
|
SetButtonSelected(button, false);
|
|
});
|
|
}
|
|
|
|
clearUpperAnimationButton.SetActive(true);
|
|
}
|
|
}
|
|
public void ClearUpperAnimation()
|
|
{
|
|
DisableProps(currentUpperProps, currentBaseProps);
|
|
currentUpperAnimationName = "";
|
|
currentAnimationText.text = currentAnimationName;
|
|
animator.SetTrigger("ClearUpper");
|
|
currentUpperProps = null;
|
|
upperAnimationToPlay = -1;
|
|
}
|
|
///
|
|
|
|
///ANIMATION FOLDER HELPERS
|
|
private bool IsDirectChildFolder(AnimationEntry entry, string currentFolder, out string childPath, out string childName)
|
|
{
|
|
childPath = null;
|
|
childName = null;
|
|
|
|
string entryPath = NormalizeFolder(entry.folderPath);
|
|
currentFolder = NormalizeFolder(currentFolder);
|
|
|
|
if(string.IsNullOrEmpty(entryPath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
string[] entryParts = entryPath.Split('/');
|
|
|
|
int currentLevel = string.IsNullOrEmpty(currentFolder)
|
|
? 0
|
|
: currentFolder.Split('/').Length;
|
|
|
|
if(!string.IsNullOrEmpty(currentFolder))
|
|
{
|
|
if(!entryPath.StartsWith(currentFolder + "/"))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(entryParts.Length <= currentLevel)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
childName = entryParts[currentLevel];
|
|
|
|
childPath = string.IsNullOrEmpty(currentFolder) ? childName : currentFolder + "/" + childName;
|
|
|
|
return true;
|
|
}
|
|
private string NormalizeFolder(string folder)
|
|
{
|
|
if(string.IsNullOrEmpty(folder))
|
|
{
|
|
return "";
|
|
}
|
|
|
|
return folder.Replace("\\", "/").Trim('/');
|
|
}
|
|
private string GetParentFolder(string folder)
|
|
{
|
|
if(string.IsNullOrEmpty(folder))
|
|
{
|
|
return "";
|
|
}
|
|
|
|
int index = folder.LastIndexOf('/');
|
|
if(index < 0)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
return folder.Substring(0, index);
|
|
}
|
|
private string GetLastFolderName(string folder)
|
|
{
|
|
if(string.IsNullOrEmpty(folder))
|
|
{
|
|
return "";
|
|
}
|
|
|
|
int index = folder.LastIndexOf('/');
|
|
if(index < 0)
|
|
{
|
|
return folder;
|
|
}
|
|
|
|
return folder.Substring(index + 1);
|
|
}
|
|
///
|
|
|
|
///CAMERA CONTROLS
|
|
public void StartRotateLeft()
|
|
{
|
|
rotationInput = -1f;
|
|
if(leftArrow != null)
|
|
{
|
|
leftArrow.localScale = pressedScale;
|
|
}
|
|
}
|
|
public void StartRotateRight()
|
|
{
|
|
rotationInput = 1f;
|
|
if(rightArrow != null)
|
|
{
|
|
rightArrow.localScale = pressedScale;
|
|
}
|
|
}
|
|
public void StopRotate()
|
|
{
|
|
rotationInput = 0f;
|
|
|
|
if(leftArrow != null)
|
|
{
|
|
leftArrow.localScale = normalScale;
|
|
}
|
|
|
|
if(rightArrow != null)
|
|
{
|
|
rightArrow.localScale = normalScale;
|
|
}
|
|
}
|
|
public void ZoomIn()
|
|
{
|
|
if(targetCamera == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
targetCamera.fieldOfView = Mathf.Clamp(targetCamera.fieldOfView - zoomStep, minFOV, maxFOV);
|
|
|
|
zoomIn.localScale = pressedScale;
|
|
}
|
|
public void ZoomOut()
|
|
{
|
|
if(targetCamera == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
targetCamera.fieldOfView = Mathf.Clamp(targetCamera.fieldOfView + zoomStep, minFOV, maxFOV);
|
|
|
|
zoomOut.localScale = pressedScale;
|
|
}
|
|
public void ZoomUp()
|
|
{
|
|
zoomIn.localScale = normalScale;
|
|
zoomOut.localScale = normalScale;
|
|
}
|
|
|
|
|
|
///SCENE
|
|
private void ResetBase()
|
|
{
|
|
baseUVRotation = 0f;
|
|
baseOffsetX = 0f;
|
|
baseOffsetY = 0f;
|
|
baseMaterial.mainTextureOffset = Vector2.zero;
|
|
baseMaterial.SetFloat("_UVRotation", 0);
|
|
backgroundMaterial.SetVector("_LightDir", backgroundLightDir);
|
|
character.localEulerAngles = Vector3.zero;
|
|
}
|
|
private void UpdateBaseMaterialOffset()
|
|
{
|
|
if(animator == null || baseMaterial == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector3 localDelta = character.InverseTransformDirection(animator.deltaPosition);
|
|
|
|
baseOffsetX -= localDelta.x * baseTreadmillMultiplier;
|
|
baseOffsetY += localDelta.z * baseTreadmillMultiplier;
|
|
|
|
float deltaYaw = Mathf.DeltaAngle(0f, animator.deltaRotation.eulerAngles.y);
|
|
baseUVRotation += deltaYaw;
|
|
|
|
if(baseUVRotation > 180f)
|
|
{
|
|
baseUVRotation -= 360f;
|
|
}
|
|
if(baseUVRotation < -180f)
|
|
{
|
|
baseUVRotation += 360f;
|
|
}
|
|
|
|
Vector2 offset = baseMaterial.mainTextureOffset;
|
|
offset.x = baseOffsetX;
|
|
offset.y = baseOffsetY;
|
|
baseMaterial.mainTextureOffset = offset;
|
|
|
|
baseMaterial.SetFloat("_UVRotation", baseUVRotation);
|
|
}
|
|
private void UpdateBlobShadowFromFeet()
|
|
{
|
|
if(character == null || shadowT == null || shadowSprite == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector3 center = character.position;
|
|
Vector3 left = currentLeftFoot != null ? currentLeftFoot.position : center;
|
|
Vector3 right = currentRightFoot != null ? currentRightFoot.position : center;
|
|
|
|
shadowT.position = new Vector3(center.x, shadowFloorY, center.z);
|
|
|
|
center.y = shadowFloorY;
|
|
left.y = shadowFloorY;
|
|
right.y = shadowFloorY;
|
|
|
|
float leftDistance = Vector3.Distance(center, left);
|
|
float rightDistance = Vector3.Distance(center, right);
|
|
|
|
float footScale = Mathf.Max(leftDistance, rightDistance);
|
|
footScale = Mathf.Clamp(footScale + shadowFootPadding, shadowMinScale, shadowMaxScale);
|
|
|
|
float distanceY = Mathf.Abs(character.position.y);
|
|
float t = Mathf.Clamp01(distanceY / maxJumpHeight);
|
|
|
|
float heightScale = Mathf.Lerp(groundedScale, airScale, t);
|
|
float alpha = Mathf.Lerp(groundedAlpha, airAlpha, t);
|
|
|
|
shadowT.localScale = Vector3.one * footScale * heightScale;
|
|
|
|
Color c = shadowSprite.color;
|
|
c.a = alpha;
|
|
shadowSprite.color = c;
|
|
}
|
|
///
|
|
|
|
///UI HELPERS
|
|
private void SetButtonSelected(Button button, bool selected)
|
|
{
|
|
Image image = button.GetComponent<Image>();
|
|
Text text = button.GetComponentInChildren<Text>();
|
|
|
|
if(image != null)
|
|
{
|
|
image.color = selected ? selectedButtonColor : normalButtonColor;
|
|
}
|
|
|
|
if(text != null)
|
|
{
|
|
text.color = selected ? selectedTextColor : normalTextColor;
|
|
}
|
|
}
|
|
///
|
|
|
|
///ANIMATIONS LOAD (EDITOR ONLY)
|
|
#if UNITY_EDITOR
|
|
[SerializeField] private string animationsFolder = "Assets/Kevin Iglesias/Human Animations/Animations";
|
|
private string[] skipSpineProxyAnimations =
|
|
{
|
|
"HumanF@Turn01_Left [RM]",
|
|
"HumanF@Turn01_Right [RM]",
|
|
"HumanM@Turn01_Left [RM]",
|
|
"HumanM@Turn01_Right [RM]",
|
|
"HumanF@CrouchTurn01_R [RM]",
|
|
"HumanF@CrouchTurn01_L [RM]",
|
|
"HumanM@CrouchTurn01_R [RM]",
|
|
"HumanM@CrouchTurn01_L [RM]"
|
|
};
|
|
|
|
[SerializeField] private GameObject propLowChair;
|
|
private string[] propLowChairAnimations =
|
|
{
|
|
"HumanF@SitLow01 - Begin",
|
|
"HumanF@SitLow01 - Loop",
|
|
"HumanF@SitLow01 - Stop",
|
|
"HumanM@SitLow01 - Begin",
|
|
"HumanM@SitLow01 - Loop",
|
|
"HumanM@SitLow01 - Stop"
|
|
};
|
|
|
|
[SerializeField] private GameObject propMediumChair;
|
|
private string[] propMediumChairAnimations =
|
|
{
|
|
"HumanF@SitMedium01 - Begin",
|
|
"HumanF@SitMedium01 - Loop",
|
|
"HumanF@SitMedium01 - Stop",
|
|
"HumanM@SitMedium01 - Begin",
|
|
"HumanM@SitMedium01 - Loop",
|
|
"HumanM@SitMedium01 - Stop"
|
|
};
|
|
|
|
[SerializeField] private GameObject propHighChair;
|
|
private string[] propHighChairAnimations =
|
|
{
|
|
"HumanF@SitHigh01 - Begin",
|
|
"HumanF@SitHigh01 - Loop",
|
|
"HumanF@SitHigh01 - Stop",
|
|
"HumanM@SitHigh01 - Begin",
|
|
"HumanM@SitHigh01 - Loop",
|
|
"HumanM@SitHigh01 - Stop"
|
|
};
|
|
|
|
[SerializeField] private GameObject[] propDrinkablesL;
|
|
private string[] propDrinkablesAnimationsL =
|
|
{
|
|
"HumanF@Drink01_L",
|
|
"HumanF@Drink01_L - Loop",
|
|
"HumanM@Drink01_L",
|
|
"HumanM@Drink01_L - Loop",
|
|
};
|
|
|
|
[SerializeField] private GameObject[] propDrinkablesR;
|
|
private string[] propDrinkablesAnimationsR =
|
|
{
|
|
"HumanF@Drink01_R",
|
|
"HumanF@Drink01_R - Loop",
|
|
"HumanM@Drink01_R",
|
|
"HumanM@Drink01_R - Loop"
|
|
};
|
|
|
|
[SerializeField] private GameObject[] propEdiblesL;
|
|
private string[] propEdiblesAnimationsL =
|
|
{
|
|
"HumanF@Eat01_L",
|
|
"HumanF@Eat01_L - Loop",
|
|
"HumanM@Eat01_L",
|
|
"HumanM@Eat01_L - Loop",
|
|
};
|
|
[SerializeField] private GameObject[] propEdiblesR;
|
|
private string[] propEdiblesAnimationsR =
|
|
{
|
|
"HumanF@Eat01_R",
|
|
"HumanF@Eat01_R - Loop",
|
|
"HumanM@Eat01_R",
|
|
"HumanM@Eat01_R - Loop"
|
|
};
|
|
[ContextMenu("Populate Animations From Folder")]
|
|
void PopulateAnimationsFromFolder()
|
|
{
|
|
string[] guids = AssetDatabase.FindAssets("t:AnimationClip", new[] { animationsFolder });
|
|
|
|
System.Collections.Generic.List<AnimationEntry> entries = new();
|
|
|
|
foreach(string guid in guids)
|
|
{
|
|
string path = AssetDatabase.GUIDToAssetPath(guid);
|
|
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
|
|
|
|
if(clip == null || clip.name.StartsWith("__preview__"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string folderPath = System.IO.Path.GetDirectoryName(path).Replace("\\", "/");
|
|
string root = animationsFolder.Replace("\\", "/");
|
|
|
|
string relativeFolder = folderPath.StartsWith(root) ? folderPath.Substring(root.Length).Trim('/') : folderPath;
|
|
|
|
string folderName = string.IsNullOrEmpty(relativeFolder) ? "Root" : System.IO.Path.GetFileName(relativeFolder);
|
|
|
|
int folderLevel = string.IsNullOrEmpty(relativeFolder) ? 0 : relativeFolder.Split('/').Length;
|
|
|
|
entries.Add(new AnimationEntry
|
|
{
|
|
clip = clip,
|
|
displayName = clip.name,
|
|
folderName = folderName,
|
|
folderPath = relativeFolder,
|
|
folderLevel = folderLevel,
|
|
|
|
prop = GetPropsForAnimation(clip.name),
|
|
disableSpineProxy = ShouldDisableSpineProxy(clip.name)
|
|
});
|
|
}
|
|
|
|
entries.Sort((a, b) =>
|
|
{
|
|
int folderCompare = string.Compare(a.folderPath, b.folderPath, System.StringComparison.Ordinal);
|
|
if (folderCompare != 0) return folderCompare;
|
|
|
|
return string.Compare(a.displayName, b.displayName, System.StringComparison.Ordinal);
|
|
});
|
|
|
|
availableAnimations = entries.ToArray();
|
|
|
|
EditorUtility.SetDirty(this);
|
|
}
|
|
private bool ShouldDisableSpineProxy(string animationName)
|
|
{
|
|
return System.Array.Exists(
|
|
skipSpineProxyAnimations,
|
|
x => x == animationName
|
|
);
|
|
}
|
|
private GameObject[] GetPropsForAnimation(string animationName)
|
|
{
|
|
if (System.Array.Exists(propLowChairAnimations, x => x == animationName))
|
|
return propLowChair != null ? new[] { propLowChair } : System.Array.Empty<GameObject>();
|
|
|
|
if (System.Array.Exists(propMediumChairAnimations, x => x == animationName))
|
|
return propMediumChair != null ? new[] { propMediumChair } : System.Array.Empty<GameObject>();
|
|
|
|
if (System.Array.Exists(propHighChairAnimations, x => x == animationName))
|
|
return propHighChair != null ? new[] { propHighChair } : System.Array.Empty<GameObject>();
|
|
|
|
if (System.Array.Exists(propDrinkablesAnimationsL, x => x == animationName))
|
|
return propDrinkablesL ?? System.Array.Empty<GameObject>();
|
|
|
|
if (System.Array.Exists(propDrinkablesAnimationsR, x => x == animationName))
|
|
return propDrinkablesR ?? System.Array.Empty<GameObject>();
|
|
|
|
if (System.Array.Exists(propEdiblesAnimationsL, x => x == animationName))
|
|
return propEdiblesL ?? System.Array.Empty<GameObject>();
|
|
|
|
if (System.Array.Exists(propEdiblesAnimationsR, x => x == animationName))
|
|
return propEdiblesR ?? System.Array.Empty<GameObject>();
|
|
|
|
return System.Array.Empty<GameObject>();
|
|
}
|
|
#endif
|
|
///
|
|
}
|
|
}
|