Skip to content

Instantly share code, notes, and snippets.

@Danil0v3s
Created August 15, 2023 18:54
Show Gist options
  • Save Danil0v3s/6c2ecee8ec78dc07e555b9622523b5a6 to your computer and use it in GitHub Desktop.
Save Danil0v3s/6c2ecee8ec78dc07e555b9622523b5a6 to your computer and use it in GitHub Desktop.
using System;
using System.Collections;
using UnityEngine;
//influence for programmatic animation controller
//https://www.youtube.com/watch?v=nBkiSJ5z-hE
public class VRShopperAnimationController : MonoBehaviour
{
private Animator _animator;
private GlobalAnimationManager _animationManager;
public State _state;
private bool isAnimated = false;
private bool noFightSequence = true;
[Range(0, 100)]
[SerializeField] private int handsAnimationFrequency;
[Range(0, 100)]
[SerializeField] private int waveAnimationFrequency;
[Range(0, 100)]
[SerializeField] private int headSetAnimationFrequency;
[Range(0, 100)]
[SerializeField] private int quickAnimationFrequency;
[Range(0, 100)]
[SerializeField] private int beginningAnimationFrequency;
private int totalIdleHandsAnimations;
private int totalFightingIdleAnimations;
private int totalLeftPunchAnimations;
private int totalRightPunchAnimations;
private int totalPayAnimations;
private int totalLowGrabAnimations;
private int totalMidGrabAnimations;
private int totalHighGrabAnimations;
private const string layer = "UpperBody.";
private string[] punchSequence;
//structure of the types of animations to play, index is appended to string to be the literal animation clip,such as quick_hands1, or quirk_wave2
private const string quirk_hands = "quirk_hands";
private const string quirk_headset = "quirk_headset";
private const string quirk_wave = "quirk_wave";
private const string quirk_quick = "quirk_quick";
private const string beginning = "beginning";
private const string fighting_idle = "fighting_idle"; //probably dont need idle handled here
private const string punch_left = "punch_left";
private const string punch_right = "punch_right";
private const string pay = "pay";
private const string grab_high = "grab_high";
private const string grab_mid = "grab_mid";
private const string grab_low = "grab_low";
private const string indle_hands = "Idle_down";
public enum State
{
beginning,
moving,
fighting
}
void Start()
{
_animator = GetComponent<Animator>();
_animationManager = GameObject.Find("AnimationManager").GetComponent<GlobalAnimationManager>();
_state = State.beginning;
InitializeTotalAnimationTypeCountInformation();
PlayIdleAnimation();
}
private void Update()
{
FightCheck();
}
//-----------Interaction Functions-----------------
//---Fighting
//Fighting animation system overall generates a sequence of punches with varying amounts and animations, then pauses for a varied time before punching again.
private void FightCheck()
{
if (_state == State.fighting)
{
if (noFightSequence)
{
punchSequence = GeneratePunchSequence();
StartCoroutine(PlayPunchSequence(punchSequence));
}
}
}
private string[] GeneratePunchSequence()
{
noFightSequence = false;
//Generate how many punches to deliver, (originally between 1 and 3 punches in one sequence)
int randomCombo = UnityEngine.Random.Range(1, totalLeftPunchAnimations + 1);
//Generate random index numbers to append to animation types to create an animation clip. example "punch_left" + "0"
int[] punchAnimationIndex = new int[randomCombo];
for (int i = 0; i < punchAnimationIndex.Length; i++)
{
int index = UnityEngine.Random.Range(0, totalLeftPunchAnimations);
punchAnimationIndex[i] = index;
}
//Randomly choose first punch to be from left or right hand, punch sequences always alternate between hands.
string[] punchAnimationSequence;
int randomPunch = UnityEngine.Random.Range(0, 2);
if (randomPunch == 0)
{
punchAnimationSequence = CompletePunchSequence(punch_left, punchAnimationIndex);
}
else
{
punchAnimationSequence = CompletePunchSequence(punch_right, punchAnimationIndex);
}
return punchAnimationSequence;
}
//Alternates between hands and appends animation type and index as string to array which holds animation clips for the animation controller to play.
private string[] CompletePunchSequence(string punchType, int[] punchAnimationIndex)
{
bool isLeft = false;
int size = punchAnimationIndex.Length;
string[] completePunchSequence = new string[size];
if (punchType == punch_left) { isLeft = true; }
for (int i = 0; i < size; i++)
{
if (isLeft)
{
completePunchSequence[i] = punch_left + punchAnimationIndex[i].ToString();
isLeft = false;
}
else
{
completePunchSequence[i] = punch_right + punchAnimationIndex[i].ToString();
isLeft = true;
}
}
return completePunchSequence;
}
//Coroutine to pace the animation clips to fully play, then starts fight transition which is a pause for a random amount of time before punching again.
IEnumerator PlayPunchSequence(string[] punchSequence)
{
for (int i = 0; i < punchSequence.Length; i++)
{
_animator.CrossFade(layer + punchSequence[i], 0.1f);
yield return new WaitForSeconds(_animationManager.GetAnimationClipDuration(punchSequence[i]));
}
StartCoroutine(EnterFightTransition());
}
IEnumerator EnterFightTransition()
{
PlayIdleAnimation();
float rng = UnityEngine.Random.Range(0.2f, 1f);
yield return new WaitForSeconds(rng);
noFightSequence = true;
}
//-----------Grabbing Functionality
public void PlayPickupAnimation(ObjComponent.Height height)
{
PlayChosenInteractionAnimation(
GetRandomGrabAnimation(height)
);
}
public void PlayPayingAnimation()
{
PlayChosenInteractionAnimation(GetRandomPayingAnimation());
}
private AnimationChoice GetRandomPayingAnimation()
{
AnimationChoice ac = null;
int rng;
rng = UnityEngine.Random.Range(0, totalPayAnimations);
ac = new AnimationChoice(pay, rng);
return ac;
}
private AnimationChoice GetRandomGrabAnimation(ObjComponent.Height height)
{
AnimationChoice ac = null;
int rng;
switch (height)
{
case ObjComponent.Height.High:
rng = UnityEngine.Random.Range(0, totalHighGrabAnimations);
ac = new AnimationChoice(grab_high, rng);
break;
case ObjComponent.Height.Mid:
rng = UnityEngine.Random.Range(0, totalMidGrabAnimations);
ac = new AnimationChoice(grab_mid, rng);
break;
case ObjComponent.Height.Low:
rng = UnityEngine.Random.Range(0, totalLowGrabAnimations);
ac = new AnimationChoice(grab_low, rng);
break;
default:
Debug.Log("No height state from desiredObj");
break;
}
return ac;
}
private void PlayChosenInteractionAnimation(AnimationChoice ac)
{
if (!isAnimated)
{
if (ac == null){ Debug.Log("AnimationChoice is null"); return; }
isAnimated = true;
string animationToPlay = ac.ConvertToString();
_animator.Play(layer + animationToPlay);
Invoke("AnimationComplete", _animationManager.GetAnimationClipDuration(animationToPlay)); //Ensures Animation will complete, equal to HasExitTime bool for transitions
}
}
//--------------- General Animation Functionality
private void PlayIdleAnimation()
{
int chance = 0;
switch (_state)
{
case State.moving:
chance = UnityEngine.Random.Range(0, totalIdleHandsAnimations);
_animator.Play(layer + indle_hands + chance.ToString());
break;
case State.beginning:
chance = UnityEngine.Random.Range(0, totalIdleHandsAnimations);
_animator.Play(layer + indle_hands + chance.ToString());
break;
case State.fighting:
chance = UnityEngine.Random.Range(0, totalFightingIdleAnimations);
_animator.Play(layer + fighting_idle + chance.ToString());
break;
default:
Debug.Log("No state PlayIdleAnimation");
break;
}
}
private void PlayChosenQuirkAnimation(AnimationChoice ac)
{
if (!isAnimated)
{
string animationToPlay = ac.ConvertToString();
isAnimated = true;
_animator.Play(layer + animationToPlay);
_animationManager.UpdateQuirkCount(ac);
Invoke("AnimationComplete", _animationManager.GetAnimationClipDuration(animationToPlay)); //Ensures Animation will complete, equal to HasExitTime bool for transitions
}
}
void AnimationComplete()
{
isAnimated = false;
PlayIdleAnimation();
}
//----------------------------------
//------------------Quirk Related Functions
//ChooseQuirk() is called in GlobalAnimationManager, manager selects controller to perform quirk animation,
//manager gives an array with 1 of each quirk subtype,
//controller chooses from array with defined probabilities
public void ChooseQuirk(AnimationChoice[] quirks)
{
AnimationChoice _animationToPlay = null;
if (_state == State.beginning)
{
//beginning animation
_animationToPlay = unpackChoice(beginning, quirks);
}
if (_state == State.moving)
{
int total_p = handsAnimationFrequency + waveAnimationFrequency + headSetAnimationFrequency + quickAnimationFrequency;
int chance = UnityEngine.Random.Range(0, total_p);
if (chance < handsAnimationFrequency)
{
//hands
_animationToPlay = unpackChoice(quirk_hands, quirks);
}
else if (chance < handsAnimationFrequency + headSetAnimationFrequency)
{
//headset
_animationToPlay = unpackChoice(quirk_wave, quirks);
}
else if (chance < handsAnimationFrequency + headSetAnimationFrequency + waveAnimationFrequency)
{
//wave
_animationToPlay = unpackChoice(quirk_headset, quirks);
}
else
{
//quick quirk
_animationToPlay = unpackChoice(quirk_quick, quirks);
}
}
PlayChosenQuirkAnimation(_animationToPlay);
}
//Gets specific animation type from Quirk array in ChooseQuirk(), wanted to make the order of the array not matter.
private AnimationChoice unpackChoice(string quirkType, AnimationChoice[] choices)
{
AnimationChoice choice;
for (int i = 0; i < choices.Length; i++)
{
if (choices[i].animationType == quirkType)
{
choice = choices[i];
return choice;
}
}
throw new ArgumentException("quirk not found in unpackChoice function");
}
//----------------------------------------
//------------State and Boolean Checks, utilities
public void EnterFightState()
{
_state = State.fighting;
PlayIdleAnimation();
}
public void EnterMovingState()
{
_state = State.moving;
PlayIdleAnimation();
}
public void UpdateAnimationState(State state)
{
_state = state;
}
public bool isMoving()
{
if (_state == State.moving) { return true; }
return false;
}
public bool isFighting()
{
if (_state == State.fighting) { return true; }
return false;
}
//Trigger is in Door entrances, shoppers start in beginning state.
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("AnimationMovingState"))
{
EnterMovingState();
}
}
private void InitializeTotalAnimationTypeCountInformation()
{
int[] input = _animationManager.InitializeShopperInteractionAnimationCounts();
totalIdleHandsAnimations = input[0];
totalFightingIdleAnimations = input[1];
totalLeftPunchAnimations = input[2];
totalRightPunchAnimations = input[3];
totalPayAnimations = input[4];
totalLowGrabAnimations = input[5];
totalMidGrabAnimations = input[6];
totalHighGrabAnimations = input[7];
}
}
using System.Collections.Generic;
using UnityEngine;
public class AnimationChoice
{
public int index { get; set; }
public string animationType { get; set; }
public AnimationChoice(string _animationType, int _index)
{
animationType = _animationType;
index = _index;
}
public string ConvertToString()
{
return animationType + index.ToString();
}
}
public class QuirkStack
{
Stack<int> green = new Stack<int>();
Stack<int> blue = new Stack<int>();
bool greenIsActive = true;
public QuirkStack(int totalCount)
{
for (int i = 0; i < totalCount; i++)
{
green.Push(i);
}
}
public void quirkPop()
{
if (greenIsActive)
{
int x = green.Pop();
blue.Push(x);
if (green.Count == 0) { greenIsActive = false; }
}
else
{
int x = blue.Pop();
green.Push(x);
if (blue.Count == 0) { greenIsActive = true; }
}
}
public int quirkPeek()
{
if (greenIsActive)
{
return green.Peek();
}
else
{
return blue.Peek();
}
}
}
public class GlobalAnimationManager : MonoBehaviour
{
[Range(0,100)]
[SerializeField] private float GlobalAnimationfrequency; //This number is checked every frame... should be very low or use time instead?
[Range(0, 100)]
[SerializeField] private float frequencyThrottle; //not used yet
private int totalShoppers;
private VRShopperAnimationController[] shopperControllers;
private int[] shopperControllerIndex;
private Dictionary<string, QuirkStack> animationType;
private Dictionary<string, float> animationDurations;
//For now must manually enter the amount of animations per type (hand, headset, wave, quick, begining, ect.)
//Unused Variables are still passed to VRShopperController so dont remove!
[SerializeField] private int totalHandAnimations;
[SerializeField] private int totalHeadsetAnimations;
[SerializeField] private int totalWaveAnimations;
[SerializeField] private int totalQuickAnimations;
[SerializeField] private int totalBeginningAnimatinons;
[SerializeField] private int totalFightingIdleAnimations;
[SerializeField] private int totalIdleHandsAnimations;
[SerializeField] private int totalLeftPunchAnimations;
[SerializeField] private int totalRightPunchAnimations;
[SerializeField] private int totalPayAnimations;
[SerializeField] private int totalLowGrabAnimations;
[SerializeField] private int totalMidGrabAnimations;
[SerializeField] private int totalHighGrabAnimations;
//structure of the types of animations to play, index is appended to string to be the literal animation clip, for example quick_hands1, or quirk_wave2
private const string quirk_hands = "quirk_hands";
private const string quirk_headset = "quirk_headset";
private const string quirk_wave = "quirk_wave";
private const string quirk_quick = "quirk_quick";
private const string beginning = "beginning";
public enum State
{
beginning,
moving,
fighting
}
void Start()
{
InitializeManager();
}
void Update()
{
CheckQuirkAnimationChance();
}
private void CheckQuirkAnimationChance()
{
int ranNum = Random.Range(0, 100);
if (ranNum < GlobalAnimationfrequency)
{
int shopperIndex = Utilities.getRandomSmallestElement(shopperControllerIndex);
if(!shopperControllers[shopperIndex].isFighting())
{
shopperControllerIndex[shopperIndex]++;
ProvideRandomQuirkAnimations(shopperIndex);
}
}
}
private void ProvideRandomQuirkAnimations(int ShopperIndex)
{
AnimationChoice[] ac = new AnimationChoice[5]
{
RequestAnimation(quirk_hands),
RequestAnimation(quirk_headset),
RequestAnimation(quirk_wave),
RequestAnimation(quirk_quick),
RequestAnimation(beginning)
};
shopperControllers[ShopperIndex].ChooseQuirk(ac);
}
public AnimationChoice RequestAnimation(string animationType) //animationChoice class in VRShopperAnimationController.cs
{
AnimationChoice ac = LeastFrequentAnimation(animationType);
return ac;
}
private AnimationChoice LeastFrequentAnimation(string quirk)
{
int index = animationType[quirk].quirkPeek();
return new AnimationChoice(quirk, index);
}
public void UpdateQuirkCount(AnimationChoice ac)
{
animationType[ac.animationType].quirkPop();
}
public float GetAnimationClipDuration(string v)
{
return animationDurations[v];
}
public int[] InitializeShopperInteractionAnimationCounts()
{
return new int[] {
totalIdleHandsAnimations,
totalFightingIdleAnimations,
totalLeftPunchAnimations,
totalRightPunchAnimations,
totalPayAnimations,
totalLowGrabAnimations,
totalMidGrabAnimations,
totalHighGrabAnimations };
}
private void InitializeAnimationDictionarys()
{
animationType = new Dictionary<string, QuirkStack>()
{
{ "quirk_hands", new QuirkStack(totalHandAnimations) },
{ "quirk_headset", new QuirkStack(totalHeadsetAnimations) },
{ "quirk_wave", new QuirkStack(totalWaveAnimations) },
{ "quirk_quick", new QuirkStack(totalQuickAnimations) },
{ "beginning", new QuirkStack(totalBeginningAnimatinons) }
};
//Gets all clips from a Shoppers animation controller to map their durations in a dictionary
AnimationClip[] clips = shopperControllers[0].GetComponent<Animator>().runtimeAnimatorController.animationClips;
animationDurations = new Dictionary<string, float>();
for (int i = 0; i < clips.Length; i++)
{
animationDurations.Add(clips[i].name, clips[i].length);
}
}
private void InitializeShopperControllers()
{
shopperControllers = FindObjectsOfType<VRShopperAnimationController>();
totalShoppers = shopperControllers.Length;
shopperControllerIndex = new int[totalShoppers];
}
private void InitializeManager()
{
InitializeShopperControllers();
InitializeAnimationDictionarys();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment