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!
Related
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
I'm making an inventory system and want to use a button to move all items from under the button to a grid (which is their original transform coordinates). want to reference the coordinate to the original game object through their index, but it does not move at all. I'm not sure if the lists I have set up have nothing indexed, or the translation isn't working. what can I correct here to make this work?
public class BaseInventory : MonoBehaviour
{
public List<GameObject> myObjects;
public Vector3 rootV3;
public float delayBetweenObjects = .1f;
public float animationDuration = .1f;
public List<Vector3> baseV3 = new List<Vector3>();
public Vector3 endV3;
void SavePositions()
{
foreach(GameObject g in myObjects)
{
baseV3.Add(g.transform.position);
}
}
private void Awake()
{
Hide();
}
private void Hide()
{
foreach (GameObject g in myObjects)
{
g.SetActive(false);
}
}
public void ShowItems()
{
StartCoroutine(Show());
}
IEnumerator Show()
{
foreach (GameObject g in myObjects)
{
int i = myObjects.IndexOf(g);
endV3 = baseV3[i];
yield return new WaitForSeconds(delayBetweenObjects);
g.SetActive(true);
g.transform.DOMove(endV3, animationDuration).From();
}
}
}
Im trying to do a property drawer for a class that i need to be updated as well as editable from the editor.
If i make the class a monobehaviour the serialisation stops working, but if i remove the monobehaviour inheritance it wont update with the game loop.
Is there any way to have both? I would need the object to be able to instantiate with default (empty) values if a monobehaviour script has non instantiated reference.
[Serializable]
public class MySmallTestProp : MonoBehaviour, ISerializationCallbackReceiver
{
[SerializeField]
private string name;
[SerializeField]
private string _name;
[SerializeField]
private float _someFloat;
public float someFloat;
public MySmallTestProp()
{ }
public void OnBeforeSerialize()
{
_name = name;
}
public void OnAfterDeserialize()
{
name = _name;
}
}
[CustomPropertyDrawer(typeof(MySmallTestProp))]
public class MySmallTestPropPropertyDrawer : PropertyDrawer
{
float rowHeight;
int rowSpacing = 5;
int index;
Rect currentPosition;
public override float GetPropertyHeight(SerializedProperty prop, GUIContent label)
{
rowHeight = base.GetPropertyHeight(prop, label);
var rows = 2;
if (Application.isPlaying)
{
rows++;
}
else
{
rows++;
}
return rowHeight * rows;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
index = 1;
currentPosition = position;
EditorGUI.BeginProperty(position, label, property);
var nameProp = property.FindPropertyRelative("_name");
EditorGUI.PropertyField(NextPosition(), nameProp, new GUIContent("Name"));
EditorGUI.EndProperty();
}
Rect NextPosition()
{
currentPosition.height = rowHeight;
currentPosition.y = rowSpacing + (rowHeight + rowSpacing) * index++;
return currentPosition;
}
}
So if I understand you correctly what you want to achieve is having a class which is
Serializable
has some default field values
Receives an Update call every frame
Actually I don't think you need any custom property drawer for this.
First two points are as simple as having e.g.
[Serializable]
public class Example
{
// By default this has the value "Default String"
public string someString = "Default String";
// This can be edited only via the Inspector
// by default it is 42
[SerializeField] private float someFloat = 42.0f;
// This is a read-only public access
public float SomeFloat => someFloat;
}
Now to the last and tricky part - the update calls.
The easiest way is to have a dedicated MonoBehaviour like e.g.
public class UpdateDispatcher : MonoBehaviour
{
// Storing the instance for Singleton Pattern
private static UpdateDispatcher _instance;
// Register to this event to receive one call each Update
public static event Action OnUpdate;
// This method is automatically called by Unity when the application is started
// or you enter play mode in the editor
[RuntimeInitializeOnLoadMethod]
private static void Init()
{
// _instsnce is already assigned and alive?
if(_instance) return;
// Otherwise search for one in the scene
_instance = FindObjectOfType<UpdateDispatcher>();
// Found one?
if(_instance) return;
// Otherwise create it now
_instance = new GameObject(nameof(UpdateDispatcher)).AddComponent<UpdateDispatcher>();
}
private void Awake ()
{
// Does another instance already exist?
if(_instance && _instance != this)
{
// Destroy this one
Destroy (gameObject);
return;
}
// Otherwise assign this as the instance and make sure it isn't destroyed when the scene chsnges
_instance = this;
DontDestroyOnLoad (gameObject);
}
private void Update ()
{
// Call the event every frame if something is registered
OnUpdate?.Invoke ();
}
}
And then you can use ISerislizationCallbackReceiver but not for actually doing the serialization (it is already done automatically for the fields) but rather for registration to the update callback like e.g.
[Serializable]
public class Example, ISerializationCallbackReceiver
{
// By default this has the value "Default String"
public string someString = "Default String";
// This can be edited only vis the Inspector
// by default it is 42
[SerializeField] private float someFloat = 42.0f;
// This is a read-only public access
public float SomeFloat => someFloat;
// Nothing to do here, only needed for the interface
public void OnBeforeSerialize() { }
public void OnAfterDeserialize()
{
// Register to the Update event
// It is save to unregister before registering even if we haven't been registered before
// this makes sure we are registered only exactly once
UpdateDispatcher.OnUpdate -= Update;
UpdateDispatcher.OnUpdate += Update;
}
private void Update ()
{
someFloat += Time.deltaTime;
}
}
This answer should be more like a comment, but due to the extension I've decided to post it here.
The objective of a PropertyDrawer is to display properties differently on the editor.
To achieve that you need 2 things:
1.One class that inherits from PropertyAttribute, this will be the reference used in your future scripts.
2.Another class that inherits from PropertyDrawer, here you can type HOW to display the attribute.
One implementation example of a property drawer that shows an attribute without leting the user to edit it from editor:
using UnityEditor;
using UnityEngine;
public class DisplayWithoutEdit : PropertyAttribute
{
}
[CustomPropertyDrawer(typeof(DisplayWithoutEdit))]
public class DisplayWithoutEditDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property, label, true);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
GUI.enabled = false;
EditorGUI.PropertyField(position, property, label, true);
GUI.enabled = true;
}
}
Then you can use it on another script doing something like:
[DisplayWithoutEdit] public float randomNumber = 0f;
I am making a running game, but I face the big problem.
In the stage, Obstacles are created. But After some time, unity didn't make new Obstacle. Why does this happen?
public class GameManager : MonoBehaviour {
public float waitingTime = 1.5f;
public static GameManager manager;
public bool ready = true;
public GameObject cactus;
float time = 0;
// Use this for initialization
void Start () {
manager = this;
}
// Update is called once per frame
void Update () {
time += Time.deltaTime;
//Debug.Log(time);
if(time>2f && ready==true)
{
ready = false;
time = 0;
InvokeRepeating("MakeCactus", 1f, waitingTime);
}
}
void MakeCactus()
{
Instantiate(cactus);
}
public void GameOver()
{
//CancelInvoke("MakeCactus");
iTween.ShakePosition(Camera.main.gameObject, iTween.Hash("x", 0.2, "y", 0.2, "time", 0.5f));
}
}
You don't need Update method at all. As you are using it just to delay your spawning. Your code can be re-written like this:
public class GameManager : MonoBehaviour
{
public float waitingTime = 1.5f;
public static GameManager manager;
public GameObject cactus;
void Awake()
{
manager = this;
InvokeRepeating("MakeCactus", 3f, waitingTime);
}
void MakeCactus()
{
Instantiate(cactus);
}
public void GameOver()
{
//CancelInvoke("MakeCactus");
iTween.ShakePosition(Camera.main.gameObject, iTween.Hash("x", 0.2, "y", 0.2, "time", 0.5f));
}
}
Hopefully this will solve the problem
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