Vector3 not serializable Unity3D - unity3d

Ok so I followed the Unity3D data persistence tutorial, everything is going smoothly until I tried to save a Vector3 type of data. The tutorial only shows how to save int and strings.
When I used the function Save() , the consoles shows me that says: "SerializationException: Type UnityEngine.Vector3 is not marked as Serializable.
But as you can see from my code, I included it under the Serializable part. I am trying to save my playerPosition. Thanks
using UnityEngine;
using System.Collections;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Collections.Generic;
public class GameManager : MonoBehaviour
{
public static GameManager control;
public float health;
public float mana;
public float experience;
public Vector3 playerPosition;
public List<Item> inventory = new List<Item>();
void Awake()
{
if(control == null)
{
DontDestroyOnLoad(gameObject);
control = this;
}
else if(control != this)
{
Destroy(gameObject);
}
}
// Use this for initialization
void Start (){}
// Update is called once per frame
void Update () {}
void OnGUI()
{
GUI.Box(new Rect(10, 10, 100, 30), "Health: " + health);
GUI.Box(new Rect(10, 30, 100, 30), "Mana: " + mana);
GUI.Box(new Rect(10, 50, 100, 30), "Experience: " + experience);
}
public void SaveSlot1()
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create(Application.persistentDataPath +
"/saveData1.dat");
PlayerData data = new PlayerData();
data.health = health;
data.mana = mana;
data.experience = experience;
data.playerPosition = playerPosition;
for(int i = 0; i <inventory.Count; i++)
{
//data.inventory[i] = inventory[i];
}
bf.Serialize(file, data);
file.Close();
}
public void LoadSlot1()
{
if(File.Exists(Application.persistentDataPath +
"/saveData1.dat") )
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath +
"/saveData1.dat", FileMode.Open);
PlayerData data = (PlayerData)bf.Deserialize(file);
file.Close();
health = data.health;
mana = data.mana;
experience = data.experience;
playerPosition = data.playerPosition;
for(int i = 0; i <inventory.Count; i++)
{
//inventory[i] = data.inventory[i];
}
}
}
[Serializable]
class PlayerData
{
public float health;
public float mana;
public float experience;
public Vector3 playerPosition;
public List<Item> inventory = new List<Item>();
//position
//spells
//
}
}

Marking something as Serializable just let's .NET to know that you are going to be using serialization. At that point every property all the way down the data model must also be serializable, either by being inherently serializable like ints and strings, or being a type that is also marked as serializable. There's a couple of options
[Serializable]
class PlayerData
{
public PlayerData()
{
playerPosition = Vector3.zero;
}
public float health;
public float mana;
public float experience;
[NonSerialized]
public Vector3 playerPosition;
public double playerPositionX
{
get
{
return playerPosition.x;
}
set
{
playerPosition.x = value;
}
}
public double playerPositionY
{
get
{
return playerPosition.y;
}
set
{
playerPosition.y = value;
}
}
public double playerPositionZ
{
get
{
return playerPosition.z;
}
set
{
playerPosition.z = value;
}
}
public List<Item> inventory = new List<Item>();
}
This first option will not serialize the vector field, and instead use the properties. Having a vector to start with is important.
The other option is to implement the ISerializable interface on your class and then specifically control what fields are being written. The interface is somewhat tricky to implement, here is a MSDN link that explains some good and bad examples

Related

Unity2D: my instantiate random object is still error even if I attached a scripts to it

I'm currently making a simple game for my project, which is spawning food and damaging it until its vanishes. I have to write a script that instantiates a random object from an array, attaches the script to it, and damages the instantiated food in another script. The current problem now is Unity telling me that I have a Null Reference Exception on both scripts, I tried to fix it by attaching the script to the instantiated object, but still, the problem remains.
Here the code on the script that attaches to the instantiated object, and also spawning the object too:
`
public class Food : MonoBehaviour
{
public GameObject[] food;
public Vector3Int spawnPosition;
public int health = 200;
public int currentHealth;
private GameObject clone;
public void Start()
{
currentHealth = health;
SpawnFood();
}
//Spawning food
public void SpawnFood()
{
int random = Random.Range(0, food.Length); //Null Reference Exception happen in this line.
clone = Instantiate(food[random], this.spawnPosition, Quaternion.identity) as GameObject;
clone.AddComponent<Food>();
}
public void TakeDamage(int damage)
{
currentHealth -= damage;
//play hurt effect
if(currentHealth < 0)
{
Vanish();
}
}
void Vanish()
{
Debug.Log("Vanished");
}
}
`
Here is the other script:
`
public class Board : MonoBehaviour
{
public Tilemap tilemap { get; private set; }
public Piece activePiece { get; private set; }
public TetrominoData[] tetrominoes;
public Vector3Int spawnPosition;
public Vector2Int boardSize = new Vector2Int(10, 20);
public int damage;
public Food clone;
public TextMeshProUGUI hud_score;
public static int currentScore = 0;
public int scoreOneLine = 40;
public int scoreTwoLine = 100;
public int scoreThreeLine = 300;
public int scoreFourLine = 1200;
private int numberOfRowsThisTurn = 0;
public RectInt Bounds
{
get
{
Vector2Int position = new Vector2Int(-this.boardSize.x / 2, -this.boardSize.y / 2);
return new RectInt(position, this.boardSize);
}
}
private void Awake()
{
this.tilemap = GetComponentInChildren<Tilemap>();
this.activePiece = GetComponentInChildren<Piece>();
//call Tetromino.Initialize() to spawn pieces
for (int i = 0; i < this.tetrominoes.Length; i++)
{
this.tetrominoes[i].Initialize();
}
}
private void Start()
{
SpawnPiece();
}
private void Update()
{
UpdateScore();
UpdateUI();
}
public void UpdateUI()
{
hud_score.text = currentScore.ToString();
}
public void UpdateScore()
{
if(numberOfRowsThisTurn > 0)
{
if(numberOfRowsThisTurn == 1)
{
ClearedOneLine();
}
else if (numberOfRowsThisTurn == 2)
{
ClearedTwoLine();
}
else if (numberOfRowsThisTurn == 3)
{
ClearedThreeLine();
}
else if (numberOfRowsThisTurn == 4)
{
ClearedFourLine();
}
numberOfRowsThisTurn = 0;
}
}
public void ClearedOneLine()
{
currentScore += scoreOneLine;
clone.GetComponent<Food>().TakeDamage(10); //Null Reference Exception happen in this line.
}
public void ClearedTwoLine()
{
currentScore += scoreTwoLine;
clone.GetComponent<Food>().TakeDamage(20); //Null Reference Exception happen in this line.
}
public void ClearedThreeLine()
{
currentScore += scoreThreeLine;
clone.GetComponent<Food>().TakeDamage(40); //Null Reference Exception happen in this line.
}
public void ClearedFourLine()
{
currentScore += scoreFourLine;
clone.GetComponent<Food>().TakeDamage(80); //Null Reference Exception happen in this line.
}
`
Please help and thank you for the help.
Here is the Inspector image with 64 elements in an array:
I have tried to attach the script to the instantiated object when that object is being spawned.
I don't see food set anywhere. You need to populate the array inside Food from the Inspector or at least, populate programmatically in Food.Start() before using it.
Can you also show us a printscr of the Inspector view where Food is attached as component? The main focus here is the food array.
the food array is empty it should be filled by you,
instantiate doesn't fill it, it takes an object from the array and spawn it.
fill it with prefabs of foods that already has the food component no need to add it in code

How can I create foldout EditorGUI element with toggle in Unity Inspector

Need your help.
Easily create a foldout element with toggle list. Like that
But I need to create foldout element with toggle in a header. Like that
I think it's possible because scripts header already have this
I tried to find the answer here but didn't find anything like it.
Thank you for help
You can override how your Serializable class is rendered using EditorGUI by creating a custom PropertyAttribute and PropertyDrawer.
Example
I cooked up an attribute that takes a boolean field (specified by you) and instead of rendering it as usual, it renders it as a checkbox at the top. You can have any number of boolean fields inside your class, they should render just fine.
This implementation renders only boolean fields. If you wish to render other kind of stuff besides that, feel free to extend this solution.
Implementation
using UnityEngine;
public class ToggleListAttribute : PropertyAttribute
{
public string StatusPropertyName { get; private set; }
public ToggleListAttribute(string statusPropertyName)
{
StatusPropertyName = statusPropertyName;
}
}
using System;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(ToggleListAttribute))]
public class ToggleListDrawer : PropertyDrawer
{
private bool show;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var statusProperty = GetStatusPropertyFrom(property);
var foldoutRect = GetLinePositionFrom(position, 1);
show = EditorGUI.Foldout(
foldoutRect,
show,
string.Empty,
false);
statusProperty.boolValue = EditorGUI.ToggleLeft(
foldoutRect,
property.displayName,
statusProperty.boolValue);
if (show)
RenderSubproperties(property, position);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
if (show)
return EditorGUIUtility.singleLineHeight * (GetBooleanPropertyCount(property) + 1);
else
return EditorGUIUtility.singleLineHeight;
}
private SerializedProperty GetStatusPropertyFrom(SerializedProperty property)
{
var listAttribute = attribute as ToggleListAttribute;
var statusProperty = property.FindPropertyRelative(
listAttribute.StatusPropertyName);
if (statusProperty == null)
throw new Exception($"No property named \"{listAttribute.StatusPropertyName}\" found!");
return statusProperty;
}
private void RenderSubproperties(SerializedProperty property, Rect position)
{
var innerPosition = new Rect(
position.x + EditorGUIUtility.standardVerticalSpacing * 4,
position.y,
position.width,
position.height);
var statusProperty = GetStatusPropertyFrom(property);
int line = 2;
foreach (var instance in property)
{
var subproperty = instance as SerializedProperty;
if (subproperty.propertyType != SerializedPropertyType.Boolean ||
subproperty.name == statusProperty.name)
{
continue;
}
subproperty.boolValue = EditorGUI.ToggleLeft(
GetLinePositionFrom(innerPosition, line),
subproperty.displayName,
subproperty.boolValue);
line++;
}
}
private int GetBooleanPropertyCount(SerializedProperty property)
{
int count = 0;
foreach (var instance in property)
{
var subproperty = instance as SerializedProperty;
if (subproperty.propertyType != SerializedPropertyType.Boolean)
continue;
count++;
}
return count - 1;
}
private Rect GetLinePositionFrom(Rect rect, int line)
{
float heightModifier = EditorGUIUtility.singleLineHeight * (line - 1);
return new Rect(
rect.x,
rect.y + heightModifier,
rect.width,
EditorGUIUtility.singleLineHeight);
}
}
Usage
using System;
using UnityEngine;
public class Example : MonoBehaviour
{
[ToggleList("enabled")]
public RenderList list1;
[ToggleList("enabled")]
public RenderList2 list2;
}
[Serializable]
public class RenderList
{
public bool enabled;
public bool floor;
public bool car;
public bool train;
}
[Serializable]
public class RenderList2
{
public bool enabled;
public bool one;
public bool two;
public bool three;
public bool four;
public bool five;
public bool six;
public bool seven;
}
Use EditorGUILayout.InspectorTitlebar(foldout: foldout, editor: targetEditor);

Swapping Two GameObjects on Keypress

When I press Space, the player and the dummy are supposed to swap places. This doesn't just happen once, it needs to happen everytime I press the Space bar so they can swap back and forth. However, sometimes it works and sometimes it doesn't. When it doesn't work, the dummy teleports to the player and then the player teleports inside of the dummy (as that is where its last position is). This just kind of pushes the player about.
Here is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerSwap : MonoBehaviour
{
public GameObject player;
public GameObject dummy;
public GameObject cameraAnchor;
public Transform playerLastPos;
public Transform dummyLastPos;
public bool haveSwapped;
public bool canSwap;
public float swapRadius;
public LayerMask dummyInRadius;
// Update is called once per frame
void Update()
{
canSwap = Physics.CheckSphere(player.transform.position, swapRadius, dummyInRadius);
SwapTransforms();
if (canSwap)
{
if (Input.GetKeyDown(KeyCode.Space))
{
haveSwapped = !haveSwapped;
Swapping();
}
}
else
{
Debug.Log("Can't Swap");
}
cameraAnchor.transform.position = player.transform.position;
}
public void SwapTransforms()
{
if (haveSwapped)
{
dummyLastPos.position = player.transform.position;
playerLastPos.position = dummy.transform.position;
}
else
{
dummyLastPos.position = dummy.transform.position;
playerLastPos.position = player.transform.position;
}
}
public void Swapping()
{
if (haveSwapped)
{
player.transform.position = dummyLastPos.position;
dummy.transform.position = playerLastPos.position;
}
else
{
player.transform.position = playerLastPos.position;
dummy.transform.position = dummyLastPos.position;
}
}
public void OnDrawGizmos()
{
Gizmos.color = Color.white;
Gizmos.DrawWireSphere(player.transform.position, swapRadius);
}
}
I've solved it. After cleaning up most of the code and realsing I had to turn off movement and the character controller on the player (Link:Unity Answers), I came up with this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerSwap : MonoBehaviour
{
public ThirdPersonMovement playerMove;
public GameObject player;
public GameObject dummy;
public GameObject cameraAnchor;
public Transform playerLastPos;
public Transform dummyLastPos;
public bool canSwap;
public float swapRadius;
public LayerMask dummyInRadius;
// Update is called once per frame
void Update()
{
canSwap = Physics.CheckSphere(player.transform.position, swapRadius, dummyInRadius);
if (canSwap)
{
if (Input.GetKeyDown(KeyCode.Space))
{
playerMove.canMove = false;
player.GetComponent<CharacterController>().enabled = false;
SetTransforms();
}
}
else
{
Debug.Log("Can't Swap");
}
cameraAnchor.transform.position = player.transform.position;
}
public void SetTransforms()
{
dummyLastPos.position = dummy.transform.position;
playerLastPos.position = player.transform.position;
Debug.Log("Set Transforms");
Swapping();
}
public void Swapping()
{
player.transform.position = dummyLastPos.position;
dummy.transform.position = playerLastPos.position;
dummy.transform.rotation = player.transform.rotation;
Debug.Log("Swap");
playerMove.canMove = true;
player.GetComponent<CharacterController>().enabled = true;
}
public void OnDrawGizmos()
{
Gizmos.color = Color.white;
Gizmos.DrawWireSphere(player.transform.position, swapRadius);
}
}

How to spawn units using DOTS?

I am following this talk https://www.youtube.com/watch?v=BNMrevfB6Q0 and try to understand how to spawn units as I click my mouse (for testing purposes).
For this in general, I created a UnitBase etc. which implements IConvertGameObjectToEntity::Convert to make sure all units can be converted to a Entity objects and looks like this:
namespace Units
{
public abstract class UnitBase : MonoBehaviour, IConvertGameObjectToEntity
{
public float health;
public bool selected;
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddComponentData(entity, new Translation {Value = transform.position});
dstManager.AddComponentData(entity, new Rotation {Value = transform.rotation});
dstManager.AddComponentData(entity, new Selected {Value = selected});
dstManager.AddComponentData(entity, new Health {Value = health});
}
}
public abstract class MobileUnitBase : UnitBase
{
public float speed;
public new void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
base.Convert(entity, dstManager, conversionSystem);
dstManager.AddComponentData(entity, new Speed {Value = speed});
}
}
}
These are the main components:
namespace ECS.Components
{
public class Health : IComponentData
{
public float Value;
}
public class Speed : IComponentData
{
public float Value;
}
public class Selected : IComponentData
{
public bool Value;
}
}
Now, in my Test script I have this:
public class Test : MonoBehaviour
{
public GameObject unitPrefab;
private EntityManager _entityManager;
private Entity _unitEntityPrefab;
// Start is called before the first frame update
void Start()
{
_entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
_unitEntityPrefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(unitPrefab, World.Active);
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
SpawnUnit(15, 15);
}
}
private void SpawnUnit(float x, float z)
{
Debug.Log($"Spawning unit at x:{x} y:{z}");
var unitEntity = this._entityManager.Instantiate(this._unitEntityPrefab);
_entityManager.SetComponentData(unitEntity, new Translation {Value = new Vector3(x, 1, z)});
_entityManager.SetComponentData(unitEntity, new Rotation {Value = Quaternion.Euler(0, 0, 0)});
_entityManager.SetComponentData(unitEntity, new Health {Value = 100f});
_entityManager.SetComponentData(unitEntity, new Selected {Value = false});
}
}
Where unitPrefab is just a simple GameObject that has a Capsule on it and a, basically empty, script which is empty for now:
public class UnitScript : UnitBase
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
The thing is, as I click, the entities get created but the unit is not rendered.
Obviously I am missing something but I don't see what it is.
Any help would be much appreciated!

Unity How to Serialize Map Data

use 2019.1.8f1 ver
I've been referring to a lot of information, but an unknown error bothers me.
Error
SerializationException: Type 'UnityEngine.GameObject' in Assembly 'UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.
[System.Serializable]
public class Map
{
public Node[,] nodes;
}
[System.Serializable]
public class Node
{
public GameObject tile;
public bool walkable;
public Vector3 worldPosition;
public int gridX;
public int gridY;
public int gCost;
public int hCost;
public int FCost { get { return gCost + hCost; } }
public Node(bool _walkable, Vector3 _worldPosition, int _gridX, int _gridY)
{
walkable = _walkable;
worldPosition = _worldPosition;
gridX = _gridX;
gridY = _gridY;
}
}
[System.Serializable]
public class Grid : MonoBehaviour
{
public Node[,] grid;
public void SaveMapData()
{
Debug.Log("Save");
FileStream stream = File.Create(Application.persistentDataPath + path);
BinaryFormatter bf = new BinaryFormatter();
Map map = new Map();
map.nodes = grid.grid;
bf.Serialize(stream, map);
stream.Close();
}
Your Node class references the GameObject class here:
public GameObject tile;
The best solution would be to get rid of that reference. This would also improve the decoupling of your model from the view.
Or try the [NonSerialized] attribute as a quick fix:
[NonSerialized] public GameObject tile;