mirror of
https://gitgud.io/AbstractConcept/rimworld-animation-studio.git
synced 2024-08-15 00:43:27 +00:00
688 lines
30 KiB
C#
688 lines
30 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("Animation settings")]
|
|
public bool isAnimating = false;
|
|
public int stageTick = Constants.minTick;
|
|
|
|
[Header("Object references")]
|
|
public Slider stageTimelineSlider;
|
|
public Dropdown stageLoopDropdown;
|
|
public InputField cyclesNormalField;
|
|
public InputField cyclesFastField;
|
|
public InputField animationClipTimeField;
|
|
public InputField animationClipLengthField;
|
|
public ActorCard actorCard;
|
|
public Transform animationTimelines;
|
|
public Transform actorBodies;
|
|
public Toggle stretchKeyframesToggle;
|
|
public InputField playBackSpeedField;
|
|
public Button playToggleButton;
|
|
public Text stageLengthText;
|
|
public Text animationLengthText;
|
|
|
|
[Header("Prefabs")]
|
|
public ActorBody actorBodyPrefab;
|
|
public GameObject animationTimelinePrefab;
|
|
|
|
// Private timing variables
|
|
private int lastStageTick = Constants.minTick;
|
|
private float timeSinceLastUpdate = 0;
|
|
private int cycleIndex = 0;
|
|
private bool isDirty = true;
|
|
private bool isTimelineDirty = true;
|
|
private float playBackSpeed = 1f;
|
|
|
|
public void MakeDirty()
|
|
{ isDirty = true; isTimelineDirty = true; }
|
|
|
|
public void MakeTimelineDirty()
|
|
{ isTimelineDirty = true; }
|
|
|
|
public bool IsDirty()
|
|
{ return isDirty; }
|
|
|
|
public bool IsTimelineDirty()
|
|
{ return isTimelineDirty; }
|
|
|
|
public void Update()
|
|
{
|
|
// No animation, exit
|
|
if (Workspace.animationDef == null) { return; }
|
|
|
|
// Dirty animation, reset
|
|
if (Workspace.animationDef != null && isDirty)
|
|
{ Initialize(); }
|
|
|
|
// Update animation lengths
|
|
if (stageLoopDropdown.value == 3)
|
|
{
|
|
stageLengthText.text = "Stage length (quickie): " + Workspace.animationDef.animationStages[Workspace.stageID].playTimeTicksQuick + " (" + Workspace.animationDef.animationStages[Workspace.stageID].playTimeTicksQuick / 60f + " s)";
|
|
animationLengthText.text = "Animation length (quickie): " + Workspace.animationDef.animationTimeTicksQuick + " (" + Workspace.animationDef.animationTimeTicksQuick / 60f + " s)";
|
|
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(stageLengthText.GetComponent<RectTransform>());
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(animationLengthText.GetComponent<RectTransform>());
|
|
}
|
|
|
|
else
|
|
{
|
|
stageLengthText.text = "Stage length (normal): " + Workspace.animationDef.animationStages[Workspace.stageID].playTimeTicks + " (" + Workspace.animationDef.animationStages[Workspace.stageID].playTimeTicks / 60f + " s)";
|
|
animationLengthText.text = "Animation length (normal): " + Workspace.animationDef.animationTimeTicks + " (" + Workspace.animationDef.animationTimeTicks / 60f + " s)";
|
|
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(stageLengthText.GetComponent<RectTransform>());
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(animationLengthText.GetComponent<RectTransform>());
|
|
}
|
|
|
|
// Update tick if animating
|
|
stageTick = Mathf.Clamp(stageTick, Constants.minTick, Workspace.StageWindowSize);
|
|
|
|
if (isAnimating)
|
|
{
|
|
timeSinceLastUpdate += Time.deltaTime;
|
|
|
|
if (timeSinceLastUpdate < 1 / (playBackSpeed * 60f))
|
|
{ return; }
|
|
|
|
timeSinceLastUpdate -= 1 / (playBackSpeed * 60f);
|
|
stageTick += 1;
|
|
|
|
if (stageTick > Workspace.StageWindowSize)
|
|
{
|
|
if (stageLoopDropdown.value == 1)
|
|
{ stageTick = Constants.minTick; }
|
|
|
|
else if (stageLoopDropdown.value >= 2)
|
|
{
|
|
++cycleIndex;
|
|
stageTick = Constants.minTick;
|
|
|
|
if ((stageLoopDropdown.value == 2 && cycleIndex >= int.Parse(cyclesNormalField.text)) ||
|
|
(stageLoopDropdown.value == 3 && cycleIndex >= int.Parse(cyclesFastField.text)))
|
|
{
|
|
++Workspace.stageID;
|
|
cycleIndex = 0;
|
|
}
|
|
|
|
if (Workspace.stageID > Workspace.animationDef.animationStages.Count - 1)
|
|
{
|
|
Workspace.stageID = Workspace.animationDef.animationStages.Count - 1;
|
|
stageTick = Workspace.StageWindowSize;
|
|
isAnimating = false;
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
//stageTick = Workspace.StageWindowSize;
|
|
//isAnimating = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update stage timeline
|
|
animationClipTimeField.interactable = isAnimating == false;
|
|
animationClipLengthField.interactable = isAnimating == false;
|
|
|
|
if (lastStageTick != stageTick)
|
|
{
|
|
stageTimelineSlider.value = stageTick;
|
|
animationClipTimeField.text = stageTick.ToString();
|
|
|
|
lastStageTick = stageTick;
|
|
}
|
|
|
|
playToggleButton.image.color = isAnimating ? Constants.ColorGoldYellow : Constants.ColorWhite;
|
|
|
|
// Update animation
|
|
UpdateAnimation();
|
|
}
|
|
|
|
public void UpdateAnimation()
|
|
{
|
|
if (AnimationTimelinesNeedUpdate())
|
|
{ InitializeAnimationTimeline(); }
|
|
|
|
List<ActorBody> _actorBodies = actorBodies.GetComponentsInChildren<ActorBody>().ToList();
|
|
|
|
for (int actorID = 0; actorID < _actorBodies.Count; actorID++)
|
|
{
|
|
if (Workspace.stageID >= Workspace.animationDef?.animationStages.Count)
|
|
{ return; }
|
|
|
|
if (actorID >= Workspace.animationDef?.animationStages[Workspace.stageID]?.animationClips.Count)
|
|
{ return; }
|
|
|
|
Actor actor = Workspace.animationDef.actors[actorID];
|
|
PawnAnimationClip clip = Workspace.animationDef?.animationStages[Workspace.stageID]?.animationClips[actorID];
|
|
|
|
if (clip == null)
|
|
{ continue; }
|
|
|
|
bool quiver = isAnimating && Workspace.Instance.GetCurrentOrPreviousKeyframe(actorID).quiver == true;
|
|
bool requiresGenitals = actor.requiredGenitals.Any(x => x == "Penis") || Workspace.animationDef.actors[actorID].isFucking;
|
|
|
|
float clipPercent = (float)(stageTick % clip.duration) / clip.duration;
|
|
if (stageTick > Constants.minTick && stageTick == clip.duration) clipPercent = 1f;
|
|
|
|
if (Workspace.animationDef.animationStages[Workspace.stageID].isLooping == false)
|
|
{ clipPercent = (float)stageTick / clip.duration; }
|
|
|
|
AlienRaceDef alienRaceDef = actor.GetAlienRaceDef();
|
|
ActorBody actorBody = _actorBodies[actorID];
|
|
string bodyType = alienRaceDef.isHumanoid ? actor.bodyType : "None";
|
|
|
|
Vector3 deltaPos = new Vector3(clip.BodyOffsetX.Evaluate(clipPercent), 0, clip.BodyOffsetZ.Evaluate(clipPercent));
|
|
|
|
float bodyAngle = clip.BodyAngle.Evaluate(clipPercent);
|
|
bodyAngle += quiver ? UnityEngine.Random.value * 2f - 1f : 0f;
|
|
|
|
float headAngle = clip.HeadAngle.Evaluate(clipPercent);
|
|
|
|
int bodyFacing = (int)clip.BodyFacing.Evaluate(clipPercent);
|
|
int headFacing = (int)clip.HeadFacing.Evaluate(clipPercent);
|
|
|
|
float headBob = clip.HeadBob.Evaluate(clipPercent);
|
|
Vector3 headOffset = new Vector3(0, 0, headBob) + PawnUtility.BaseHeadOffsetAt(bodyType, bodyFacing);
|
|
|
|
Vector3 bodyPos = new Vector3(deltaPos.x, deltaPos.z, 0);
|
|
Vector3 headPos = new Vector3(headOffset.x, headOffset.z, 0);
|
|
|
|
Vector3 appendagePos = PawnUtility.AppendageOffsetAt(bodyType, bodyFacing);
|
|
float appendageRotation = clip.GenitalAngle.Evaluate(clipPercent);
|
|
|
|
actorBody.transform.position = bodyPos + actor.GetFinalTransformOffset();
|
|
actorBody.transform.eulerAngles = new Vector3(0, 0, -bodyAngle);
|
|
|
|
actorBody.headRenderer.transform.localPosition = headPos;
|
|
actorBody.headRenderer.transform.eulerAngles = new Vector3(0, 0, -headAngle);
|
|
|
|
actorBody.appendageRenderer.transform.localPosition = new Vector3(appendagePos.x, appendagePos.z, 0f);
|
|
actorBody.appendageRenderer.transform.eulerAngles = new Vector3(0, 0, -appendageRotation);
|
|
|
|
actorBody.bodyRenderer.sprite = alienRaceDef.GetBodyTypeGraphic((CardinalDirection)bodyFacing, bodyType);
|
|
actorBody.headRenderer.sprite = alienRaceDef.isHumanoid ? alienRaceDef.GetHeadGraphic((CardinalDirection)headFacing) : null;
|
|
actorBody.appendageRenderer.sprite = requiresGenitals && alienRaceDef.isHumanoid && bodyFacing != 0 ? Resources.Load<Sprite>("Textures/Humanlike/Appendages/Appendage" + bodyFacing) : null;
|
|
|
|
actorBody.bodyRenderer.gameObject.SetActive(actorBody.bodyRenderer.sprite != null);
|
|
actorBody.headRenderer.gameObject.SetActive(actorBody.headRenderer.sprite != null);
|
|
actorBody.appendageRenderer.gameObject.SetActive(actorBody.appendageRenderer.sprite != null);
|
|
|
|
actorBody.bodyRenderer.sortingLayerName = clip.layer;
|
|
actorBody.headRenderer.sortingLayerName = clip.layer;
|
|
actorBody.headRenderer.sortingOrder = bodyFacing == 0 ? -1 : 1;
|
|
actorBody.appendageRenderer.sortingLayerName = clip.layer;
|
|
|
|
actorBody.bodyRenderer.flipX = bodyFacing == 3;
|
|
actorBody.headRenderer.flipX = headFacing == 3;
|
|
//actorBody.appendageRenderer.flipX = bodyFacing == 3;
|
|
|
|
actorBody.transform.localScale = new Vector3(alienRaceDef.scale, alienRaceDef.scale, alienRaceDef.scale);
|
|
|
|
// ActorKeyframeCard update
|
|
if (actorID != Workspace.actorID) continue;
|
|
if (ActorKeyframeCard.Instance.positionXField.isFocused == false) { ActorKeyframeCard.Instance.positionXField.text = bodyPos.x.ToString("0.000"); }
|
|
if (ActorKeyframeCard.Instance.positionZField.isFocused == false) { ActorKeyframeCard.Instance.positionZField.text = bodyPos.y.ToString("0.000"); }
|
|
if (ActorKeyframeCard.Instance.rotationField.isFocused == false) { ActorKeyframeCard.Instance.rotationField.text = bodyAngle.ToString("0.000"); }
|
|
if (ActorKeyframeCard.Instance.headBobField.isFocused == false) { ActorKeyframeCard.Instance.headBobField.text = headBob.ToString("0.000"); }
|
|
if (ActorKeyframeCard.Instance.headRotationField.isFocused == false) { ActorKeyframeCard.Instance.headRotationField.text = headAngle.ToString("0.000"); }
|
|
if (ActorKeyframeCard.Instance.appendageRotationField.isFocused == false) { ActorKeyframeCard.Instance.appendageRotationField.text = appendageRotation.ToString("0.000"); }
|
|
}
|
|
}
|
|
|
|
public void Initialize()
|
|
{
|
|
isDirty = true;
|
|
Debug.Log("Initializing animation preview");
|
|
|
|
foreach (Transform child in transform)
|
|
{ child.gameObject.SetActive(true); }
|
|
|
|
InitializeAnimationTimeline();
|
|
StageCardManager.Instance.Initialize();
|
|
|
|
isDirty = false;
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
isAnimating = false;
|
|
timeSinceLastUpdate = 0;
|
|
cycleIndex = 0;
|
|
|
|
MakeDirty();
|
|
}
|
|
|
|
public void InitializeAnimationTimeline()
|
|
{
|
|
isTimelineDirty = true;
|
|
|
|
cyclesNormalField.text = Mathf.Max(Mathf.CeilToInt((float)Workspace.animationDef.animationStages[Workspace.stageID].playTimeTicks / Workspace.StageWindowSize), 1).ToString();
|
|
cyclesFastField.text = Mathf.Max(Mathf.CeilToInt((float)Workspace.animationDef.animationStages[Workspace.stageID].playTimeTicksQuick / Workspace.StageWindowSize), 0).ToString();
|
|
|
|
Workspace.animationDef.animationStages[Workspace.stageID].isLooping = int.Parse(cyclesNormalField.text) > 1 ? true : false;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
animationClipLengthField.text = Workspace.StageWindowSize.ToString();
|
|
stageTimelineSlider.maxValue = Workspace.StageWindowSize;
|
|
|
|
isTimelineDirty = false;
|
|
|
|
foreach (AnimationTimeline timeline in animationTimelines.GetComponentsInChildren<AnimationTimeline>())
|
|
{ timeline.InitiateUpdateOfGhostFrames(); }
|
|
}
|
|
|
|
public void AddActor()
|
|
{
|
|
Actor actor = new Actor();
|
|
actor.MakeNew();
|
|
|
|
Workspace.Instance.RecordEvent("Actor addition");
|
|
}
|
|
|
|
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;
|
|
|
|
Workspace.Instance.RecordEvent("Actor deletion");
|
|
}
|
|
|
|
public void AddPawnKeyframe()
|
|
{
|
|
PawnAnimationClip clip = Workspace.Instance.GetCurrentPawnAnimationClip();
|
|
List<PawnKeyframe> 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.GetComponentsInChildren<AnimationTimeline>()[Workspace.actorID].AddPawnKeyFrame(keyframe.keyframeID);
|
|
|
|
Workspace.Instance.RecordEvent("Keyframe addition");
|
|
}
|
|
|
|
public void ClonePawnKeyframe()
|
|
{
|
|
List<PawnKeyframe> keyframesToClone = Workspace.Instance.GetPawnKeyframesByID(Workspace.keyframeID);
|
|
|
|
foreach (PawnKeyframe keyframe in keyframesToClone)
|
|
{
|
|
PawnAnimationClip clip = Workspace.Instance.GetAnimationClipThatOwnsKeyframe(keyframe.keyframeID, out int clipID);
|
|
|
|
if (clip == null)
|
|
{ Debug.LogWarning("Cannot clone pawn keyframe - no clip owns this keyframe"); continue; }
|
|
|
|
if (clip.keyframes.FirstOrDefault(x => x.atTick == stageTick) != null)
|
|
{ Debug.LogWarning("Cannot clone pawn keyframe - a keyframe already exists at this tick"); return; }
|
|
|
|
PawnKeyframe cloneFrame = keyframe.Copy();
|
|
cloneFrame.GenerateKeyframeID(clipID);
|
|
cloneFrame.atTick = stageTick;
|
|
|
|
PawnKeyframe nextKeyframe = clip.keyframes.FirstOrDefault(x => x.atTick > stageTick);
|
|
|
|
if (nextKeyframe != null)
|
|
{ clip.keyframes.Insert(clip.keyframes.IndexOf(nextKeyframe), cloneFrame); }
|
|
|
|
else
|
|
{ clip.keyframes.Add(cloneFrame); }
|
|
|
|
clip.BuildSimpleCurves();
|
|
|
|
animationTimelines.GetComponentsInChildren<AnimationTimeline>()[clipID].AddPawnKeyFrame(cloneFrame.keyframeID);
|
|
}
|
|
|
|
Workspace.Instance.RecordEvent("Keyframe clone");
|
|
}
|
|
|
|
public void CopyPawnKeyframes()
|
|
{
|
|
Workspace.copiedKeyframes.Clear();
|
|
|
|
List<PawnKeyframe> keyframesToClone = Workspace.Instance.GetPawnKeyframesByID(Workspace.keyframeID);
|
|
|
|
foreach (PawnKeyframe keyframe in keyframesToClone)
|
|
{ Workspace.copiedKeyframes.Add(keyframe.Copy()); }
|
|
}
|
|
|
|
public void PastePawnKeyframes()
|
|
{
|
|
MakeTimelineDirty();
|
|
|
|
int originalWindowSize = Workspace.StageWindowSize;
|
|
|
|
|
|
List<int> actorsInvolved = Workspace.copiedKeyframes.Select(x => x.actorID)?.ToList();
|
|
actorsInvolved = actorsInvolved?.Distinct()?.ToList();
|
|
|
|
if (actorsInvolved.NullOrEmpty()) { Debug.Log("Cannot paste keyframes - there were no copied keyframes to paste"); return; }
|
|
if (actorsInvolved.Count > 1 && actorsInvolved.Contains(Workspace.actorID) == false) { Debug.Log("Cannot paste keyframes - keyframes copied across multiple timelines can only be pasted back into these source timelines"); return; }
|
|
|
|
int earliestTick = actorsInvolved.Count == 1 ? Workspace.Instance.GetEarliestAtTickInCopiedKeyframes(actorsInvolved[0]) : Workspace.Instance.GetEarliestAtTickInCopiedKeyframes(Workspace.actorID);
|
|
if (earliestTick < 1) { Debug.Log("Unknown error occured during keyframe paste operation"); return; }
|
|
|
|
foreach (PawnKeyframe copiedKeyframe in Workspace.copiedKeyframes)
|
|
{
|
|
int tickToPasteAt = stageTick + (copiedKeyframe.atTick.Value - earliestTick);
|
|
|
|
if (tickToPasteAt < 1) continue;
|
|
if (tickToPasteAt > Workspace.StageWindowSize)
|
|
{
|
|
if (stretchKeyframesToggle.isOn)
|
|
{ ResizeStageWindowSize(tickToPasteAt); }
|
|
|
|
else continue;
|
|
}
|
|
|
|
int targetActorID = actorsInvolved.Count == 1 ? Workspace.actorID : copiedKeyframe.actorID;
|
|
|
|
if (Workspace.Instance.DoesPawnKeyframeExistAtTick(Workspace.stageID, targetActorID, tickToPasteAt))
|
|
{
|
|
PawnKeyframe oldKeyframe = Workspace.animationDef.animationStages[Workspace.stageID].animationClips[targetActorID].keyframes.First(x => x.atTick == tickToPasteAt);
|
|
RemovePawnKeyframe(targetActorID, oldKeyframe.keyframeID, true);
|
|
}
|
|
|
|
PawnKeyframe clonedKeyframe = copiedKeyframe.Copy();
|
|
clonedKeyframe.GenerateKeyframeID(targetActorID);
|
|
clonedKeyframe.atTick = tickToPasteAt;
|
|
|
|
PawnAnimationClip clip = Workspace.animationDef.animationStages[Workspace.stageID].animationClips[targetActorID];
|
|
PawnKeyframe nextKeyframe = clip.keyframes.FirstOrDefault(x => x.atTick > tickToPasteAt);
|
|
|
|
if (nextKeyframe != null)
|
|
{ clip.keyframes.Insert(clip.keyframes.IndexOf(nextKeyframe), clonedKeyframe); }
|
|
|
|
else
|
|
{ clip.keyframes.Add(clonedKeyframe); }
|
|
|
|
clip.BuildSimpleCurves();
|
|
animationTimelines.GetComponentsInChildren<AnimationTimeline>()[clonedKeyframe.actorID].AddPawnKeyFrame(clonedKeyframe.keyframeID);
|
|
}
|
|
|
|
if (originalWindowSize != Workspace.StageWindowSize)
|
|
{
|
|
StretchKeyframes(originalWindowSize);
|
|
ResizeStageWindowSize(originalWindowSize);
|
|
}
|
|
|
|
Workspace.Instance.RecordEvent("Keyframe pasted");
|
|
}
|
|
|
|
public void RemovePawnKeyframe()
|
|
{
|
|
foreach (int keyframeID in Workspace.keyframeID)
|
|
{
|
|
if (Workspace.Instance.GetAnimationClipThatOwnsKeyframe(keyframeID, out int clipID) != null)
|
|
{ RemovePawnKeyframe(clipID, keyframeID); }
|
|
}
|
|
}
|
|
|
|
public void RemovePawnKeyframe(int actorID, int keyframeID, bool force = false)
|
|
{
|
|
PawnKeyframe keyframe = Workspace.Instance.GetPawnKeyframe(actorID, keyframeID);
|
|
PawnAnimationClip clip = Workspace.animationDef.animationStages[Workspace.stageID].animationClips[actorID];
|
|
|
|
if (keyframe == null || clip == null) return;
|
|
|
|
if (keyframe.atTick == Constants.minTick && force == false)
|
|
{ Debug.LogWarning("Cannot delete key frame - the first key frame of an animation clip cannot be deleted"); return; }
|
|
|
|
if (clip.keyframes.Count <= 2 && force == false)
|
|
{ Debug.LogWarning("Cannot delete key frame - an animation clip must have two or more keyframes"); return; }
|
|
|
|
animationTimelines.GetComponentsInChildren<AnimationTimeline>()[actorID].RemovePawnKeyFrame(keyframe.keyframeID);
|
|
|
|
clip.keyframes.Remove(keyframe);
|
|
clip.BuildSimpleCurves();
|
|
|
|
Workspace.Instance.RecordEvent("Keyframe deletion");
|
|
}
|
|
|
|
public void ToggleAnimation()
|
|
{
|
|
isAnimating = !isAnimating;
|
|
}
|
|
|
|
public void ToggleActorManipulationMode(int mode)
|
|
{
|
|
Workspace.actorManipulationMode = (ActorManipulationMode)mode;
|
|
}
|
|
|
|
public void OnStageTimelineSliderChange()
|
|
{
|
|
if (Workspace.animationDef == null) return;
|
|
|
|
if (stageTick != (int)stageTimelineSlider.value)
|
|
{
|
|
stageTick = (int)stageTimelineSlider.value;
|
|
animationClipTimeField.text = stageTick.ToString();
|
|
}
|
|
}
|
|
|
|
public void OnAnimationClipTimeFieldChange()
|
|
{
|
|
if (Workspace.animationDef == null) return;
|
|
|
|
int.TryParse(animationClipTimeField.text, out int newStageTick);
|
|
stageTick = Mathf.Clamp(newStageTick, Constants.minTick, Workspace.StageWindowSize);
|
|
stageTimelineSlider.value = stageTick;
|
|
}
|
|
|
|
public void OnAnimationClipLengthFieldChange()
|
|
{
|
|
if (Workspace.animationDef == null) return;
|
|
|
|
int.TryParse(animationClipLengthField.text, out int newStageWindowSize);
|
|
newStageWindowSize = Mathf.Clamp(newStageWindowSize, Constants.minAnimationClipLength, Constants.maxAnimationClipLength);
|
|
|
|
Debug.Log("Resizing animation clip length to " + newStageWindowSize.ToString() + " ticks.");
|
|
|
|
if (stretchKeyframesToggle.isOn)
|
|
{ StretchKeyframes(newStageWindowSize); }
|
|
|
|
else
|
|
{
|
|
for (int i = 0; i < Workspace.animationDef.animationStages[Workspace.stageID].animationClips.Count; i++)
|
|
{
|
|
PawnAnimationClip clip = Workspace.animationDef.animationStages[Workspace.stageID].animationClips[i];
|
|
List<PawnKeyframe> keyframes = clip.keyframes.Where(x => x.atTick > newStageWindowSize)?.ToList();
|
|
|
|
if (keyframes.NullOrEmpty())
|
|
{ continue; }
|
|
|
|
foreach (PawnKeyframe keyframe in keyframes)
|
|
{
|
|
RemovePawnKeyframe(i, keyframe.keyframeID);
|
|
|
|
if (Workspace.animationDef.animationStages[Workspace.stageID].animationClips[i].keyframes.Count <= 2)
|
|
{ break; }
|
|
}
|
|
}
|
|
}
|
|
|
|
ResizeStageWindowSize(newStageWindowSize);
|
|
Workspace.Instance.RecordEvent("Stage length");
|
|
}
|
|
|
|
public void StretchKeyframes(int newStageWindowSize)
|
|
{
|
|
float scale = (float)newStageWindowSize / Workspace.StageWindowSize;
|
|
|
|
foreach (PawnAnimationClip clip in Workspace.animationDef.animationStages[Workspace.stageID].animationClips)
|
|
{
|
|
foreach (PawnKeyframe keyframe in clip.keyframes)
|
|
{
|
|
keyframe.tickDuration = Mathf.RoundToInt(keyframe.tickDuration * scale);
|
|
keyframe.atTick = null;
|
|
}
|
|
|
|
clip.BuildSimpleCurves();
|
|
}
|
|
}
|
|
|
|
public void ResizeStageWindowSize(int newStageWindowSize)
|
|
{
|
|
animationClipLengthField.text = newStageWindowSize.ToString();
|
|
|
|
Workspace.animationDef.animationStages[Workspace.stageID].stageWindowSize = newStageWindowSize;
|
|
Workspace.animationDef.animationStages[Workspace.stageID].playTimeTicks = newStageWindowSize * int.Parse(cyclesNormalField.text);
|
|
Workspace.animationDef.animationStages[Workspace.stageID].playTimeTicksQuick = newStageWindowSize * int.Parse(cyclesFastField.text);
|
|
}
|
|
|
|
public void OnCycleNormalFieldChange()
|
|
{
|
|
if (Workspace.animationDef == null) return;
|
|
|
|
if (int.TryParse(cyclesNormalField.text, out int cycles))
|
|
{
|
|
cycles = cycles <= 0 ? 1 : cycles;
|
|
cyclesNormalField.text = cycles.ToString();
|
|
|
|
Workspace.animationDef.animationStages[Workspace.stageID].playTimeTicks = cycles * Workspace.StageWindowSize;
|
|
Workspace.animationDef.animationStages[Workspace.stageID].isLooping = cycles > 1;
|
|
|
|
foreach(AnimationTimeline animationTimeline in animationTimelines.GetComponentsInChildren<AnimationTimeline>())
|
|
{ animationTimeline.InitiateUpdateOfGhostFrames(); }
|
|
}
|
|
|
|
Workspace.Instance.RecordEvent("Cycle count (normal)");
|
|
}
|
|
|
|
public void OnCycleFastFieldChange()
|
|
{
|
|
if (Workspace.animationDef == null) return;
|
|
|
|
if (int.TryParse(cyclesFastField.text, out int fastCycles))
|
|
{
|
|
fastCycles = fastCycles < 0 ? 0 : fastCycles;
|
|
|
|
int.TryParse(cyclesNormalField.text, out int cycles);
|
|
if (fastCycles > cycles) fastCycles = cycles;
|
|
cyclesFastField.text = fastCycles.ToString();
|
|
|
|
Workspace.animationDef.animationStages[Workspace.stageID].playTimeTicksQuick = fastCycles * Workspace.StageWindowSize;
|
|
}
|
|
|
|
Workspace.Instance.RecordEvent("Cycle count (fast)");
|
|
}
|
|
|
|
public void OnPlayBackSpeedChange()
|
|
{
|
|
if (float.TryParse(playBackSpeedField.text, out playBackSpeed))
|
|
{ playBackSpeed = Mathf.Clamp(playBackSpeed, 0.01f, 16f); }
|
|
|
|
playBackSpeedField.text = playBackSpeed.ToString();
|
|
}
|
|
|
|
private int lastactorCount = 0;
|
|
private int lastStageID = 0;
|
|
private int lastStageCount = 0;
|
|
private int lastStageWindowSize = 0;
|
|
|
|
public bool AnimationTimelinesNeedUpdate()
|
|
{
|
|
if (Workspace.animationDef == null) return false;
|
|
|
|
bool update = isTimelineDirty;
|
|
|
|
if (lastStageID != Workspace.stageID)
|
|
{ update = true; }
|
|
|
|
if (lastStageCount != Workspace.animationDef.animationStages.Count)
|
|
{ update = true; }
|
|
|
|
if (lastactorCount != Workspace.animationDef.actors.Count)
|
|
{ update = true; }
|
|
|
|
if (lastStageWindowSize != Workspace.StageWindowSize)
|
|
{ update = true; }
|
|
|
|
if (update)
|
|
{
|
|
lastStageID = Workspace.stageID;
|
|
lastStageCount = Workspace.animationDef.animationStages.Count;
|
|
lastactorCount = Workspace.animationDef.actors.Count;
|
|
lastStageWindowSize = Workspace.StageWindowSize;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|