HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Snake Game in C# - follow-up

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
gamefollowsnake

Problem

Follow up to this question : Snake Game in C#

The game plays like it did in my first question. The design is altered.

Gameplay Video

Some of the changes:

  • Polymorphism is removed, replaced by Interfaces (see interfaces.cs)



  • CreateGame, DeleteGame and GameOver are now methods in Game



  • Snake movement is handled differenly, moved from SnakePart to Snake



  • Removed DirectionObj, it is no longer needed



  • Database is now Grid



  • Created MapCreator



  • Before(), After(), Next() are now just Update(Game game)



  • Created Graphicand GraphicInstantiator



  • Direction is now just an enum



  • GameObjectInstantiator is now GraphicInstantiator



Overview

  • class Game : Monobehaviour



-
class Snake

Interfaces, all are in interfaces.cs

  • interface IUpdatable



  • interface IGraphical



-
interface IGridable : IGraphical

Classes with IGridable

  • class Apple : IGridable, IUpdatable



  • class Barrier : IGridable, IUpdatable



-
class SnakePart : IGridable

Utilities

-
class InputHandler

  • class MapCreator



  • class Score



-
enum Direction {Left, Right, Up, Down, None}

-
class Grid : MultipleValuesDictionary

-
class MultipleValuesDictionary : Dictionary>

-
class GraphicInstantiator

-
class Graphic

-
struct IVector2 (Not in the project itself, is a 2D coordinate with int x and int y)

Game.cs

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Misc;
namespace SnakeGameSOLID
{
//Monobehaviour is from Unity
public class Game : MonoBehaviour
{
public GameObject cube;

public Grid Grid { get; private set; }
public Score Score { get; private set; }
public MapCreator MapCreator { get; private set; }

//Used to handle graphics in Unity
public GraphicInstantiator GraphicInstantiator { get; private set; }

InputHandler input;
Snake snake;

Direction inputDirection;

float time

Solution

I can see some FPS drop in your game each time you eat or spawn new food object. This is important because that's a game and FPS is crucial factor for a pleasant experience, not only code-style, architecture etc.. You should consider applying the object pooling concept to your project.

Which works like this :

-
It creates a list of GameObject at the start of the game (optionally when requested) and sets all of them to inactive (gameObject.SetActive(false)).

-
If you need to show an object you take it from the "pool" of already created GameObjects, and set it to active (gameObject.SetActive(true)).

-
Once you are done using the object you set it back to inactive state (gameObject.SetActive(false)).

This technique allows you avoid calling methods such as Instantiate and Destroy which are quite expensive to call and perform. All of the objects are created at the beginning so there will be almost no fps drop when activating one of them.

Note

Most Object Poolers allow dynamic increment of the size of the List in case you need an object but you don't have any "free"(inactive) objects.

Here is an example of Object Pooler class (reference).

/// 
/// Information holder for a pooled object
/// 
[Serializable]
public class PooledObject
{
    [Tooltip(@"Name is used to differ the objects from one another")]
    public string Name;

    [Tooltip(@"What object should be created ?")]
    public GameObject Object;

    [Range(1, 10000)] [Tooltip(@"How much objects should be created ?")]
    public int Amount;

    [Tooltip(@"Can new objects be created in case there are none left ?")]
    public bool CanGrow;

    [Tooltip(@"False - objects must be created manually using Populate method
True - objects will be created automatically on awake")]
    public bool CreateOnAwake;
}


And this is the actual object pooling class :

/// 
/// Object pooler class which can hold a single type of objects.
/// 
public class MonoObjectPooler : MonoBehaviour
{
    /// 
    /// Object to be pooled.
    /// 
    public PooledObject PooledObject;

    /// 
    /// List to store all the objects that will be pooled.
    /// 
    private readonly List pooledObjects = new List();

    private void Awake()
    {
        //Create the pooled object if the CreateOnAwake variable is set to true.
        if (PooledObject.CreateOnAwake)
        {
            Populate();
        }
    }

    /// 
    /// Populates the list of pooled objects with PooledObjects.
    /// 
    public void Populate()
    {
        //Clear the previous items in the list.
        pooledObjects.Clear();

        //Load the items again
        for (int i = 0; i 
    /// Returns a PooledObject.
    /// 
    public GameObject GetPooledObject()
    {
        for (int i = 0; i < pooledObjects.Count; i++)
        {
            if (!pooledObjects[i].activeInHierarchy)
            {
                //we have an available object
                return pooledObjects[i];
            }
        }
        if (PooledObject.CanGrow)
        {
            //we ran out of objects but we can create more
            GameObject obj = Instantiate(PooledObject.Object);
            pooledObjects.Add(obj);
            return obj;
        }
        //We ran out of objects and we cant create more
        return null;
    }
}


Just attach as much MonoObjectPooler objects you need, to some GameManager GameObject in your scene, set up the values and you are good to go. Note that you will need to attach separate MonoObjectPooler script to the GameManager for each different object type. IF you like to have everything in one place you can check out the PolyObjectPooler class in the reference link, but keep in mind that many MonoObjectPoolers will be more performant than a single PolyObjectPooler.

After you obtain free object just set it to active and adjust the position and the other properties you need to.

Code Snippets

/// <summary>
/// Information holder for a pooled object
/// </summary>
[Serializable]
public class PooledObject
{
    [Tooltip(@"Name is used to differ the objects from one another")]
    public string Name;

    [Tooltip(@"What object should be created ?")]
    public GameObject Object;

    [Range(1, 10000)] [Tooltip(@"How much objects should be created ?")]
    public int Amount;

    [Tooltip(@"Can new objects be created in case there are none left ?")]
    public bool CanGrow;

    [Tooltip(@"False - objects must be created manually using Populate method
True - objects will be created automatically on awake")]
    public bool CreateOnAwake;
}
/// <summary>
/// Object pooler class which can hold a single type of objects.
/// </summary>
public class MonoObjectPooler : MonoBehaviour
{
    /// <summary>
    /// Object to be pooled.
    /// </summary>
    public PooledObject PooledObject;

    /// <summary>
    /// List to store all the objects that will be pooled.
    /// </summary>
    private readonly List<GameObject> pooledObjects = new List<GameObject>();

    private void Awake()
    {
        //Create the pooled object if the CreateOnAwake variable is set to true.
        if (PooledObject.CreateOnAwake)
        {
            Populate();
        }
    }

    /// <summary>
    /// Populates the list of pooled objects with PooledObjects.
    /// </summary>
    public void Populate()
    {
        //Clear the previous items in the list.
        pooledObjects.Clear();

        //Load the items again
        for (int i = 0; i < PooledObject.Amount; i++)
        {
            GameObject obj = Instantiate(PooledObject.Object);
            obj.SetActive(false);
            pooledObjects.Add(obj);
        }
    }

    /// <summary>
    /// Returns a PooledObject.
    /// </summary>
    public GameObject GetPooledObject()
    {
        for (int i = 0; i < pooledObjects.Count; i++)
        {
            if (!pooledObjects[i].activeInHierarchy)
            {
                //we have an available object
                return pooledObjects[i];
            }
        }
        if (PooledObject.CanGrow)
        {
            //we ran out of objects but we can create more
            GameObject obj = Instantiate(PooledObject.Object);
            pooledObjects.Add(obj);
            return obj;
        }
        //We ran out of objects and we cant create more
        return null;
    }
}

Context

StackExchange Code Review Q#152140, answer score: 5

Revisions (0)

No revisions yet.