
Because I can’t even use Unity Bug Reporter, let alone use Unity to open up the project, I cannot report a really major bug I found in UNET. This major crash can easily be reproduced.
Unity compiled my just-recently-edited script which causes it to hard crash immediately, not even triggering a Unity Bug Reporter window.
The offending code lies in a structurally syntax-correct code placement. This is what I know:
- Need a [System.Serializable] struct with methods inside it.
- Use SyncListStruct, where T is the struct created.
- Make a class that inherits SyncListStruct.
- Make this class a class member variable in a NetworkBehaviour class.
- (Optional) Add an initializer to the class member where declared.
- Make the class member [UnityEngine.SerializeField].
- Add a few [Command] and [ClientRpc].
The Just-Recently-Edited script in full text is given below. To reproduce the issue, just copy/paste in a new script in an empty Unity 5.2.1f1 project, and let Unity compile:
using UnityEngine;
using UnityEngine.Networking;
using System;
using System.Collections.Generic;
[System.Serializable]
public struct SplitGroup {
public GameUnit ownerUnit;
public GameUnit splitUnit;
public float elapsedTime;
public Vector3 rotationVector;
public Vector3 origin;
public SplitGroup(GameUnit ownerUnit, GameUnit splitUnit) {
this.ownerUnit = ownerUnit;
this.splitUnit = splitUnit;
this.elapsedTime = 0f;
this.origin = ownerUnit.gameObject.transform.position;
//TODO: Add a radius where the unit will always go towards.
SpawnRange range = this.ownerUnit.GetComponentInChildren<SpawnRange>();
float angle = UnityEngine.Random.Range(-180f, 180f);
this.rotationVector = Quaternion.Euler(0f, angle, 0f) * (Vector3.one * range.radius);
//Alternately, Stop() the nav mesh agent when the split group is created. Resume() the nav mesh agent when removing the split group.
//If that doesn't work, then this is the best solution to "halt" the nav mesh agent.
NavMeshAgent agent = this.ownerUnit.GetComponent<NavMeshAgent>();
if (agent != null) {
agent.Stop();
}
agent = this.splitUnit.GetComponent<NavMeshAgent>();
if (agent != null) {
agent.Stop();
}
}
public void Update() {
Debug.Log("Updating... from Split Group ");
this.ownerUnit.isSelected = false;
this.splitUnit.isSelected = false;
Vector3 pos = Vector3.Lerp(this.origin, this.origin + this.rotationVector, this.elapsedTime);
this.ownerUnit.gameObject.transform.position = pos;
pos = Vector3.Lerp(this.origin, this.origin - this.rotationVector, this.elapsedTime);
this.splitUnit.gameObject.transform.position = pos;
}
public void Stop() {
NavMeshAgent agent = this.ownerUnit.GetComponent<NavMeshAgent>();
if (agent != null) {
agent.ResetPath();
agent.Resume();
}
agent = this.splitUnit.GetComponent<NavMeshAgent>();
if (agent != null) {
agent.ResetPath();
agent.Resume();
}
}
};
public class SyncSplitGroup : SyncListStruct<SplitGroup> {
}
public class SplitManager : NetworkBehaviour {
[SerializeField]
public SyncSplitGroup splitGroupList;
[SerializeField]
public List<SplitGroup> removeList;
[SerializeField]
public SelectionManager selectionManager;
[SerializeField]
public Spawner spawner;
public GameObject gameUnitPrefab;
//Split Manager is designed to streamline the creation of new game units.
//To achieve this, there needs to be two different array list that keeps track of all the creations, called Split Groups.
//One keeps track of the Split Groups, the other removes them from the tracking list.
public void Start() {
if (!this.hasAuthority) {
return;
}
if (this.splitGroupList == null) {
this.splitGroupList = new SyncSplitGroup();
}
if (this.selectionManager == null) {
GameObject[] managers = GameObject.FindGameObjectsWithTag("SelectionManager");
foreach (GameObject manager in managers) {
SelectionManager select = manager.GetComponent<SelectionManager>();
if (select != null && select.hasAuthority) {
this.selectionManager = select;
break;
}
}
if (this.selectionManager == null) {
Debug.LogError("Cannot find Selection Manager. Aborting");
}
}
if (this.spawner == null) {
GameObject[] spawners = GameObject.FindGameObjectsWithTag("Spawner");
foreach (GameObject obj in spawners) {
Spawner spawner = obj.GetComponent<Spawner>();
if (spawner != null && spawner.hasAuthority) {
this.spawner = spawner;
break;
}
}
if (this.spawner == null) {
Debug.LogError("Spawner is never set. Please check.");
}
}
}
public void Update() {
if (!this.hasAuthority) {
return;
}
//When the player starts the action to split a game unit into two, it takes in all the selected game units
//one by one, and splits them individually.
if (Input.GetKeyDown(KeyCode.S)) {
if (this.selectionManager != null) {
AddingNewSplitGroup();
}
}
UpdateSplitGroup();
}
private void UpdateSplitGroup() {
if (this.splitGroupList != null && this.splitGroupList.Count > 0) {
for (int i = 0; i < this.splitGroupList.Count; i++) {
Debug.Log("Updating split group...." + i);
SplitGroup group = this.splitGroupList[i];
if (group.elapsedTime > 1f) {
CmdGroupStop(i);
if (!this.selectionManager.allObjects.Contains(group.splitUnit.gameObject)) {
this.selectionManager.allObjects.Add(group.splitUnit.gameObject);
}
if (!this.selectionManager.allObjects.Contains(group.ownerUnit.gameObject)) {
this.selectionManager.allObjects.Add(group.ownerUnit.gameObject);
}
this.removeList.Add(group);
}
else {
//Some weird C# language design...
group.Update();
group.elapsedTime += Time.deltaTime / 3f;
this.splitGroupList[i] = group;
}
}
}
if (this.removeList != null && this.removeList.Count > 0) {
foreach (SplitGroup group in this.removeList) {
this.splitGroupList.Remove(group);
}
this.removeList.Clear();
}
}
private void AddingNewSplitGroup() {
foreach (GameObject obj in this.selectionManager.selectedObjects) {
CmdSplit(obj);
}
return;
}
[Command]
public void CmdSplit(GameObject obj) {
//This is profoundly one of the hardest puzzles I had tackled. Non-player object spawning non-player object.
//Instead of the usual spawning design used in the Spawner script, the spawning codes here are swapped around.
//In Spawner, you would called on NetworkServer.SpawnWithClientAuthority() in the [ClientRpc]. Here, it's in [Command].
//I am guessing it has to do with how player objects and non-player objects interact with UNET.
GameObject split = MonoBehaviour.Instantiate(this.gameUnitPrefab) as GameObject;
split.transform.position = obj.transform.position;
NetworkIdentity managerIdentity = this.GetComponent<NetworkIdentity>();
NetworkServer.SpawnWithClientAuthority(split, managerIdentity.clientAuthorityOwner);
RpcSplit(obj, split);
}
[ClientRpc]
public void RpcSplit(GameObject obj, GameObject split) {
//We do not call on NetworkServer methods here. This is used only to sync up with the original game unit for all clients.
//This includes adding the newly spawned game unit into the Selection Manager that handles keeping track of all game units.
//if (!this.hasAuthority) {
// return;
//}
GameUnit original = obj.GetComponent<GameUnit>();
GameUnit copy = split.GetComponent<GameUnit>();
Copy(original, copy);
NavMeshAgent originalAgent = obj.GetComponent<NavMeshAgent>();
originalAgent.ResetPath();
NavMeshAgent copyAgent = copy.GetComponent<NavMeshAgent>();
copyAgent.ResetPath();
this.splitGroupList.Add(new SplitGroup(original, copy));
this.selectionManager.allObjects.Add(split);
}
[Command]
public void CmdGroupStop(int i) {
RpcGroupStop(i);
}
[ClientRpc]
public void RpcGroupStop(int i) {
if (i < this.splitGroupList.Count) {
SplitGroup group = this.splitGroupList[i];
group.Stop();
}
}
private void Copy(GameUnit original, GameUnit copy) {
copy.isSelected = original.isSelected;
copy.transform.position = original.transform.position;
copy.transform.rotation = original.transform.rotation;
copy.transform.localScale = original.transform.localScale;
copy.oldTargetPosition = original.oldTargetPosition;
copy.isDirected = original.isDirected = false;
}
}
Note: The code below works normally, so I really don’t know how to SSCCE the code above:
using UnityEngine;
using UnityEngine.Networking;
using System;
using System.Collections.Generic;
[System.Serializable]
public struct SplitGroup {
public SplitGroup() {
}
};
public class SyncSplitGroup : SyncListStruct<SplitGroup> {
}
public class SplitManager : NetworkBehaviour {
[SerializeField]
public SyncSplitGroup splitGroupList = new SyncSplitGroup();
[Command]
public void CmdTest(){
RpcTest();
}
[ClientRpc]
public void RpcTest(){
}
}