Hi all, I’m trying to control the time of my player i.e., I want to play animation back and forth and not using Unity’s inverse feature by setting the speed to “-1” instead to do something like in game Braid.
My player is not a single sprite but consists separate sprite for every organ like head, torso, hands, etc. For some parts I’m using bones like Legs, torso, player cloths.
Everything work’s fine except at one point when the animation state changes from one to another say from ‘Run’ to ‘Idle’. When it shifts to new state then the objects with bones are not properly transformed i.e., they have some random value for ‘Position’ and ‘Rotation’. Like torso is little up instead of attached to the legs, similarly other sprite objects with bones are also not correctly configured.
While the objects which do not have bones like head and hands are right at their place.
Please help me. Thank you in advance.
I’m using three scripts Player Movement, TimeController, and RecordAnimation
I’m sharing TimeController and RecordAnimation fully while for Player sharing only Start() method coz rest is irrelevant.
RecordAnimation is also attached to player while TimeController is attached to any emptyobject
TC
using System.Collections.Generic;
using UnityEngine;
using static GetObjectData;
public class TimeController : MonoBehaviour
{
private GetObjectData getObjectData;
private bool isFacingRight = true;
public struct RecordedData
{
public Vector2 pos;
public Vector2 vel;
public float animationTime;
public List<Transform> bonesRecorded;
public AnimationName animationClipRecorded;
public Transform bone;
}
RecordedData[,] recordedData;
int recordMax = 100000;
int recordCount = 0;
int recordIndex = 0;
bool wasSteppingBack = false;
RecordAnimation[] recordAnimations;
private void Awake()
{
recordAnimations = GameObject.FindObjectsOfType<RecordAnimation>();
recordedData = new RecordedData[recordAnimations.Length, recordMax];
getObjectData = new GetObjectData();
}
private void Update()
{
if(GameBase.recordAnimation)
{
if(GameBase.recordingJustStarted)
{
GameBase.clipName = AnimationName.Idle;
GameBase.recordingJustStarted = false;
}
bool pause = Input.GetKey(KeyCode.H);
bool stepBack = Input.GetKey(KeyCode.R);
bool stepforward = Input.GetKey(KeyCode.F);
if (Input.inputString.Length > 0)
{
KeyCode keyCode;
if (System.Enum.TryParse(Input.inputString.ToUpper(), out keyCode))
{
RunAnimation(keyCode);
}
}
MovePlayer(Input.GetAxisRaw("Horizontal"));
if (stepBack && pause)
{
GameBase.playerVelocityHalt = true;
wasSteppingBack = true;
if (recordIndex > 0)
{
recordIndex--;
for (int objectIndex = 0; objectIndex < recordAnimations.Length; objectIndex++)
{
RecordAnimation recordAnimation = recordAnimations[objectIndex];
RecordedData data = recordedData[objectIndex, recordIndex];
recordAnimation.transform.position = data.pos;
recordAnimation.animationVelocity = data.vel;
recordAnimation.animationTime = data.animationTime;
recordAnimation.animationClipRecord = data.animationClipRecorded;
recordAnimation.SampleAnimationOfCurrentClip(recordAnimation.animationClipRecord);
}
}
}
else if (pause && stepforward)
{
GameBase.playerVelocityHalt = true;
wasSteppingBack = true;
if (recordIndex < recordCount)
{
recordIndex++;
for (int objectIndex = 0; objectIndex < recordAnimations.Length; objectIndex++)
{
RecordAnimation recordAnimation = recordAnimations[objectIndex];
RecordedData data = recordedData[objectIndex, recordIndex];
recordAnimation.transform.position = data.pos;
recordAnimation.animationVelocity = data.vel;
recordAnimation.animationTime = data.animationTime;
recordAnimation.animationClipRecord = data.animationClipRecorded;
recordAnimation.SampleAnimationOfCurrentClip(recordAnimation.animationClipRecord);
}
}
}
else if (!pause && !stepBack)
{
GameBase.playerVelocityHalt = false;
if (wasSteppingBack)
{
recordCount = recordIndex;
wasSteppingBack = false;
}
for (int objectIndex = 0; objectIndex < recordAnimations.Length; objectIndex++)
{
RecordAnimation recordAnimation = recordAnimations[objectIndex];
RecordedData data = new();
recordAnimation.animationClipRecord = GameBase.clipName;
data.pos = recordAnimation.transform.position;
data.vel = recordAnimation.animationVelocity;
data.animationTime = recordAnimation.animationTime;
data.animationClipRecorded = recordAnimation.animationClipRecord;
recordedData[objectIndex, recordCount] = data;
}
recordCount++;
recordIndex = recordCount;
foreach (RecordAnimation recordAnimation in recordAnimations)
{
recordAnimation.SyncAnimationTime(getObjectData.GetClipNumber(GameBase.clipName));
recordAnimation.SampleAnimationOfCurrentClip(GameBase.clipName);
}
}
}
}
private void RunAnimation(KeyCode keyCode)
{
switch (keyCode)
{
case KeyCode.Z:
GameBase.clipName = AnimationName.Attack1;
break;
}
}
private void MovePlayer(float horizontal)
{
Flip(horizontal);
GameBase.playerRigidBodyBase.velocity = new Vector2(horizontal * GameBase.playerBaseSpeed, GameBase.playerRigidBodyBase.velocity.y);
if (horizontal > 0 || horizontal < 0)
{
GameBase.clipName = AnimationName.Run;
}
if(horizontal == 0 && Input.GetKey(KeyCode.M) && Input.GetKeyUp(KeyCode.H))
{
ResetToIdle();
GameBase.clipName = AnimationName.Idle;
}
}
private void Flip(float horizontal)
{
if (isFacingRight && horizontal < 0f || !isFacingRight && horizontal > 0f)
{
isFacingRight = !isFacingRight;
Vector3 localScale = transform.localScale;
localScale.x *= -1f;
transform.localScale = localScale;
}
}
}
RA
using System.Collections.Generic;
using UnityEngine;
using static GetObjectData;
public class RecordAnimation : MonoBehaviour
{
[SerializeField]
private AnimationClip currentAnimationClip;
[HideInInspector]
public float animationTime;
[HideInInspector]
public Vector2 animationVelocity;
[HideInInspector]
public AnimationName animationClipRecord;
[HideInInspector]
public List<Transform> bonesRecord;
public GameObject bone;
public void SyncAnimationTime(int clipNumber)
{
if (currentAnimationClip != null)
{
animationTime += Time.deltaTime;
if (animationTime > currentAnimationClip[clipNumber].length)
{
animationTime = animationTime - currentAnimationClip[clipNumber].length;
}
}
}
public void SampleAnimationOfCurrentClip(AnimationName animationName)
{
if (currentAnimationClip != null)
{
switch (animationName)
{
case AnimationName.Idle:
currentAnimationClip[0].SampleAnimation(gameObject, animationTime);
break;
case AnimationName.Run:
currentAnimationClip[1].SampleAnimation(gameObject, animationTime);
break;
case AnimationName.Attack1:
currentAnimationClip[2].SampleAnimation(gameObject, animationTime);
break;
case AnimationName.Jump:
currentAnimationClip[3].SampleAnimation(gameObject, animationTime);
break;
case AnimationName.JumpFinish:
currentAnimationClip[4].SampleAnimation(gameObject, animationTime);
break;
default:
break;
}
}
}
//public void IdleAnimation()
//{
// currentAnimationClip[0].SampleAnimation(gameObject, 0.0f);
//}
}
Player Start() method
void Start()
{
playerAnimator = GetComponent();
playerAnimation = GetComponent<Animation>();
GameBase.playerBaseSpeed = baseSpeed;
GameBase.playerJumpSpeed = jumpSpeed;
GameBase.playerAnimatorBase = playerAnimator;
GameBase.recordAnimation = false;
GameBase.playerAnimatorBase.enabled = true;
GameBase.recordingJustStarted = true;
}
I disable the Animator when doing animation capturing/recording and rewinding so that SampleAnimation can be used.