Initial Commit

This commit is contained in:
2026-03-16 14:38:46 +02:00
commit b8f7327a21
2327 changed files with 253610 additions and 0 deletions
@@ -0,0 +1,5 @@
fileFormatVersion: 2
guid: be4d1f9900f1746418149a1b3ab2c2b0
folderAsset: yes
DefaultImporter:
userData:
@@ -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>
{
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5608cae0fd6ebbd46b5ad3c03b150682
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -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;
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a9a361be236d95c48a1de52c435fcc9a
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -0,0 +1,5 @@
fileFormatVersion: 2
guid: a1b545d343661c54bbb60bc94fe13453
folderAsset: yes
DefaultImporter:
userData:
@@ -0,0 +1,129 @@
using System;
using UnityEngine;
namespace Zenject.Asteroids
{
public class GameInstaller : MonoInstaller
{
[Inject]
Settings _settings = null;
public override void InstallBindings()
{
// In this example there is only one 'installer' but in larger projects you
// will likely end up with many different re-usable installers
// that you'll want to use in several different scenes
//
// There are several ways to do this. You can store your installer as a prefab,
// a scriptable object, a component within the scene, etc. Or, if you don't
// need your installer to be a MonoBehaviour then you can just simply call
// Container.Install
//
// See here for more details:
// https://github.com/modesttree/zenject#installers
//
//Container.Install<MyOtherInstaller>();
// Install the main game
InstallAsteroids();
InstallShip();
InstallMisc();
InstallSignals();
InstallExecutionOrder();
}
void InstallAsteroids()
{
// ITickable, IFixedTickable, IInitializable and IDisposable are special Zenject interfaces.
// Binding a class to any of these interfaces creates an instance of the class at startup.
// Binding to any of these interfaces is also necessary to have the method defined in that interface be
// called on the implementing class as follows:
// Binding to ITickable or IFixedTickable will result in Tick() or FixedTick() being called like Update() or FixedUpdate().
// Binding to IInitializable means that Initialize() will be called on startup during Unity's Start event.
// Binding to IDisposable means that Dispose() will be called when the app closes or the scene changes
// Any time you use To<Foo>().AsSingle, what that means is that the DiContainer will only ever instantiate
// one instance of the type given inside the To<> (in this example, Foo). So in this case, any classes that take ITickable,
// IFixedTickable, or AsteroidManager as inputs will receive the same instance of AsteroidManager.
// We create multiple bindings for ITickable, so any dependencies that reference this type must be lists of ITickable.
Container.BindInterfacesAndSelfTo<AsteroidManager>().AsSingle();
// Note that the above binding is equivalent to the following:
//Container.Bind(typeof(ITickable), typeof(IFixedTickable), typeof(AsteroidManager)).To<AsteroidManager>.AsSingle();
// Here, we're defining a generic factory to create asteroid objects using the given prefab
// So any classes that want to create new asteroid objects can simply include an injected field
// or constructor parameter of type Asteroid.Factory, then call Create() on that
Container.BindFactory<Asteroid, Asteroid.Factory>()
// This means that any time Asteroid.Factory.Create is called, it will instantiate
// this prefab and then search it for the Asteroid component
.FromComponentInNewPrefab(_settings.AsteroidPrefab)
// We can also tell Zenject what to name the new gameobject here
.WithGameObjectName("Asteroid")
// GameObjectGroup's are just game objects used for organization
// This is nice so that it doesn't clutter up our scene hierarchy
.UnderTransformGroup("Asteroids");
}
void InstallMisc()
{
Container.BindInterfacesAndSelfTo<GameController>().AsSingle();
Container.Bind<LevelHelper>().AsSingle();
Container.BindInterfacesTo<AudioHandler>().AsSingle();
// FromComponentInNewPrefab matches the first transform only just like GetComponentsInChildren
// So can be useful in cases where we don't need a custom MonoBehaviour attached
Container.BindFactory<Transform, ExplosionFactory>()
.FromComponentInNewPrefab(_settings.ExplosionPrefab);
Container.BindFactory<Transform, BrokenShipFactory>()
.FromComponentInNewPrefab(_settings.BrokenShipPrefab);
}
void InstallSignals()
{
// Every scene that uses signals needs to install the built-in installer SignalBusInstaller
// Or alternatively it can be installed at the project context level (see docs for details)
SignalBusInstaller.Install(Container);
// Signals can be useful for game-wide events that could have many interested parties
Container.DeclareSignal<ShipCrashedSignal>();
}
void InstallShip()
{
Container.Bind<ShipStateFactory>().AsSingle();
// Note that the ship itself is bound using a ZenjectBinding component (see Ship
// game object in scene heirarchy)
Container.BindFactory<ShipStateWaitingToStart, ShipStateWaitingToStart.Factory>().WhenInjectedInto<ShipStateFactory>();
Container.BindFactory<ShipStateDead, ShipStateDead.Factory>().WhenInjectedInto<ShipStateFactory>();
Container.BindFactory<ShipStateMoving, ShipStateMoving.Factory>().WhenInjectedInto<ShipStateFactory>();
}
void InstallExecutionOrder()
{
// In many cases you don't need to worry about execution order,
// however sometimes it can be important
// If for example we wanted to ensure that AsteroidManager.Initialize
// always gets called before GameController.Initialize (and similarly for Tick)
// Then we could do the following:
Container.BindExecutionOrder<AsteroidManager>(-20);
Container.BindExecutionOrder<GameController>(-10);
// Note that they will be disposed of in the reverse order given here
}
[Serializable]
public class Settings
{
public GameObject ExplosionPrefab;
public GameObject BrokenShipPrefab;
public GameObject AsteroidPrefab;
public GameObject ShipPrefab;
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ac59283f2813c5643a2495056b74c1c0
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -0,0 +1,56 @@
using System;
namespace Zenject.Asteroids
{
// We prefer to use ScriptableObjectInstaller for installers that contain game settings
// There's no reason why you couldn't use a MonoInstaller here instead, however
// using ScriptableObjectInstaller has advantages here that make it nice for settings:
//
// 1) You can change these values at runtime and have those changes persist across play
// sessions. If it was a MonoInstaller then any changes would be lost when you hit stop
// 2) You can easily create multiple ScriptableObject instances of this installer to test
// different customizations to settings. For example, you might have different instances
// for each difficulty mode of your game, such as "Easy", "Hard", etc.
// 3) If your settings are associated with a game object composition root, then using
// ScriptableObjectInstaller can be easier since there will only ever be one definitive
// instance for each setting. Otherwise, you'd have to change the settings for each game
// object composition root separately at runtime
//
// Uncomment if you want to add alternative game settings
//[CreateAssetMenu(menuName = "Asteroids/Game Settings")]
public class GameSettingsInstaller : ScriptableObjectInstaller<GameSettingsInstaller>
{
public ShipSettings Ship;
public AsteroidSettings Asteroid;
public AudioHandler.Settings AudioHandler;
public GameInstaller.Settings GameInstaller;
// We use nested classes here to group related settings together
[Serializable]
public class ShipSettings
{
public ShipStateMoving.Settings StateMoving;
public ShipStateDead.Settings StateDead;
public ShipStateWaitingToStart.Settings StateStarting;
}
[Serializable]
public class AsteroidSettings
{
public AsteroidManager.Settings Spawner;
public Asteroid.Settings General;
}
public override void InstallBindings()
{
Container.BindInstance(Ship.StateMoving);
Container.BindInstance(Ship.StateDead);
Container.BindInstance(Ship.StateStarting);
Container.BindInstance(Asteroid.Spawner);
Container.BindInstance(Asteroid.General);
Container.BindInstance(AudioHandler);
Container.BindInstance(GameInstaller);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: ed9935f795c01cc498910bae6d24d10a
timeCreated: 1461785453
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,5 @@
fileFormatVersion: 2
guid: 400be1c46a6fb414f856927bc0910da7
folderAsset: yes
DefaultImporter:
userData:
@@ -0,0 +1,127 @@
using System;
using ModestTree;
using UnityEngine;
namespace Zenject.Asteroids
{
public enum GameStates
{
WaitingToStart,
Playing,
GameOver
}
public class GameController : IInitializable, ITickable, IDisposable
{
readonly SignalBus _signalBus;
readonly Ship _ship;
readonly AsteroidManager _asteroidSpawner;
GameStates _state = GameStates.WaitingToStart;
float _elapsedTime;
public GameController(
Ship ship, AsteroidManager asteroidSpawner,
SignalBus signalBus)
{
_signalBus = signalBus;
_asteroidSpawner = asteroidSpawner;
_ship = ship;
}
public float ElapsedTime
{
get { return _elapsedTime; }
}
public GameStates State
{
get { return _state; }
}
public void Initialize()
{
Physics.gravity = Vector3.zero;
Cursor.visible = false;
_signalBus.Subscribe<ShipCrashedSignal>(OnShipCrashed);
}
public void Dispose()
{
_signalBus.Unsubscribe<ShipCrashedSignal>(OnShipCrashed);
}
public void Tick()
{
switch (_state)
{
case GameStates.WaitingToStart:
{
UpdateStarting();
break;
}
case GameStates.Playing:
{
UpdatePlaying();
break;
}
case GameStates.GameOver:
{
UpdateGameOver();
break;
}
default:
{
Assert.That(false);
break;
}
}
}
void UpdateGameOver()
{
Assert.That(_state == GameStates.GameOver);
if (Input.GetMouseButtonDown(0))
{
StartGame();
}
}
void OnShipCrashed()
{
Assert.That(_state == GameStates.Playing);
_state = GameStates.GameOver;
_asteroidSpawner.Stop();
}
void UpdatePlaying()
{
Assert.That(_state == GameStates.Playing);
_elapsedTime += Time.deltaTime;
}
void UpdateStarting()
{
Assert.That(_state == GameStates.WaitingToStart);
if (Input.GetMouseButtonDown(0))
{
StartGame();
}
}
void StartGame()
{
Assert.That(_state == GameStates.WaitingToStart || _state == GameStates.GameOver);
_ship.Position = Vector3.zero;
_elapsedTime = 0;
_asteroidSpawner.Start();
_ship.ChangeState(ShipStates.Moving);
_state = GameStates.Playing;
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d1fe837eefbc3fa4ab06a9081d26caf9
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -0,0 +1,5 @@
fileFormatVersion: 2
guid: f289e0dd7cbe36b408dcfd072332f8b6
folderAsset: yes
DefaultImporter:
userData:
@@ -0,0 +1,43 @@
using System;
using UnityEngine;
namespace Zenject.Asteroids
{
public class AudioHandler : IInitializable, IDisposable
{
readonly SignalBus _signalBus;
readonly Settings _settings;
readonly AudioSource _audioSource;
public AudioHandler(
AudioSource audioSource,
Settings settings,
SignalBus signalBus)
{
_signalBus = signalBus;
_settings = settings;
_audioSource = audioSource;
}
public void Initialize()
{
_signalBus.Subscribe<ShipCrashedSignal>(OnShipCrashed);
}
public void Dispose()
{
_signalBus.Unsubscribe<ShipCrashedSignal>(OnShipCrashed);
}
void OnShipCrashed()
{
_audioSource.PlayOneShot(_settings.CrashSound);
}
[Serializable]
public class Settings
{
public AudioClip CrashSound;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 24cbab5eaeb84b44481d817f66ea7b27
timeCreated: 1461797898
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,218 @@
using System;
using ModestTree;
using UnityEngine;
#pragma warning disable 649
namespace Zenject.Asteroids
{
public class GuiHandler : MonoBehaviour, IDisposable, IInitializable
{
GameController _gameController;
[SerializeField]
GUIStyle _titleStyle;
[SerializeField]
GUIStyle _instructionsStyle;
[SerializeField]
GUIStyle _timeStyle;
[SerializeField]
float _gameOverFadeInTime;
[SerializeField]
float _gameOverStartFadeTime;
[SerializeField]
float _restartTextStartFadeTime;
[SerializeField]
float _restartTextFadeInTime;
float _gameOverElapsed;
SignalBus _signalBus;
[Inject]
public void Construct(
GameController gameController, SignalBus signalBus)
{
_gameController = gameController;
_signalBus = signalBus;
}
void OnGUI()
{
GUILayout.BeginArea(new Rect(0, 0, Screen.width, Screen.height));
{
switch (_gameController.State)
{
case GameStates.WaitingToStart:
{
StartGui();
break;
}
case GameStates.Playing:
{
PlayingGui();
break;
}
case GameStates.GameOver:
{
PlayingGui();
GameOverGui();
break;
}
default:
{
Assert.That(false);
break;
}
}
}
GUILayout.EndArea();
}
void GameOverGui()
{
_gameOverElapsed += Time.deltaTime;
if (_gameOverElapsed > _gameOverStartFadeTime)
{
var px = Mathf.Min(1.0f, (_gameOverElapsed - _gameOverStartFadeTime) / _gameOverFadeInTime);
_titleStyle.normal.textColor = new Color(1, 1, 1, px);
}
else
{
_titleStyle.normal.textColor = new Color(1, 1, 1, 0);
}
if (_gameOverElapsed > _restartTextStartFadeTime)
{
var px = Mathf.Min(1.0f, (_gameOverElapsed - _restartTextStartFadeTime) / _restartTextFadeInTime);
_instructionsStyle.normal.textColor = new Color(1, 1, 1, px);
}
else
{
_instructionsStyle.normal.textColor = new Color(1, 1, 1, 0);
}
GUILayout.BeginHorizontal();
{
GUILayout.FlexibleSpace();
GUILayout.BeginVertical();
{
GUILayout.FlexibleSpace();
GUILayout.BeginVertical();
{
GUILayout.FlexibleSpace();
GUILayout.BeginHorizontal();
{
GUILayout.FlexibleSpace();
GUILayout.Label("GAME OVER", _titleStyle);
GUILayout.FlexibleSpace();
}
GUILayout.EndHorizontal();
GUILayout.Space(60);
GUILayout.BeginHorizontal();
{
GUILayout.FlexibleSpace();
GUILayout.Label("Click to restart", _instructionsStyle);
GUILayout.FlexibleSpace();
}
GUILayout.EndHorizontal();
}
GUILayout.EndVertical();
GUILayout.FlexibleSpace();
}
GUILayout.EndVertical();
GUILayout.FlexibleSpace();
}
GUILayout.EndHorizontal();
}
void PlayingGui()
{
GUILayout.BeginVertical();
{
GUILayout.Space(30);
GUILayout.BeginHorizontal();
{
GUILayout.Space(30);
GUILayout.Label("Time: " + _gameController.ElapsedTime.ToString("0.##"), _timeStyle);
GUILayout.FlexibleSpace();
}
GUILayout.EndHorizontal();
}
GUILayout.EndVertical();
}
void StartGui()
{
GUILayout.BeginHorizontal();
{
GUILayout.FlexibleSpace();
GUILayout.BeginVertical();
{
GUILayout.Space(100);
GUILayout.FlexibleSpace();
GUILayout.BeginVertical();
{
GUILayout.FlexibleSpace();
GUILayout.BeginHorizontal();
{
GUILayout.FlexibleSpace();
GUILayout.Label("ASTEROIDS", _titleStyle);
GUILayout.FlexibleSpace();
}
GUILayout.EndHorizontal();
GUILayout.Space(60);
GUILayout.BeginHorizontal();
{
GUILayout.FlexibleSpace();
GUILayout.Label("Click to start", _instructionsStyle);
GUILayout.FlexibleSpace();
}
GUILayout.EndHorizontal();
}
GUILayout.EndVertical();
GUILayout.FlexibleSpace();
}
GUILayout.EndVertical();
GUILayout.FlexibleSpace();
}
GUILayout.EndHorizontal();
}
public void Initialize()
{
_signalBus.Subscribe<ShipCrashedSignal>(OnShipCrashed);
}
public void Dispose()
{
_signalBus.Unsubscribe<ShipCrashedSignal>(OnShipCrashed);
}
void OnShipCrashed()
{
_gameOverElapsed = 0;
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 79984cb687438fd469b84d7e916d0574
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -0,0 +1,57 @@
using UnityEngine;
namespace Zenject.Asteroids
{
public class LevelHelper
{
readonly Camera _camera;
public LevelHelper(
[Inject(Id = "Main")]
Camera camera)
{
_camera = camera;
}
public float Bottom
{
get { return -ExtentHeight; }
}
public float Top
{
get { return ExtentHeight; }
}
public float Left
{
get { return -ExtentWidth; }
}
public float Right
{
get { return ExtentWidth; }
}
public float ExtentHeight
{
get { return _camera.orthographicSize; }
}
public float Height
{
get { return ExtentHeight * 2.0f; }
}
public float ExtentWidth
{
get { return _camera.aspect * _camera.orthographicSize; }
}
public float Width
{
get { return ExtentWidth * 2.0f; }
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 439c34e98ba09b546be28d2b13fda970
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -0,0 +1,26 @@
using UnityEngine;
#pragma warning disable 649
namespace Zenject.Asteroids
{
public class TilingBackground : MonoBehaviour
{
[SerializeField]
float _speed;
Vector2 _offset;
Renderer _renderer;
void Awake()
{
_renderer = GetComponent<Renderer>();
}
void Update()
{
_offset.y += _speed * Time.deltaTime;
_renderer.material.mainTextureOffset = _offset;
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d72ecc6be0485ff4f96c39e24aea61f3
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -0,0 +1,5 @@
fileFormatVersion: 2
guid: bb012d11f2128404dbc27a5b0ed3e26a
folderAsset: yes
DefaultImporter:
userData:
@@ -0,0 +1,87 @@
using UnityEngine;
#pragma warning disable 649
#pragma warning disable 618
namespace Zenject.Asteroids
{
public class Ship : MonoBehaviour
{
[SerializeField]
MeshRenderer _meshRenderer;
#if UNITY_2018_1_OR_NEWER
[SerializeField]
ParticleSystem _particleSystem;
#else
[SerializeField]
ParticleEmitter _particleEmitter;
#endif
ShipStateFactory _stateFactory;
ShipState _state;
[Inject]
public void Construct(ShipStateFactory stateFactory)
{
_stateFactory = stateFactory;
}
public MeshRenderer MeshRenderer
{
get { return _meshRenderer; }
}
#if UNITY_2018_1_OR_NEWER
public ParticleSystem ParticleEmitter
{
get { return _particleSystem; }
}
#else
public ParticleEmitter ParticleEmitter
{
get { return _particleEmitter; }
}
#endif
public Vector3 Position
{
get { return transform.position; }
set { transform.position = value; }
}
public Quaternion Rotation
{
get { return transform.rotation; }
set { transform.rotation = value; }
}
public void Start()
{
ChangeState(ShipStates.WaitingToStart);
}
public void Update()
{
_state.Update();
}
public void OnTriggerEnter(Collider other)
{
_state.OnTriggerEnter(other);
}
public void ChangeState(ShipStates state)
{
if (_state != null)
{
_state.Dispose();
_state = null;
}
_state = _stateFactory.CreateState(state);
_state.Start();
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aab42c7e45a13404daf7de27c7366efc
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -0,0 +1,50 @@
using ModestTree;
namespace Zenject.Asteroids
{
public enum ShipStates
{
Moving,
Dead,
WaitingToStart,
Count
}
public class ShipStateFactory
{
readonly ShipStateWaitingToStart.Factory _waitingFactory;
readonly ShipStateMoving.Factory _movingFactory;
readonly ShipStateDead.Factory _deadFactory;
public ShipStateFactory(
ShipStateDead.Factory deadFactory,
ShipStateMoving.Factory movingFactory,
ShipStateWaitingToStart.Factory waitingFactory)
{
_waitingFactory = waitingFactory;
_movingFactory = movingFactory;
_deadFactory = deadFactory;
}
public ShipState CreateState(ShipStates state)
{
switch (state)
{
case ShipStates.Dead:
{
return _deadFactory.Create();
}
case ShipStates.WaitingToStart:
{
return _waitingFactory.Create();
}
case ShipStates.Moving:
{
return _movingFactory.Create();
}
}
throw Assert.CreateException();
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 732d3e232d9f7084ca4f03e6129bcbb6
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 35ea476df49732c408f30d07debaf227
folderAsset: yes
timeCreated: 1461796083
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
using UnityEngine;
namespace Zenject.Asteroids
{
public class BrokenShipFactory : PlaceholderFactory<Transform>
{
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: b9b5eb959c3c75149a4153c9c7d7a7d4
timeCreated: 1528645987
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
using UnityEngine;
namespace Zenject.Asteroids
{
public class ExplosionFactory : PlaceholderFactory<Transform>
{
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: aa6c7bf121e4f8343b05c7d09eb12ec6
timeCreated: 1528645987
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,25 @@
using System;
using UnityEngine;
namespace Zenject.Asteroids
{
public abstract class ShipState : IDisposable
{
public abstract void Update();
public virtual void Start()
{
// optionally overridden
}
public virtual void Dispose()
{
// optionally overridden
}
public virtual void OnTriggerEnter(Collider other)
{
// optionally overridden
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a68789b75fb09ef43b854fd3c59241b0
timeCreated: 1461796083
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,78 @@
using System;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Zenject.Asteroids
{
public class ShipStateDead : ShipState
{
readonly SignalBus _signalBus;
readonly BrokenShipFactory _brokenShipFactory;
readonly ExplosionFactory _explosionFactory;
readonly Settings _settings;
readonly Ship _ship;
GameObject _shipBroken;
GameObject _explosion;
public ShipStateDead(
Settings settings, Ship ship,
ExplosionFactory explosionFactory,
BrokenShipFactory brokenShipFactory,
SignalBus signalBus)
{
_signalBus = signalBus;
_brokenShipFactory = brokenShipFactory;
_explosionFactory = explosionFactory;
_settings = settings;
_ship = ship;
}
public override void Start()
{
_ship.MeshRenderer.enabled = false;
_ship.ParticleEmitter.gameObject.SetActive(false);
_explosion = _explosionFactory.Create().gameObject;
_explosion.transform.position = _ship.Position;
_shipBroken = _brokenShipFactory.Create().gameObject;
_shipBroken.transform.position = _ship.Position;
_shipBroken.transform.rotation = _ship.Rotation;
foreach (var rigidBody in _shipBroken.GetComponentsInChildren<Rigidbody>())
{
var randomTheta = Random.Range(0, Mathf.PI * 2.0f);
var randomDir = new Vector3(Mathf.Cos(randomTheta), Mathf.Sin(randomTheta), 0);
rigidBody.AddForce(randomDir * _settings.explosionForce);
}
_signalBus.Fire<ShipCrashedSignal>();
}
public override void Dispose()
{
_ship.MeshRenderer.enabled = true;
_ship.ParticleEmitter.gameObject.SetActive(true);
GameObject.Destroy(_explosion);
GameObject.Destroy(_shipBroken);
}
public override void Update()
{
}
[Serializable]
public class Settings
{
public float explosionForce;
}
public class Factory : PlaceholderFactory<ShipStateDead>
{
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 379e1c748b9152a449b52cd01e25a642
timeCreated: 1461796083
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,112 @@
using System;
using ModestTree;
using UnityEngine;
namespace Zenject.Asteroids
{
public class ShipStateMoving : ShipState
{
readonly Settings _settings;
readonly Camera _mainCamera;
readonly Ship _ship;
Vector3 _lastPosition;
float _oscillationTheta;
public ShipStateMoving(
Settings settings, Ship ship,
[Inject(Id = "Main")]
Camera mainCamera)
{
_ship = ship;
_settings = settings;
_mainCamera = mainCamera;
}
public override void Update()
{
UpdateThruster();
Move();
ApplyOscillation();
}
void ApplyOscillation()
{
var obj = _ship.MeshRenderer.gameObject;
var cycleInterval = 1.0f / _settings.oscillationFrequency;
var thetaMoveSpeed = 2 * Mathf.PI / cycleInterval;
_oscillationTheta += thetaMoveSpeed * Time.deltaTime;
obj.transform.position = obj.transform.parent.position + new Vector3(0, _settings.oscillationAmplitude * Mathf.Sin(_oscillationTheta), 0);
}
void UpdateThruster()
{
var speed = (_ship.Position - _lastPosition).magnitude / Time.deltaTime;
var speedPx = Mathf.Clamp(speed / _settings.speedForMaxEmisssion, 0.0f, 1.0f);
#if UNITY_2018_1_OR_NEWER
var emission = _ship.ParticleEmitter.emission;
emission.rateOverTime = _settings.maxEmission * speedPx;
#else
_ship.ParticleEmitter.maxEmission = _settings.maxEmission * speedPx;
#endif
}
void Move()
{
var mouseRay = _mainCamera.ScreenPointToRay(Input.mousePosition);
var mousePos = mouseRay.origin;
mousePos.z = 0;
_lastPosition = _ship.Position;
_ship.Position = Vector3.Lerp(_ship.Position, mousePos, Mathf.Min(1.0f, _settings.moveSpeed * Time.deltaTime));
var moveDelta = _ship.Position - _lastPosition;
var moveDistance = moveDelta.magnitude;
if (moveDistance > 0.01f)
{
var moveDir = moveDelta / moveDistance;
_ship.Rotation = Quaternion.LookRotation(-moveDir);
}
}
public override void Start()
{
_lastPosition = _ship.Position;
_ship.ParticleEmitter.gameObject.SetActive(true);
}
public override void Dispose()
{
_ship.ParticleEmitter.gameObject.SetActive(false);
}
public override void OnTriggerEnter(Collider other)
{
Assert.That(other.GetComponent<Asteroid>() != null);
_ship.ChangeState(ShipStates.Dead);
}
[Serializable]
public class Settings
{
public float moveSpeed;
public float rotateSpeed;
public float speedForMaxEmisssion;
public float maxEmission;
public float oscillationFrequency;
public float oscillationAmplitude;
}
public class Factory : PlaceholderFactory<ShipStateMoving>
{
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6519cef92721b644c9e0b46f2f92430a
timeCreated: 1461796083
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
using System;
using UnityEngine;
namespace Zenject.Asteroids
{
public class ShipStateWaitingToStart : ShipState
{
readonly Settings _settings;
readonly Ship _ship;
float _theta;
public ShipStateWaitingToStart(
Ship ship,
Settings settings)
{
_settings = settings;
_ship = ship;
}
public override void Start()
{
_ship.Position = _settings.StartOffset;
_ship.Rotation = Quaternion.AngleAxis(90.0f, Vector3.up) * Quaternion.AngleAxis(90.0f, Vector3.right);
}
public override void Update()
{
_ship.Position = _settings.StartOffset + Vector3.up * _settings.Amplitude * Mathf.Sin(_theta);
_theta += Time.deltaTime * _settings.Frequency;
}
[Serializable]
public class Settings
{
public Vector3 StartOffset;
public float Amplitude;
public float Frequency;
}
public class Factory : PlaceholderFactory<ShipStateWaitingToStart>
{
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: ce3520353cbeb084691b14a57a7bec93
timeCreated: 1461796083
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,5 @@
fileFormatVersion: 2
guid: ca259dfabfd45a54eba7128747f28149
folderAsset: yes
DefaultImporter:
userData:
@@ -0,0 +1,6 @@
namespace Zenject.Asteroids
{
public class ShipCrashedSignal
{
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 44a6c869bd4f68c479f02155b98fbeca
timeCreated: 1483817409
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,32 @@
using UnityEngine;
namespace Zenject.Asteroids
{
public static class UnityExtensionMethods
{
// Since transforms return their position as a property,
// you can't set the x/y/z values directly, so you have to
// store a temporary Vector3
// Or you can use these methods instead
public static void SetX(this Transform transform, float x)
{
var pos = transform.position;
pos.x = x;
transform.position = pos;
}
public static void SetY(this Transform transform, float y)
{
var pos = transform.position;
pos.y = y;
transform.position = pos;
}
public static void SetZ(this Transform transform, float z)
{
var pos = transform.position;
pos.z = z;
transform.position = pos;
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1018c66230d7f7b408485a0d8c28147d
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: