tweaks to LevelManager

le fait qu'il faille updater des tiles du tilemap posait un réel problème

création d'un système d'héritage pour que les game objects et les tiles puissent cohabiter dans une même game loop

- ILevelObject contient des fonctions de start, de destroy, d'update, d'égalité et une position

- LevelTile et LevelObject héritent de ILevelObject et peuvent être hérité par des tiles ou des MonoBehaviours.

- le level manager est un monobehaviour singleton qui update tous les ILevelObjects à chaque frame.
This commit is contained in:
Felix Boucher 2023-05-24 18:49:28 -04:00
parent 4c2ad3dbd2
commit d10677db6d
15 changed files with 410 additions and 49 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@ -0,0 +1,147 @@
fileFormatVersion: 2
guid: 692cbdef9f1490b4b85ea6fc032ca4a8
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,24 +1,47 @@
using UnityEngine;
using UnityEngine.Tilemaps;
[CreateAssetMenu(menuName = "Gather And Defend/Spawner Tile")]
public class SpawnerTile : TileBase
public class SpawnerTile : LevelTile
{
[SerializeField]
private Sprite _sprite;
[SerializeField]
private GameObject _prefab;
[SerializeField]
private bool _spawnOnStart = true;
[SerializeField]
private float _spawnSpeed = 0;
private float _spawnCounter = 0;
public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData)
public override bool Equals(ILevelObject other)
{
tileData.sprite = _sprite;
tileData.transform.SetTRS(Vector3.zero, Quaternion.identity, Vector3.one);
tileData.color = Color.white;
return other is SpawnerTile spawner
&& spawner.Position == Position
&& spawner.Tilemap == Tilemap
&& spawner._prefab == _prefab
&& spawner._spawnOnStart == _spawnOnStart
&& spawner._spawnSpeed == _spawnSpeed;
}
public override bool StartUp(Vector3Int position, ITilemap tilemap, GameObject go)
public override void LevelDestroy()
{
if (!Application.isPlaying) return base.StartUp(position, tilemap, go);
Instantiate(_prefab, new Vector3(0.5f, 0.5f) + position, Quaternion.identity);
return base.StartUp(position, tilemap, go);
//nothing
}
public override void LevelStart()
{
if (!_spawnOnStart) return;
var instance = Instantiate(_prefab, Position, Quaternion.identity);
instance.transform.SetParent(LevelManager.Instance.transform);
}
public override void LevelUpdate()
{
_spawnCounter += Time.deltaTime * _spawnSpeed;
if (_spawnCounter < 1) return;
_spawnCounter = 0;
var instance = Instantiate(_prefab, Position, Quaternion.identity);
instance.transform.SetParent(LevelManager.Instance.transform);
}
}

View File

@ -0,0 +1,9 @@
using UnityEngine;
public static class Extensions
{
public static bool Approximately(this Vector3 vect, Vector3 other)
{
return Mathf.Approximately(Vector3.Distance(vect, other), 0);
}
}

View File

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

View File

@ -1,3 +1,10 @@
public interface ILevelObject
using UnityEngine;
public interface ILevelObject
{
Vector3 Position { get; }
void LevelUpdate();
void LevelStart();
void LevelDestroy();
bool Equals(ILevelObject other);
}

View File

@ -1,58 +1,79 @@
using System;
using System.Collections.Generic;
public class LevelManager : Singleton<LevelManager>
public class LevelManager : SingletonBehaviour<LevelManager>
{
public event System.Action<ILevelObject> Added;
public event System.Action<ILevelObject> Removed;
public event Action<ILevelObject> Added;
public event Action<ILevelObject> Removed;
private List<ILevelObject> toAdd;
private List<ILevelObject> toRemove;
private List<ILevelObject> levelObjects;
public LevelManager()
{
toAdd = new List<ILevelObject>();
toRemove = new List<ILevelObject>();
levelObjects = new List<ILevelObject>();
}
public void Add(ILevelObject levelObject)
{
if (levelObjects.Contains(levelObject)) return;
levelObjects.Add(levelObject);
Added?.Invoke(levelObject);
if (levelObjects.Exists(obj => obj.Equals(levelObject))) return;
toAdd.Add(levelObject);
}
public void Remove(ILevelObject levelObject)
{
if (!levelObjects.Contains(levelObject)) return;
levelObjects.Remove(levelObject);
Removed?.Invoke(levelObject);
toRemove.Add(levelObject);
}
public void Clear()
{
levelObjects.RemoveAll(obj =>
{
Removed?.Invoke(obj);
return true;
});
toAdd.Clear();
toRemove.Clear();
levelObjects.Clear();
}
public T Get<T>(System.Func<T, bool> predicate = default) where T : ILevelObject
public T Get<T>(Func<T, bool> predicate = null) where T : ILevelObject
{
if (predicate == default) predicate = (t) => true;
if (predicate == null) predicate = (t) => true;
return (T)levelObjects.Find(t => t is T t1 && predicate(t1));
}
public List<T> GetAll<T>(System.Func<T, bool> predicate = default) where T : ILevelObject
public List<T> GetAll<T>(Func<T, bool> predicate = null) where T : ILevelObject
{
if (predicate == default) predicate = (t) => true;
if (predicate == null) predicate = (t) => true;
List<T> ret = new List<T>();
foreach (var t in levelObjects) if (t is T t1 && predicate(t1)) ret.Add(t1);
return ret;
}
public int Count<T>(Func<T, bool> predicate = default) where T : ILevelObject
public int Count<T>(Func<T, bool> predicate = null) where T : ILevelObject
{
return GetAll(predicate).Count;
}
public bool Has<T>(System.Func<T, bool> predicate = default)
public bool Has<T>(Func<T, bool> predicate = null)
{
if (predicate == default) predicate = (t) => true;
if (predicate == null) predicate = (t) => true;
return levelObjects.Exists(t => t is T && predicate((T)t));
}
void Update()
{
levelObjects.ForEach(obj => obj.LevelUpdate());
toAdd.ForEach(tile =>
{
levelObjects.Add(tile);
Added?.Invoke(tile);
tile.LevelStart();
});
toAdd.Clear();
toRemove.ForEach(tile =>
{
levelObjects.Remove(tile);
Removed?.Invoke(tile);
tile.LevelDestroy();
});
toRemove.Clear();
}
}

View File

@ -0,0 +1,54 @@
using UnityEngine;
using UnityEngine.Tilemaps;
/// <summary>
/// can be inherited by tiles in order to be added to the level manager
/// </summary>
public abstract class LevelTile : TileBase, ILevelObject
{
[SerializeField]
private Sprite _sprite;
public Vector3 Position { get; protected set; }
public Tilemap Tilemap { get; private set; }
public abstract void LevelStart();
public abstract void LevelDestroy();
public abstract void LevelUpdate();
public abstract bool Equals(ILevelObject other);
public override bool StartUp(Vector3Int position, ITilemap tilemap, GameObject go)
{
//only execute if application is in play mode
if (!Application.isPlaying)
{
return base.StartUp(position, tilemap, go);
}
var comp = tilemap.GetComponent<Tilemap>();
//need to create an instance of the tile, otherwise the position will change for all tiles instead of only this one.
var instance = Instantiate(this);
instance.Position = position;
instance.Tilemap = comp;
//lambda expression to be used to check if the tile already exists
bool isSameTile(LevelTile tile) => tile.Equals(instance);
//if tile is exactly the same as before, don't add.
if (LevelManager.Instance.Has<LevelTile>(isSameTile)) return base.StartUp(position, tilemap, go);
LevelManager.Instance.Add(instance);
return base.StartUp(position, tilemap, go);
}
public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData)
{
tileData.sprite = _sprite;
tileData.transform.SetTRS(Vector3.zero, Quaternion.identity, Vector3.one);
tileData.color = Color.white;
}
}

View File

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

View File

@ -0,0 +1,16 @@
using UnityEngine;
public class SingletonBehaviour<T> : MonoBehaviour where T : SingletonBehaviour<T>
{
public static T Instance
{
get;
private set;
}
protected virtual void Awake()
{
if (!Instance) Instance = this as T;
else Destroy(gameObject);
}
}

View File

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

View File

@ -0,0 +1,22 @@
using UnityEngine;
using UnityEngine.Tilemaps;
using UnityEngine.UIElements;
/// <summary>
/// can be inherited by MonoBehaviours in order to be added to the level manager
/// </summary>
public abstract class LevelObject : MonoBehaviour, ILevelObject
{
public Vector3 Position => transform.position;
void Awake()
{
if (LevelManager.Instance.Has<LevelTile>(tile => tile.Position.Approximately(Position))) return;
LevelManager.Instance.Add(this);
}
public abstract void LevelStart();
public abstract void LevelDestroy();
public abstract void LevelUpdate();
public abstract bool Equals(ILevelObject other);
}

View File

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

View File

@ -5,30 +5,47 @@ using UnityEngine.Tilemaps;
[CreateAssetMenu(menuName = "Gather And Defend/Resource Tile")]
public class ResourceTile : TileBase, ILevelObject
public class ResourceTile : LevelTile
{
[SerializeField]
[Tooltip("the prefab of the currency that will be spawned when mining this resource")]
private GameObject _yieldPrefab;
[SerializeField]
private Sprite _sprite;
public Vector3 Position { get; private set; }
private float _yieldSpeed = 1; //resource per second
private float _yieldCounter = 0;
public Sprite Sprite { get => _sprite; set => _sprite = value; }
public override bool StartUp(Vector3Int position, ITilemap tilemap, GameObject go)
public bool Occupied { get; set; }
public override void LevelDestroy()
{
if (!Application.isPlaying) return base.StartUp(position, tilemap, go);
//need to create an instance of the tile, otherwise the position will change for all tiles instead of only this one.
var instance = Instantiate(this);
instance.Position = position;
LevelManager.Instance.Add(instance);
return base.StartUp(position, tilemap, go);
//nothing
}
public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData)
public override void LevelStart()
{
tileData.sprite = _sprite;
tileData.transform.SetTRS(Vector3.zero, Quaternion.identity, Vector3.one);
tileData.color = Color.white;
//nothing
}
public override void LevelUpdate()
{
if (!Occupied) return;
_yieldCounter += Time.deltaTime * _yieldSpeed;
if (_yieldCounter < 1) return;
_yieldCounter = 0;
var yielded = Instantiate(_yieldPrefab, Position, Quaternion.identity);
yielded.transform.SetParent(LevelManager.Instance.transform);
}
public override bool Equals(ILevelObject other)
{
return other is ResourceTile otherRes &&
Position == otherRes.Position
&& Tilemap == otherRes.Tilemap
&& _yieldPrefab == otherRes._yieldPrefab
&& _yieldCounter == otherRes._yieldCounter;
}
}

View File

@ -16,6 +16,7 @@ public class TestLevelManager
[SetUp]
public void SetUp()
{
new GameObject("LevelManager").AddComponent<LevelManager>();
_farm = ScriptableObject.CreateInstance<ResourceTile>();
_farm.name = nameof(_farm);