Let your players create their own map – Unity 2D level creation in runtime with save and load system

Welcome to our tutorial on custom map creation for our dynamic 2D portrait game! Our game offers pre-made levels with obstacles, all presented in a unique portrait orientation. But the real excitement lies in the ability for users to design their own maps, placing obstacles and challenges as they see fit. With our grid-based editor, creating custom maps is intuitive and fun. Save your creations for later. Join us as we explore the process of crafting your own adventure in our immersive 2D game!

CameraController.cs

using UnityEngine;
using UnityEngine.UI;

public class CameraController : MonoBehaviour
{
    public Slider slider; // Reference to the UI Slider
    public float minY = 0f; // Minimum y position
    public float maxY = 38f; // Maximum y position
    public float sensitivity = 0.1f; // Touch or mouse sensitivity

    private Vector2 lastInputPosition;

    void Start()
    {
        // Subscribe to the slider's OnValueChanged event
        slider.onValueChanged.AddListener(OnSliderValueChanged);

        // Set initial slider value based on the camera's initial position
        UpdateSliderValue();
    }

    void Update()
    {
        if (Input.touchSupported && Input.touchCount > 0)
        {
            Touch touch = Input.GetTouch(0);

            if (touch.phase == TouchPhase.Began)
            {
                lastInputPosition = touch.position;
            }
            else if (touch.phase == TouchPhase.Moved)
            {
                Vector2 delta = touch.position - lastInputPosition;
                float deltaY = delta.y * sensitivity;

                // Invert deltaY to match touch direction
                deltaY *= -1;

                MoveCamera(deltaY);

                lastInputPosition = touch.position;
            }
        }
        else if (Input.mousePresent)
        {
            if (Input.GetMouseButtonDown(0))
            {
                lastInputPosition = Input.mousePosition;
            }
            else if (Input.GetMouseButton(0))
            {
                Vector2 delta = (Vector2)Input.mousePosition - lastInputPosition;
                float deltaY = delta.y * sensitivity;

                MoveCamera(deltaY);

                lastInputPosition = Input.mousePosition;
            }
        }
    }

    void MoveCamera(float deltaY)
    {
        // Invert deltaY to reverse camera movement
        //deltaY *= -1;

        // Update camera position within range
        Vector3 newPosition = transform.position + new Vector3(0f, deltaY, 0f);
        newPosition.y = Mathf.Clamp(newPosition.y, minY, maxY);
        transform.position = newPosition;

        // Update slider value based on camera's new position
        UpdateSliderValue();
    }

    void UpdateSliderValue()
    {
        // Invert camera's Y-position to reverse slider value
        float invertedY = maxY - (transform.position.y - minY);
        
        // Map inverted camera's Y-position to slider value within range
        float sliderValue = Mathf.InverseLerp(minY, maxY, invertedY);
        slider.value = sliderValue;
    }

    void OnSliderValueChanged(float value)
    {
        // Invert the slider value to reverse the movement
        value = 1f - value;

        // Map inverted slider value to camera's Y-position within range
        float newY = Mathf.Lerp(minY, maxY, value);
        Vector3 newPosition = transform.position;
        newPosition.y = newY;
        transform.position = newPosition;
    }
}

This script is a CameraController class responsible for handling camera movement in a 2D environment using touch input or mouse input. It also syncs the camera’s position with a UI Slider to provide visual feedback of the camera’s vertical position and allows the user to control it.

Here’s a breakdown of its functionalities:

  1. Slider Syncing: The script synchronizes the camera’s vertical position with a UI Slider’s value, providing visual feedback to the user about the camera’s position in the scene.
  2. Touch and Mouse Input Handling: The script detects touch or mouse input and calculates the delta movement of the input device.
  3. Camera Movement: It moves the camera vertically based on the input received, ensuring it stays within specified bounds (minimum and maximum Y positions).
  4. Inverted Movement: The script provides an option to invert the camera’s movement direction by uncommenting the respective lines.
  5. Slider Value Update: When the camera moves, the script updates the Slider’s value to reflect the new camera position, allowing users to interactively control the camera’s position.
  6. Slider Value Change Handling: The script also listens for changes in the Slider’s value and adjusts the camera’s position accordingly.

Overall, this script provides a simple and intuitive way to control the camera’s movement in a 2D environment, enhancing the user experience in your game or application.

GridSpawner.cs

using UnityEngine;

public class GridSpawner : MonoBehaviour
{
    public GameObject objectToSpawn; // The object to spawn
    public Vector3 startingPosition; // Starting position for spawning
    public float gridWidth = 10f; // Width of the grid area
    public float gridHeight = 10f; // Height of the grid area
    public int rows = 5; // Number of rows in the grid
    public int columns = 5; // Number of columns in the grid

    void OnDrawGizmosSelected()
    {
        // Calculate the starting position based on gridHeight
        Vector3 adjustedStartingPosition = startingPosition + new Vector3(0f, gridHeight, 0f);

        // Draw wireframe cube for the grid area
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireCube(adjustedStartingPosition + new Vector3(gridWidth / 2f, -gridHeight / 2f, 0f), new Vector3(gridWidth, gridHeight, 0f));

        // Calculate row and column spacing
        float rowSpacing = gridHeight / (float)(rows - 1);
        float columnSpacing = gridWidth / (float)(columns - 1);

        // Draw grid lines for rows
        for (int i = 0; i < rows; i++)
        {
            Vector3 rowStart = adjustedStartingPosition + new Vector3(0f, -i * rowSpacing, 0f);
            Vector3 rowEnd = rowStart + new Vector3(gridWidth, 0f, 0f);
            Gizmos.DrawLine(rowStart, rowEnd);
        }

        // Draw grid lines for columns
        for (int i = 0; i < columns; i++)
        {
            Vector3 columnStart = adjustedStartingPosition + new Vector3(i * columnSpacing, 0f, 0f);
            Vector3 columnEnd = columnStart + new Vector3(0f, -gridHeight, 0f);
            Gizmos.DrawLine(columnStart, columnEnd);
        }
    }

    void Awake()
    {
        SpawnGrid();
    }

    void SpawnGrid()
    {
        // Calculate row and column spacing
        float rowSpacing = gridHeight / (float)(rows - 1);
        float columnSpacing = gridWidth / (float)(columns - 1);

        for (int row = 0; row < rows; row++)
        {
            for (int col = 0; col < columns; col++)
            {
                // Calculate the x, y, and z positions for the current grid cell
                float xPosition = startingPosition.x + col * columnSpacing;
                float yPosition = startingPosition.y + row * rowSpacing;
                float zPosition = startingPosition.z;

                // Spawn the object at the calculated position
                Vector3 spawnPosition = new Vector3(xPosition, yPosition, zPosition);
                GameObject newObj = Instantiate(objectToSpawn, spawnPosition, Quaternion.identity);
                newObj.transform.SetParent(this.gameObject.transform);
                
                // Attach a ClickableObject component to the instantiated object
                ClickableObject clickableObj = newObj.AddComponent<ClickableObject>();
                clickableObj.OnClick += (clickableObj,position) => MapManager.Instance.HandleClick(clickableObj,position);
            }
        }
    }
}

This script, GridSpawner, is responsible for spawning a grid layout of objects in a 2D environment. It provides a visual representation of the grid in the Unity Editor using Gizmos and spawns objects at each grid point based on specified parameters.

Here’s a breakdown of its functionalities:

  1. Gizmos Drawing: The OnDrawGizmosSelected method draws a wireframe cube representing the grid area in the Unity Editor. It also draws grid lines for rows and columns within this area.
  2. Object Spawning: The SpawnGrid method calculates the positions for each grid cell based on the specified number of rows and columns. It then instantiates the objectToSpawn GameObject at each calculated position, creating a grid layout of objects.
  3. ClickableObject Component: Upon instantiation of each object, a ClickableObject component is attached. This component allows for interaction with the spawned objects by detecting clicks on them. When an object is clicked, it triggers a callback to the MapManager instance, passing the clicked object and its position.
  4. Grid Parameters: The script allows customization of the grid’s width, height, number of rows, and number of columns, providing flexibility in designing various grid layouts.

Overall, this script provides a convenient way to spawn a grid layout of objects in Unity, which can be used for various purposes such as placing obstacles or creating game levels with structured layouts.

ClickableObject.cs

using UnityEngine;

public class ClickableObject : MonoBehaviour
{
    public event System.Action<GameObject,Vector3> OnClick;

    void OnMouseDown()
    {
        Debug.Log("Clicked on obstacle");
        OnClick?.Invoke(gameObject,transform.position);
    }
}

The ClickableObject script enables interaction with GameObjects by detecting mouse clicks on them. It utilizes Unity’s OnMouseDown method to trigger an event when a GameObject is clicked.

Here’s a breakdown of its functionality:

  1. OnClick Event: The script defines an event named OnClick using System.Action<GameObject,Vector3>. This event allows other scripts to subscribe to it and be notified when a GameObject is clicked.
  2. OnMouseDown Method: When a GameObject with this script attached is clicked, Unity calls the OnMouseDown method. Within this method, it logs a debug message indicating that the object has been clicked and then invokes the OnClick event. It passes two parameters: the clicked GameObject itself (gameObject) and its position (transform.position).
  3. Event Invocation: The OnClick event is invoked using the null-conditional operator (?.). This ensures that the event is only invoked if there are subscribers to it, preventing null reference errors.

Overall, the ClickableObject script provides a simple and efficient way to handle mouse clicks on GameObjects in Unity, making them interactive and responsive to user input.

MapManager.cs

using System.Collections;
using System.Collections.Generic;
using UMGS;
using UnityEngine;
using UnityEngine.Serialization;

public class MapManager : SingletonPersistent<MapManager>
{
    public GameObject obstaclesPanel;
    private GameObject pointObject;
    private Vector3 position;

    public GameObject grid;
    public GameObject obstaclesHolder;

    public GameObject mapsGallery;
    public GameObject galleryMapButton;

    public void HandleClick(GameObject pointObject,Vector3 position)
    {
        Debug.Log("Clicked object name: "+pointObject.name+"\n"+"Clicked object position: " + position);
        obstaclesPanel.SetActive(true);

        this.pointObject = pointObject;
        this.position = position;
    }

    public void SpawnObstacle(GameObject objectToSpawn,bool isNone)
    {
        Destroy(pointObject);
        GameObject newObj = Instantiate(objectToSpawn, position, Quaternion.identity);
        if (isNone)
        {
            newObj.GetComponent<SpriteRenderer>().sortingOrder = 1;
            newObj.transform.SetParent(grid.transform);
        }
        else
        {
            newObj.GetComponent<SpriteRenderer>().sortingOrder = 2;
            newObj.transform.SetParent(obstaclesHolder.transform);
        }
        ClickableObject clickableObj = newObj.AddComponent<ClickableObject>();
        clickableObj.OnClick += (clickableObj,position) => HandleClick(clickableObj,position);
        obstaclesPanel.SetActive(false);
    }

    public void ShowLevelEditor()
    {
        grid.SetActive(true);
    }

    public void hideLevelEditor()
    {
        grid.SetActive(false);
    }
}

The MapManager script is responsible for managing the creation and manipulation of maps in your game environment. It handles user clicks on grid points, spawns obstacles, manages obstacle layers, and provides functionality to show/hide the level editor.

Here’s a breakdown of its functionalities:

  1. HandleClick Method: This method is invoked when a grid point is clicked. It logs the name and position of the clicked object, activates the obstacles panel for further interaction, and stores the clicked object and its position for future use.
  2. SpawnObstacle Method: This method spawns obstacles at the clicked grid point. It destroys the clicked point object, instantiates the obstacle object at the stored position, and assigns it to either the grid or obstacles holder based on the isNone parameter. It also adjusts the sorting order of the obstacle object to ensure proper layering. Additionally, it attaches a ClickableObject component to the spawned obstacle for further interaction.
  3. ShowLevelEditor and hideLevelEditor Methods: These methods respectively show and hide the level editor grid in the game environment.
  4. Public Fields: The script declares public GameObject fields for various game objects such as the obstacles panel, grid, obstacles holder, maps gallery, and gallery map button. These fields are likely assigned in the Unity Editor for easy reference and manipulation.

Overall, the MapManager script serves as a central hub for managing map-related interactions and elements within your game, providing essential functionality for map creation and editing.

Obstacles.cs

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

public class Obstacles : MonoBehaviour
{
    public GameObject obstacleToSpawn;
    public Button button;

    public bool none=false;

    private void Start()
    {
        button.onClick.AddListener(SetSpawnInfo);
    }

    private void SetSpawnInfo()
    {
        MapManager.Instance.SpawnObstacle(obstacleToSpawn,none);
    }
}

The Obstacles script handles the spawning of obstacles in your game environment. It allows users to select an obstacle type from a UI button and then spawns that obstacle onto the map.

Here’s a breakdown of its functionalities:

  1. Public Fields: The script declares public GameObject and Button fields for the obstacle prefab to spawn (obstacleToSpawn) and the UI button (button) respectively. These fields are likely assigned in the Unity Editor to link the script with the corresponding game objects.
  2. none Field: This boolean field indicates whether the spawned obstacle should be placed on the grid directly (none = true) or on an obstacle layer (none = false).
  3. Start Method: This method is called when the script instance is initialized. It subscribes the SetSpawnInfo method to the button’s onClick event listener. This means that when the button is clicked, the SetSpawnInfo method will be invoked.
  4. SetSpawnInfo Method: This method is called when the UI button is clicked. It notifies the MapManager instance to spawn the selected obstacle (obstacleToSpawn) based on the none flag indicating whether it should be placed directly on the grid or on an obstacle layer.

Overall, the Obstacles script provides a mechanism for users to interactively spawn obstacles onto the map by selecting them from a UI button interface. It facilitates easy customization and manipulation of the game environment.

ObstacleManager.cs

using System;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using UnityEngine.UI;

public class ObstacleManager : MonoBehaviour
{
    // Data structure to hold information about spawned obstacles
    [System.Serializable]
    public class ObstacleData
    {
        public string name;
        public Vector3 position;
        public Quaternion rotation;
        public string spriteName; // Additional data for SpriteRenderer
        // Add any other relevant data fields here
    }

    // List to hold spawned obstacles' data
    public List<ObstacleData> spawnedObstaclesData = new List<ObstacleData>();
    
    // List to hold JSON data strings
    private List<string> jsonDataList = new List<string>();

    // Key to save/load jsonDataList using PlayerPrefs
    private string jsonDataListKey = "ObstacleDataList";

    // private void Start()
    // {
    //     LoadJsonDataList();
    // }

    public void InitMaps()
    {
        foreach (Transform child in MapManager.Instance.mapsGallery.transform)
        {
            // Destroy the child GameObject
            Destroy(child.gameObject);
        }
        
        LoadJsonDataList();
        Debug.Log(jsonDataList.Count);
        if (MapManager.Instance.galleryMapButton == null)
        {
            Debug.LogError("Obstacle prefab is not assigned!");
            return;
        }

        if (jsonDataList == null)
        {
            Debug.LogWarning("No obstacle data available to spawn!");
            return;
        }

        // foreach (string jsonData in jsonDataList)
        // {
        //     // Instantiate the obstacle prefab using the data from each ObstacleData object
        //     GameObject newMap = Instantiate(MapManager.Instance.galleryMapButton, MapManager.Instance.mapsGallery.transform, true);
        //     newMap.GetComponent<Button>().onClick.AddListener(() => LoadObstacles(jsonDataList.IndexOf(jsonData)));
        // }
        
        for (int i = 0; i < jsonDataList.Count; i++)
        {
            int index = i; // Capture the index in a local variable to avoid closure issues
            // Instantiate the obstacle prefab using the data from each ObstacleData object
            GameObject newMap = Instantiate(MapManager.Instance.galleryMapButton, MapManager.Instance.mapsGallery.transform, true);
            newMap.GetComponent<Button>().onClick.AddListener(() => LoadObstacles(index));
            newMap.GetComponent<Button>().onClick.AddListener(ClosePanel);
        }
    }

    private void ClosePanel()
    {
        MapManager.Instance.mapsGallery.SetActive(false);
    }

    // Spawn a new obstacle
    public void SpawnObstacle(GameObject obstacle)
    {
        SpriteRenderer spriteRenderer = obstacle.GetComponent<SpriteRenderer>();
        string spriteName = (spriteRenderer != null) ? spriteRenderer.sprite.name : ""; // Get sprite name if SpriteRenderer exists
        // Record obstacle data
        spawnedObstaclesData.Add(new ObstacleData
        {
            name = obstacle.name,
            position = obstacle.transform.position,
            rotation = obstacle.transform.rotation,
            spriteName = spriteName // Additional data for SpriteRenderer
        });
    }

    // Save spawned obstacles' data
    public void SaveObstacles()
    {
        // Clear the existing list
        spawnedObstaclesData.Clear();

        // Populate the list with data from children
        foreach (Transform child in transform)
        {
            SpawnObstacle(child.gameObject);
        }

        // Serialize the list of ObstacleData objects
        string jsonData = JsonUtility.ToJson(new ObstacleDataWrapper { obstacles = spawnedObstaclesData });

        // Save the JSON data to the file
        File.WriteAllText(Application.persistentDataPath + "/obstaclesData.json", jsonData);

        // Add the JSON data to the list
        jsonDataList.Add(jsonData);

        // Save jsonDataList using PlayerPrefs
        SaveJsonDataList();
        
        // Debug log to indicate successful saving
        Debug.Log("Obstacles data saved successfully.");
        MapManager.Instance.hideLevelEditor();
    }

    // Load spawned obstacles' data and recreate obstacles
    public void LoadObstacles(int index)
    {
        // Load jsonDataList from PlayerPrefs
        LoadJsonDataList();

        if (index >= 0 && index < jsonDataList.Count)
        {
            string jsonData = jsonDataList[index];

            // Deserialize the JSON data into a ObstacleDataWrapper object
            ObstacleDataWrapper wrapper = JsonUtility.FromJson<ObstacleDataWrapper>(jsonData);

            // Clear existing obstacles
            foreach (Transform child in transform)
            {
                Destroy(child.gameObject);
            }

            // Recreate obstacles...
            foreach (ObstacleData obstacleData in wrapper.obstacles)
            {
                GameObject newObstacle = new GameObject(obstacleData.name);
                newObstacle.transform.position = obstacleData.position;
                newObstacle.transform.rotation = obstacleData.rotation;

                // Add SpriteRenderer component if sprite name exists
                if (!string.IsNullOrEmpty(obstacleData.spriteName))
                {
                    SpriteRenderer spriteRenderer = newObstacle.AddComponent<SpriteRenderer>();
                    // Load sprite by name
                    Sprite sprite = Resources.Load<Sprite>(obstacleData.spriteName);
                    if (sprite != null)
                    {
                        spriteRenderer.sprite = sprite;
                    }
                    else
                    {
                        Debug.LogWarning("Sprite not found with name: " + obstacleData.spriteName);
                    }
                }

                newObstacle.AddComponent<CircleCollider2D>();
                ClickableObject clickableObj = newObstacle.AddComponent<ClickableObject>();
                clickableObj.OnClick += (clickableObj,position) => MapManager.Instance.HandleClick(clickableObj,position);
                newObstacle.GetComponent<SpriteRenderer>().sortingOrder = 2;
                newObstacle.transform.SetParent(transform);
            }

            // Debug log to indicate successful loading
            Debug.Log("Obstacles data loaded successfully.");
        }
        else
        {
            // Debug log to indicate invalid index
            Debug.LogWarning("Invalid index provided for loading obstacles.");
        }
    }
    
    // Save jsonDataList using PlayerPrefs
    private void SaveJsonDataList()
    {
        string jsonDataListString = JsonUtility.ToJson(new StringListWrapper { dataList = jsonDataList });
        PlayerPrefs.SetString(jsonDataListKey, jsonDataListString);
        PlayerPrefs.Save();
    }
    
    
    // Load jsonDataList from PlayerPrefs
    private void LoadJsonDataList()
    {
        if (PlayerPrefs.HasKey(jsonDataListKey))
        {
            string jsonDataListString = PlayerPrefs.GetString(jsonDataListKey);
            StringListWrapper wrapper = JsonUtility.FromJson<StringListWrapper>(jsonDataListString);
            jsonDataList = wrapper.dataList;
        }
    }

    // Wrapper class to serialize and deserialize the list of ObstacleData objects
    [System.Serializable]
    private class ObstacleDataWrapper
    {
        public List<ObstacleData> obstacles;
    }
    
    // Wrapper class to serialize and deserialize a list of strings
    [System.Serializable]
    private class StringListWrapper
    {
        public List<string> dataList;
    }
}

Let’s break down this code. Its for saving and loading the maps..

using System;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using UnityEngine.UI;

public class ObstacleManager : MonoBehaviour
{
    // Data structure to hold information about spawned obstacles
    [System.Serializable]
    public class ObstacleData
    {
        public string name;
        public Vector3 position;
        public Quaternion rotation;
        public string spriteName; // Additional data for SpriteRenderer
        // Add any other relevant data fields here
    }

    // List to hold spawned obstacles' data
    public List<ObstacleData> spawnedObstaclesData = new List<ObstacleData>();
    
    // List to hold JSON data strings
    private List<string> jsonDataList = new List<string>();

    // Key to save/load jsonDataList using PlayerPrefs
    private string jsonDataListKey = "ObstacleDataList";

    // private void Start()
    // {
    //     LoadJsonDataList();
    // }
  • The script starts by declaring a class named ObstacleData inside ObstacleManager. This class holds information about spawned obstacles, such as name, position, rotation, and sprite name.
  • spawnedObstaclesData is a list that holds instances of ObstacleData.
  • jsonDataList is another list used to hold JSON data strings.
  • jsonDataListKey is a string used as a key for saving and loading data using PlayerPrefs.
public void InitMaps()
    {
        foreach (Transform child in MapManager.Instance.mapsGallery.transform)
        {
            // Destroy the child GameObject
            Destroy(child.gameObject);
        }
        
        LoadJsonDataList();
        Debug.Log(jsonDataList.Count);
        if (MapManager.Instance.galleryMapButton == null)
        {
            Debug.LogError("Obstacle prefab is not assigned!");
            return;
        }
  • InitMaps() method initializes the map.
  • It first clears any existing map objects in the gallery.
  • Then, it loads JSON data list.
  • It checks if the obstacle prefab is assigned. If not, it logs an error and returns.
        if (jsonDataList == null)
        {
            Debug.LogWarning("No obstacle data available to spawn!");
            return;
        }
        
        for (int i = 0; i < jsonDataList.Count; i++)
        {
            int index = i; // Capture the index in a local variable to avoid closure issues
            // Instantiate the obstacle prefab using the data from each ObstacleData object
            GameObject newMap = Instantiate(MapManager.Instance.galleryMapButton, MapManager.Instance.mapsGallery.transform, true);
            newMap.GetComponent<Button>().onClick.AddListener(() => LoadObstacles(index));
            newMap.GetComponent<Button>().onClick.AddListener(ClosePanel);
        }
    }
  • The method checks if there is no obstacle data available, and if so, logs a warning and returns.
  • It then iterates through the JSON data list and instantiates the obstacle prefab for each entry.
  • It adds a listener to the instantiated button to load obstacles for each map.
  • It also adds a listener to close the panel.
private void ClosePanel()
{
    MapManager.Instance.mapsGallery.SetActive(false);
}

ClosePanel() method simply deactivates the map gallery panel.

    // Spawn a new obstacle
    public void SpawnObstacle(GameObject obstacle)
    {
        SpriteRenderer spriteRenderer = obstacle.GetComponent<SpriteRenderer>();
        string spriteName = (spriteRenderer != null) ? spriteRenderer.sprite.name : ""; // Get sprite name if SpriteRenderer exists
        // Record obstacle data
        spawnedObstaclesData.Add(new ObstacleData
        {
            name = obstacle.name,
            position = obstacle.transform.position,
            rotation = obstacle.transform.rotation,
            spriteName = spriteName // Additional data for SpriteRenderer
        });
    }
  • SpawnObstacle() method is responsible for recording data of newly spawned obstacles.
  • It gets the sprite name if the obstacle has a SpriteRenderer component attached.
  • Then, it adds a new instance of ObstacleData to the spawnedObstaclesData list containing the name, position, rotation, and sprite name of the obstacle.
    // Save spawned obstacles' data
    public void SaveObstacles()
    {
        // Clear the existing list
        spawnedObstaclesData.Clear();

        // Populate the list with data from children
        foreach (Transform child in transform)
        {
            SpawnObstacle(child.gameObject);
        }

        // Serialize the list of ObstacleData objects
        string jsonData = JsonUtility.ToJson(new ObstacleDataWrapper { obstacles = spawnedObstaclesData });

        // Save the JSON data to the file
        File.WriteAllText(Application.persistentDataPath + "/obstaclesData.json", jsonData);

        // Add the JSON data to the list
        jsonDataList.Add(jsonData);

        // Save jsonDataList using PlayerPrefs
        SaveJsonDataList();
        
        // Debug log to indicate successful saving
        Debug.Log("Obstacles data saved successfully.");
        MapManager.Instance.hideLevelEditor();
    }
  • SaveObstacles() method is responsible for saving spawned obstacles’ data.
  • It first clears the existing list of obstacles’ data.
  • Then, it populates the list with data from the spawned obstacles.
  • Next, it serializes the list of ObstacleData objects into JSON format.
  • It writes the JSON data to a file named “obstaclesData.json” in the persistent data path.
  • It adds the JSON data to the jsonDataList.
  • It saves the jsonDataList using PlayerPrefs.
  • Finally, it logs a message indicating successful saving and hides the level editor.
    // Load spawned obstacles' data and recreate obstacles
    public void LoadObstacles(int index)
    {
        // Load jsonDataList from PlayerPrefs
        LoadJsonDataList();

        if (index >= 0 && index < jsonDataList.Count)
        {
            string jsonData = jsonDataList[index];

            // Deserialize the JSON data into a ObstacleDataWrapper object
            ObstacleDataWrapper wrapper = JsonUtility.FromJson<ObstacleDataWrapper>(jsonData);

            // Clear existing obstacles
            foreach (Transform child in transform)
            {
                Destroy(child.gameObject);
            }

            // Recreate obstacles...
            foreach (ObstacleData obstacleData in wrapper.obstacles)
            {
                GameObject newObstacle = new GameObject(obstacleData.name);
                newObstacle.transform.position = obstacleData.position;
                newObstacle.transform.rotation = obstacleData.rotation;

                // Add SpriteRenderer component if sprite name exists
                if (!string.IsNullOrEmpty(obstacleData.spriteName))
                {
                    SpriteRenderer spriteRenderer = newObstacle.AddComponent<SpriteRenderer>();
                    // Load sprite by name
                    Sprite sprite = Resources.Load<Sprite>(obstacleData.spriteName);
                    if (sprite != null)
                    {
                        spriteRenderer.sprite = sprite;
                    }
                    else
                    {
                        Debug.LogWarning("Sprite not found with name: " + obstacleData.spriteName);
                    }
                }

                newObstacle.AddComponent<CircleCollider2D>();
                ClickableObject clickableObj = newObstacle.AddComponent<ClickableObject>();
                clickableObj.OnClick += (clickableObj,position) => MapManager.Instance.HandleClick(clickableObj,position);
                newObstacle.GetComponent<SpriteRenderer>().sortingOrder = 2;
                newObstacle.transform.SetParent(transform);
            }

            // Debug log to indicate successful loading
            Debug.Log("Obstacles data loaded successfully.");
        }
        else
        {
            // Debug log to indicate invalid index
            Debug.LogWarning("Invalid index provided for loading obstacles.");
        }
    }
  • LoadObstacles() method is responsible for loading obstacles’ data and recreating them.
  • It first loads the jsonDataList from PlayerPrefs.
  • Then, it checks if the provided index is valid and within the range of jsonDataList.
  • If the index is valid, it retrieves the JSON data at that index.
  • It deserializes the JSON data into an ObstacleDataWrapper object.
  • It clears existing obstacles.
  • It recreates obstacles using the data from the ObstacleDataWrapper.
  • If a sprite name exists for an obstacle, it adds a SpriteRenderer component and loads the sprite by name from the Resources folder.
  • It adds a CircleCollider2D component to each obstacle for interaction.
  • It attaches a ClickableObject component to each obstacle for handling clicks.
  • It sets the sorting order and parent of each obstacle.
  • Finally, it logs a message indicating successful loading or an invalid index.
    // Save jsonDataList using PlayerPrefs
    private void SaveJsonDataList()
    {
        string jsonDataListString = JsonUtility.ToJson(new StringListWrapper { dataList = jsonDataList });
        PlayerPrefs.SetString(jsonDataListKey, jsonDataListString);
        PlayerPrefs.Save();
    }
  • SaveJsonDataList() method serializes the jsonDataList into a JSON string using JsonUtility.ToJson() method.
  • It then stores this JSON string in PlayerPrefs using a specified key.
  • Finally, it saves the PlayerPrefs to disk using PlayerPrefs.Save().
    // Load jsonDataList from PlayerPrefs
    private void LoadJsonDataList()
    {
        if (PlayerPrefs.HasKey(jsonDataListKey))
        {
            string jsonDataListString = PlayerPrefs.GetString(jsonDataListKey);
            StringListWrapper wrapper = JsonUtility.FromJson<StringListWrapper>(jsonDataListString);
            jsonDataList = wrapper.dataList;
        }
    }
  • LoadJsonDataList() method checks if the PlayerPrefs contains data with the specified key.
  • If data exists, it retrieves the JSON string from PlayerPrefs using the key.
  • It then deserializes this JSON string into a StringListWrapper object using JsonUtility.FromJson() method.
  • Finally, it assigns the dataList from the wrapper object to jsonDataList.
    // Wrapper class to serialize and deserialize the list of ObstacleData objects
    [System.Serializable]
    private class ObstacleDataWrapper
    {
        public List<ObstacleData> obstacles;
    }
    
    // Wrapper class to serialize and deserialize a list of strings
    [System.Serializable]
    private class StringListWrapper
    {
        public List<string> dataList;
    }
}
  • ObstacleDataWrapper is a wrapper class used to serialize and deserialize a list of ObstacleData objects.
  • StringListWrapper is a wrapper class used to serialize and deserialize a list of strings.

These wrapper classes provide a convenient way to serialize and deserialize lists of objects and strings using Unity’s JsonUtility. They encapsulate the list data and make it easier to work with during serialization and deserialization processes.

Overall, the ObstacleManager script provides functionality to manage obstacles in the game environment, including spawning, saving, and loading obstacle data. It utilizes PlayerPrefs and JSON serialization for data persistence and provides methods to interact with obstacles dynamically during gameplay.

Endless Existence
Endless Existence
Articles: 74

Leave a Reply

Your email address will not be published. Required fields are marked *