Skip to content

Instantly share code, notes, and snippets.

@juliomarcos
Last active July 18, 2018 05:17
Show Gist options
  • Save juliomarcos/e69f103b8a1fb3bbd9eb5c0fd1cbade6 to your computer and use it in GitHub Desktop.
Save juliomarcos/e69f103b8a1fb3bbd9eb5c0fd1cbade6 to your computer and use it in GitHub Desktop.
Weighted Enemy Picker / Unity.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using System.Linq;
public enum WaveType {
Annihilation,
Timed,
}
[Serializable]
public class EnemySpawn {
public GameObject prefab;
[Range(1, 10)]
public int frequency = 1;
}
[Serializable]
public class Wave {
public WaveType waveType;
public float timeToNextSpawn;
public EnemySpawn[] enemies;
private EnemyPicker picker;
public void Init() {
picker = new EnemyPicker(enemies);
}
public GameObject PickEnemy() {
return picker.WeightedPick();
}
}
class EnemyPicker {
private EnemySpawn[] enemies;
private int[] weights;
private int cumulativeSum;
public EnemyPicker(EnemySpawn[] enemies) {
this.enemies = enemies;
InitWeightsVector();
}
public GameObject WeightedPick() {
if (enemies.Length == 1) return enemies.First().prefab;
int r = UnityEngine.Random.Range(1, cumulativeSum+1);
for(int i=0,j=0;;j++)
{
i += weights[j];
if (i >= r) return enemies[j].prefab;
}
}
private void InitWeightsVector()
{
weights = new int[enemies.Length];
for (int i = 0; i < enemies.Length; i++)
{
EnemySpawn enemySpawn = enemies[i];
cumulativeSum += enemySpawn.frequency;
weights[i] = cumulativeSum;
}
}
}
public class WaveSpawner : MonoBehaviour
{
public Transform[] positions;
public Wave[] waves;
public UnityEvent onEndAllWaves;
private int waveIndex;
private Transform parent;
public void Start() {
parent = positions.First().transform;
foreach (var wave in waves) { wave.Init(); }
SpawnCurrentWave();
StartCoroutine(WaitAndMayAdvanceWave());
}
private IEnumerator WaitAndMayAdvanceWave() {
yield return new WaitForSeconds(waves[waveIndex].timeToNextSpawn);
waveIndex = waveIndex + 1;
if (waveIndex == waves.Length) {
onEndAllWaves.Invoke();
} else {
SpawnCurrentWave();
StartCoroutine(WaitAndMayAdvanceWave());
}
}
private void SpawnCurrentWave()
{
var wave = waves[waveIndex];
for (int i = 0; i < positions.Length; i++)
{
var enemyPrefab = wave.PickEnemy();
var transform = positions[i];
var go = Instantiate(enemyPrefab, transform.position, transform.rotation);
go.transform.SetParent(parent, true);
}
}
}
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;
public class NewTestScript {
[Test]
public void single_enemy_picker_works() {
var refObj = new GameObject();
EnemySpawn es = new EnemySpawn(refObj, 1);
EnemySpawn[] enemies = new EnemySpawn[] {
es
};
EnemyPicker ep = new EnemyPicker(enemies);
var result = ep.WeightedPick();
Assert.AreEqual(refObj, result);
}
public class BiasedEnemyPicker : EnemyPicker {
public BiasedEnemyPicker(EnemySpawn[] enemies) : base(enemies) {
}
protected override int RandomPickWeight() {
return cumulativeSum / 2;
}
}
[Test]
public void two_enemies_picker_works() {
var refObj1 = new GameObject();
var refObj2 = new GameObject();
EnemySpawn es1 = new EnemySpawn(refObj1, 1);
EnemySpawn es2 = new EnemySpawn(refObj2, 3);
EnemySpawn[] enemies = new EnemySpawn[] {
es1,
es2
};
EnemyPicker ep = new BiasedEnemyPicker(enemies);
var result = ep.WeightedPick();
Assert.AreEqual(refObj2, result);
}
// // A UnityTest behaves like a coroutine in PlayMode
// // and allows you to yield null to skip a frame in EditMode
// [UnityTest]
// public IEnumerator NewTestScriptWithEnumeratorPasses() {
// // Use the Assert class to test conditions.
// // yield to skip a frame
// yield return null;
// }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment