Advanced Game Development Techniques in Unity

As you progress in Unity game development, mastering advanced techniques becomes crucial for creating polished, performant games. This guide covers essential professional practices including object pooling, level management, save systems, and event architecture.

Object Pooling for Performance

Object pooling is essential for games with frequent instantiation/destruction of objects (bullets, enemies, collectibles).

Basic Object Pool Implementation

using UnityEngine;
using System.Collections.Generic;

public class ObjectPool : MonoBehaviour
{
    public static ObjectPool Instance;
    public GameObject prefab;
    public int poolSize = 20;
    
    private Queue<GameObject> objectPool = new Queue<GameObject>();

    void Awake()
    {
        Instance = this;
        InitializePool();
    }

    void InitializePool()
    {
        for (int i = 0; i < poolSize; i++)
        {
            GameObject obj = Instantiate(prefab);
            obj.SetActive(false);
            objectPool.Enqueue(obj);
        }
    }

    public GameObject GetObject()
    {
        if (objectPool.Count > 0)
        {
            GameObject obj = objectPool.Dequeue();
            obj.SetActive(true);
            return obj;
        }
        else
        {
            // Optional: Expand pool dynamically
            GameObject obj = Instantiate(prefab);
            return obj;
        }
    }

    public void ReturnObject(GameObject obj)
    {
        obj.SetActive(false);
        objectPool.Enqueue(obj);
    }
}

Using the Pool

// Instead of Instantiate:
GameObject bullet = ObjectPool.Instance.GetObject();
bullet.transform.position = firePoint.position;
bullet.transform.rotation = firePoint.rotation;

// Instead of Destroy:
void OnCollisionEnter(Collision collision)
{
    ObjectPool.Instance.ReturnObject(gameObject);
}

Level Design and Scene Management

Professional games require structured level management.

Scene Management Setup

using UnityEngine;
using UnityEngine.SceneManagement;

public class LevelManager : MonoBehaviour
{
    public static LevelManager Instance;
    
    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
    
    public void LoadLevel(string sceneName)
    {
        SceneManager.LoadScene(sceneName);
    }
    
    public void LoadLevelAsync(string sceneName)
    {
        StartCoroutine(LoadAsync(sceneName));
    }
    
    IEnumerator LoadAsync(string sceneName)
    {
        AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName);
        
        while (!operation.isDone)
        {
            float progress = Mathf.Clamp01(operation.progress / 0.9f);
            // Update loading UI here
            yield return null;
        }
    }
}

Level Design Best Practices

  • Use additive scene loading for complex levels
  • Create a scene hierarchy: Main, UI, Lighting, LevelGeometry
  • Implement scene transition effects with coroutines
  • Use SceneReference instead of string names for safety

Save and Load Systems

Persistent data is crucial for player progression.

Simple PlayerPrefs Implementation

public class SaveSystem
{
    public static void SaveGame(int level, int score, float volume)
    {
        PlayerPrefs.SetInt("CurrentLevel", level);
        PlayerPrefs.SetInt("PlayerScore", score);
        PlayerPrefs.SetFloat("MasterVolume", volume);
        PlayerPrefs.Save();
    }
    
    public static (int level, int score, float volume) LoadGame()
    {
        int level = PlayerPrefs.GetInt("CurrentLevel", 1);
        int score = PlayerPrefs.GetInt("PlayerScore", 0);
        float volume = PlayerPrefs.GetFloat("MasterVolume", 0.8f);
        return (level, score, volume);
    }
}

Advanced JSON Serialization

using System.IO;
using UnityEngine;

[System.Serializable]
public class GameData
{
    public int level;
    public int score;
    public float volume;
    public Vector3 playerPosition;
}

public class JsonSaveSystem
{
    public static void SaveGame(GameData data)
    {
        string json = JsonUtility.ToJson(data);
        string path = Path.Combine(Application.persistentDataPath, "savegame.json");
        File.WriteAllText(path, json);
    }
    
    public static GameData LoadGame()
    {
        string path = Path.Combine(Application.persistentDataPath, "savegame.json");
        if (File.Exists(path))
        {
            string json = File.ReadAllText(path);
            return JsonUtility.FromJson<GameData>(json);
        }
        return new GameData(); // Return default if no save exists
    }
}

Implementing Game Events and Managers

Decoupled systems communicate through events.

Event Manager System

using UnityEngine;
using System;
using System.Collections.Generic;

public class EventManager : MonoBehaviour
{
    private Dictionary<string, Action<object>> eventDictionary;
    private static EventManager eventManager;
    
    public static EventManager Instance
    {
        get
        {
            if (!eventManager)
            {
                eventManager = FindObjectOfType<EventManager>();
                
                if (!eventManager)
                {
                    Debug.LogError("No EventManager found in scene!");
                }
                else
                {
                    eventManager.Init();
                }
            }
            return eventManager;
        }
    }
    
    void Init()
    {
        if (eventDictionary == null)
        {
            eventDictionary = new Dictionary<string, Action<object>>();
        }
    }
    
    public static void StartListening(string eventName, Action<object> listener)
    {
        Action<object> thisEvent;
        if (Instance.eventDictionary.TryGetValue(eventName, out thisEvent))
        {
            thisEvent += listener;
            Instance.eventDictionary[eventName] = thisEvent;
        }
        else
        {
            thisEvent += listener;
            Instance.eventDictionary.Add(eventName, thisEvent);
        }
    }
    
    public static void StopListening(string eventName, Action<object> listener)
    {
        if (eventManager == null) return;
        Action<object> thisEvent;
        if (Instance.eventDictionary.TryGetValue(eventName, out thisEvent))
        {
            thisEvent -= listener;
            Instance.eventDictionary[eventName] = thisEvent;
        }
    }
    
    public static void TriggerEvent(string eventName, object eventParam)
    {
        Action<object> thisEvent;
        if (Instance.eventDictionary.TryGetValue(eventName, out thisEvent))
        {
            thisEvent.Invoke(eventParam);
        }
    }
}

Using the Event System

// Subscribing to events
void OnEnable()
{
    EventManager.StartListening("PlayerDied", OnPlayerDied);
    EventManager.StartListening("ScoreChanged", OnScoreChanged);
}

void OnDisable()
{
    EventManager.StopListening("PlayerDied", OnPlayerDied);
    EventManager.StopListening("ScoreChanged", OnScoreChanged);
}

void OnPlayerDied(object data)
{
    // Handle player death
}

void OnScoreChanged(object data)
{
    int newScore = (int)data;
    // Update score display
}

// Triggering events
EventManager.TriggerEvent("ScoreChanged", currentScore);

Game Manager Architecture

A robust game manager handles:

  • Game state (menu, playing, paused, game over)
  • Score and progression tracking
  • Scene transitions
  • Difficulty scaling

Professional Game Development

These advanced techniques form the foundation of professional Unity development. Key takeaways:

  • Object pooling dramatically improves performance
  • Proper scene management enables complex games
  • Robust save systems enhance player experience
  • Event-driven architecture keeps code decoupled

Implement these patterns early in your projects to build scalable, maintainable game systems that will support your game as it grows in complexity.

Post a Comment

0 Comments