Unity learns through examples (shooting monsters on the Jiugong grid): archiving and reading files of the game (binary, json, xml)(

Posted by kharbat on Tue, 01 Feb 2022 15:34:50 +0100

Interface

8 use synergy to control the monster's life cycle

Synergetic process

Timing, labor law, getting off work on time
Each has its own countermeasures

    void Update()
    { 
        timer += Time.deltaTime;
        if (timer > spawnTime)
        {
            timer = 0;
           //01 SpawnEnemy();
            StartCoroutine(RandomSpawnTime());
        }

    }

    IEnumerator RandomSpawnTime()
    {
        yield return new WaitForSeconds(Random.Range(1f,5f));
        SpawnEnemy();
    }   

9 control the monster's death

Co process control generates death time, and min death time > Max generates events

But at present, the enemy is generated from the grid. When I come out and leave the house, I accelerate to rush to the player. When I meet the player, the player will lose blood and the enemy will destroy himself.
Therefore, only co process generation is used

10 set the rotation of the pistol

Correspondence between rotation axis and setting axis number

Rotating that position is equivalent to "turning on the motorcycle". The rotation of the handle is like the rotation of the x red axis; The rotation of the wheel is like the lifting and pressing of the muzzle of a gun

Observe the direction limit of the gun before operation

Explain the origin of that pile of multiplication and addition and subtraction

    void RotateMuzzle()//Rotating muzzle
    {
        rotateXAxis = Input.mousePosition.y / Screen.height * maxYDir;//Up and down
        rotateYAxis = Input.mousePosition.x / Screen.width * maxXDir;//about

        *** rotateXAxis = -((Mathf.Clamp(rotateXAxis, minYDir, maxYDir))*4-20);
        rotateYAxis = (Mathf.Clamp(rotateYAxis, minXDir, maxXDir) -35)*2;***

        transform.eulerAngles = new Vector3(rotateXAxis, rotateYAxis, 0f);
    }

17 making menu UI

Grid layout group = = grid of ngui

27 create a Save class

Summarize the labels you have encountered so far

[System.Serializable]//Class details
[SerializeField]  //Object details
[Tooltip("")]//Tips, editors and codes all have tips
[HideInInspector]//Editor hidden

Save class

[System.Serializable]
public class SaveGame : MonoBehaviour
{
    public List<int> enemyPosList=new List<int>();
    public List<int> enemyTypeList=new List<int>();
    public int score;
    public int killCount;

call

    public SaveGame SaveGame()//Save the enemy's type, position, score and kill count
    {
         SaveGame saveGame = new SaveGame();
        foreach(GameObject go in enemyObjectList)
        {
            saveGame.enemyTypeList.Add(go.GetComponent<Enemy>().type);
            saveGame.enemyPosList.Add(enemyObjectList.IndexOf(go));
        }

        list1 = saveGame.enemyTypeList;//Print
        list2 = saveGame.enemyPosList;
        a1 =saveGame.score = Player._instance.score;
        a2=saveGame.killCount = Player._instance.killCount;

        return saveGame;
    }

output

28 save game (binary method)

(problem) saving Transform, Vector3, etc. is not supported

System.Runtime.Serialization.FormatterServices.InternalGetSerializableMember
A small pit encountered in Unity serialization
The video is a Jiugong grid, and the position remains unchanged, so the index value is used. I need to record the transformation (at least position and rotation) in the way of flying
The four data types, scores and kill counts are all integers. Only Transform can't do it. Position can't do it. I haven't tried. The following structure is used to record position.

The structure is used to process Vector3 on the Internet. So split the Transform into three parts POS, rotate and scale (I won't)
At present, only position Vector3 is recorded, which is represented by structure

technological process

The previous save class modification

[Serializable]
public class SaveGame
{
    public List<int> enemyTypeList = new List<int>();
    public List<Vector3Serializer> enemyPosList = new List<Vector3Serializer>();
    public int score;
    public int killCount;
}

Enemy

[Serializable]public struct Vector3Serializer
{
    public float x;
    public float y;
    public float z;

    public void Fill(Vector3 v3)
    {
        x = v3.x;
        y = v3.y;
        z = v3.z;
    }

    public Vector3 V3
    { get { return new Vector3(x, y, z); } }
}
public class Enemy : MonoBehaviour
{
 ...
    public Vector3Serializer pos;

EnemyController

    public SaveGame SaveGame()//Save the enemy's type, position, score and kill count
    {
         SaveGame save = new SaveGame();
        foreach(GameObject go in enemyObjectList)
        {
            Enemy enemy = go.GetComponent<Enemy>();
            //type
            save.enemyTypeList.Add(enemy.type);           
            //Position assignment, in the form of structure
            go.GetComponent<Enemy>().pos.Fill(go.transform.position);
            save.enemyPosList.Add(enemy.pos);

            save.score = enemy.score;
            save.killCount = enemy.score;                           
        }
        return save;
    }

UI code, file flow

StreamingFile is a new folder created under the root directory of Unity. I don't know if it will automatically help us create it. After all, bybin1 Txt didn't exist at first
Save again to overwrite

FileStream feels like a inserted "funnel". SaveGame is oil and BinaryFormatter is to "pour oil"

    void SaveByBin()
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        FileStream fileStream = File.Create(Application.dataPath + "/StreamingFile" + "/ByBin1.txt");
        SaveGame save = EnemyController._instance.SaveGame();

        binaryFormatter.Serialize(fileStream, save);

        fileStream.Close();
    } 

result

29 read game (binary method)

UIController

BinaryFormatter "flips" data from FileStream to SaveGame

    SaveGame LoadByBin() {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        FileStream fileStream = File.Open(Application.dataPath + "/StreamingFile" + "/ByBin1.txt", FileMode.Open);

        SaveGame save = (SaveGame)binaryFormatter.Deserialize(fileStream);

        fileStream.Close();
        return save;
    }
    public void LoadGame()
    {
        ResetGame();//Clear set 0

        SaveGame save= LoadByBin();//File reading

        EnemyController._instance.LoadGame(save);//enemy
        Player._instance.LoadGame(save);//Kill count, score
        scoreText.text = save.score.ToString();
        killCountText.text = save.killCount.ToString();
    }

EnemyController

    public void LoadGame(SaveGame save)
    {
        for (int i = 0; i < save.enemyTypeList.Count; i++)
        {
            GameObject go = Instantiate( enemyPrefabList[save.enemyTypeList[i]] );
            go.transform.position = save.enemyPosList[i].V3;

            enemyObjectList.Add(go);
        }
    }

Make code snippets

It's meaningless if you can't reuse it. The style is in xml format

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
	<CodeSnippet Format="1.0.0">
		<Header>
			<Title>SaveLoadByBin</Title>
			<Shortcut>SaveLoadByBin</Shortcut>
			<Description>Binary Reading Archive</Description>
			<Author>user name</Author>
			<SnippetTypes>
				<SnippetType>Expansion</SnippetType>
				<SnippetType>SurroundsWith</SnippetType>
			</SnippetTypes>
		</Header>
		<Snippet>
	<Code Language="csharp"><![CDATA[
	[Serializable]
public class SaveGame
{
    public List<int> enemyTypeList = new List<int>();
    public List<Vector3Serializer> enemyPosList = new List<Vector3Serializer>();
    public int score;
    public int killCount;
}
    void SaveByBin()
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        FileStream fileStream = File.Create(Application.dataPath + "/StreamingFile" + "/ByBin1.txt");
        SaveGame save = EnemyController._instance.SaveGame();

        binaryFormatter.Serialize(fileStream, save);

        fileStream.Close();
    } 
    SaveGame LoadByBin() {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        FileStream fileStream = File.Open(Application.dataPath + "/StreamingFile" + "/ByBin1.txt", FileMode.Open);

        SaveGame save = (SaveGame)binaryFormatter.Deserialize(fileStream);

        fileStream.Close();
        return save;
    }
]]>
	</Code>
		</Snippet>
	</CodeSnippet>
</CodeSnippets>

30-31 save game (JSON)

phenomenon

After archiving, unity will not be refreshed automatically in time. It needs to be refreshed manually. At first, I thought it was unsuccessful or the speed was too slow

Binary does not support Vector3, so use structure. Now json does not support Vector3 and structure does not support Vector3

01 split list < vector3 > into list < int >, list < int >, list < int > and store x, y and Z respectively
02 store x,y,z in every three places in a list, and so on

Adopt 2, because json is also a string of data together, waiting to be cut
So I gave up the previous structure

JsonException: Max allowed object depth reached while trying to export from type System.Single

jsonData does not support float, so double is used
JsonException: Max allowed object depth reached while trying to export from type System.Single

restructure

The part of data read and saved before is put into UIC controller, and it is taken out and put into the new GameController

Save class

[Serializable]
public class SaveGame
{
    public List<int> enemyTypeList = new List<int>();
    public List<double> enemyXYZList = new List<double>();
    public int score;
    public int killCount;
}

GameController

    void SaveByJson(SaveGame save, string path)
    {
        string str = JsonMapper.ToJson(save);

        StreamWriter streamWriter = new StreamWriter(Application.dataPath + path);

        streamWriter.Write(str);
        streamWriter.Close();
    }
    SaveGame LoadByJson(string path)
    {
        StreamReader streamreader = new StreamReader(Application.dataPath + path);//Read data and convert it into data stream
        JsonReader json = new JsonReader(streamreader);//Then convert it into json data
        SaveGame save = JsonMapper.ToObject<SaveGame>(json);//read

        streamreader.Close();

        return save;
    }

    public void SaveGame()
    {
        print("Save the game!");
        SaveGame save = EnemyController._instance.SaveGame();

        SaveByJson(save, "/StreamingFile" + "/ByJson.json");
        if (File.Exists(Application.dataPath + "/StreamingFile" + "/ByJson.json"))
        {
            UIController._instance.ShowMessage("Saved successfully!");
        }
    }
    public void LoadGame()
    {
        ResetGame();//Clear set 0

        //SaveGame save= LoadByBin("/StreamingFile" + "/ByBin.txt");// File reading
        SaveGame save = LoadByJson("/StreamingFile" + "/ByJson.json");//File reading
        //SaveGame save= LoadByXml();// File reading

        EnemyController._instance.LoadGame(save);//enemy
        Player._instance.LoadGame(save);//Kill count, score
        UIController._instance.SetRecordUI(save.score, save.killCount);
    }

EnemyController

Called by LoadGame of UI

    public SaveGame SaveGame()//Save the enemy's type, position, score and kill count
    {
        SaveGame save = new SaveGame();
        foreach(GameObject go in enemyObjectList)
        {
            Enemy enemy = go.GetComponent<Enemy>();
            //type
            save.enemyTypeList.Add(enemy.type);
            //Position assignment
            Vector3 pos= go.transform.position;
            save.enemyXYZList.Add(pos.x);
            save.enemyXYZList.Add(pos.y);
            save.enemyXYZList.Add(pos.z);

            enemyObjectXYZList = save.enemyXYZList;//Print and see
            this.enemyTypeList = save.enemyTypeList;//Print and see
            save.score = Player._instance.score;
            save.killCount = Player._instance.score;                           
        }

        UIController._instance.ShowMessage("Storage complete");
        return save;
    }

    public void LoadGame(SaveGame save)
    {
        for (int i = 0; i < save.enemyTypeList.Count; i++)
        {
            GameObject go = Instantiate( enemyPrefabList[save.enemyTypeList[i]] );

            int j = i * 3;//012 345 678 ......
            go.transform.position = new Vector3((float)save.enemyXYZList[j], (float)save.enemyXYZList[j+1], (float)save.enemyXYZList[j+2]);

            enemyObjectList.Add(go);
        }
    }

result

There are 3 enemies in the archive, 2, 3 and 2 respectively. The position is vector3 (- 12.699999092651, 14.9000015258789, 8.44331550598145), and so on

{
	"enemyTypeList": [2, 3, 2],
	"enemyXYZList": [**-12.6999998092651, 14.9000015258789, 8.4433155059814**5, -0.200000002980232, 29.0, 15.3022012710571, -0.400000005960464, 1.40000081062317, 22.9358730316162],
	"score": 10,
	"killCount": 10
}

32-33 save game (XML)

(question) can be printed, but there is only the last one in the file


Put variables outside the loop, resulting in only one

Save class

Although it was written earlier, it is the benchmark for writing, reading and archiving. I'm still writing it. It is suggested that all the code be made into code fragments

[Serializable]
public class SaveGame
{
    public List<int> enemyTypeList = new List<int>();
    public List<double> enemyXYZList = new List<double>();
    public int score;
    public int killCount;
}

Save, GameController

    public void SaveGame()
    {
        print("Save the game!");
        SaveGame save = EnemyController._instance.SaveGame();
        
        SaveByXml(save, "/StreamingFile" + "/ByXml.txt");
        if (File.Exists(Application.dataPath + "/StreamingFile" + "/ByXml.txt"))
        {
            UIController._instance.ShowMessage("Saved successfully!");
        }
    }


    void SaveByXml(SaveGame save, string path) {

        XmlDocument xmlDoc = new XmlDocument();
        XmlElement root = xmlDoc.CreateElement("save");
        root.SetAttribute("name","SaveFile1");


        for (int i = 0; i < save.enemyTypeList.Count; i++)
        {
            XmlElement target = xmlDoc.CreateElement("target");

            //Type and location
            XmlElement type = xmlDoc.CreateElement("type");
            XmlElement x = xmlDoc.CreateElement("x");
            XmlElement y = xmlDoc.CreateElement("y");
            XmlElement z = xmlDoc.CreateElement("z");
            type.InnerText = save.enemyTypeList[i].ToString();
            int j = i * 3;
            x.InnerText = save.enemyXYZList[j].ToString();
            y.InnerText = save.enemyXYZList[j+1].ToString();
            z.InnerText = save.enemyXYZList[j+2].ToString();

            print(type.InnerText+","+x.InnerText+ "," + y.InnerText+ "," + z.InnerText);//Print and see

            target.AppendChild(type);
            target.AppendChild(x);
            target.AppendChild(y);
            target.AppendChild(z);

            root.AppendChild(target);
        }

Load GameController

    public void LoadGame()
    {
        SaveGame save= LoadByXml("/StreamingFile" + "/ByXml.txt");//File reading

        EnemyController._instance.LoadGame(save);//enemy
        Player._instance.LoadGame(save);//Kill count, score
        UIController._instance.SetRecordUI(save.score, save.killCount);
    }
    SaveGame LoadByXml(string path) 
    {
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.Load(Application.dataPath+path);
        SaveGame save = new SaveGame();
        //
        XmlNodeList targetList = xmlDoc.GetElementsByTagName("target");
        for (int i = 0; i < targetList.Count; i++)
        {
            int type = int.Parse(targetList[i].ChildNodes[0].InnerText);
            double x = double.Parse(targetList[i].ChildNodes[1].InnerText);
            double y = double.Parse(targetList[i].ChildNodes[2].InnerText);
            double z = double.Parse(targetList[i].ChildNodes[3].InnerText);

            save.enemyTypeList.Add(type);
            save.enemyXYZList.Add(x);
            save.enemyXYZList.Add(y);
            save.enemyXYZList.Add(z);
        }
        //
        int score = int.Parse(xmlDoc.GetElementsByTagName("score")[0].InnerText);
        int killCount = int.Parse(xmlDoc.GetElementsByTagName("killCount")[0].InnerText);
        save.score = score;
        save.killCount = killCount;

        return save;
    }

result

Continue the screen operation of the game, and the screenshot description is of little significance (only moving pictures)