UniRx Space Invaders: Código y Tutoriales

Episodio 1 - YouTube Tutorial

Parte 1 - Video [spanish]

Episodio 2 - Code

Parte 1 - ShipController.cs

The main controller of the ship, 2D movement, only horizontal

C#
    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);
    }
}
  

Parte 2 - Laser.cs

The code for the laser proyectile

C#
    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";
}
  

Parte 3 - Ufo.cs

The Ufo enemy script

C#
    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;
    }
}
  

Parte 4 - UfoMovement.cs

The movement of the ufo group, must be attached to a gameobject that is parent of the group of ufos (see video tutorial)

C#
    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";
    
}
  

Parte 5 - GameController.cs

The main game controller

C#
    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();
        }
    }

}