rimworld-animation-studio/Source/Assets/Scripts/Managers/AnimationController.cs

251 lines
11 KiB
C#

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Xml.Serialization;
using UnityEngine;
using UnityEngine.UI;
namespace RimWorldAnimationStudio
{
public class AnimationController : Singleton<AnimationController>
{
[Header("Object references")]
public Transform animationTimelines;
public Transform actorBodies;
public Dropdown stageLoopDropdown;
[Header("Prefabs")]
public ActorBody actorBodyPrefab;
public GameObject animationTimelinePrefab;
// Private timing variables
private float timeSinceLastUpdate = 0;
private int loopCount = 0;
private float playBackSpeed = 1f;
public void Start()
{
EventsManager.onAnimationChanged.AddListener(delegate{ Initialize(); });
EventsManager.onStageIDChanged.AddListener(delegate { Initialize(); });
EventsManager.onActorCountChanged.AddListener(delegate { Initialize(); });
}
public void Update()
{
if (Workspace.animationDef == null) return;
// Update stage tick / loop count if animating
if (Workspace.IsAnimating)
{
timeSinceLastUpdate += Time.deltaTime;
if (timeSinceLastUpdate < 1 / (playBackSpeed * 60f))
{ return; }
timeSinceLastUpdate -= 1 / (playBackSpeed * 60f);
Workspace.StageTick += 1;
if (Workspace.StageTick >= Workspace.StageWindowSize)
{
switch (stageLoopDropdown.value)
{
case 1: Workspace.StageTick = Constants.minTick; break;
case 2: UpdateLoopCount(Workspace.GetCurrentAnimationStage().StageLoopsNormal); break;
case 3: UpdateLoopCount(Workspace.GetCurrentAnimationStage().StageLoopsQuick); break;
default: Workspace.IsAnimating = false; break;
}
}
}
// Update animation preview
UpdateAnimationPreview();
}
public void UpdateLoopCount(int stageLoops)
{
loopCount++;
Workspace.StageTick = Constants.minTick;
if (loopCount >= stageLoops)
{
if (Workspace.StageID >= Workspace.animationDef.AnimationStages.Count - 1)
{ Workspace.IsAnimating = false; }
else
{
Workspace.StageID++;
loopCount = 0;
}
}
}
public void UpdateAnimationPreview()
{
if (Workspace.animationDef == null || Workspace.StageID >= Workspace.animationDef?.AnimationStages.Count) return;
List<ActorBody> actorBodiesList = actorBodies.GetComponentsInChildren<ActorBody>().ToList();
for (int actorID = 0; actorID < Workspace.animationDef.actors.Count; actorID++)
{
// Get the current actor
Actor actor = Workspace.GetActor(actorID);
// Get their animation clip and rebuild it to ensure it is up to date
PawnAnimationClip clip = Workspace.GetPawnAnimationClip(actorID);
clip.BuildSimpleCurves();
// Get flags
bool quiver = Workspace.IsAnimating && Workspace.GetCurrentOrPreviousKeyframe(actorID).Quiver == true;
bool requiresGenitals = actor.RequiredGenitals.Any(x => x == "Penis") || actor.IsFucking;
// Get clip percentage
float clipPercent = (float)(Workspace.StageTick % clip.duration) / clip.duration;
if (Workspace.StageTick > Constants.minTick && Workspace.StageTick == clip.duration) clipPercent = 1f;
if (Workspace.GetCurrentAnimationStage().IsLooping == false)
{ clipPercent = (float)Workspace.StageTick / clip.duration; }
// Get the actors race and body type
PawnRaceDef pawnRaceDef = actor.GetPawnRaceDef();
string bodyType = pawnRaceDef.isHumanoid ? actor.bodyType : "None";
// Evalute the actor's basic offsets and facing
ActorPosition actorPosition = actor.GetCurrentPosition();
float bodyPosX = actorPosition.bodyOffsetX;
float bodyPosZ = actorPosition.bodyOffsetZ;
float bodyAngle = actorPosition.bodyAngle + (quiver ? UnityEngine.Random.value * 2f - 1f : 0f);
float headBob = actorPosition.headBob;
float headAngle = actorPosition.headAngle;
float genitalAngle = actorPosition.genitalAngle;
int bodyFacing = actorPosition.bodyFacing;
int headFacing = actorPosition.headFacing;
// Convert values to world coordinates
Vector3 bodyPos = new Vector3(bodyPosX, bodyPosZ, 0f);
Vector3 headPos = new Vector3(0f, headBob, 0f) + PawnUtility.BaseHeadOffsetAt(bodyType, bodyFacing);
Vector3 appendagePos = PawnUtility.GroinOffsetAt(bodyType, bodyFacing);
// Update actorbody
ActorBody actorBody = actorBodiesList[actorID];
ActorBodyPart actorBodypart;
actorBody.transform.localScale = new Vector3(pawnRaceDef.scale, pawnRaceDef.scale, pawnRaceDef.scale);
// Body
actorBody.transform.position = bodyPos + actor.GetFinalTransformOffset();
actorBody.transform.eulerAngles = new Vector3(0, 0, -bodyAngle);
actorBody.bodyRenderer.sortingLayerName = clip.Layer;
actorBody.bodyRenderer.sprite = pawnRaceDef.GetBodyTypeGraphic((CardinalDirection)bodyFacing, bodyType);
actorBody.bodyRenderer.flipX = bodyFacing == 3;
actorBody.bodyRenderer.gameObject.SetActive(actorBody.bodyRenderer.sprite != null);
// Head
actorBodypart = actorBody.GetActorBodyPart("head");
actorBodypart.transform.localPosition = headPos;
actorBodypart.transform.eulerAngles = new Vector3(0, 0, -headAngle);
actorBodypart.bodyPartRenderer.sortingLayerName = clip.Layer;
actorBodypart.bodyPartRenderer.sortingOrder = bodyFacing == 0 ? -1 : 1;
actorBodypart.bodyPartRenderer.sprite = pawnRaceDef.isHumanoid ? pawnRaceDef.GetHeadGraphic((CardinalDirection)headFacing) : null;
actorBodypart.bodyPartRenderer.flipX = headFacing == 3;
actorBodypart.gameObject.SetActive(actorBodypart.bodyPartRenderer.sprite != null);
// Appendage
actorBodypart = actorBody.GetActorBodyPart("appendage");
actorBodypart.transform.localPosition = new Vector3(appendagePos.x, appendagePos.z, 0f);
actorBodypart.transform.eulerAngles = new Vector3(0, 0, -genitalAngle);
actorBodypart.bodyPartRenderer.sortingLayerName = clip.Layer;
actorBodypart.bodyPartRenderer.sprite = requiresGenitals && pawnRaceDef.isHumanoid ? Resources.Load<Sprite>("Textures/Humanlike/Appendages/Appendage" + bodyFacing) : null;
//actorBody.appendageRenderer.flipX = bodyFacing == 3;
actorBodypart.gameObject.SetActive(actorBodypart.bodyPartRenderer.sprite != null);
// Add-ons
foreach (ActorAddon addon in clip.Addons)
{
actorBodypart = actorBody.GetActorBodyPart(addon.AddonName);
if (actorBodypart == null) continue;
ActorBody anchoringActorBody = actorBodies.GetComponentsInChildren<ActorBody>()?.FirstOrDefault(x => x.actorID == addon.AnchoringActor);
Vector3 anchor = PawnUtility.GetBodyPartAnchor(anchoringActorBody, addon.anchorName);
actorBodypart.transform.position = anchor + new Vector3(addon.PosX.Evaluate(clipPercent), addon.PosZ.Evaluate(clipPercent), 0);
actorBodypart.transform.eulerAngles = new Vector3(0, 0, -addon.Rotation.Evaluate(clipPercent));
actorBodypart.bodyPartRenderer.sortingLayerName = addon.Layer;
//actorBodypart.bodyPartRenderer.sprite
actorBodypart.gameObject.SetActive(addon.Render);
}
// Play sounds
if (Workspace.IsAnimating)
{
PawnKeyframe keyframe = clip.keyframes.FirstOrDefault(x => x.atTick == (int)(clip.duration * clipPercent));
if (keyframe != null && string.IsNullOrEmpty(keyframe.soundEffect) == false)
{ actorBody.GetComponent<AudioController>().PlaySound(keyframe.soundEffect); }
}
}
}
public void Initialize()
{
//Debug.Log("Initializing animation preview");
int actorCount = Workspace.animationDef.Actors.Count;
int childCount = animationTimelines.GetComponentsInChildren<AnimationTimeline>().Count();
for (int actorID = 0; actorID < Mathf.Max(actorCount, childCount); actorID++)
{
// Add new actors as required
if (actorID >= childCount)
{
Instantiate(animationTimelinePrefab, animationTimelines);
Instantiate(actorBodyPrefab, actorBodies.transform);
}
// Get objects to update
AnimationTimeline animationTimeline = animationTimelines.GetComponentsInChildren<AnimationTimeline>()[actorID];
ActorBody actorBody = actorBodies.GetComponentsInChildren<ActorBody>()[actorID];
// Update values
if (actorID < actorCount)
{
animationTimeline.Initialize(actorID);
actorBody.Initialize(actorID);
}
// Remove excess objects as required
else
{
Destroy(animationTimeline.transform.parent.gameObject);
Destroy(actorBody.gameObject);
}
}
EventsManager.OnAnimationTimelinesChanged();
}
public void Reset()
{
Workspace.IsAnimating = false;
timeSinceLastUpdate = 0;
loopCount = 0;
foreach (Transform child in transform)
{ child.gameObject.SetActive(true); }
}
}
}