I implemented 2 different types of Time Reversal Ability in this project.
Below are some detailed example of both ability from above video.
Version 1 :
Storage of Time - Unlimited Seconds
Reversal Time Rate - Normal Speed
Control Over Ability - Can travel back Till toggle turned off or First position reached
Video and Code Snippet are below.
private void Update()
{
// Adding time when the update starts
currentDataTimer += Time.deltaTime;
totalTimeBeforeAbility += Time.deltaTime;
// Checking if the player is not currently time walking
if (canCollectTimeWalkData)
{
if (currentDataTimer >= secondsBTWData)
{
// Getting data
timeWalkData.Add(GetTimeWalkData());
currentDataTimer = 0f;
}
}
// Drawing gizmos of the data we stored earlier
for (int i = 0; i < timeWalkData.Count - 1; i++)
{
Debug.DrawLine(timeWalkData[i].objectPosition, timeWalkData[i + 1].objectPosition);
}
if (Input.GetKeyDown(KeyCode.E))
{
// Stop collecting data when ability is performed
canCollectTimeWalkData = false;
StartCoroutine(Cast());
// Stopping the rotation of player and camera
if (isPlayer)
cameraController.Lock(true);
}
else if (Input.GetKeyUp(KeyCode.E))
{
StopCoroutine(Cast());
timeWalkData.Clear();
totalTimeBeforeAbility = 0f;
if (isPlayer)
cameraController.Lock(false);
canCollectTimeWalkData = true;
}
}
// Getting data
public TimeWalkData GetTimeWalkData()
{
//for player, to store camera info as well
if (isPlayer)
{
return new TimeWalkData
{
objectPosition = transform.position,
objectRotation = transform.rotation,
cameraPosition = cameraController.transform.position,
cameraRotation = cameraController.transform.rotation
};
}
//for platform, only to store position and rotation of platform
else
{
return new TimeWalkData
{
objectPosition = transform.position,
objectRotation = transform.rotation,
};
}
}
// Overriding the cast method from Ability
public override IEnumerator Cast()
{
// Stop collecting data when ability is performed
//canCollectTimeWalkData = false;
// Setting fields for calculation
float secondsForEachData = totalTimeBeforeAbility / timeWalkData.Count;
Vector3 currentDataStartPosition = transform.position;
Quaternion currentDataStartRotation = transform.rotation;
Vector3 currentDataCameraPosotion = Vector3.zero;
Quaternion currentDataCameraRotation = Quaternion.identity;
//if player, set camera position and rotation too
//if platform, return
if (isPlayer)
{
currentDataCameraPosotion = cameraController.transform.position;
currentDataCameraRotation = cameraController.transform.rotation;
}
// Only perform time walk if there are any data in list
while (timeWalkData.Count > 0)
{
// Set the ability time to zero
float abilityTime = 0;
// Performing the ability untill next list's data
while (abilityTime < secondsForEachData && !canCollectTimeWalkData)
{
transform.position = Vector3.Lerp(currentDataStartPosition, timeWalkData[timeWalkData.Count - 1].objectPosition, abilityTime / secondsForEachData);
transform.rotation = Quaternion.Slerp(currentDataStartRotation, timeWalkData[timeWalkData.Count - 1].objectRotation, abilityTime / secondsForEachData);
if (isPlayer)
{
//cameraController.transform.position = Vector3.Lerp(currentDataCameraPosotion, timeWalkData[timeWalkData.Count - 1].cameraPosition, abilityTime / secondsForEachData);
cameraController.transform.rotation = Quaternion.Slerp(currentDataCameraRotation, timeWalkData[timeWalkData.Count - 1].cameraRotation, abilityTime / secondsForEachData);
}
abilityTime += Time.deltaTime;
yield return null;
}
// Adding next list's data to current data
if (!canCollectTimeWalkData)
{
currentDataStartPosition = timeWalkData[timeWalkData.Count - 1].objectPosition;
currentDataStartRotation = timeWalkData[timeWalkData.Count - 1].objectRotation;
if (isPlayer)
{
currentDataCameraPosotion = timeWalkData[timeWalkData.Count - 1].cameraPosition;
currentDataCameraRotation = timeWalkData[timeWalkData.Count - 1].cameraRotation;
}
}
//positionCam = timeWalkData[0].cameraPosition;
// Remove performed data
if (!canCollectTimeWalkData)
timeWalkData.RemoveAt(timeWalkData.Count - 1);
}
//if (isPlayer)
// cameraController.Lock(false);
//
//canCollectTimeWalkData = true;
}
Version 2 :
Storage of Time - Limited Seconds
Reversal Time Rate - Fast-Paced Reversal
Control Over Ability - Can't Stop the Reversal in between until First position reached.
Video and Code Snippet are below.
private void Update()
{
// Adding time when the update starts
currentDataTimer += Time.deltaTime;
// Checking if the player is not currently time walking
if (canCollectTimeWalkData)
{
if (currentDataTimer >= secondsBTWData)
{
if (timeWalkData.Count >= maxTimeWalkData)
{
// Romove data from the start of the list
timeWalkData.RemoveAt(0);
}
// Getting data
timeWalkData.Add(GetTimeWalkData());
currentDataTimer = 0f;
}
}
// Drawing gizmos of the data we stored earlier
for (int i = 0; i < timeWalkData.Count - 1; i++)
{
Debug.DrawLine(timeWalkData[i].objectPosition, timeWalkData[i + 1].objectPosition);
}
if (Input.GetKeyDown(KeyCode.E))
{
StartCoroutine(Cast());
// Stopping the rotation of player and camera
if(isPlayer)
cameraController.Lock(true);
}
}
// Getting data
public TimeWalkData GetTimeWalkData()
{
//for player, to store camera info as well
if (isPlayer)
{
return new TimeWalkData
{
objectPosition = transform.position,
objectRotation = transform.rotation,
cameraPosition = cameraController.transform.position,
cameraRotation = cameraController.transform.rotation
};
}
//for platform, only to store position and rotation of platform
else
{
return new TimeWalkData
{
objectPosition = transform.position,
objectRotation = transform.rotation,
};
}
}
// Overriding the cast method from Ability
public override IEnumerator Cast()
{
// Stop collecting data when ability is performed
canCollectTimeWalkData = false;
// Setting fields for calculation
float secondsForEachData = timeWalkDuration / timeWalkData.Count;
Vector3 currentDataStartPosition = transform.position;
Quaternion currentDataStartRotation = transform.rotation;
Vector3 currentDataCameraPosotion = Vector3.zero;
Quaternion currentDataCameraRotation = Quaternion.identity;
//if player, set camera position and rotation too
//if platform, return
if (isPlayer)
{
currentDataCameraPosotion = cameraController.transform.position;
currentDataCameraRotation = cameraController.transform.rotation;
}
// Only perform time walk if there are any data in list
while (timeWalkData.Count > 0)
{
// Set the ability time to zero
float abilityTime = 0;
// Performing the ability untill next list's data
while (abilityTime < secondsForEachData)
{
transform.position = Vector3.Lerp(currentDataStartPosition, timeWalkData[timeWalkData.Count - 1].objectPosition, abilityTime / secondsForEachData);
transform.rotation = Quaternion.Slerp(currentDataStartRotation, timeWalkData[timeWalkData.Count - 1].objectRotation, abilityTime / secondsForEachData);
if (isPlayer)
{
//cameraController.transform.position = Vector3.Lerp(currentDataCameraPosotion, timeWalkData[timeWalkData.Count - 1].cameraPosition, abilityTime / secondsForEachData);
cameraController.transform.rotation = Quaternion.Slerp(currentDataCameraRotation, timeWalkData[timeWalkData.Count - 1].cameraRotation, abilityTime / secondsForEachData);
}
abilityTime += Time.deltaTime;
yield return null;
}
// Adding next list's data to current data
currentDataStartPosition = timeWalkData[timeWalkData.Count - 1].objectPosition;
currentDataStartRotation = timeWalkData[timeWalkData.Count - 1].objectRotation;
if (isPlayer)
{
currentDataCameraPosotion = timeWalkData[timeWalkData.Count - 1].cameraPosition;
currentDataCameraRotation = timeWalkData[timeWalkData.Count - 1].cameraRotation;
}
//positionCam = timeWalkData[0].cameraPosition;
// Remove performed data
timeWalkData.RemoveAt(timeWalkData.Count - 1);
}
if(isPlayer)
cameraController.Lock(false);
canCollectTimeWalkData = true;
}