This is a Breakout clone made in 5 days with Unity and UniRx. It was a programming task assigned to me as part of the hiring process for a Unity software engineer position at a KidsLoop. For this project I thought it would be fun to combine reactive programming and the model-view-presenter (MVP) pattern. So all key objects (ball, paddle, brick, etc.) are represented by both a MonoBehaviour-derived presenter and a POCO model which the presenter references.

Source Code

Here’s an example of the MVP + reactive programming style:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using UniRx;

public class Paddle
{
    public Paddle()
    {
        Width = new ReactiveProperty<float>(1);
        ResetBallPos = new ReactiveCommand<Unit>();
    }

    public IReactiveProperty<float> Width { get; }

    public ReactiveCommand<Unit> ResetBallPos { get; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using UniRx;
using UnityEngine;

public class PaddlePresenter : MonoBehaviour
{
    [SerializeField]
    private BallPresenter _ballPresenter;
    [SerializeField]
    private Transform _initialBallPosTrfm;
    [SerializeField]
    private Transform _graphicTrfm;

    public void Init()
    {
        Paddle = new Paddle();

        Observable
            .EveryUpdate()
            .Select(_ => Input.mousePosition)
            .Subscribe(UpdateXPosition)
            .AddTo(this);

        Paddle
            .Width
            .Subscribe(xScale => _graphicTrfm.localScale = new Vector3(xScale, _graphicTrfm.localScale.y))
            .AddTo(this);

        Paddle
            .ResetBallPos
            .Subscribe(_ => ResetBallPos())
            .AddTo(this);
    }

    public Paddle Paddle { get; private set; }

    private void UpdateXPosition(Vector3 mousePos)
    {
        mousePos.x = Mathf.Clamp(mousePos.x, 0, Screen.width);
        var xPos = Camera.main.ScreenToWorldPoint(mousePos).x;
        transform.position = new Vector3(xPos, transform.position.y, transform.position.z);
    }

    private void ResetBallPos()
    {
        _ballPresenter.transform.parent = transform;
        _ballPresenter.transform.position = _initialBallPosTrfm.position;
    }
}

Updated: