239 lines
10 KiB
C#
239 lines
10 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 and their animation clip
|
|
Actor actor = Workspace.GetActor(actorID);
|
|
PawnAnimationClip clip = Workspace.GetPawnAnimationClip(actorID);
|
|
|
|
// 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 = clip.GetStageTickPercentage();
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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); }
|
|
}
|
|
}
|
|
}
|