The main controller of the ship, 2D movement, only horizontal
    using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UniRx.Triggers;
public class ShipController : MonoBehaviour
{
    [Header("Movement")]
    public float speed = 5f;
    private Rigidbody2D rigidBody2D;
    Vector2 movement;
    [Header("Fire")]
    public GameObject laser;
    public float fireRate = 0.8f;
    public float laserSpeed = 600f;
    private float nextFire = 0f;
    [Header("Explosion")]
    public GameObject explosion;
    [Header("Other")]
    public float immortalityDuration = 1f;
    void Start()
    {
        StartCoroutine(TemporalImmortalityCoroutine());
        rigidBody2D = GetComponent<Rigidbody2D>();
        IObservable<Unit> update = this.UpdateAsObservable();
        update
        .Do(_ => HandleMovement())
        .Where(_ => CanShoot())
        .Subscribe(_ => Fire());
    }
    private void HandleMovement()
    {
        movement.x = Input.GetAxisRaw("Horizontal");
        rigidBody2D.MovePosition(rigidBody2D.position + movement * speed * Time.fixedDeltaTime);
    }
    public void Explode() 
    {
        GameObject explosionInstance = Instantiate(explosion, transform.position, transform.rotation);
        Destroy(explosionInstance, 0.4f);
        Destroy(gameObject);
        GameController controller = 
            GameObject.FindGameObjectWithTag("GameController")
            .GetComponent("GameController") 
            as GameController;
        
        controller.SubstractLive();
    }
    private void Fire() 
    {
        GameObject firedLaser = Instantiate(
            laser,
            transform.position,
            transform.rotation
        );
        Rigidbody2D firedLaserRigidBody2D = firedLaser.GetComponent<Rigidbody2D>();
        firedLaserRigidBody2D.AddForce(Vector3.up * laserSpeed);
        Physics2D.IgnoreCollision(firedLaser.GetComponent<Collider2D>(), GetComponent<Collider2D>());
        nextFire = Time.time + fireRate;
    }
    private bool CanShoot() => Input.GetButton("Fire1") && Time.time > nextFire;
    IEnumerator TemporalImmortalityCoroutine()
    {
        float startTime = Time.time;
        GetComponent<Collider2D>().enabled = false;
        GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, 0.5f);
        yield return new WaitForSeconds(immortalityDuration);
        GetComponent<Collider2D>().enabled = true;
        GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, 1f);
    }
}
  The code for the laser proyectile
    using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UniRx.Triggers;
public class Laser : MonoBehaviour
{
    void Start()
    {
        IObservable<Collision2D> collisionEnterObservable = this.OnCollisionEnter2DAsObservable();
        collisionEnterObservable
        .Do(Impact)
        .Where(IsUfo)
        .Subscribe(DestroyUfo);
        collisionEnterObservable
        .Where(IsPlayer)
        .Subscribe(DestroyPlayer);
    }
    private void Impact(Collision2D collision)
    {
        Destroy(gameObject);
    }
    private void DestroyUfo(Collision2D collision)
    {
        Ufo ufo = collision.gameObject.GetComponent<Ufo>();
        ufo.Explode();
    }
    private void DestroyPlayer(Collision2D collision)
    {
        ShipController player = collision.gameObject.GetComponent<ShipController>();
        player.Explode();
    }
    private bool IsUfo(Collision2D collision) => collision.gameObject.tag == "Ufo";
    private bool IsPlayer(Collision2D collision) => collision.gameObject.tag == "Player";
}
  The Ufo enemy script
    using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
public class Ufo : MonoBehaviour
{
    public GameObject explosion;
    public GameObject laser;
    public float laserSpeed = 200f;
    private void Start()
    {
        Physics2D.queriesStartInColliders = false;
        Observable
        .Interval(TimeSpan.FromSeconds(UnityEngine.Random.Range(1f, 2f)))
        .Where(_ => CanShoot())
        .Subscribe(_ => Fire())
        .AddTo(this);
    }
    public void Explode() 
    {
        GameObject explosionInstance = Instantiate(explosion, transform.position, transform.rotation);
        Destroy(explosionInstance, 0.4f);
        Destroy(gameObject); 
        GameController controller = 
            GameObject.FindGameObjectWithTag("GameController")
            .GetComponent("GameController") 
            as GameController;
        
        controller.checkUfosLeft();
    }
    private void Fire() 
    {
        GameObject firedLaser = Instantiate(
            laser,
            transform.position,
            Quaternion.Euler(0, 0, 180)
        );
        Physics2D.IgnoreCollision(firedLaser.GetComponent<Collider2D>(), GetComponent<Collider2D>());
        
        Rigidbody2D firedLaserRigidBody2D = firedLaser.GetComponent<Rigidbody2D>();
        firedLaserRigidBody2D.AddForce(Vector2.down * laserSpeed);
    }
    private bool CanShoot() 
    {
        RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.down);
        return hit.collider.tag != "Ufo" && UnityEngine.Random.Range(1, 4) == 2;
    }
}
  The movement of the ufo group, must be attached to a gameobject that is parent of the group of ufos (see video tutorial)
    using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniRx;
using UniRx.Triggers;
public class UfoMovement : MonoBehaviour
{
    private bool isMovingRight = true;
    public float baseSpeed = 0.4f;
    void Start()
    {
        IObservable<Unit> update = this.UpdateAsObservable();
        IObservable<Collider2D> triggerEnterObservable = this.OnTriggerEnter2DAsObservable();
        update.Subscribe(_ => Move());
        triggerEnterObservable
        .Where(IsBorder)
        .Subscribe(_ => ChangeDirection());
        triggerEnterObservable
        .Where(IsPlayer)
        .Subscribe(_ => GameOver());
    }
    private void Move()
    {
        float movement = baseSpeed * Time.fixedDeltaTime * 0.1f * Time.timeScale;
        transform.Translate(isMovingRight ? movement : -movement, 0, 0);
    }
    private void ChangeDirection() 
    {
        isMovingRight = !isMovingRight;
        baseSpeed += 0.3f;
        transform.Translate(0, -0.4f, 0);
    }
    private void GameOver() 
    {
        GameController controller = 
            GameObject.FindGameObjectWithTag("GameController")
            .GetComponent("GameController") 
            as GameController;
        controller.GameOver();
    }
    private bool IsBorder(Collider2D collider) => collider.tag == "MainCamera";
    private bool IsPlayer(Collider2D collider) => collider.tag == "Player";
    
}
  The main game controller
    using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using UniRx;
public class GameController : MonoBehaviour
{
    [Header("Game Settings")]
    [SerializeField] private int lives = 3;
    public GameObject playerPrefab;
    private ReactiveProperty<int> reactiveLives;
    public GameObject ufosContainer;
    private ReactiveProperty<int> ufosLeft;
    [Header("UI")]
    public GameObject livesUI;
    public GameObject gameOverUI;
    public GameObject winUI;
    private Text livesLeftText;
    void Start()
    {
        reactiveLives = new ReactiveProperty<int>(lives);
        livesLeftText = livesUI.GetComponent<Text>();
        livesLeftText.text = $"Lives: {lives}";
        ufosLeft = new ReactiveProperty<int>(1);
        BindRestartButtons();
        ObserveLives();
        ObserveUfos();
    }
    private void BindRestartButtons() 
    {
        gameOverUI.transform.Find("RestartButton")
        .GetComponent<Button>()
        .onClick.AddListener(RestartGame);
        winUI.transform.Find("RestartButton")
        .GetComponent<Button>()
        .onClick.AddListener(RestartGame);
    }
    private void ObserveUfos() 
    {
        ufosLeft
        .Where(ufos => ufos == 0)
        .Take(1)
        .Subscribe(_ => WinGame())
        .AddTo(this);
    }
    private void ObserveLives() 
    {
        reactiveLives
        .Where(livesLeft => livesLeft == 0)
        .Take(1)
        .Subscribe(_ => GameOver())
        .AddTo(this);
    }
    public void GameOver() 
    {
        gameOverUI.SetActive(true);
        Time.timeScale = 0;
    }
    public void WinGame() 
    {
        winUI.SetActive(true);
        Time.timeScale = 0;
    }
    public void RestartGame() 
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        Time.timeScale = 1;
    }
    private void SpawnPlayer() 
    {
        Instantiate(playerPrefab, new Vector3(0, -4 ,0), Quaternion.Euler(0, 0, 0));
    }
    public void checkUfosLeft() 
    {
        Collider2D ufosContainerCollider = ufosContainer.GetComponent<Collider2D>();
        ContactFilter2D filter = new ContactFilter2D();
        filter.SetLayerMask(LayerMask.GetMask("Ufos"));
        ufosLeft.Value = Physics2D.OverlapCollider(ufosContainerCollider, filter, new List<Collider2D>());
    }
    public void SubstractLive() 
    {
        reactiveLives.Value -= 1;
        livesLeftText.text = $"Lives: {reactiveLives.Value}";
        if (reactiveLives.Value != 0) {
            SpawnPlayer();
        }
    }
}
  