GithubHelp home page GithubHelp logo

awpas50 / clickergame Goto Github PK

View Code? Open in Web Editor NEW
0.0 1.0 0.0 665.75 MB

City builder game. Includes save system tutorial in README.

Home Page: https://awpas50.itch.io/motorland

C# 85.94% ShaderLab 12.46% HLSL 0.86% GLSL 0.75%
unity binary-format

clickergame's Introduction

Summary

1-year long Unity project.

Technique used: Binary Formatter, Async, LeanTween API, URP Shader Graph

Learnt: Writing step-to-step tutorials, writing post-release devlogs for users (players)

Step-to-step guide on saving data in a city building game (on PC/Mac/Linux/WebGL) using binary formatter

Today's example will be saving multiple building positions in a city builder game. As building positions are stored as Vector3, but binary formatter cannot store Unity variables such as GameObject, Transform, Vector3, we need to convert it to non Unity-only variables (int, float, etc.).

The scene will be reloaded to wipe any old data before new save data is read and loaded.

4 scripts are needed:

SaveSystem.cs: Static class that handles the binary formatter

SaveLoadHandler.cs: A script that has to be placed in the Unity hierarchy (using a empty GameObject) in order to work. Has functions SaveGame() and CreateSceneLoader() to trigger saving and loading

AllSaveData.cs: Stores the game data you want to save (must be a string, int, float, bool)

SceneLoader.cs: A script used to properly load the game using LoadSceneAsync() (instead of LoadScene()). Otherwise, the save data loading will happened before the scene reloads, and thus loading will fail.

Screenshot 2022-08-05 17 03 24

SaveSystem.cs
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
using System.Runtime.InteropServices;

public static class SaveSystem
{
    [DllImport("__Internal")]
    private static extern void JS_FileSystem_Sync();
    
    // Will be called in the script "SaveLoadHandler"
    public static void Save()
    {
        // create a binary formatter
        BinaryFormatter formatter = new BinaryFormatter();
        // declare a path 
        string path;
#if UNITY_STANDALONE_WIN || UNITY_EDITOR
        path = Application.persistentDataPath + "/save1.txt";
#elif UNITY_STANDALONE_OSX
        path = Application.persistentDataPath + "/save1.txt";
#elif UNITY_STANDALONE_LINUX
        path = Application.persistentDataPath + "/save1.txt";
        // Important: This path must have a custom name or the data will be wiped every time a new build was uploaded
#elif UNITY_WEBGL
        path = "/idbfs/anyNameYouWant" + "/save1.dat";
        if (!Directory.Exists(path))
        {
            Directory.CreateDirectory("/idbfs/anyNameYouWant");
        }
#endif
        using (FileStream stream = new FileStream(path, FileMode.Create)) {
            AllSaveData allSaveData = new AllSaveData();
            // encrypt the data into binary format
            formatter.Serialize(stream, allSaveData);
            // remember to close the stream
            stream.Close();
            // Sync
            JS_FileSystem_Sync();
        }
    }
    
    // Will be called in the script "SaveLoadHandler"
    public static AllSaveData Load()
    {
        string path;
#if UNITY_STANDALONE_WIN || UNITY_EDITOR
        path = Application.persistentDataPath + "/save1.txt";
#elif UNITY_STANDALONE_OSX
        path = Application.persistentDataPath + "/save1.txt";
#elif UNITY_STANDALONE_LINUX
        path = Application.persistentDataPath + "/save1.txt";
#elif UNITY_WEBGL
        // Important: This path must have a custom name or the data will be wiped every time a new build was uploaded
        path = "/idbfs/anyNameYouWant" + "/save1.dat";
#endif
        if (File.Exists(path))
        {
            BinaryFormatter formatter = new BinaryFormatter();
            FileStream stream = new FileStream(path, FileMode.Open);
            // decrypt the data from binary to readable format
            AllSaveData data = formatter.Deserialize(stream) as AllSaveData;
            // remember to close the stream
            stream.Close();
            return data;
        }
        else
        {
            //DEBUG
            Debug.LogError("Save file not found in " + path);
            return null;
        }
    }
}
SaveLoadHandler.cs
using System;
using UnityEngine;

public class SaveLoadHandler : MonoBehaviour
{
    // THe building you want to be loaded
    public GameObject buildingA;
    // Will be called in runtime (usually with a button)
    public void SaveGame()
    {
        SaveSystem.Save();
        Debug.Log("Save successful");
    }
    // Will be called in runtime (usually with a button)
    public void CreateSceneLoader()
    {
        GameObject sceneLoader = new GameObject("SceneLoader");
        DontDestroyOnLoad(sceneLoader);
        sceneLoader.AddComponent<SceneLoader>();
    }
    // *** Will be called when SceneLoader.cs is initialized.
    public void LoadGame()
    {
        AllSaveData data = SaveSystem.Load();
        LoadHouses(data);
        Debug.Log("Load successful");
    }
    private void LoadHouses(AllSaveData data)
    {
        for (int i = 0; i < data.saveData_allHousesCount; i++)
        {
            // Instantiate a new house with the position previously saved.
            GameObject newHouse = Instantiate(buildingA, 
                new Vector3(data.saveData_housePos[i,0],
                data.saveData_housePos[i,1],
                data.saveData_housePos[i,2]), Quaternion.identity);
        }
    }
}
AllSaveData.cs
using UnityEngine;

[System.Serializable]
public class AllSaveData
{
    [Header("House")]
    public float[,] saveData_housePos;
    public int saveData_allHousesCount;
    // Will be called in SaveLoadHandler.cs
    public AllSaveData()
    {
        StoreHouseData();
    } 
    private void StoreHouseData()
    {
        // Obtain all the houses with the tag 'House' in the scene
        GameObject[] allHouses = GameObject.FindGameObjectsWithTag("House");
        // The number of houses has to be stored. This will be used when loading.
        saveData_allHousesCount = allHouses.Length;
        // Initializing the array
        saveData_housePos = new float[allHouses.Length, 3];
        for (int i = 0; i < allHouses.Length; i++)
        {
            // Storing a Vector3 variable into 3 float variables.
            saveData_housePos[i, 0] = allHouses[i].transform.position.x;
            saveData_housePos[i, 1] = allHouses[i].transform.position.y;
            saveData_housePos[i, 2] = allHouses[i].transform.position.z;
        }
    }
}
SceneLoader.cs
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneLoader : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(LoadScene());
    }
    public IEnumerator LoadScene()
    {
        // Access SaveLoadHandler.cs
        SaveLoadHandler slh = FindObjectOfType<SaveLoadHandler>();
        // Start loading the scene
        AsyncOperation asyncLoadLevel = SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().buildIndex, LoadSceneMode.Single);
        // Wait until the level finish loading
        while (!asyncLoadLevel.isDone)
            yield return null;
        // Wait a frame so every Awake() and Start() method is called
        yield return new WaitForEndOfFrame();
        // Load save data in the next frame
        slh.LoadGame();
        // Destroy itself after everything has loaded, so that this function will only be called once.
        Destroy(gameObject);
    }
}

This is everything needed for the save system. To test saving or loading in the game, create 2 buttons and simply assign SaveLoadHandler.SaveGame() and SaveLoadHandler.CreateSceneLoader() to the OnClick() function respectively.

Screenshot 2022-08-05 17 11 50

Tips

If you want to save multiple types of buildings, simply put all the data you need to save in SaveData.SaveData() and everything you need to load in the script SaveLoadHandler.LoadGame(). Below is a example storing different entities in the game:

SaveData.cs
public AllSaveData()
{
    StorePlatformData();
    StoreHouseData();
    StoreFactoryData();
    StoreParkData();
    StoreTurretData();
    StoreAirportData();
    StoreLogisticCenterData();
    StorePerpetualMachineData();
    StoreRuinData();
    StoreResourcesAndPollution();
    StoreCurrentGameTime();
    StoreTownHallRelated();
    StoreAsteroidsData();
    StoreBulletData();
}
SaveLoadHandler.cs
public void LoadGame()
{
    AllSaveData data = SaveSystem.Load();
    LoadPlatforms(data);
    LoadHouses(data);
    LoadFactories(data);
    LoadParks(data);
    LoadTurrets(data);
    LoadAirports(data);
    LoadLogisticCenters(data);
    LoadPerpetualMachines(data);
    LoadRuins(data);
    LoadResourcesAndPollution(data);
    LoadCurrentGameTime(data);
    LoadTownHallRelated(data);
    LoadAsteroids(data);
    LoadBullets(data);
}

clickergame's People

Contributors

awpas50 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.