using System; using System.Text; using System.Collections.Generic; using System.Linq; using System.IO; using System.Xml.Serialization; using UnityEngine; using SFB; using UnityEngine.UI; namespace RimWorldAnimationStudio { public class AnimationController : Singleton { [Header("Animation settings")] public bool isAnimating = false; public int stageTick = 1; [Header("Object references")] public Slider stageTimelineSlider; public Dropdown stageLoopDropdown; public InputField cyclesNormalField; public InputField cyclesFastField; public Text stageTickText; public Text stageLengthText; public ActorCard actorCard; public Transform animationTimelines; [Header("Prefabs")] public ActorBody actorBodyPrefab; public AnimationTimeline animationTimelinePrefab; // Private timing variables private float timeSinceLastUpdate = 0; private int cycleIndex = 0; public void Update() { // No animation, exit if (Workspace.animationDef == null) { return; } // Dirty animation, reset if (Workspace.animationDef != null && Workspace.isDirty) { Initialize(); return; } // Update tick if animating if (isAnimating) { timeSinceLastUpdate += Time.deltaTime; if (timeSinceLastUpdate < 1f / 60f) { return; } timeSinceLastUpdate -= 1f / 60f; stageTick += 1; if (stageTick > Workspace.animationClipWindowSize) { if (stageLoopDropdown.value == 1) { stageTick = 1; } else if (stageLoopDropdown.value == 2 && Workspace.stageID < Workspace.animationDef.animationStages.Count - 1) { ++cycleIndex; stageTick = 1; if (cycleIndex > int.Parse(cyclesNormalField.text)) { ++Workspace.stageID; cycleIndex = 0; } } else { stageTick = Workspace.animationClipWindowSize; } } } // Update stage timeline stageTimelineSlider.maxValue = Workspace.animationClipWindowSize; stageTimelineSlider.value = stageTick; stageTickText.text = stageTick.ToString(); stageLengthText.text = Workspace.animationClipWindowSize.ToString(); // Update animation UpdateAnimation(); } public void UpdateAnimation() { List actorBodies = GetComponentsInChildren().ToList(); for (int actorID = 0; actorID < actorBodies.Count; actorID++) { PawnAnimationClip clip = Workspace.animationDef?.animationStages[Workspace.stageID]?.animationClips[actorID]; if (clip == null) { continue; } float clipPercent = (float)(stageTick % clip.duration) / clip.duration; ActorBody actorBody = actorBodies[actorID]; string bodyType = actorBody.bodyType; Vector3 deltaPos = new Vector3(clip.BodyOffsetX.Evaluate(clipPercent), 0, clip.BodyOffsetZ.Evaluate(clipPercent)); deltaPos += Workspace.animationDef.actors[actorID].bodyTypeOffset.GetOffset(bodyType); float bodyAngle = clip.BodyAngle.Evaluate(clipPercent); float headAngle = clip.HeadAngle.Evaluate(clipPercent); if (bodyAngle < 0) bodyAngle = 360 - ((-1f * bodyAngle) % 360); if (bodyAngle > 360) bodyAngle %= 360; if (headAngle < 0) headAngle = 360 - ((-1f * headAngle) % 360); if (headAngle > 360) headAngle %= 360; int bodyFacing = (int)clip.BodyFacing.Evaluate(clipPercent); int headFacing = (int)clip.HeadFacing.Evaluate(clipPercent); Vector3 headBob = new Vector3(0, 0, clip.HeadBob.Evaluate(clipPercent)) + PawnUtility.BaseHeadOffsetAt(bodyType, bodyFacing); Vector3 bodyPos = new Vector3(deltaPos.x, deltaPos.z, 0); Vector3 headPos = new Vector3(headBob.x, headBob.z, 0); actorBody.transform.position = bodyPos; actorBody.transform.eulerAngles = new Vector3(0, 0, bodyAngle); actorBody.headRenderer.transform.localPosition = headPos; actorBody.headRenderer.transform.eulerAngles = new Vector3(0, 0, headAngle); actorBody.bodyRenderer.sprite = Resources.Load("Textures/Humanlike/Bodies/" + bodyType + bodyFacing); actorBody.headRenderer.sprite = Resources.Load("Textures/Humanlike/Heads/Head" + headFacing); actorBody.bodyRenderer.sortingLayerName = clip.layer; actorBody.headRenderer.sortingLayerName = clip.layer; actorBody.headRenderer.sortingOrder = headFacing == 0 ? -1 : 1; } } public void Initialize() { Debug.Log("Initializing animation preview"); Reset(); InitializeAnimationTimeline(); StageCardManager.Instance.Initialize(); Workspace.isDirty = false; } public void InitializeAnimationTimeline() { Workspace.animationClipWindowSize = Workspace.animationDef.animationStages[Workspace.stageID].animationClips.Select(x => x.duration).Max(); cyclesNormalField.text = Mathf.Max(Mathf.CeilToInt((float)Workspace.animationDef.animationStages[Workspace.stageID].playTimeTicks / Workspace.animationClipWindowSize), 1).ToString(); cyclesFastField.text = Mathf.Max(Mathf.CeilToInt((float)Workspace.animationDef.animationStages[Workspace.stageID].playTimeTicksQuick / Workspace.animationClipWindowSize), 1).ToString(); for (int actorID = 0; actorID < Workspace.animationDef.actors.Count; actorID++) { ActorBody actorBody = Instantiate(actorBodyPrefab, transform); actorBody.Initialize(actorID); AnimationTimeline animationTimeline = Instantiate(animationTimelinePrefab, animationTimelines); animationTimeline.Initialize(actorID); } stageTimelineSlider.maxValue = Workspace.animationClipWindowSize; stageTimelineSlider.value = 1; stageTick = 1; } public void Reset() { Workspace.stageID = 0; ResetAnimationTimeline(); StageCardManager.Instance.Reset(); } public void ResetAnimationTimeline() { isAnimating = false; timeSinceLastUpdate = 0; cycleIndex = 0; foreach (ActorBody actorBody in GetComponentsInChildren()) { Destroy(actorBody.gameObject); } foreach (Transform animationTimeline in animationTimelines) { Destroy(animationTimeline.gameObject); } } public bool AddAnimationStage() { AnimationStage stage = new AnimationStage(); return stage.MakeNew(); } public bool CloneAnimationStage() { AnimationStage stage = Workspace.animationDef.animationStages[Workspace.stageID].Copy(); stage.Initialize(); stage.stageName += " (Clone)"; Workspace.animationDef.animationStages.Insert(Workspace.stageID + 1, stage); return true; } public bool MoveAnimationStage(int startIndex, int delta) { if (startIndex + delta < 0 || startIndex + delta >= Workspace.animationDef.animationStages.Count) { return false; } AnimationStage stage = Workspace.animationDef.animationStages[startIndex]; Workspace.animationDef.animationStages[startIndex] = Workspace.animationDef.animationStages[startIndex + delta]; Workspace.animationDef.animationStages[startIndex + delta] = stage; return true; } public bool RemoveAnimationStage() { if (Workspace.animationDef.animationStages.Count == 1) { Debug.LogWarning("Cannot delete animation stage - the animation must contain at least one animation stage."); return false; } Workspace.animationDef.animationStages.RemoveAt(Workspace.stageID); Workspace.stageID = Workspace.stageID >= Workspace.animationDef.animationStages.Count ? Workspace.stageID = Workspace.animationDef.animationStages.Count - 1 : Workspace.stageID; return true; } public void AddActor() { Actor actor = new Actor(); if (actor.MakeNew()) { Initialize(); } } public void RemoveActor() { if (Workspace.animationDef.actors.Count == 1) { Debug.LogWarning("Cannot delete actor - the animation must contain at least one actor."); return; } foreach (AnimationStage stage in Workspace.animationDef.animationStages) { stage.animationClips.RemoveAt(Workspace.actorID); } Workspace.animationDef.actors.RemoveAt(Workspace.actorID); Workspace.actorID = Workspace.actorID >= Workspace.animationDef.actors.Count ? Workspace.actorID = Workspace.animationDef.actors.Count - 1 : Workspace.actorID; Initialize(); } public void AddPawnKeyframe() { PawnAnimationClip clip = Workspace.Instance.GetCurrentPawnAnimationClip(); List keyframes = clip?.keyframes; if (clip == null || keyframes == null) { Debug.LogWarning("Cannot add pawn keyframe - the AnimationDef is invalid"); return; } if (keyframes.FirstOrDefault(x => x.atTick == stageTick) != null) { Debug.LogWarning("Cannot add pawn keyframe - a keyframe already exists at this tick"); return; } float clipPercent = (float)(stageTick % clip.duration) / clip.duration; PawnKeyframe keyframe = new PawnKeyframe(); keyframe.bodyAngle = clip.BodyAngle.Evaluate(clipPercent); keyframe.headAngle = clip.HeadAngle.Evaluate(clipPercent); keyframe.headBob = clip.HeadBob.Evaluate(clipPercent); keyframe.bodyOffsetX = clip.BodyOffsetX.Evaluate(clipPercent); keyframe.bodyOffsetZ = clip.BodyOffsetZ.Evaluate(clipPercent); keyframe.headFacing = clip.HeadFacing.Evaluate(clipPercent); keyframe.bodyFacing = clip.BodyFacing.Evaluate(clipPercent); keyframe.genitalAngle = clip.GenitalAngle.Evaluate(clipPercent); keyframe.atTick = stageTick; PawnKeyframe nextKeyframe = keyframes.FirstOrDefault(x => x.atTick > stageTick); if (nextKeyframe != null) { keyframes.Insert(keyframes.IndexOf(nextKeyframe), keyframe); } else { keyframes.Add(keyframe); } clip.BuildSimpleCurves(); animationTimelines.GetChild(Workspace.actorID).GetComponent().AddPawnKeyFrame(keyframe.keyframeID); } public void RemovePawnKeyframe() { PawnKeyframe keyframe = Workspace.Instance.GetCurrentPawnKeyframe(false); if (keyframe != null) { animationTimelines.GetChild(Workspace.actorID).GetComponent().RemovePawnKeyFrame(keyframe.keyframeID); PawnAnimationClip clip = Workspace.Instance.GetCurrentPawnAnimationClip(); clip.keyframes.Remove(keyframe); clip.BuildSimpleCurves(); } } public void ToggleAnimation() { isAnimating = !isAnimating; } public void UpdateFromStageTimelineSlider() { if (Workspace.animationDef == null) { return; } if (stageTick != (int)stageTimelineSlider.value) { stageTick = (int)stageTimelineSlider.value; } } public void ToggleActorManipulationMode(int mode) { Workspace.actorManipulationMode = (ActorManipulationMode)mode; } } }