You've already forked OpenRCT2-Unity
mirror of
https://github.com/izzy2lost/OpenRCT2-Unity.git
synced 2026-03-10 12:38:22 -07:00
Abstracting the movement of sprites as preparation for vehicle sprites.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 910d2182882387a409c326938ace4804
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OpenRCT2.Unity
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic sprite information.
|
||||
/// </summary>
|
||||
public interface ISprite
|
||||
{
|
||||
ushort Id { get; }
|
||||
Vector3 Position { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80ecc49feebf47d44b54106aeb9581c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a77311af412dc2247b76d7143fde237b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user