Pull request #34: WaveEditor

Merge in CGD/gather-and-defend from feature/waveEditor to main

* commit '0ce0acae5e21971dab1a72d87f5d151b7bcc3058':
  Fix MissingRefException when monster calls method OnDestroy
  Reworked constant spawn
  Start reworking constant spawn
  Removed debuging comments
  Changed comments to english
  Fix enemy count bug where the number of enemies spawned was inconsistent with the amount desired
  Ajout d'un WaveConfig aux niveaux du LevelSelect
  Changement du nom de dossier de LevelConfig vers WaveConfig
  Ajout d'instructions pour la configuration de la vague d'ennemi
  Progrès WaveEditor
  Progrès WaveEditor
  Debut Wave Editor
This commit is contained in:
Ader Alisma 01 2023-10-01 23:34:21 +00:00
commit 1ee9e0b27b
26 changed files with 409 additions and 5 deletions

View File

@ -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();
}
}
}

View File

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

View File

@ -246,3 +246,4 @@ MonoBehaviour:
_renderLayer: Default
_position: {x: 0, y: 0}
_scale: {x: 1, y: 1}
_waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2}

View File

@ -198,3 +198,4 @@ MonoBehaviour:
_renderLayer: Default
_position: {x: 0, y: 0}
_scale: {x: 1, y: 1}
_waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2}

View File

@ -196,3 +196,4 @@ MonoBehaviour:
_renderLayer: Default
_position: {x: 0, y: 0}
_scale: {x: 1, y: 1}
_waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2}

View File

@ -228,3 +228,4 @@ MonoBehaviour:
_renderLayer: Default
_position: {x: 0, y: 0}
_scale: {x: 1, y: 1}
_waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2}

View File

@ -204,3 +204,4 @@ MonoBehaviour:
_renderLayer: Default
_position: {x: 0, y: 0}
_scale: {x: 1, y: 1}
_waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2}

View File

@ -202,3 +202,4 @@ MonoBehaviour:
_renderLayer: Default
_position: {x: 0, y: 0}
_scale: {x: 1, y: 1}
_waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2}

View File

@ -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}

View File

@ -188,3 +188,4 @@ MonoBehaviour:
_renderLayer: Default
_position: {x: 0, y: 0}
_scale: {x: 1, y: 1}
_waveConfig: {fileID: 11400000, guid: 21b0f85f7c746974db1e72f2df646f5d, type: 2}

View File

@ -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);

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a386aeb53fe226d41bbee33ac6fafa4e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,49 @@
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "Gather And Defend/Levels/WaveConfig")]
public class WaveConfig : ScriptableObject
{
[SerializeField]
private List<EnemyType> _constantSpawn = new List<EnemyType>();
[SerializeField]
private float _gameDuration = 0;
private float _enemySpawndOnStart = 0;
private int _enemySum = 0;
public List<EnemyType> 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();
}
}

View File

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

View File

@ -0,0 +1,142 @@
using System.Collections.Generic;
using UnityEngine;
using System;
public class WaveObserver : Singleton<WaveObserver>
{
private List<SpawnerTile> _subjects = new List<SpawnerTile>();
private List<float> _aliveEnemyCount = new List<float>();
private List<int> _copyConstantSpawn;
private WaveConfig _levelConfig;
private const int MAXTOUGHNESS = 10;
private int _spawnerTiming = 0;
private List<int> _intervalTiming = new List<int>();
public WaveConfig LevelConfig
{
set
{
_levelConfig = value;
_copyConstantSpawn = new List<int>();
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);
}
}

View File

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

View File

@ -11,6 +11,8 @@ namespace GatherAndDefend.LevelEditor
public Rect Bounds => _bounds;
[SerializeField]
private List<TilemapData> _data = new List<TilemapData>();
[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<TilemapData> GetEnumerator()
{

View File

@ -48,7 +48,6 @@ namespace GatherAndDefend.LevelEditor
collision.isTrigger = _isTrigger;
}
foreach (TileData data in _tiles)
{
reference.SetTile(data.Position, data.Tile);

View File

@ -23,6 +23,7 @@ public class LevelManager : Singleton<LevelManager>
private readonly List<ILevelObject> _toAdd;
private readonly List<ILevelObject> _toRemove;
private readonly List<ILevelObject> _levelObjects;
private WaveObserver _waveObserver;
private Tilemap _dynamicTilemap;
public Tilemap DynamicTilemap
@ -150,6 +151,8 @@ public class LevelManager : Singleton<LevelManager>
}
_currentLevel = level;
_waveObserver = WaveObserver.Instance;
_waveObserver.LevelConfig = _currentLevel.WaveConfig;
Grid grid = Object.FindObjectOfType<Grid>();
//create new grid if there is none
if (!grid)

View File

@ -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; }
}
}

View File

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

View File

@ -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<Rigidbody2D>();
Animation = gameObject.AddComponent<AnimationEntity>();
_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();
}
}

View File

@ -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<string, object> 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;
}
}

8
Assets/WaveConfig.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b3ad07c069cd06e4ebbd5f190b9aa25b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 21b0f85f7c746974db1e72f2df646f5d
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant: