Gradius style options - follow

Hello everyone

I want to experiment with a gradius style game.

The problem I’m facing is concerning the options.
In any other setting I’d store the ship’s position over the last x frames in an array updating only if the ship actually moves.
Updating the option would mean setting it’s position to an earlier ship position received from this array.

As Unity works with time and a non-fixed framerate I fear this approach won’t work.
This could take up way too much memory and would cause a delayed lag effect if for some reason the framerate drops at a certain point during gameplay (the options would experience this lag a bit later).

Does anyone have a suggestion as to how to tackle this problem?
Thanks in advance.

Found the answer by doing some tinkering.

I’m working with 2d local positions (z is fixed).

I just realized this code doesn’t need to be global and would be better if put on the ship itself, but this should be sufficient information for people facing the same problem.

// Option data
// Max amount of options
static var maxOptionCount : int = 3;
// How many frames is the option behind the ship?
static var optionFrameDelay : int = 40;

// FPS to store in memory
static var posMemoryFps : int = 100;
// Amount of frames to store in memory
static var storeFrameCount : int = (maxOptionCount+1) * optionFrameDelay;
// Positon memory storage (player,frame,axis)
static var shipPositions : float[,,] = new float[2,storeFrameCount,2];
// Amount of time the ship spent moving
static var shipMoveTime : float = 0.0;

// Retrieves the position for the nth option relative to the player's ship (defined by dist)
public static function getOptionPos(player, dist) {
	// Distance in frames
	var frameDist = dist * optionFrameDelay;
	// Return array containing an x and y position
	var returnPos : float[] = new float[2];
	returnPos[0] = shipPositions[player,frameDist,0];
	returnPos[1] = shipPositions[player,frameDist,1];
	return returnPos;
}

// Empties the option memory
public static function resetShipPosMemory(player,x,y) {
	shipMoveTime = 0.0;
	// By default all positions are the current position of the ship
	for (var pos = 0; pos < storeFrameCount; pos++) {
		shipPositions[player,pos,0] = x;
		shipPositions[player,pos,1] = y;
	}
}

// Adds a new position to the storage
public static function addShipPos(player,x,y) {
	// Shift all ship positions one to the right
	for (var pos = storeFrameCount - 1; pos > 0; pos--) {
		shipPositions[player,pos,0] = shipPositions[player,pos-1,0];
		shipPositions[player,pos,1] = shipPositions[player,pos-1,1];
	}
	// Put the new position up front
	shipPositions[player,0,0] = x;
	shipPositions[player,0,1] = y;
}

public static function getDeltaPos(prevX, prevY, currX, currY, part) {
	var deltaPos : float[] = new float[2];
	// Difference between the positions over time
	diffPosX = (currX - prevX) * part;
	diffPosY = (currY - prevY) * part;
	// Add the difference to the previous position (subtract if negative)
	deltaPos[0] = prevX + diffPosX;
	deltaPos[1] = prevY + diffPosY;
	
	return deltaPos;
}

public static function setShipPositionMemory(player,deltaTime,x,y) {
	// Store previous time locally
	var prevTime = shipMoveTime;
	// Update last move time globally
	shipMoveTime += deltaTime;
	// Does the x/y differ from the previous? Then we moved
	if (x != shipPositions[player,0,0] || y != shipPositions[player,0,1]) {
		// The difference between both times must be at least 1 posMemoryFps unit
		var posMemoryFrameTime : float = 1.0 / posMemoryFps;
		// Framenumbers rounded down for the previous and the current position
		var prevFrameNo : int = Mathf.FloorToInt(prevTime/posMemoryFrameTime);
		var thisFrameNo : int = Mathf.FloorToInt(shipMoveTime/posMemoryFrameTime);
		// If they do not match, we've moved on at least 1 frame
		if (prevFrameNo < thisFrameNo) {
			// In case of low game fps the framecount might be bigger than one
			var frameCount = thisFrameNo - prevFrameNo;
			// Get the last stored ship position
			var lastShipX = shipPositions[player,0,0];
			var lastShipY = shipPositions[player,0,1];
			// Add one ship position for each frame
			for (var deltaFrame = 0; deltaFrame < frameCount; deltaFrame++) {
				if (deltaFrame == frameCount-1) {
					addShipPos(player,x,y);
				} else {
					// Time elapsed between the keyframe and the current time
					var moveDeltaTime : float = shipMoveTime - ((prevFrameNo + deltaFrame) * posMemoryFrameTime);
					// Float between 0 and 1 to determine the percentage of the frame time relative to the current time
					var movePart : float = (posMemoryFrameTime * (deltaFrame+1)) / moveDeltaTime;
					// Calculated frame position
					var framePosition : float[] = getDeltaPos(lastShipX,lastShipY,x,y,movePart);
					
					// Store the frame position
					addShipPos(player,framePosition[0],framePosition[1]);
				}
			}
		}
	}
}

In the movement script you probably already have for the ship add the following:

#pragma strict
var player : int = 0;

function Start() {
	globals.resetShipPosMemory(player, transform.localPosition.x, transform.localPosition.y);
}

function Update () {

	// ..... your movement code here ...... //

	// Store the ship's position for following options
	globals.setShipPositionMemory(player, Time.deltaTime, transform.localPosition.x, transform.localPosition.y);
}

And this is for the option:

#pragma strict

var player : int = 0;
var distanceFromShip : int = 1;

function Start () {

}

function Update () {
	var position : float[] = globals.getOptionPos(player,distanceFromShip);
	transform.localPosition = Vector3(position[0], position[1], 0);
}

Apart from the obvious fact that this doesn’t need to be global, Any constructive criticism would be welcome.
I’ve still only got about a week of Unity3d experience

Can someone translate this to C#?

Gradius style Options. This only worked when I assigned the followTransform to what each option is following. So the first ‘option’ is following the player, the 2nd ‘option’ is following the 1st ‘option’ etc…

using System.Collections.Generic;
using UnityEngine;

public class OptionMovement : MonoBehaviour
{
public Transform followTransform;
[SerializeField] int optionFrameDelay = 30;

//list of positions the player took
private List positionList = new List();

private void Start()
{
for (int i = 0; i < optionFrameDelay; i++)
{
positionList.Add(followTransform.position);
}
}

void Update()
{
//if the player position changed
if (followTransform.hasChanged)
{
//Follow the player
positionList.Add(followTransform.position);
if (positionList.Count > optionFrameDelay)
{
positionList.RemoveAt(0);
transform.position = positionList[0];
}
followTransform.hasChanged = false;
}
}
}