Abstracting the movement of sprites as preparation for vehicle sprites.

This commit is contained in:
Bas
2020-04-26 15:38:48 +02:00
parent e652f34784
commit cd480e6ba3
10 changed files with 363 additions and 195 deletions

View File

@@ -1,4 +1,3 @@
#include <openrct2/world/Sprite.h>
#include "Openrct2-dll.h"
@@ -6,12 +5,15 @@
extern "C"
{
// Gets the amount of sprites currently active for the given type.
// All type possibilities are found in the 'SPRITE_LIST'-enum in 'world/Sprite.h'.
EXPORT int GetSpriteCount(int spriteType)
{
return gSpriteListCount[spriteType];
}
// Loads all the peeps into the specified buffer, returns the total amount of peeps loaded.
EXPORT int GetAllPeeps(Peep* peeps, int arraySize)
{
Peep* peep;
@@ -28,4 +30,24 @@ extern "C"
}
return peepCount;
}
// Loads all the vehicles into the specified buffer, returns the total amount of vehicles loaded.
EXPORT int GetAllVehicles(Vehicle* vehicles, int arraySize)
{
Vehicle* vehicle;
int vehicleCount = 0;
for (uint16_t i = gSpriteListHead[SPRITE_LIST_VEHICLE]; i != SPRITE_INDEX_NULL; i = vehicle->next)
{
vehicle = &get_sprite(i)->vehicle;
vehicles[vehicleCount] = *vehicle;
vehicleCount++;
if (vehicleCount >= arraySize)
break;
}
return vehicleCount;
}
}

View File

@@ -1,4 +1,3 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
@@ -7,74 +6,8 @@ namespace OpenRCT2.Unity
/// <summary>
/// Controller which moves and updates all the peeps in the park.
/// </summary>
[RequireComponent(typeof(Map))]
public class PeepController : MonoBehaviour
public class PeepController : SpriteController<Peep>
{
[SerializeField] GameObject peepPrefab;
const int MaxPeeps = 8000;
Peep[] peepBuffer;
Dictionary<ushort, PeepObject> peepObjects;
int currentUpdateTick;
/// <summary>
/// Internal peep object.
/// </summary>
class PeepObject
{
public int bufferIndex;
public GameObject gameObject;
public float timeSinceStart;
public int lastUpdate;
public Vector3 from;
public Vector3 towards;
}
void Start()
{
peepBuffer = new Peep[MaxPeeps];
int amount = OpenRCT2.GetAllPeeps(peepBuffer);
peepObjects = new Dictionary<ushort, PeepObject>(amount);
for (int i = 0; i < amount; i++)
{
AddPeep(i, ref peepBuffer[i]);
}
}
/// <summary>
/// Update ~40 per second to get the peep information from OpenRCT2.
/// </summary>
void FixedUpdate()
{
currentUpdateTick++;
int amount = OpenRCT2.GetAllPeeps(peepBuffer);
for (int i = 0; i < amount; i++)
{
SetPeepPositions(i, ref peepBuffer[i]);
}
}
/// <summary>
/// Update and lerp the peeps every frame for smoother transitions.
/// </summary>
void LateUpdate()
{
foreach (var peep in peepObjects.Values)
{
UpdatePeepPosition(peep);
}
}
/// <summary>
/// Find the associated peep id for the specified gameobject, or
@@ -82,10 +15,10 @@ namespace OpenRCT2.Unity
/// </summary>
public ushort FindPeepIdForGameObject(GameObject peepObject)
{
var entry = peepObjects.FirstOrDefault(p => p.Value.gameObject == peepObject);
var entry = spriteObjects.FirstOrDefault(p => p.Value.gameObject == peepObject);
int bufferIndex = entry.Value.bufferIndex;
return peepBuffer[bufferIndex].Id;
return spriteBuffer[bufferIndex].Id;
}
@@ -95,11 +28,61 @@ namespace OpenRCT2.Unity
/// </summary>
public Peep? GetPeepById(ushort peepId)
{
if (!peepObjects.TryGetValue(peepId, out PeepObject peepObject))
if (!spriteObjects.TryGetValue(peepId, out SpriteObject peepObject))
return null;
int bufferIndex = peepObject.bufferIndex;
return peepBuffer[bufferIndex];
return spriteBuffer[bufferIndex];
}
/// <summary>
/// Gets all the peep sprites from the dll hook.
/// </summary>
protected override int FillSpriteBuffer(Peep[] buffer)
=> OpenRCT2.GetAllPeeps(buffer);
/// <summary>
/// Sets the name and colors of the peep sprite.
/// </summary>
protected override SpriteObject AddSprite(int index, ref Peep sprite)
{
SpriteObject spriteObject = base.AddSprite(index, ref sprite);
GameObject obj = spriteObject.gameObject;
ushort id = sprite.Id;
PeepType type = sprite.type;
obj.name = $"{type} {id}";
UpdateColours(obj, sprite);
return spriteObject;
}
/// <summary>
/// Sets the new start and end positions for this game tick.
/// </summary>
///
protected override SpriteObject UpdateSprite(int index, ref Peep sprite)
{
SpriteObject obj = base.UpdateSprite(index, ref sprite);
obj.gameObject.GetComponent<PeepInformation>().UpdateInformation(sprite);
return obj;
}
void UpdateColours(GameObject peepObj, Peep peep)
{
GameObject tshirt = peepObj.transform.GetChild(0).gameObject;
GameObject trousers = peepObj.transform.GetChild(1).gameObject;
var tshirtRenderer = tshirt.GetComponent<Renderer>();
var trousersRenderer = trousers.GetComponent<Renderer>();
tshirtRenderer.material.color = DecodeColour(peep.tshirtColour);
trousersRenderer.material.color = DecodeColour(peep.trousersColour);
}
@@ -209,126 +192,5 @@ namespace OpenRCT2.Unity
}
return colourRGB;
}
void UpdateColours(GameObject peepObj, Peep peep)
{
GameObject tshirt = peepObj.transform.GetChild(0).gameObject;
GameObject trousers = peepObj.transform.GetChild(1).gameObject;
var tshirtRenderer = tshirt.GetComponent<Renderer>();
var trousersRenderer = trousers.GetComponent<Renderer>();
tshirtRenderer.material.color = DecodeColour(peep.tshirtColour);
trousersRenderer.material.color = DecodeColour(peep.trousersColour);
}
/// <summary>
/// Adds a new peep object to the dictionary.
/// </summary>
PeepObject AddPeep(int index, ref Peep peep)
{
ushort id = peep.Id;
PeepType type = peep.type;
GameObject peepObj = Instantiate(peepPrefab, Vector3.zero, Quaternion.identity, transform);
peepObj.name = $"{type} {id}";
UpdateColours(peepObj, peep);
Vector3 position = Map.CoordsToVector3(peep.Position);
PeepObject instance = new PeepObject
{
bufferIndex = index,
gameObject = peepObj,
from = position,
towards = position
};
peepObjects.Add(peep.Id, instance);
return instance;
}
/// <summary>
/// Sets the new start and end positions for this game tick.
/// </summary>
void SetPeepPositions(int index, ref Peep peep)
{
ushort id = peep.Id;
if (!peepObjects.TryGetValue(id, out PeepObject obj))
{
obj = AddPeep(index, ref peep);
}
else
{
obj.bufferIndex = index;
}
obj.lastUpdate = currentUpdateTick;
obj.gameObject.GetComponent<PeepInformation>().UpdateInformation(peep);
Vector3 target = Map.CoordsToVector3(peep.Position);
if (obj.towards == target)
return;
obj.from = obj.towards;
obj.towards = target;
obj.timeSinceStart = Time.timeSinceLevelLoad;
}
/// <summary>
/// Updates the actual object position by lerping between the information
/// from last game tick.
/// </summary>
void UpdatePeepPosition(PeepObject obj)
{
if (obj.lastUpdate < currentUpdateTick)
{
DisablePeep(obj);
return;
}
else
EnablePeep(obj);
Transform transf = obj.gameObject.transform;
if (transf.position == obj.towards)
return;
// Update position
float time = (Time.timeSinceLevelLoad - obj.timeSinceStart) / Time.fixedDeltaTime;
Vector3 lerped = Vector3.Lerp(transf.position, obj.towards, time);
transf.position = lerped;
// Update rotation
Vector3 forward = new Vector3(obj.from.x - obj.towards.x, 0, obj.from.z - obj.towards.z);
if (forward != Vector3.zero)
transf.rotation = Quaternion.LookRotation(forward);
}
void EnablePeep(PeepObject obj)
{
if (!obj.gameObject.activeSelf)
obj.gameObject.SetActive(true);
}
void DisablePeep(PeepObject obj)
{
if (obj.gameObject.activeSelf)
obj.gameObject.SetActive(false);
}
}
}

View File

@@ -0,0 +1,190 @@
using System.Collections.Generic;
using UnityEngine;
namespace OpenRCT2.Unity
{
/// <summary>
/// Abstract base class for shared code in moving sprites around.
/// </summary>
public abstract class SpriteController<TSprite> : MonoBehaviour where TSprite : ISprite
{
[SerializeField] GameObject spritePrefab;
const int MaxSprites = 8000;
protected TSprite[] spriteBuffer;
protected Dictionary<ushort, SpriteObject> spriteObjects;
int currentUpdateTick;
/// <summary>
/// Bind the method to fill the sprite buffer.
/// </summary>
protected abstract int FillSpriteBuffer(TSprite[] buffer);
/// <summary>
/// Initializes the sprite controller.
/// </summary>
void Start()
{
spriteBuffer = new TSprite[MaxSprites];
int amount = FillSpriteBuffer(spriteBuffer);
spriteObjects = new Dictionary<ushort, SpriteObject>(amount);
for (int i = 0; i < amount; i++)
{
AddSprite(i, ref spriteBuffer[i]);
}
}
/// <summary>
/// Update ~40 per second to get the sprite information from OpenRCT2.
/// </summary>
void FixedUpdate()
{
currentUpdateTick++;
int amount = FillSpriteBuffer(spriteBuffer);
for (int i = 0; i < amount; i++)
{
UpdateSprite(i, ref spriteBuffer[i]);
}
}
/// <summary>
/// Update and lerp the sprites every frame for smoother transitions.
/// </summary>
void LateUpdate()
{
foreach (var sprite in spriteObjects.Values)
{
MoveSprite(sprite);
}
}
/// <summary>
/// Adds a new peep object to the dictionary.
/// </summary>
protected virtual SpriteObject AddSprite(int index, ref TSprite sprite)
{
Vector3 position = Map.CoordsToVector3(sprite.Position);
GameObject peepObj = Instantiate(spritePrefab, position, Quaternion.identity, transform);
SpriteObject instance = new SpriteObject
{
bufferIndex = index,
gameObject = peepObj,
from = position,
towards = position
};
spriteObjects.Add(sprite.Id, instance);
return instance;
}
/// <summary>
/// Sets the new start and end positions for this game tick.
/// </summary>
protected virtual SpriteObject UpdateSprite(int index, ref TSprite sprite)
{
ushort id = sprite.Id;
if (!spriteObjects.TryGetValue(id, out SpriteObject obj))
{
obj = AddSprite(index, ref sprite);
}
else
{
obj.bufferIndex = index;
}
obj.lastUpdate = currentUpdateTick;
Vector3 target = Map.CoordsToVector3(sprite.Position);
if (obj.towards != target)
{
obj.from = obj.towards;
obj.towards = target;
obj.timeSinceStart = Time.timeSinceLevelLoad;
}
return obj;
}
/// <summary>
/// Updates the actual object position by lerping between the information
/// from last game tick.
/// </summary>
void MoveSprite(SpriteObject spriteObject)
{
if (spriteObject.lastUpdate < currentUpdateTick)
{
DisableSprite(spriteObject);
return;
}
else
EnableSprite(spriteObject);
Transform transf = spriteObject.gameObject.transform;
if (transf.position == spriteObject.towards)
return;
// Update position
float time = (Time.timeSinceLevelLoad - spriteObject.timeSinceStart) / Time.fixedDeltaTime;
Vector3 lerped = Vector3.Lerp(transf.position, spriteObject.towards, time);
transf.position = lerped;
// Update rotation
Vector3 forward = new Vector3(spriteObject.from.x - spriteObject.towards.x, 0, spriteObject.from.z - spriteObject.towards.z);
if (forward != Vector3.zero)
transf.rotation = Quaternion.LookRotation(forward);
}
/// <summary>
/// Activates the gameobject of the sprite.
/// </summary>
void EnableSprite(SpriteObject spriteObject)
{
if (!spriteObject.gameObject.activeSelf)
spriteObject.gameObject.SetActive(true);
}
/// <summary>
/// Deactivates the gameobject of the sprite.
/// </summary>
void DisableSprite(SpriteObject spriteObject)
{
if (spriteObject.gameObject.activeSelf)
spriteObject.gameObject.SetActive(false);
}
/// <summary>
/// Internal sprite object.
/// </summary>
protected class SpriteObject
{
public int bufferIndex;
public GameObject gameObject;
public float timeSinceStart;
public int lastUpdate;
public Vector3 from;
public Vector3 towards;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 910d2182882387a409c326938ace4804
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -112,6 +112,10 @@ namespace OpenRCT2.Unity
static extern int GetAllPeeps([Out] Peep[] elements, int arraySize);
[DllImport(PluginFile, CallingConvention = CallingConvention.Cdecl)]
static extern int GetAllVehicles([Out] Vehicle[] elements, int arraySize);
/// <summary>
/// Returns all peeps in the park.
/// </summary>

View File

@@ -0,0 +1,13 @@
using UnityEngine;
namespace OpenRCT2.Unity
{
/// <summary>
/// Generic sprite information.
/// </summary>
public interface ISprite
{
ushort Id { get; }
Vector3 Position { get; }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 80ecc49feebf47d44b54106aeb9581c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -8,7 +8,7 @@ namespace OpenRCT2.Unity
/// The struct of a peep, which can be either a guest or a staff member.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = (256 + Ptr.Size))]
public struct Peep
public struct Peep : ISprite
{
public SpriteBase sprite;
public IntPtr namePtr; // The real name

View File

@@ -0,0 +1,44 @@
using System.Runtime.InteropServices;
using UnityEngine;
namespace OpenRCT2.Unity
{
/// <summary>
/// The struct of a ride vehicle.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 234)]
public struct Vehicle : ISprite
{
public SpriteBase sprite;
public byte spriteType;
public byte bankRotation;
public int remainingDistance;
public int velocity;
public int acceleration;
public ushort rideId;
public byte vehicleType;
public byte colourBody;
public byte colourTrim;
public ushort trackProgress; // this memory slot is a union with 'var_34' and 'var_35'.
public short trackDirectionOrType; // direction = first 2 bits, type = next 8 bits.
public int trackLocationX;
public int trackLocationY;
public int trackLocationZ;
// ...
/// <summary>
/// Returns an id of the vehicle, currently based on the sprite index.
/// </summary>
public ushort Id
=> sprite.spriteIndex;
/// <summary>
/// Returns the vehicle's position in RCT2 coordinates.
/// </summary>
public Vector3 Position
=> new Vector3(sprite.x, sprite.z, sprite.y);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a77311af412dc2247b76d7143fde237b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: