TAP MATCH in UNITY

TapMatch.mov

Architecture Decision

Single Grid Manager Class

Advantages:

  • The most straight-forward approach.

Disadvantages:

  • Not testable
  • Gigantic class with too much responsibilities
  • Hard to maintain

MVC

Advantages:

  • Simpler approach to separate view and business logic from each other
  • Testable
  • Easy to maintain

Disadvantages:

  • Requires Controller -> View dependency
    • This may not be very desirable for this problem. Because the controller can drive the view by calling the methods on it, but it also needs to listen to inputs / commands from somewhere (IInteractionProvider). Having references from Controller to both view and interaction provider interface may not make sense because view is already an interaction provider.
interface IInteractionProvider { /* Events */ }

class EditorGridInteractionProvider : IInteractionProvider {}

class GridView : MonoBehaviour, IInteractionProvider {}

class GridController { /* ref. to GridView, IInteractionProvider[] */ }

class GridModel {}

MVVM

Advantages:

  • Testable
  • Easy to maintain
  • View -> Controller dependency makes more sense when an interaction provider is introduced:
interface IInteractionProvider { /* Events */ }

class EditorGridInteractionProvider : IInteractionProvider {}

class GridView : MonoBehaviour, IInteractionProvider { /* ref. to GridViewModel */ }

class GridViewModel { /* ref. to IInteractionProvider[] */ }

class GridModel {}

This project is developed with MVVM.


Assemblies, Classes and Dependencies

There are multiple assemblies in the project, each with different responsibilites.

Main Assemblies

MainAssemblies

Grid System contains the main logic with Views, ViewModels, Models and search algorithm. And the other assemblies provide required interfaces and implementations to setup and run the game.

For a such small project multiple assemblies are maybe not required, but this repository demonstrates how they can be used to separate concerns into multiple assemblies and also reduce the compile times when the project grows.

Test Assembly

TestAssembly

There are play-mode (unit) tests in the project for finding the matches with depth-first search algorithm, and for the Grid ViewModel. With the help of MVVM, business logic is extracted from the view easily into the ViewModel so it can be tested easily without any Unity dependency.

There's also NSubstitute dependency to mock some interfaces to reduce the scope of testing.

There's currently also 100% code coverage for the ViewModel and DFS class:

Coverage

Overall coverage can be increased by adding tests for Grid View, and Grid Item views. Since they don't hold the business logic in them, a mocked ViewModel can be created to drive them and assert UI changes in the test easily.

Item (Candy) Types

ItemTypes

Item types are implemented using ScriptableObjects which hold readonly settings in them. This data includes color, sprite type, item id, etc. to identify the item and define its visuals.

With the help of this approach, new item / candy types can be easily added without requiring any code change by just creating new scriptable objects and assigning them in the editor.

Main Class Dependency

MainClassDependency

  • With this approach, ViewModel holds the business logic and does not care about the view and Unity related logic.
  • View does not care about the business logic, only listens to the ViewModel to adjust itself.
  • Model classes hold data that can be accessed and modified through ViewModel.
  • GridViewModel depends on IInteractionProvider abstraction rather than a certain interaction provider (e.g. the view) so new interaction providers can be defined easily, e.g. EditorGridInteractionProvider to drive interactions in Unity Editor.

This approach makes testing extremely easy, because the concerns are separated into different classes and the dependencies can be easily mocked.

DFS Search Dependency

DFS Search Dependency

Similar to interaction providers, search algorithms are also abstracted behind an interface (IGridMatchFinder) so the algorithm can be replaced without modifying the dependants directly.


External Controller

Game can be controlled externally, through a Unity Editor window that can be accessed with "Tools/Game Controller" menu. It provides a simple interface to select a certain row and column to remove specified candy and all the connected ones, similar to clicking on that.

image


Optimizations

Some of the optimizations in project:

  • Used ObjectPool in GridView to instantiate and store items in the grid to not destroy and re-instantiate them over and over again.
  • Used SpriteAtlas for items (candies) to render them at once.
  • Optimized garbage collection by escaping redundant array initializations where possible by iterating through the same collection, or accepting an arrays in methods to be filled in e.g. in GridItemModelGenerator.