diff --git a/Assets/Prefabs/Tower.prefab b/Assets/Prefabs/Tower.prefab
index cc673093afaff645b43be476985f1302238e8cd1..01597522a044f8aad9eeb2d34a8cb95d5c4ab317 100644
--- a/Assets/Prefabs/Tower.prefab
+++ b/Assets/Prefabs/Tower.prefab
@@ -11,6 +11,7 @@ GameObject:
   - component: {fileID: 2849581468949299191}
   - component: {fileID: 7040201590077353716}
   - component: {fileID: -5257472747468331865}
+  - component: {fileID: 4195717166071304880}
   m_Layer: 0
   m_Name: Tower
   m_TagString: Untagged
@@ -95,5 +96,101 @@ MonoBehaviour:
   m_Script: {fileID: 11500000, guid: 67aafe08abf8467fb869765ad1410d15, type: 3}
   m_Name: 
   m_EditorClassIdentifier: 
-  blueTowerData: {fileID: 11400000, guid: 7a55e55983e119942a28e07adc7bb9e3, type: 2}
-  redTowerData: {fileID: 11400000, guid: 1a87325dc264be941b2d558ea6e8987b, type: 2}
+--- !u!120 &4195717166071304880
+LineRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1091641560809420229}
+  m_Enabled: 0
+  m_CastShadows: 0
+  m_ReceiveShadows: 1
+  m_DynamicOccludee: 1
+  m_MotionVectors: 0
+  m_LightProbeUsage: 0
+  m_ReflectionProbeUsage: 0
+  m_RayTracingMode: 0
+  m_RayTraceProcedural: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 10306, guid: 0000000000000000f000000000000000, type: 0}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 1
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: -1196961719
+  m_SortingLayer: 2
+  m_SortingOrder: 0
+  m_Positions:
+  - {x: 0, y: 0, z: 0}
+  - {x: 0, y: 0, z: 0}
+  m_Parameters:
+    serializedVersion: 3
+    widthMultiplier: 0.2037388
+    widthCurve:
+      serializedVersion: 2
+      m_Curve:
+      - serializedVersion: 3
+        time: 0
+        value: 1
+        inSlope: 0
+        outSlope: 0
+        tangentMode: 0
+        weightedMode: 0
+        inWeight: 0.33333334
+        outWeight: 0.33333334
+      m_PreInfinity: 2
+      m_PostInfinity: 2
+      m_RotationOrder: 4
+    colorGradient:
+      serializedVersion: 2
+      key0: {r: 0, g: 1, b: 0, a: 1}
+      key1: {r: 0, g: 1, b: 0, a: 1}
+      key2: {r: 0, g: 0, b: 0, a: 0}
+      key3: {r: 0, g: 0, b: 0, a: 0}
+      key4: {r: 0, g: 0, b: 0, a: 0}
+      key5: {r: 0, g: 0, b: 0, a: 0}
+      key6: {r: 0, g: 0, b: 0, a: 0}
+      key7: {r: 0, g: 0, b: 0, a: 0}
+      ctime0: 0
+      ctime1: 65535
+      ctime2: 0
+      ctime3: 0
+      ctime4: 0
+      ctime5: 0
+      ctime6: 0
+      ctime7: 0
+      atime0: 0
+      atime1: 65535
+      atime2: 0
+      atime3: 0
+      atime4: 0
+      atime5: 0
+      atime6: 0
+      atime7: 0
+      m_Mode: 1
+      m_NumColorKeys: 2
+      m_NumAlphaKeys: 2
+    numCornerVertices: 0
+    numCapVertices: 10
+    alignment: 0
+    textureMode: 0
+    shadowBias: 0.5
+    generateLightingData: 0
+  m_UseWorldSpace: 1
+  m_Loop: 0
diff --git a/Assets/Scripts/Logic/Event/EventDispatcher.cs b/Assets/Scripts/Logic/Event/EventDispatcher.cs
index b2843bc7b8a65bc4d375a5d68a1be601e60f439b..bccf6bbb536fba293a38c67a47bfdb60755bfb55 100644
--- a/Assets/Scripts/Logic/Event/EventDispatcher.cs
+++ b/Assets/Scripts/Logic/Event/EventDispatcher.cs
@@ -1,5 +1,6 @@
 ďťżusing System;
 using System.Collections.Generic;
+using System.Linq;
 
 namespace Logic.Event {
 
@@ -16,7 +17,9 @@ namespace Logic.Event {
 /// </p>
 /// </summary>
 public class EventDispatcher {
-	private readonly IDictionary<Type, IList<Delegate>> _listeners = new Dictionary<Type, IList<Delegate>>();
+	private readonly IDictionary<Type, IList<RegisteredListener>> _listeners =
+		new Dictionary<Type, IList<RegisteredListener>>();
+
 	private readonly Action<ICollection<EventInvocationException>> _eventRaisingErrorHandler;
 
 	/// <summary>
@@ -28,6 +31,13 @@ public class EventDispatcher {
 		_eventRaisingErrorHandler = eventRaisingErrorHandler;
 	}
 
+	/// <summary>
+	/// Same as calling <see cref="AddListener{T}(Ordering, Listener{T})"/> with <see cref="Ordering.Normal"/>.
+	/// </summary>
+	public void AddListener<T>(Listener<T> listener) where T : BaseEvent {
+		AddListener(Ordering.Normal, listener);
+	}
+
 	/// <summary>
 	/// Registers a new listener to the specified event.
 	/// The same listener is allowed to listen to the same event multiple times.
@@ -35,15 +45,16 @@ public class EventDispatcher {
 	/// A listener is also called if a superclass of the specified event is raised,
 	/// see <see cref="Raise"/> for more information about this.
 	/// </summary>
+	/// <param name="ordering">when the listener should be called relative to other listeners</param>
 	/// <param name="listener">the consumer of the event</param>
 	/// <typeparam name="T">the event to listen to</typeparam>
-	public void AddListener<T>(Listener<T> listener) where T : BaseEvent {
-		if (!_listeners.TryGetValue(typeof(T), out IList<Delegate> list)) {
-			list = new List<Delegate>();
-			_listeners[typeof(T)] = list;
+	public void AddListener<T>(Ordering ordering, Listener<T> listener) where T : BaseEvent {
+		if (!_listeners.TryGetValue(typeof(T), out IList<RegisteredListener> set)) {
+			set = new List<RegisteredListener>();
+			_listeners[typeof(T)] = set;
 		}
 
-		list.Add(listener);
+		set.Add(new RegisteredListener(ordering, listener));
 	}
 
 	/// <summary>
@@ -52,13 +63,15 @@ public class EventDispatcher {
 	/// If the same listener was registered multiple times,
 	/// then this method only removes one occurrence of the listener.
 	/// </summary>
+	/// <param name="ordering">the ordering of the listener that should be unregistered</param>
 	/// <param name="listener">the listener to unregister</param>
 	/// <typeparam name="T">the event to unregister the listener from</typeparam>
 	/// <exception cref="IllegalListenerStateException">if the specified listener isn't
 	/// among the listeners of the specified event</exception>
-	public void RemoveListener<T>(Listener<T> listener) where T : BaseEvent {
-		if (!_listeners.TryGetValue(typeof(T), out IList<Delegate> list) || !list.Remove(listener)) {
-			throw new IllegalListenerStateException($"{listener} isn't a registered listener of {typeof(T)}");
+	public void RemoveListener<T>(Ordering ordering, Listener<T> listener) where T : BaseEvent {
+		RegisteredListener registered = new RegisteredListener(ordering, listener);
+		if (!_listeners.TryGetValue(typeof(T), out IList<RegisteredListener> set) || !set.Remove(registered)) {
+			throw new IllegalListenerStateException($"{registered} isn't a registered listener of {typeof(T)}");
 		}
 	}
 
@@ -71,28 +84,43 @@ public class EventDispatcher {
 	public void Raise(BaseEvent eventData) {
 		List<EventInvocationException> errors = new List<EventInvocationException>();
 
-		//Call listeners which listen to the exact event type or one of its superclasses
-		Type type = eventData.GetType();
-		do {
-			// ReSharper disable once AssignNullToNotNullAttribute
-			if (!_listeners.TryGetValue(type, out IList<Delegate> listeners)) break;
-
-			foreach (Delegate del in listeners) {
-				try {
-					del.DynamicInvoke(eventData);
-				} catch (Exception e) {
-					errors.Add(new EventInvocationException($"Error consuming {eventData} by {del}", e));
+		foreach (Ordering ordering in Enum.GetValues(typeof(Ordering))) {
+			//Call listeners which listen to the exact event type or one of its superclasses
+			Type type = eventData.GetType();
+			do {
+				// ReSharper disable once AssignNullToNotNullAttribute
+				if (!_listeners.TryGetValue(type, out IList<RegisteredListener> listeners)) break;
+
+				foreach (RegisteredListener registered in listeners.Where(x => x.Order == ordering)) {
+					try {
+						registered.Listener.DynamicInvoke(eventData);
+					} catch (Exception e) {
+						errors.Add(new EventInvocationException($"Error consuming {eventData} by {registered}", e));
+					}
 				}
-			}
 
-			type = type.BaseType;
-		} while (type != typeof(object));
+				type = type.BaseType;
+			} while (type != typeof(object));
+		}
 
 		if (errors.Count > 0) {
 			_eventRaisingErrorHandler.Invoke(errors);
 		}
 	}
 
+	/// <summary>
+	/// Determines when a <see cref="Listener{T}"/> should be called
+	/// relative to other listeners.
+	/// The order when multiple listeners share the same enum value is unspecified.
+	/// </summary>
+	public enum Ordering {
+		First,
+		Sooner,
+		Normal,
+		Later,
+		Last
+	}
+
 	/// <summary>
 	/// Consumes an event and executes action(s) based on said event.
 	/// </summary>
@@ -112,6 +140,20 @@ public class EventDispatcher {
 	public class EventInvocationException : Exception {
 		public EventInvocationException(string message, Exception e) : base(message, e) {}
 	}
+
+	private readonly struct RegisteredListener {
+		public Ordering Order { get; }
+		public Delegate Listener { get; }
+
+		public RegisteredListener(Ordering order, Delegate listener) {
+			Order = order;
+			Listener = listener;
+		}
+
+		public override string ToString() {
+			return "{" + Order + ">>" + Listener + "}";
+		}
+	}
 }
 
 }
diff --git a/Assets/Scripts/Logic/System/DestroyUnitSystem.cs b/Assets/Scripts/Logic/System/DestroyUnitSystem.cs
index 59ecf1aa5d4a1308e7f8cf57b31bad33edf28685..ea77f25b5d9fce6392063ae30026f1354e75c16c 100644
--- a/Assets/Scripts/Logic/System/DestroyUnitSystem.cs
+++ b/Assets/Scripts/Logic/System/DestroyUnitSystem.cs
@@ -5,7 +5,9 @@ using Logic.Event.World.Unit;
 namespace Logic.System {
 public class DestroyUnitSystem : BaseSystem {
 	public override void RegisterListeners(EventDispatcher dispatcher) {
-		dispatcher.AddListener<UnitDamagedEvent>(On);
+		//Call later than usual: let listeners get notified about the damaged event
+		// before the destroyed event is invoked.
+		dispatcher.AddListener<UnitDamagedEvent>(EventDispatcher.Ordering.Later, On);
 	}
 
 	private void On(UnitDamagedEvent e) {
diff --git a/Assets/Scripts/LogicTests/EventDispatcherTest.cs b/Assets/Scripts/LogicTests/EventDispatcherTest.cs
index 64e61f8d146a77cd63d3113cd1a76eacc3fdee9c..2b9cea50cdd9d6f98f3581454da1da675941418c 100644
--- a/Assets/Scripts/LogicTests/EventDispatcherTest.cs
+++ b/Assets/Scripts/LogicTests/EventDispatcherTest.cs
@@ -19,13 +19,17 @@ public class EventDispatcherTest {
 		EventDispatcher dispatcher = NewErrorRethrowingDispatcher();
 		EventDispatcher.Listener<BaseEvent> listener = _ => {};
 		Assert.Throws<EventDispatcher.IllegalListenerStateException>(() =>
-			dispatcher.RemoveListener<BicycleEvent>(listener));
+			dispatcher.RemoveListener<BicycleEvent>(EventDispatcher.Ordering.Normal, listener));
 		dispatcher.AddListener<BicycleEvent>(listener);
 		dispatcher.AddListener<BicycleEvent>(listener);
-		dispatcher.RemoveListener<BicycleEvent>(listener);
-		dispatcher.RemoveListener<BicycleEvent>(listener);
+		dispatcher.AddListener<BicycleEvent>(EventDispatcher.Ordering.First, listener);
+		dispatcher.RemoveListener<BicycleEvent>(EventDispatcher.Ordering.Normal, listener);
+		dispatcher.RemoveListener<BicycleEvent>(EventDispatcher.Ordering.Normal, listener);
 		Assert.Throws<EventDispatcher.IllegalListenerStateException>(() =>
-			dispatcher.RemoveListener<BicycleEvent>(listener));
+			dispatcher.RemoveListener<BicycleEvent>(EventDispatcher.Ordering.Normal, listener));
+		dispatcher.RemoveListener<BicycleEvent>(EventDispatcher.Ordering.First, listener);
+		Assert.Throws<EventDispatcher.IllegalListenerStateException>(() =>
+			dispatcher.RemoveListener<BicycleEvent>(EventDispatcher.Ordering.First, listener));
 	}
 
 	[Test]
@@ -116,6 +120,21 @@ public class EventDispatcherTest {
 		Assert.IsTrue(called);
 	}
 
+	[Test]
+	public void TestInvocationOrder() {
+		EventDispatcher dispatcher = NewErrorRethrowingDispatcher();
+		var concat = "-";
+		// ReSharper disable once AccessToModifiedClosure
+		dispatcher.AddListener<BicycleEvent>(_ => concat += "3");
+		dispatcher.AddListener<BaseEvent>(EventDispatcher.Ordering.Normal, _ => concat += "3");
+		dispatcher.AddListener<BaseEvent>(EventDispatcher.Ordering.Sooner, _ => concat += "2");
+		dispatcher.AddListener<BicycleEvent>(EventDispatcher.Ordering.Last, _ => concat += "5");
+		dispatcher.AddListener<BicycleEvent>(EventDispatcher.Ordering.First, _ => concat += "1");
+		dispatcher.AddListener<BicycleEvent>(EventDispatcher.Ordering.Later, _ => concat += "4");
+		dispatcher.Raise(new BicycleEvent());
+		Assert.AreEqual("-123345", concat);
+	}
+
 	private static EventDispatcher NewErrorRethrowingDispatcher() {
 		return new EventDispatcher(errors => throw new AggregateException(errors));
 	}
diff --git a/Assets/Scripts/Presentation/World/Barrack.cs b/Assets/Scripts/Presentation/World/Barrack.cs
index 950505fa222479ec71fb22232e43e26532c64942..c4d89e64c88ba4502a88bf2658062b57f10de504 100644
--- a/Assets/Scripts/Presentation/World/Barrack.cs
+++ b/Assets/Scripts/Presentation/World/Barrack.cs
@@ -3,7 +3,7 @@ using Color = Logic.Data.Color;
 
 namespace Presentation.World {
 [RequireComponent(typeof(SpriteRenderer))]
-public class Barrack : MonoBehaviour {
+public class Barrack : Structure {
 	[SerializeField]
 	private BarrackData blueBarrackData;
 
diff --git a/Assets/Scripts/Presentation/World/Tower.cs b/Assets/Scripts/Presentation/World/Tower.cs
index e3ccd6952ff1c58be2ac618440d56555fb80a4f0..8de3600e8ac183bad90688ea58a2cf9a9db1daef 100644
--- a/Assets/Scripts/Presentation/World/Tower.cs
+++ b/Assets/Scripts/Presentation/World/Tower.cs
@@ -3,6 +3,7 @@ using Color = Logic.Data.Color;
 
 namespace Presentation.World {
 [RequireComponent(typeof(SpriteRenderer))]
+[RequireComponent(typeof(LineRenderer))]
 public class Tower : Structure {
 	private Logic.Data.World.Tower _data;
 
@@ -15,6 +16,12 @@ public class Tower : Structure {
 		_spriteRenderer = GetComponent<SpriteRenderer>();
 		_spriteRenderer.sprite = data.Sprite;
 		_spriteRenderer.color = color;
+
+		LineRenderer laserRenderer = GetComponent<LineRenderer>();
+		Gradient laserGradient = new Gradient();
+		laserGradient.SetKeys(new[] { new GradientColorKey(color, 0), new GradientColorKey(color, 1) },
+			new[] { new GradientAlphaKey(1, 0), new GradientAlphaKey(1, 1) });
+		laserRenderer.colorGradient = laserGradient;
 	}
 
 	public void SetData(Logic.Data.World.Tower data) {
diff --git a/Assets/Scripts/Presentation/World/World.cs b/Assets/Scripts/Presentation/World/World.cs
index 0d21a8e94a0a606fe1b33576bb7ff4a06b87525a..d803aac18a434f9f81b093e1204bf0ac4bfc69e9 100644
--- a/Assets/Scripts/Presentation/World/World.cs
+++ b/Assets/Scripts/Presentation/World/World.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using Logic.Data.World;
 using Logic.Event.World.Tower;
@@ -14,17 +15,22 @@ public class World : MonoBehaviour {
 	public GameObject Castle;
 	public GameObject Unit;
 
-	public float TilePadding = 0.1f;
-
 	private GameObject[,] _map;
 	private Dictionary<Logic.Data.World.Unit, Unit> _units;
 
+	public T LogicToPresentation<T>(TileObject tileObject) where T : Structure
+		=> _map[tileObject.Position.X, tileObject.Position.Y].GetComponentInChildren<T>();
+
+	public Unit LogicToPresentation(Logic.Data.World.Unit unit) => _units[unit];
+
 	private void Start() {
 		var simulation = FindObjectOfType<SimulationManager>();
 
 		_units = new Dictionary<Logic.Data.World.Unit, Unit>();
 
 		simulation.GameOverview.Events.AddListener<TowerBuiltEvent>(OnTowerBuilt);
+		simulation.GameOverview.Events.AddListener<TowerShotEvent>(OnTowerShot);
+		simulation.GameOverview.Events.AddListener<TowerCooledDownEvent>(OnTowerCooledDown);
 
 		simulation.GameOverview.Events.AddListener<UnitDeployedEvent>(OnUnitDeployed);
 		simulation.GameOverview.Events.AddListener<UnitMovedTileEvent>(OnUnitMovedTile);
@@ -76,6 +82,40 @@ public class World : MonoBehaviour {
 		InstantiateTower(_map[tilePosition.X, tilePosition.Y], tower);
 	}
 
+	private void OnTowerShot(TowerShotEvent e) {
+		Tower tower = LogicToPresentation<Tower>(e.Tower);
+		LineRenderer laserRenderer = tower.GetComponent<LineRenderer>();
+		Transform target = LogicToPresentation(e.Target).transform;
+
+		Vector3[] positions = { tower.transform.position, target.position };
+		laserRenderer.SetPositions(positions);
+		laserRenderer.enabled = true;
+
+		IEnumerator LaserUpdater() {
+			var remainingTime = 0.15f;
+			while (remainingTime > 0) {
+				yield return new WaitForFixedUpdate();
+				remainingTime -= Time.fixedDeltaTime;
+
+				if (e.Target.IsAlive) {
+					positions[1] = target.position;
+					laserRenderer.SetPositions(positions);
+				}
+			}
+
+			laserRenderer.enabled = false;
+		}
+
+		StartCoroutine(LaserUpdater());
+	}
+
+	private void OnTowerCooledDown(TowerCooledDownEvent e) {
+		Tower tower = LogicToPresentation<Tower>(e.Tower);
+		//Disable the laser renderer when the tower is ready to shoot again:
+		// we want to avoid activating a new laser pulse and then deactivating the old one
+		tower.GetComponent<LineRenderer>().enabled = false;
+	}
+
 	private GameObject InstantiateTower(GameObject parent, Logic.Data.World.Tower tower) {
 		GameObject structure = Instantiate(Tower, parent.transform);
 		var barrackComponent = structure.GetComponent<Tower>();
@@ -84,12 +124,8 @@ public class World : MonoBehaviour {
 	}
 
 	private GameObject InstantiateTile(int x, int y, GameWorld world) {
-		float sizeMultiplier = TilePadding + 1.0f;
-
-		Vector3 position = new Vector3(x, y) * sizeMultiplier;
-
 		GameObject tile = Instantiate(Tile, transform);
-		tile.transform.localPosition = position;
+		tile.transform.localPosition = new Vector3(x, y);
 		var tileComponent = tile.GetComponent<Tile>();
 		tileComponent.Position = new TilePosition(x, y);
 		_map[x, y] = tile;
diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset
index 9e30ccc19d643102a74868b8d6b3efc899bb239f..5900abf1cfa1a9438abf63ca2fe22cd2e188bb9c 100644
--- a/ProjectSettings/TagManager.asset
+++ b/ProjectSettings/TagManager.asset
@@ -47,6 +47,9 @@ TagManager:
   - name: Structure
     uniqueID: 520840465
     locked: 0
+  - name: Projectile
+    uniqueID: 3098005577
+    locked: 0
   - name: Unit
     uniqueID: 1800257023
     locked: 0