diff --git a/Assets/Editor/WaveConfigCustomInspector.cs b/Assets/Editor/WaveConfigCustomInspector.cs new file mode 100644 index 0000000..8ee92ad --- /dev/null +++ b/Assets/Editor/WaveConfigCustomInspector.cs @@ -0,0 +1,22 @@ +using UnityEditor; + +namespace GatherAndDefend.LevelEditor +{ + [CustomEditor(typeof(WaveConfig))] + public class WaveConfigCustomInspector : Editor + { + + public override void OnInspectorGUI() + { + EditorGUILayout.HelpBox(@"How to use : +- ConstantSpawn: Drag the chosen Enemy and insert the number to make in Count +- GameDuration : Duration of the game, in minutes +- Usage : Drop this config into a level +- Creation interval is determined by the game duration and the number of enemies to create + +Important consideration : +- You must assign at least 1 enemy of Count of at least 1 to play.", MessageType.None); + base.OnInspectorGUI(); + } + } +} \ No newline at end of file diff --git a/Assets/Editor/WaveConfigCustomInspector.cs.meta b/Assets/Editor/WaveConfigCustomInspector.cs.meta new file mode 100644 index 0000000..2edbb5e --- /dev/null +++ b/Assets/Editor/WaveConfigCustomInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8c38e7df17e51a6448682caeaba0f29e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/LevelEditor/Levels/Level1.asset b/Assets/LevelEditor/Levels/Level1.asset index 2fd9e7d..7f0fdca 100644 --- a/Assets/LevelEditor/Levels/Level1.asset +++ b/Assets/LevelEditor/Levels/Level1.asset @@ -246,3 +246,4 @@ MonoBehaviour: _renderLayer: Default _position: {x: 0, y: 0} _scale: {x: 1, y: 1} + _waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2} diff --git a/Assets/LevelEditor/Levels/Level2.asset b/Assets/LevelEditor/Levels/Level2.asset index 9ab0e48..38d9c27 100644 --- a/Assets/LevelEditor/Levels/Level2.asset +++ b/Assets/LevelEditor/Levels/Level2.asset @@ -198,3 +198,4 @@ MonoBehaviour: _renderLayer: Default _position: {x: 0, y: 0} _scale: {x: 1, y: 1} + _waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2} diff --git a/Assets/LevelEditor/Levels/Level3.asset b/Assets/LevelEditor/Levels/Level3.asset index 5bc3d2e..e834439 100644 --- a/Assets/LevelEditor/Levels/Level3.asset +++ b/Assets/LevelEditor/Levels/Level3.asset @@ -196,3 +196,4 @@ MonoBehaviour: _renderLayer: Default _position: {x: 0, y: 0} _scale: {x: 1, y: 1} + _waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2} diff --git a/Assets/LevelEditor/Levels/Level4.asset b/Assets/LevelEditor/Levels/Level4.asset index 30ce5a1..01582a4 100644 --- a/Assets/LevelEditor/Levels/Level4.asset +++ b/Assets/LevelEditor/Levels/Level4.asset @@ -228,3 +228,4 @@ MonoBehaviour: _renderLayer: Default _position: {x: 0, y: 0} _scale: {x: 1, y: 1} + _waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2} diff --git a/Assets/LevelEditor/Levels/Level5.asset b/Assets/LevelEditor/Levels/Level5.asset index 91f7bcf..70774fa 100644 --- a/Assets/LevelEditor/Levels/Level5.asset +++ b/Assets/LevelEditor/Levels/Level5.asset @@ -204,3 +204,4 @@ MonoBehaviour: _renderLayer: Default _position: {x: 0, y: 0} _scale: {x: 1, y: 1} + _waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2} diff --git a/Assets/LevelEditor/Levels/Level6.asset b/Assets/LevelEditor/Levels/Level6.asset index 6a0ddf9..1e96254 100644 --- a/Assets/LevelEditor/Levels/Level6.asset +++ b/Assets/LevelEditor/Levels/Level6.asset @@ -202,3 +202,4 @@ MonoBehaviour: _renderLayer: Default _position: {x: 0, y: 0} _scale: {x: 1, y: 1} + _waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2} diff --git a/Assets/LevelEditor/Levels/TestDrag&Drop.asset b/Assets/LevelEditor/Levels/TestDrag&Drop.asset index e3e45d9..b206bd0 100644 --- a/Assets/LevelEditor/Levels/TestDrag&Drop.asset +++ b/Assets/LevelEditor/Levels/TestDrag&Drop.asset @@ -309,6 +309,7 @@ MonoBehaviour: _position: {x: 10, y: 1, z: 0} - _tile: {fileID: 11400000, guid: ef5a154519b23a34aaded32e86bf7f2f, type: 2} _position: {x: 10, y: 2, z: 0} + _isSpawner: 0 _isInvisible: 0 _isCollidable: 0 _isTrigger: 0 @@ -318,6 +319,7 @@ MonoBehaviour: _scale: {x: 1, y: 1} - _key: Entities _tiles: [] + _isSpawner: 0 _isInvisible: 0 _isCollidable: 0 _isTrigger: 0 @@ -343,6 +345,7 @@ MonoBehaviour: _position: {x: 10, y: 0, z: 0} - _tile: {fileID: 11400000, guid: 4002377ed7e87b34699f126f2b10c703, type: 2} _position: {x: 10, y: 2, z: 0} + _isSpawner: 0 _isInvisible: 0 _isCollidable: 0 _isTrigger: 0 @@ -350,3 +353,4 @@ MonoBehaviour: _renderLayer: Default _position: {x: 0, y: 0} _scale: {x: 1, y: 1} + _waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2} diff --git a/Assets/LevelEditor/Levels/TestGame.asset b/Assets/LevelEditor/Levels/TestGame.asset index bbb8aeb..e55efd0 100644 --- a/Assets/LevelEditor/Levels/TestGame.asset +++ b/Assets/LevelEditor/Levels/TestGame.asset @@ -188,3 +188,4 @@ MonoBehaviour: _renderLayer: Default _position: {x: 0, y: 0} _scale: {x: 1, y: 1} + _waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2} diff --git a/Assets/Scripts/Entity.cs b/Assets/Scripts/Entity.cs index 5fc8a74..9886edf 100644 --- a/Assets/Scripts/Entity.cs +++ b/Assets/Scripts/Entity.cs @@ -35,7 +35,7 @@ public abstract class Entity : LevelObject Animation.SpeedMultiplier = SpeedMultiplier; } //Start the animation of death and the fading of the entity - public void Death() + public virtual void Death() { _animation.PlayDieAnim(); Invoke("Dying", 0.1f); diff --git a/Assets/Scripts/LevelConfig.meta b/Assets/Scripts/LevelConfig.meta new file mode 100644 index 0000000..f06a54d --- /dev/null +++ b/Assets/Scripts/LevelConfig.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a386aeb53fe226d41bbee33ac6fafa4e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/LevelConfig/WaveConfig.cs b/Assets/Scripts/LevelConfig/WaveConfig.cs new file mode 100644 index 0000000..e4bc275 --- /dev/null +++ b/Assets/Scripts/LevelConfig/WaveConfig.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using UnityEngine; + +[CreateAssetMenu(menuName = "Gather And Defend/Levels/WaveConfig")] +public class WaveConfig : ScriptableObject +{ + + [SerializeField] + private List _constantSpawn = new List(); + [SerializeField] + private float _gameDuration = 0; + private float _enemySpawndOnStart = 0; + private int _enemySum = 0; + public List ConstantSpawn + { + get + { + return _constantSpawn; + } + } + public float GetInterval() + { + float interval = _gameDuration * 60.0f / (_enemySum > 0 ? _enemySum.ToFloat() : SumCount()) ; + return interval; + } + public float GetUpdatedInterval() + { + _enemySpawndOnStart++; + float interval = Mathf.Max(_gameDuration * 60.0f / (_enemySum - _enemySpawndOnStart).ToFloat(), 0); + return interval; + } + public EnemyType GetRandomSpawn() + { + if (_constantSpawn.Count == 1) + { + return _constantSpawn[0]; + } + return _constantSpawn[Random.Range(0, _constantSpawn.Count - 1)]; + } + private float SumCount() + { + foreach (EnemyType enemy in _constantSpawn) + { + _enemySum += enemy.Count; + } + return _enemySum.ToFloat(); + } + +} diff --git a/Assets/Scripts/LevelConfig/WaveConfig.cs.meta b/Assets/Scripts/LevelConfig/WaveConfig.cs.meta new file mode 100644 index 0000000..4c684ab --- /dev/null +++ b/Assets/Scripts/LevelConfig/WaveConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb0795e326609f0499365f5b65c2b5cd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/LevelConfig/WaveObserver.cs b/Assets/Scripts/LevelConfig/WaveObserver.cs new file mode 100644 index 0000000..f1ee0e3 --- /dev/null +++ b/Assets/Scripts/LevelConfig/WaveObserver.cs @@ -0,0 +1,142 @@ +using System.Collections.Generic; +using UnityEngine; +using System; + +public class WaveObserver : Singleton +{ + private List _subjects = new List(); + private List _aliveEnemyCount = new List(); + private List _copyConstantSpawn; + private WaveConfig _levelConfig; + private const int MAXTOUGHNESS = 10; + private int _spawnerTiming = 0; + private List _intervalTiming = new List(); + public WaveConfig LevelConfig + { + set + { + _levelConfig = value; + _copyConstantSpawn = new List(); + foreach (EnemyType enemy in _levelConfig.ConstantSpawn) + { + _copyConstantSpawn.Add(enemy.Count); + } + } + } + + /** + * Called by spawner at the start of the game + * Assigns enemy to spawn and registers them + */ + public void Attach(SpawnerTile spawnerSubject) + { + + spawnerSubject.Prefab = _levelConfig.GetRandomSpawn().GetEnemyObject(); + _subjects.Add(spawnerSubject); + _aliveEnemyCount.Add(0); + _intervalTiming.Add(++_spawnerTiming); + } + + /** + * Called by spawner when making enemies + * Assigns a new interval + */ + public void NotifySpawned(SpawnerTile spawnerSubject) + { + GameObject paramPrefab = spawnerSubject.Prefab; + spawnerSubject.ChangeSpawnSpeed(_levelConfig.GetInterval() * _spawnerTiming); + if (paramPrefab.Equals(_levelConfig.ConstantSpawn[0].GetEnemyObject())) + { + int currentCount = 0; + for (int i = 0; i < _copyConstantSpawn.Count; i++) + { + if (_levelConfig.ConstantSpawn[i].GetEnemyObject() == paramPrefab) + { + currentCount = --_copyConstantSpawn[i]; + break; + } + } + if (currentCount <= 0) + { + foreach (SpawnerTile spawner in _subjects) + { + if (spawner.Prefab.Equals(paramPrefab)) + { + spawner.StopSpawn(); + } + } + } + } + } + + /** + * Called by enemy when they spawn + * Keeps track of their row + */ + public int NotifyEnemy(float yPosition, float toughness) + { + int index = FindEnemyIndex(yPosition); + _aliveEnemyCount[index] += toughness; + if (_aliveEnemyCount[index] >= MAXTOUGHNESS) + { + _subjects[index].StopSpawn(); + } + return index; + } + + /** + * Called when an enemy dies + * Reactivates spawning on that row if disabled before + */ + public void NotifyDies(int position, float toughness) + { + _aliveEnemyCount[position] -= toughness; + if (_aliveEnemyCount[position] < MAXTOUGHNESS) + { + _subjects[position].StartSpawn(); + } + } + + /** + * Called when an enemy is spawned automatically at the start of the game + * Adjusts the intervall between spawns + */ + public void NotifyOnStart(SpawnerTile spawnerSubject) + { + float interval = _levelConfig.GetUpdatedInterval(); + foreach (var subject in _subjects) + { + subject.ChangeSpawnSpeed(interval); + } + NotifySpawned(spawnerSubject); + } + + // To find which spawner an ennemy came from. + private int FindEnemyIndex(float yPosition) + { + for (int i = 0; i < _subjects.Count; i++) + { + if (_subjects[i].Position.y == yPosition) + { + return i; + } + } + return -1; + } + + /** + * Spawners wait 1 second to have time to register them all + * Then gets assigned a random spawn interval + */ + public void NotifyEndCooldown(SpawnerTile spawnerTile) + { + System.Random rand = new System.Random(); + int index; + do + { + index = rand.Next(_subjects.Count); + } while (_intervalTiming.Count <= index); + spawnerTile.ChangeSpawnSpeed(_levelConfig.GetInterval() * _intervalTiming[index]); + _intervalTiming.Remove(index); + } +} diff --git a/Assets/Scripts/LevelConfig/WaveObserver.cs.meta b/Assets/Scripts/LevelConfig/WaveObserver.cs.meta new file mode 100644 index 0000000..f704317 --- /dev/null +++ b/Assets/Scripts/LevelConfig/WaveObserver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6eab34a14c5d4d746a70792d4d7914a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/LevelEditor/Level.cs b/Assets/Scripts/LevelEditor/Level.cs index 73d7bb8..0fc33b4 100644 --- a/Assets/Scripts/LevelEditor/Level.cs +++ b/Assets/Scripts/LevelEditor/Level.cs @@ -11,6 +11,8 @@ namespace GatherAndDefend.LevelEditor public Rect Bounds => _bounds; [SerializeField] private List _data = new List(); + [SerializeField] + private WaveConfig _waveConfig; public void SaveFromTilemap(Tilemap tilemap) { var data = new TilemapData(); @@ -24,6 +26,7 @@ namespace GatherAndDefend.LevelEditor data.LoadToTilemap(tilemap); } + public WaveConfig WaveConfig { get { return _waveConfig; } } public IEnumerator GetEnumerator() { diff --git a/Assets/Scripts/LevelEditor/TilemapData.cs b/Assets/Scripts/LevelEditor/TilemapData.cs index fb0a7ea..6bfd19c 100644 --- a/Assets/Scripts/LevelEditor/TilemapData.cs +++ b/Assets/Scripts/LevelEditor/TilemapData.cs @@ -48,7 +48,6 @@ namespace GatherAndDefend.LevelEditor collision.isTrigger = _isTrigger; } - foreach (TileData data in _tiles) { reference.SetTile(data.Position, data.Tile); diff --git a/Assets/Scripts/LevelManager/LevelManager.cs b/Assets/Scripts/LevelManager/LevelManager.cs index fafbd8e..28924e4 100644 --- a/Assets/Scripts/LevelManager/LevelManager.cs +++ b/Assets/Scripts/LevelManager/LevelManager.cs @@ -23,6 +23,7 @@ public class LevelManager : Singleton private readonly List _toAdd; private readonly List _toRemove; private readonly List _levelObjects; + private WaveObserver _waveObserver; private Tilemap _dynamicTilemap; public Tilemap DynamicTilemap @@ -150,6 +151,8 @@ public class LevelManager : Singleton } _currentLevel = level; + _waveObserver = WaveObserver.Instance; + _waveObserver.LevelConfig = _currentLevel.WaveConfig; Grid grid = Object.FindObjectOfType(); //create new grid if there is none if (!grid) diff --git a/Assets/Scripts/Opponent/EnemyType.cs b/Assets/Scripts/Opponent/EnemyType.cs new file mode 100644 index 0000000..1bb9e02 --- /dev/null +++ b/Assets/Scripts/Opponent/EnemyType.cs @@ -0,0 +1,28 @@ +using UnityEngine; +using System; + + +[Serializable] +public class EnemyType +{ + [SerializeField] + private Opponent _enemy; + [SerializeField] + private int _count; + + public int GetEnemyToughness() + { + float toughness = Mathf.Round((_enemy.Hp / 10) / 2); + return (int)toughness; + } + + public GameObject GetEnemyObject() + { + return _enemy.gameObject; + } + public int Count + { + get { return _count; } + } + +} diff --git a/Assets/Scripts/Opponent/EnemyType.cs.meta b/Assets/Scripts/Opponent/EnemyType.cs.meta new file mode 100644 index 0000000..981da41 --- /dev/null +++ b/Assets/Scripts/Opponent/EnemyType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a747157705819b94499ad98134da8f88 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Opponent/Opponent.cs b/Assets/Scripts/Opponent/Opponent.cs index b0d7b26..a8ca40f 100644 --- a/Assets/Scripts/Opponent/Opponent.cs +++ b/Assets/Scripts/Opponent/Opponent.cs @@ -12,6 +12,9 @@ public class Opponent : Entity private Vector2 _movementVector = Vector2.zero; private Rigidbody2D _rigidbody; + private WaveObserver _observer; + private int _observerIndex; + private float _toughness; public override void Start() { @@ -19,6 +22,9 @@ public class Opponent : Entity _rigidbody = GetComponent(); Animation = gameObject.AddComponent(); + _observer = WaveObserver.Instance; + _toughness = Mathf.Round((Hp / 10) / 2); + _observerIndex = _observer.NotifyEnemy(transform.position.y, _toughness); } public override void Update() @@ -55,4 +61,10 @@ public class Opponent : Entity AttackSpeedWait += Time.deltaTime; } + public override void Death() + { + _observer.NotifyDies(_observerIndex, _toughness); + base.Death(); + } + } diff --git a/Assets/Scripts/Tiles/SpawnerTile.cs b/Assets/Scripts/Tiles/SpawnerTile.cs index eadafc7..38450a7 100644 --- a/Assets/Scripts/Tiles/SpawnerTile.cs +++ b/Assets/Scripts/Tiles/SpawnerTile.cs @@ -10,27 +10,43 @@ public class SpawnerTile : LevelTile private bool _spawnOnStart; private float _lifetime; [SerializeField] - private float _spawnSpeed = 0; + private float _spawnSpeed = 1.0f; [SerializeField, Range(0, 1.001f)] private float _spawnCounter = 0; + private WaveObserver _observer; + private bool _stopped = false; + private bool _cooldownEnded = false; public override void LevelStart() { + _observer = WaveObserver.Instance; + _observer.Attach(this); if (_spawnOnStart && _lifetime <= 0) { _prefab.Create(Position, parent: LevelManager.Instance.LevelTransform); + _observer.NotifyOnStart(this); } } public override void LevelUpdate() { _lifetime += Time.deltaTime; - _spawnCounter += Time.deltaTime * _spawnSpeed; - if (_spawnCounter < 1) return; + if (!_stopped) + { + _spawnCounter += Time.deltaTime; + } + if (_spawnCounter < _spawnSpeed) return; _spawnCounter = 0; + if (!_cooldownEnded) + { + _observer.NotifyEndCooldown(this); + _cooldownEnded = true; + return; + } _prefab.Create(Position, parent: LevelManager.Instance.LevelTransform); + _observer.NotifySpawned(this); } public override bool Equals(ILevelObject other) { @@ -50,6 +66,18 @@ public class SpawnerTile : LevelTile dict[nameof(_spawnOnStart)] = _spawnOnStart; return dict; } + + internal void StopSpawn() + { + _stopped = true; + } + + internal void StartSpawn() + { + _stopped = false; + } + + public override void LoadDictionary(Dictionary dict) { base.LoadDictionary(dict); @@ -61,4 +89,25 @@ public class SpawnerTile : LevelTile _lifetime = dict[nameof(_lifetime)].ToFloat(); _spawnOnStart = dict[nameof(_spawnOnStart)].ToBool(); } + + public GameObject Prefab + { + set + { + _prefab = value; + } + get + { + return _prefab; + } + } + + /** + * Called by observer + * Assigns a new spawn interval + */ + public void ChangeSpawnSpeed(float value) + { + _spawnSpeed = value; + } } \ No newline at end of file diff --git a/Assets/WaveConfig.meta b/Assets/WaveConfig.meta new file mode 100644 index 0000000..b7bd07b --- /dev/null +++ b/Assets/WaveConfig.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b3ad07c069cd06e4ebbd5f190b9aa25b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WaveConfig/Config01.asset b/Assets/WaveConfig/Config01.asset new file mode 100644 index 0000000..371055b --- /dev/null +++ b/Assets/WaveConfig/Config01.asset @@ -0,0 +1,18 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: eb0795e326609f0499365f5b65c2b5cd, type: 3} + m_Name: Config01 + m_EditorClassIdentifier: + _constantSpawn: + - _enemy: {fileID: 313037212318601125, guid: 5bbf0d85fa5bb3f4599da79f0a84e3a9, type: 3} + _count: 5 + _gameDuration: 1 diff --git a/Assets/WaveConfig/Config01.asset.meta b/Assets/WaveConfig/Config01.asset.meta new file mode 100644 index 0000000..7ed0ea8 --- /dev/null +++ b/Assets/WaveConfig/Config01.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 21b0f85f7c746974db1e72f2df646f5d +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: