Initial Commit
This commit is contained in:
+115
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using ModestTree;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Zenject.Asteroids
|
||||
{
|
||||
public class Asteroid : MonoBehaviour
|
||||
{
|
||||
LevelHelper _level;
|
||||
Rigidbody _rigidBody;
|
||||
Settings _settings;
|
||||
|
||||
// We could just add [Inject] to the field declarations but
|
||||
// it's often better practice to use PostInject methods
|
||||
// Note that we can't use Constructors here because this is
|
||||
// a MonoBehaviour
|
||||
[Inject]
|
||||
public void Construct(LevelHelper level, Settings settings)
|
||||
{
|
||||
_level = level;
|
||||
_settings = settings;
|
||||
_rigidBody = GetComponent<Rigidbody>();
|
||||
}
|
||||
|
||||
public Vector3 Position
|
||||
{
|
||||
get { return transform.position; }
|
||||
set { transform.position = value; }
|
||||
}
|
||||
|
||||
public float Mass
|
||||
{
|
||||
get { return _rigidBody.mass; }
|
||||
set { _rigidBody.mass = value; }
|
||||
}
|
||||
|
||||
public float Scale
|
||||
{
|
||||
get
|
||||
{
|
||||
var scale = transform.localScale;
|
||||
// We assume scale is uniform
|
||||
Assert.That(scale[0] == scale[1] && scale[1] == scale[2]);
|
||||
|
||||
return scale[0];
|
||||
}
|
||||
set
|
||||
{
|
||||
transform.localScale = new Vector3(value, value, value);
|
||||
_rigidBody.mass = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Velocity
|
||||
{
|
||||
get { return _rigidBody.linearVelocity; }
|
||||
set { _rigidBody.linearVelocity = value; }
|
||||
}
|
||||
|
||||
public void FixedTick()
|
||||
{
|
||||
// Limit speed to a maximum
|
||||
var speed = _rigidBody.linearVelocity.magnitude;
|
||||
|
||||
if (speed > _settings.maxSpeed)
|
||||
{
|
||||
var dir = _rigidBody.linearVelocity / speed;
|
||||
_rigidBody.linearVelocity = dir * _settings.maxSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
CheckForTeleport();
|
||||
}
|
||||
|
||||
void CheckForTeleport()
|
||||
{
|
||||
if (Position.x > _level.Right + Scale && IsMovingInDirection(Vector3.right))
|
||||
{
|
||||
transform.SetX(_level.Left - Scale);
|
||||
}
|
||||
else if (Position.x < _level.Left - Scale && IsMovingInDirection(-Vector3.right))
|
||||
{
|
||||
transform.SetX(_level.Right + Scale);
|
||||
}
|
||||
else if (Position.y < _level.Bottom - Scale && IsMovingInDirection(-Vector3.up))
|
||||
{
|
||||
transform.SetY(_level.Top + Scale);
|
||||
}
|
||||
else if (Position.y > _level.Top + Scale && IsMovingInDirection(Vector3.up))
|
||||
{
|
||||
transform.SetY(_level.Bottom - Scale);
|
||||
}
|
||||
|
||||
transform.RotateAround(transform.position, Vector3.up, 30 * Time.deltaTime);
|
||||
}
|
||||
|
||||
bool IsMovingInDirection(Vector3 dir)
|
||||
{
|
||||
return Vector3.Dot(dir, _rigidBody.linearVelocity) > 0;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Settings
|
||||
{
|
||||
public float massScaleFactor;
|
||||
public float maxSpeed;
|
||||
}
|
||||
|
||||
public class Factory : PlaceholderFactory<Asteroid>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5608cae0fd6ebbd46b5ad3c03b150682
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ModestTree;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace Zenject.Asteroids
|
||||
{
|
||||
public class AsteroidManager : ITickable, IFixedTickable
|
||||
{
|
||||
readonly List<Asteroid> _asteroids = new List<Asteroid>();
|
||||
readonly Queue<AsteroidAttributes> _cachedAttributes = new Queue<AsteroidAttributes>();
|
||||
readonly Settings _settings;
|
||||
readonly Asteroid.Factory _asteroidFactory;
|
||||
readonly LevelHelper _level;
|
||||
|
||||
float _timeToNextSpawn;
|
||||
float _timeIntervalBetweenSpawns;
|
||||
bool _started;
|
||||
|
||||
[InjectOptional]
|
||||
bool _autoSpawn = true;
|
||||
|
||||
public AsteroidManager(
|
||||
Settings settings, Asteroid.Factory asteroidFactory, LevelHelper level)
|
||||
{
|
||||
_settings = settings;
|
||||
_timeIntervalBetweenSpawns = _settings.maxSpawnTime / (_settings.maxSpawns - _settings.startingSpawns);
|
||||
_timeToNextSpawn = _timeIntervalBetweenSpawns;
|
||||
_asteroidFactory = asteroidFactory;
|
||||
_level = level;
|
||||
}
|
||||
|
||||
public IEnumerable<Asteroid> Asteroids
|
||||
{
|
||||
get { return _asteroids; }
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Assert.That(!_started);
|
||||
_started = true;
|
||||
|
||||
ResetAll();
|
||||
GenerateRandomAttributes();
|
||||
|
||||
for (int i = 0; i < _settings.startingSpawns; i++)
|
||||
{
|
||||
SpawnNext();
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the full list of size and speeds so that we can maintain an approximate average
|
||||
// this way we don't get wildly different difficulties each time the game is run
|
||||
// For example, if we just chose speed randomly each time we spawned an asteroid, in some
|
||||
// cases that might result in the first set of asteroids all going at max speed, or min speed
|
||||
void GenerateRandomAttributes()
|
||||
{
|
||||
Assert.That(_cachedAttributes.Count == 0);
|
||||
|
||||
var speedTotal = 0.0f;
|
||||
var sizeTotal = 0.0f;
|
||||
|
||||
for (int i = 0; i < _settings.maxSpawns; i++)
|
||||
{
|
||||
var sizePx = Random.Range(0.0f, 1.0f);
|
||||
var speed = Random.Range(_settings.minSpeed, _settings.maxSpeed);
|
||||
|
||||
_cachedAttributes.Enqueue(new AsteroidAttributes {
|
||||
SizePx = sizePx,
|
||||
InitialSpeed = speed
|
||||
});
|
||||
|
||||
speedTotal += speed;
|
||||
sizeTotal += sizePx;
|
||||
}
|
||||
|
||||
var desiredAverageSpeed = (_settings.minSpeed + _settings.maxSpeed) * 0.5f;
|
||||
var desiredAverageSize = 0.5f;
|
||||
|
||||
var averageSize = sizeTotal / _settings.maxSpawns;
|
||||
var averageSpeed = speedTotal / _settings.maxSpawns;
|
||||
|
||||
var speedScaleFactor = desiredAverageSpeed / averageSpeed;
|
||||
var sizeScaleFactor = desiredAverageSize / averageSize;
|
||||
|
||||
foreach (var attributes in _cachedAttributes)
|
||||
{
|
||||
attributes.SizePx *= sizeScaleFactor;
|
||||
attributes.InitialSpeed *= speedScaleFactor;
|
||||
}
|
||||
|
||||
Assert.That(Mathf.Approximately(_cachedAttributes.Average(x => x.InitialSpeed), desiredAverageSpeed));
|
||||
Assert.That(Mathf.Approximately(_cachedAttributes.Average(x => x.SizePx), desiredAverageSize));
|
||||
}
|
||||
|
||||
void ResetAll()
|
||||
{
|
||||
foreach (var asteroid in _asteroids)
|
||||
{
|
||||
GameObject.Destroy(asteroid.gameObject);
|
||||
}
|
||||
|
||||
_asteroids.Clear();
|
||||
_cachedAttributes.Clear();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
Assert.That(_started);
|
||||
_started = false;
|
||||
}
|
||||
|
||||
public void FixedTick()
|
||||
{
|
||||
for (int i = 0; i < _asteroids.Count; i++)
|
||||
{
|
||||
_asteroids[i].FixedTick();
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
for (int i = 0; i < _asteroids.Count; i++)
|
||||
{
|
||||
_asteroids[i].Tick();
|
||||
}
|
||||
|
||||
if (_started && _autoSpawn)
|
||||
{
|
||||
_timeToNextSpawn -= Time.deltaTime;
|
||||
|
||||
if (_timeToNextSpawn < 0 && _asteroids.Count < _settings.maxSpawns)
|
||||
{
|
||||
_timeToNextSpawn = _timeIntervalBetweenSpawns;
|
||||
SpawnNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SpawnNext()
|
||||
{
|
||||
var asteroid = _asteroidFactory.Create();
|
||||
|
||||
var attributes = _cachedAttributes.Dequeue();
|
||||
|
||||
asteroid.Scale = Mathf.Lerp(_settings.minScale, _settings.maxScale, attributes.SizePx);
|
||||
asteroid.Mass = Mathf.Lerp(_settings.minMass, _settings.maxMass, attributes.SizePx);
|
||||
asteroid.Position = GetRandomStartPosition(asteroid.Scale);
|
||||
asteroid.Velocity = GetRandomDirection() * attributes.InitialSpeed;
|
||||
|
||||
_asteroids.Add(asteroid);
|
||||
}
|
||||
|
||||
Vector3 GetRandomDirection()
|
||||
{
|
||||
var theta = Random.Range(0, Mathf.PI * 2.0f);
|
||||
return new Vector3(Mathf.Cos(theta), Mathf.Sin(theta), 0);
|
||||
}
|
||||
|
||||
Vector3 GetRandomStartPosition(float scale)
|
||||
{
|
||||
var side = (Side)Random.Range(0, (int)Side.Count);
|
||||
var rand = Random.Range(0.0f, 1.0f);
|
||||
|
||||
switch (side)
|
||||
{
|
||||
case Side.Top:
|
||||
{
|
||||
return new Vector3(_level.Left + rand * _level.Width, _level.Top + scale, 0);
|
||||
}
|
||||
case Side.Bottom:
|
||||
{
|
||||
return new Vector3(_level.Left + rand * _level.Width, _level.Bottom - scale, 0);
|
||||
}
|
||||
case Side.Right:
|
||||
{
|
||||
return new Vector3(_level.Right + scale, _level.Bottom + rand * _level.Height, 0);
|
||||
}
|
||||
case Side.Left:
|
||||
{
|
||||
return new Vector3(_level.Left - scale, _level.Bottom + rand * _level.Height, 0);
|
||||
}
|
||||
}
|
||||
|
||||
throw Assert.CreateException();
|
||||
}
|
||||
|
||||
enum Side
|
||||
{
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right,
|
||||
Count
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Settings
|
||||
{
|
||||
public float minSpeed;
|
||||
public float maxSpeed;
|
||||
|
||||
public float minScale;
|
||||
public float maxScale;
|
||||
|
||||
public int startingSpawns;
|
||||
public int maxSpawns;
|
||||
|
||||
public float maxSpawnTime;
|
||||
|
||||
public float maxMass;
|
||||
public float minMass;
|
||||
}
|
||||
|
||||
class AsteroidAttributes
|
||||
{
|
||||
public float SizePx;
|
||||
public float InitialSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9a361be236d95c48a1de52c435fcc9a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
Reference in New Issue
Block a user