Cant destroy object

I have a boss object that i instantiate with the following code:
using Normal.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpawnBoss : MonoBehaviour
{
#region Variables
[Header("Variables")]
[SerializeField] private Transform spawnPoint;
public string bossName;


#endregion

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
Realtime.Instantiate(bossName, spawnPoint.position, spawnPoint.rotation);
}
}
}
using Normal.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpawnBoss : MonoBehaviour
{
#region Variables
[Header("Variables")]
[SerializeField] private Transform spawnPoint;
public string bossName;


#endregion

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
Realtime.Instantiate(bossName, spawnPoint.position, spawnPoint.rotation);
}
}
}
But when i try to destroy it with a Realtime.Destroy i get the error in the attached image
No description
44 Replies
kapa.ai
kapa.ai3w ago
Hi @✨Mike the Floof Wizard✨. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on your code and the error you're encountering when trying to destroy a boss object instantiated with Normcore, I can help troubleshoot this issue. The error likely occurs because you're trying to destroy an object that wasn't properly instantiated with Realtime.Instantiate() or you're not referencing the object correctly when calling Realtime.Destroy(). Here are some potential solutions: 1. Make sure you're storing a reference to the instantiated boss object:
GameObject bossInstance = Realtime.Instantiate(bossName, spawnPoint.position, spawnPoint.rotation);
GameObject bossInstance = Realtime.Instantiate(bossName, spawnPoint.position, spawnPoint.rotation);
2. When destroying the object, use that reference:
Realtime.Destroy(bossInstance);
Realtime.Destroy(bossInstance);
Alternatively, you can destroy it using the RealtimeView component:
Realtime.Destroy(bossInstance.GetComponent<RealtimeView>());
Realtime.Destroy(bossInstance.GetComponent<RealtimeView>());
According to the Normcore documentation, Realtime.Destroy() must be used instead of Unity's GameObject.Destroy() for objects instantiated with Realtime.Instantiate() Realtime Class Reference. Also, make sure that: - Your boss prefab has a RealtimeView component on its root GameObject - The prefab is located in a Resources folder so it can be found by all clients - You're not trying to destroy the object from a client that doesn't have ownership of it If you're still encountering issues, you might need to check ownership of the object before destroying it:
RealtimeView view = bossInstance.GetComponent<RealtimeView>();
if (view.isOwnedLocally) {
Realtime.Destroy(bossInstance);
}
RealtimeView view = bossInstance.GetComponent<RealtimeView>();
if (view.isOwnedLocally) {
Realtime.Destroy(bossInstance);
}
This approach is recommended to prevent multiple clients from trying to destroy the same object simultaneously, which can cause errors like "Server attempted to delete value from set for key that doesn't exist in storage" Common Questions. Unity: 2021.3.16f1 Normcore: 2.1 ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant I am now spawning it correctly from one script. But when i try to destroy it in another script i get a nullreferenceexception
using Normal.Realtime;
using UnityEngine;


public class BossHealth : RealtimeComponent<BossMoveModel>
{
[SerializeField] private int maxHealth;
[SerializeField] private int currentHealth;
private SpawnBoss spawner;


private void Start()
{
//model.currentHealth = model.maxHealth;
currentHealth = maxHealth;


}

void Update()
{
if (currentHealth <= 0)
{
//gameObject.SetActive(false);
Realtime.Destroy(spawner.bossInstance);
Realtime.Destroy(spawner.bossBodyInstance);
}

/*
if (model != null)
{

}*/
}

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
currentHealth--;
}
}
}
using Normal.Realtime;
using UnityEngine;


public class BossHealth : RealtimeComponent<BossMoveModel>
{
[SerializeField] private int maxHealth;
[SerializeField] private int currentHealth;
private SpawnBoss spawner;


private void Start()
{
//model.currentHealth = model.maxHealth;
currentHealth = maxHealth;


}

void Update()
{
if (currentHealth <= 0)
{
//gameObject.SetActive(false);
Realtime.Destroy(spawner.bossInstance);
Realtime.Destroy(spawner.bossBodyInstance);
}

/*
if (model != null)
{

}*/
}

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
currentHealth--;
}
}
}
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I can see why you're getting a NullReferenceException in your BossHealth script. The issue is that you're trying to access spawner.bossInstance and spawner.bossBodyInstance, but the spawner variable is never initialized in your code. Here are a few ways to fix this: Option 1: Find the SpawnBoss reference You need to initialize the spawner variable in your Start method:
private void Start()
{
currentHealth = maxHealth;

// Find the SpawnBoss component in the scene
spawner = FindObjectOfType<SpawnBoss>();

// Alternatively, if there are multiple SpawnBoss components:
// spawner = GameObject.Find("SpawnerObjectName").GetComponent<SpawnBoss>();
}
private void Start()
{
currentHealth = maxHealth;

// Find the SpawnBoss component in the scene
spawner = FindObjectOfType<SpawnBoss>();

// Alternatively, if there are multiple SpawnBoss components:
// spawner = GameObject.Find("SpawnerObjectName").GetComponent<SpawnBoss>();
}
Option 2: Use the current GameObject If you don't need to reference the spawner, you can simply destroy the current GameObject:
if (currentHealth <= 0)
{
Realtime.Destroy(gameObject);
}
if (currentHealth <= 0)
{
Realtime.Destroy(gameObject);
}
Option 3: Pass the reference when instantiating A better approach would be to modify your SpawnBoss script to set a reference to the boss instance on the BossHealth component after instantiation:
// In SpawnBoss.cs
public GameObject bossInstance;
public GameObject bossBodyInstance;

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
bossInstance = Realtime.Instantiate(bossName, spawnPoint.position, spawnPoint.rotation);

// Get the BossHealth component and set a reference to this spawner
BossHealth bossHealth = bossInstance.GetComponent<BossHealth>();
if (bossHealth != null)
{
bossHealth.SetSpawner(this);
}
}
}
// In SpawnBoss.cs
public GameObject bossInstance;
public GameObject bossBodyInstance;

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
bossInstance = Realtime.Instantiate(bossName, spawnPoint.position, spawnPoint.rotation);

// Get the BossHealth component and set a reference to this spawner
BossHealth bossHealth = bossInstance.GetComponent<BossHealth>();
if (bossHealth != null)
{
bossHealth.SetSpawner(this);
}
}
}
Then in your BossHealth script:
public void SetSpawner(SpawnBoss spawnerRef)
{
spawner = spawnerRef;
}
public void SetSpawner(SpawnBoss spawnerRef)
{
spawner = spawnerRef;
}
Additional Notes 1. Since your BossHealth inherits from RealtimeComponent, you should be using the model to synchronize health across clients RealtimeComponent. Your commented code suggests you were working on this. 2. Make sure your BossHealth script is attached to a GameObject that has a RealtimeView component, and that the RealtimeView lists BossHealth in its components section RealtimeView. 3. If you're still having issues, check if your custom RealtimeComponent is properly syncing by ensuring OnRealtimeModelReplaced is being called Synchronizing Custom Data. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant I tried using my commented code for managing the bosshealth but i am still getting the same nullreference exception
using Normal.Realtime;
using UnityEngine;


public class BossHealth : RealtimeComponent<BossMoveModel>
{
[SerializeField] private int maxHealth;
[SerializeField] private int currentHealth;
private SpawnBoss spawner;


private void Start()
{
model.currentHealth = model.maxHealth;
//currentHealth = maxHealth;
spawner = GetComponent<SpawnBoss>();

}

void Update()
{
if (model.currentHealth <= 0)
{
//gameObject.SetActive(false);
Realtime.Destroy(spawner.bossInstance);
Realtime.Destroy(spawner.bossBodyInstance);
}

/*
if (model != null)
{

}*/
}

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
model.currentHealth--;
}
}

public void SetSpawner(SpawnBoss spawnerRef)
{
spawner = spawnerRef;
}
}
using Normal.Realtime;
using UnityEngine;


public class BossHealth : RealtimeComponent<BossMoveModel>
{
[SerializeField] private int maxHealth;
[SerializeField] private int currentHealth;
private SpawnBoss spawner;


private void Start()
{
model.currentHealth = model.maxHealth;
//currentHealth = maxHealth;
spawner = GetComponent<SpawnBoss>();

}

void Update()
{
if (model.currentHealth <= 0)
{
//gameObject.SetActive(false);
Realtime.Destroy(spawner.bossInstance);
Realtime.Destroy(spawner.bossBodyInstance);
}

/*
if (model != null)
{

}*/
}

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
model.currentHealth--;
}
}

public void SetSpawner(SpawnBoss spawnerRef)
{
spawner = spawnerRef;
}
}
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I can see a few issues in your code that are likely causing the NullReferenceException: Issue 1: Model Access You're trying to access model.currentHealth and model.maxHealth in your Start method, but the model might not be initialized yet. When using RealtimeComponents, the model is set by Realtime when you connect to the room, and it might not be available in Start(). Issue 2: SpawnBoss Component You're trying to get the SpawnBoss component from the same GameObject that has the BossHealth component:
spawner = GetComponent<SpawnBoss>();
spawner = GetComponent<SpawnBoss>();
But it's likely that the SpawnBoss component is on a different GameObject, not on the boss itself. Solution: Here's how to fix these issues: 1. Use the OnRealtimeModelReplaced method to initialize your model values:
protected override void OnRealtimeModelReplaced(BossMoveModel previousModel, BossMoveModel currentModel)
{
if (currentModel != null)
{
// Only set initial values if this is a fresh model
if (currentModel.isFreshModel)
{
currentModel.maxHealth = maxHealth;
currentModel.currentHealth = maxHealth;
}
}
}
protected override void OnRealtimeModelReplaced(BossMoveModel previousModel, BossMoveModel currentModel)
{
if (currentModel != null)
{
// Only set initial values if this is a fresh model
if (currentModel.isFreshModel)
{
currentModel.maxHealth = maxHealth;
currentModel.currentHealth = maxHealth;
}
}
}
2. Make sure your BossMoveModel has the necessary properties:
[RealtimeModel]
public partial class BossMoveModel
{
[RealtimeProperty(1, true)]
private int _maxHealth;

[RealtimeProperty(2, true)]
private int _currentHealth;
}
[RealtimeModel]
public partial class BossMoveModel
{
[RealtimeProperty(1, true)]
private int _maxHealth;

[RealtimeProperty(2, true)]
private int _currentHealth;
}
3. Use the SetSpawner method you've already created, but make sure it's called from the SpawnBoss script after instantiation:
// In your SpawnBoss.cs
public GameObject bossInstance;
public GameObject bossBodyInstance;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
bossInstance = Realtime.Instantiate(bossName, spawnPoint.position, spawnPoint.rotation);

// Get the BossHealth component and set a reference to this spawner
BossHealth bossHealth = bossInstance.GetComponent<BossHealth>();
if (bossHealth != null)
{
bossHealth.SetSpawner(this);
}
}
}
// In your SpawnBoss.cs
public GameObject bossInstance;
public GameObject bossBodyInstance;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
bossInstance = Realtime.Instantiate(bossName, spawnPoint.position, spawnPoint.rotation);

// Get the BossHealth component and set a reference to this spawner
BossHealth bossHealth = bossInstance.GetComponent<BossHealth>();
if (bossHealth != null)
{
bossHealth.SetSpawner(this);
}
}
}
4. Add a null check before trying to access the spawner:
void Update()
{
if (model != null && model.currentHealth <= 0 && spawner != null)
{
Realtime.Destroy(spawner.bossInstance);
Realtime.Destroy(spawner.bossBodyInstance);
}
}
void Update()
{
if (model != null && model.currentHealth <= 0 && spawner != null)
{
Realtime.Destroy(spawner.bossInstance);
Realtime.Destroy(spawner.bossBodyInstance);
}
}
According to the Normcore documentation, when using RealtimeComponents, you should wait for the model to be set before accessing it Common Questions. The model is set by Realtime when you connect to the room, and it might not be available in Start(). Also, make sure your BossHealth component is properly registered with a RealtimeView on your boss prefab Synchronizing Custom Data. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant i am still getting the same nullreference exception. At line 25 of my BossHealth script. Somehow the script cant find my bossInstance
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I can see that you're still having issues with the NullReferenceException at line 25 of your BossHealth script, which is likely the line where you're trying to access spawner.bossInstance. The issue is that your spawner variable is still null when you try to access it. Let's troubleshoot this step by step: The Problem Looking at your code, there are two possible causes for the NullReferenceException: 1. The spawner variable is null 2. The spawner variable is not null, but spawner.bossInstance is null Solutions 1. Verify the SetSpawner method is being called Make sure your SpawnBoss script is actually calling the SetSpawner method after instantiating the boss:
// In SpawnBoss.cs
public GameObject bossInstance;
public GameObject bossBodyInstance;

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
bossInstance = Realtime.Instantiate(bossName, spawnPoint.position, spawnPoint.rotation);

// Get the BossHealth component and set a reference to this spawner
BossHealth bossHealth = bossInstance.GetComponent<BossHealth>();
if (bossHealth != null)
{
bossHealth.SetSpawner(this);
}
else
{
Debug.LogError("BossHealth component not found on instantiated boss!");
}
}
}
// In SpawnBoss.cs
public GameObject bossInstance;
public GameObject bossBodyInstance;

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
bossInstance = Realtime.Instantiate(bossName, spawnPoint.position, spawnPoint.rotation);

// Get the BossHealth component and set a reference to this spawner
BossHealth bossHealth = bossInstance.GetComponent<BossHealth>();
if (bossHealth != null)
{
bossHealth.SetSpawner(this);
}
else
{
Debug.LogError("BossHealth component not found on instantiated boss!");
}
}
}
2. Add null checks in your BossHealth script Add thorough null checks to prevent the exception:
void Update()
{
if (model != null && model.currentHealth <= 0)
{
if (spawner != null)
{
if (spawner.bossInstance != null)
Realtime.Destroy(spawner.bossInstance);

if (spawner.bossBodyInstance != null)
Realtime.Destroy(spawner.bossBodyInstance);
}
else
{
// If spawner reference is missing, just destroy this gameObject
Realtime.Destroy(gameObject);
}
}
}
void Update()
{
if (model != null && model.currentHealth <= 0)
{
if (spawner != null)
{
if (spawner.bossInstance != null)
Realtime.Destroy(spawner.bossInstance);

if (spawner.bossBodyInstance != null)
Realtime.Destroy(spawner.bossBodyInstance);
}
else
{
// If spawner reference is missing, just destroy this gameObject
Realtime.Destroy(gameObject);
}
}
}
3. Alternative approach: Use direct GameObject reference Instead of relying on the spawner reference, you could simply destroy the current GameObject:
void Update()
{
if (model != null && model.currentHealth <= 0)
{
Realtime.Destroy(gameObject);
}
}
void Update()
{
if (model != null && model.currentHealth <= 0)
{
Realtime.Destroy(gameObject);
}
}
4. Check for RealtimeView issues According to the Normcore documentation, you might be encountering issues with your RealtimeComponent not being properly registered with a RealtimeView Synchronizing Custom Data. Make sure: - Your boss prefab has a RealtimeView component - The BossHealth component is listed in the RealtimeView's components list - If it's not listed, try removing and re-adding the BossHealth component 5. Debug with logging Add debug logs to help identify where the issue is occurring:
void Start()
{
Debug.Log("BossHealth Start called");
// Your existing code
}

public void SetSpawner(SpawnBoss spawnerRef)
{
Debug.Log("SetSpawner called with: " + (spawnerRef != null ? "valid spawner" : "null spawner"));
spawner = spawnerRef;
}
void Update()
{
if (model != null && model.currentHealth <= 0)
{
Debug.Log("Health <= 0, spawner is: " + (spawner != null ? "not null" : "null"));
// Your existing code
}
}
void Start()
{
Debug.Log("BossHealth Start called");
// Your existing code
}

public void SetSpawner(SpawnBoss spawnerRef)
{
Debug.Log("SetSpawner called with: " + (spawnerRef != null ? "valid spawner" : "null spawner"));
spawner = spawnerRef;
}
void Update()
{
if (model != null && model.currentHealth <= 0)
{
Debug.Log("Health <= 0, spawner is: " + (spawner != null ? "not null" : "null"));
// Your existing code
}
}
These debugging steps should help you identify exactly where the issue is occurring and fix the NullReferenceException. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant My script is not finding the bosshealth component and i am also getting the error in the attached image
using Normal.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpawnBoss : MonoBehaviour
{
#region Variables
[Header("Variables")]
[SerializeField] private Transform spawnPoint;
[SerializeField] private float waitTime;
public string bossName;
public string bodyName;

public GameObject bossInstance;
public GameObject bossBodyInstance;


#endregion

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
bossInstance = Realtime.Instantiate(bossName, spawnPoint.position, spawnPoint.rotation);
StartCoroutine(SpawnBody());

BossHealth bossHealth = bossBodyInstance.GetComponent<BossHealth>();
if (bossHealth != null)
{
bossHealth.SetSpawner(this);
}
else
{
Debug.LogError("BossHealth component not found on instantiated boss!");
}
}
}

IEnumerator SpawnBody()
{
yield return new WaitForSeconds(waitTime);
bossBodyInstance = Realtime.Instantiate(bodyName, spawnPoint.position, spawnPoint.rotation);
bossBodyInstance.transform.parent = bossInstance.transform;
bossBodyInstance.transform.localPosition = Vector3.zero;
bossBodyInstance.transform.localScale = Vector3.one;
}
}
using Normal.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpawnBoss : MonoBehaviour
{
#region Variables
[Header("Variables")]
[SerializeField] private Transform spawnPoint;
[SerializeField] private float waitTime;
public string bossName;
public string bodyName;

public GameObject bossInstance;
public GameObject bossBodyInstance;


#endregion

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
bossInstance = Realtime.Instantiate(bossName, spawnPoint.position, spawnPoint.rotation);
StartCoroutine(SpawnBody());

BossHealth bossHealth = bossBodyInstance.GetComponent<BossHealth>();
if (bossHealth != null)
{
bossHealth.SetSpawner(this);
}
else
{
Debug.LogError("BossHealth component not found on instantiated boss!");
}
}
}

IEnumerator SpawnBody()
{
yield return new WaitForSeconds(waitTime);
bossBodyInstance = Realtime.Instantiate(bodyName, spawnPoint.position, spawnPoint.rotation);
bossBodyInstance.transform.parent = bossInstance.transform;
bossBodyInstance.transform.localPosition = Vector3.zero;
bossBodyInstance.transform.localScale = Vector3.one;
}
}
No description
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I can see the issue in your SpawnBoss script. The problem is with the timing of when you're trying to access the BossHealth component. The Issue You're trying to get the BossHealth component from bossBodyInstance immediately after instantiating bossInstance, but before bossBodyInstance is actually created. Looking at your code: 1. You instantiate bossInstance first 2. You start a coroutine to spawn bossBodyInstance after a delay (waitTime) 3. You immediately try to access bossHealth = bossBodyInstance.GetComponent<BossHealth>() before the coroutine has completed Since bossBodyInstance is null at this point (it hasn't been instantiated yet), you get a NullReferenceException. Solution Move the BossHealth component check inside the coroutine, after bossBodyInstance has been instantiated:
IEnumerator SpawnBody()
{
yield return new WaitForSeconds(waitTime);
bossBodyInstance = Realtime.Instantiate(bodyName, spawnPoint.position, spawnPoint.rotation);
bossBodyInstance.transform.parent = bossInstance.transform;
bossBodyInstance.transform.localPosition = Vector3.zero;
bossBodyInstance.transform.localScale = Vector3.one;

// Now that bossBodyInstance exists, we can get the BossHealth component
BossHealth bossHealth = bossBodyInstance.GetComponent<BossHealth>();
if (bossHealth != null)
{
bossHealth.SetSpawner(this);
}
else
{
Debug.LogError("BossHealth component not found on instantiated boss body!");
}
}
IEnumerator SpawnBody()
{
yield return new WaitForSeconds(waitTime);
bossBodyInstance = Realtime.Instantiate(bodyName, spawnPoint.position, spawnPoint.rotation);
bossBodyInstance.transform.parent = bossInstance.transform;
bossBodyInstance.transform.localPosition = Vector3.zero;
bossBodyInstance.transform.localScale = Vector3.one;

// Now that bossBodyInstance exists, we can get the BossHealth component
BossHealth bossHealth = bossBodyInstance.GetComponent<BossHealth>();
if (bossHealth != null)
{
bossHealth.SetSpawner(this);
}
else
{
Debug.LogError("BossHealth component not found on instantiated boss body!");
}
}
Additional Notes 1. According to the Normcore documentation, you should be careful when parenting objects with RealtimeViews. The documentation states: "We generally recommend that you don't [reparent]. Normcore will not synchronize parent relationships between objects." Common Questions 2. Make sure your BossHealth component is properly registered with a RealtimeView on your boss body prefab. If it's not showing up in the RealtimeView's components list, try removing and re-adding the BossHealth component Synchronizing Custom Data. 3. If you're still having issues, check if the BossHealth component is on the bossBodyInstance prefab or if it's actually on the bossInstance prefab. Your code assumes it's on the body, but it might be on the main boss object. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant The destroy works now, but my bossBodyInstance is an empty with 4 children attached to it. If i hit 1 of these children everything gets destroyed, how do i give each child a seperate health bar
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I understand you want to implement separate health bars for each child of your bossBodyInstance in your NormalVR project. Let's address this using Normcore's components. Creating Separate Health for Children You have a few options to implement separate health for each child of your bossBodyInstance: Option 1: Add BossHealth Component to Each Child You can add a separate BossHealth component to each child object:
// Modify your BossHealth script to handle just one object
public class BossHealth : RealtimeComponent<BossMoveModel>
{
[SerializeField] private int maxHealth;
private SpawnBoss spawner;

protected override void OnRealtimeModelReplaced(BossMoveModel previousModel, BossMoveModel currentModel)
{
if (currentModel != null && currentModel.isFreshModel)
{
currentModel.maxHealth = maxHealth;
currentModel.currentHealth = maxHealth;
}
}

void Update()
{
if (model != null && model.currentHealth <= 0)
{
// Destroy just this part
Realtime.Destroy(gameObject);
}
}

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
model.currentHealth--;
}
}

public void SetSpawner(SpawnBoss spawnerRef)
{
spawner = spawnerRef;
}
}
// Modify your BossHealth script to handle just one object
public class BossHealth : RealtimeComponent<BossMoveModel>
{
[SerializeField] private int maxHealth;
private SpawnBoss spawner;

protected override void OnRealtimeModelReplaced(BossMoveModel previousModel, BossMoveModel currentModel)
{
if (currentModel != null && currentModel.isFreshModel)
{
currentModel.maxHealth = maxHealth;
currentModel.currentHealth = maxHealth;
}
}

void Update()
{
if (model != null && model.currentHealth <= 0)
{
// Destroy just this part
Realtime.Destroy(gameObject);
}
}

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
model.currentHealth--;
}
}

public void SetSpawner(SpawnBoss spawnerRef)
{
spawner = spawnerRef;
}
}
Option 2: Create a Manager Script Create a manager script on the parent that tracks the health of all children:
public class BossPartsManager : RealtimeComponent<BossPartsModel>
{
[SerializeField] private List<BossPartHealth> bossParts = new List<BossPartHealth>();
private SpawnBoss spawner;
protected override void OnRealtimeModelReplaced(BossPartsModel previousModel, BossPartsModel currentModel)
{
// Initialize model if needed
}

void Start()
{
// Find all child parts
bossParts.AddRange(GetComponentsInChildren<BossPartHealth>());
}

void Update()
{
// Check if all parts are destroyed
bool allPartsDestroyed = true;
foreach (var part in bossParts)
{
if (part != null && part.IsAlive())
{
allPartsDestroyed = false;
break;
}
}

if (allPartsDestroyed && spawner != null)
{
// All parts destroyed, destroy the whole boss
Realtime.Destroy(spawner.bossInstance);
}
}

public void SetSpawner(SpawnBoss spawnerRef)
{
spawner = spawnerRef;
}
}
public class BossPartsManager : RealtimeComponent<BossPartsModel>
{
[SerializeField] private List<BossPartHealth> bossParts = new List<BossPartHealth>();
private SpawnBoss spawner;
protected override void OnRealtimeModelReplaced(BossPartsModel previousModel, BossPartsModel currentModel)
{
// Initialize model if needed
}

void Start()
{
// Find all child parts
bossParts.AddRange(GetComponentsInChildren<BossPartHealth>());
}

void Update()
{
// Check if all parts are destroyed
bool allPartsDestroyed = true;
foreach (var part in bossParts)
{
if (part != null && part.IsAlive())
{
allPartsDestroyed = false;
break;
}
}

if (allPartsDestroyed && spawner != null)
{
// All parts destroyed, destroy the whole boss
Realtime.Destroy(spawner.bossInstance);
}
}

public void SetSpawner(SpawnBoss spawnerRef)
{
spawner = spawnerRef;
}
}
Then create a part-specific health script:
public class BossPartHealth : RealtimeComponent<BossPartModel>
{
[SerializeField] private int maxHealth;

protected override void OnRealtimeModelReplaced(BossPartModel previousModel, BossPartModel currentModel)
{
if (currentModel != null && currentModel.isFreshModel)
{
currentModel.maxHealth = maxHealth;
currentModel.currentHealth = maxHealth;
}
}

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
model.currentHealth--;

if (model.currentHealth <= 0)
{
// Destroy just this part
Realtime.Destroy(gameObject);
}
}
}

public bool IsAlive()
{
return model != null && model.currentHealth > 0;
}
}
public class BossPartHealth : RealtimeComponent<BossPartModel>
{
[SerializeField] private int maxHealth;

protected override void OnRealtimeModelReplaced(BossPartModel previousModel, BossPartModel currentModel)
{
if (currentModel != null && currentModel.isFreshModel)
{
currentModel.maxHealth = maxHealth;
currentModel.currentHealth = maxHealth;
}
}

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
model.currentHealth--;

if (model.currentHealth <= 0)
{
// Destroy just this part
Realtime.Destroy(gameObject);
}
}
}

public bool IsAlive()
{
return model != null && model.currentHealth > 0;
}
}
Important Considerations 1. RealtimeView Components: Each child object that has a health component needs its own RealtimeView component RealtimeView. 2. Model Registration: Make sure your custom RealtimeComponents are properly registered with their respective RealtimeViews. If you don't see your component in the RealtimeView's components list, try removing and re-adding it Synchronizing Custom Data. 3. Ownership: Be mindful of ownership when destroying objects. You may need to request ownership before destroying Ownership and Lifetime Flags. 4. Parent-Child Relationships: Remember that Normcore doesn't automatically synchronize parent-child relationships between objects with RealtimeViews. You'll need to handle this manually if needed. 5. Model Implementation: Don't forget to create the appropriate model classes (BossPartModel, BossPartsModel) with the necessary RealtimeProperty attributes for health values. By implementing one of these approaches, you should be able to give each child of your bossBodyInstance its own health system while still maintaining the overall boss structure. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant I see you are using a BossPartsModel in the manager script. Can you show me what BossPartsModel looks like
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I'd be happy to show you what a BossPartsModel might look like for your NormalVR project. Here's an example implementation of the BossPartsModel class:
using Normal.Realtime.Serialization;
using UnityEngine;

[RealtimeModel]
public partial class BossPartsModel
{
// A RealtimeArray to track all the part IDs
[RealtimeProperty(1, true)]
private RealtimeArray<int> _partIDs;

// A property to track if all parts are destroyed
[RealtimeProperty(2, true)]
private bool _allPartsDestroyed;

// Optional: track total number of parts for progress indication
[RealtimeProperty(3, true)]
private int _totalParts;

// Optional: track remaining parts
[RealtimeProperty(4, true)]
private int _remainingParts;
}

/* ----- Begin Normal Autogenerated Code ----- */
/* Your autogenerated model code will appear here when compiled. */
/* ----- End Normal Autogenerated Code ----- */
using Normal.Realtime.Serialization;
using UnityEngine;

[RealtimeModel]
public partial class BossPartsModel
{
// A RealtimeArray to track all the part IDs
[RealtimeProperty(1, true)]
private RealtimeArray<int> _partIDs;

// A property to track if all parts are destroyed
[RealtimeProperty(2, true)]
private bool _allPartsDestroyed;

// Optional: track total number of parts for progress indication
[RealtimeProperty(3, true)]
private int _totalParts;

// Optional: track remaining parts
[RealtimeProperty(4, true)]
private int _remainingParts;
}

/* ----- Begin Normal Autogenerated Code ----- */
/* Your autogenerated model code will appear here when compiled. */
/* ----- End Normal Autogenerated Code ----- */
This model uses a RealtimeArray to track the IDs of all boss parts. The model also includes properties to track whether all parts are destroyed and optionally the total and remaining part counts. After creating this class, you'll need to compile the model in Unity. The model compiler will generate the necessary code to synchronize this data across all clients. For the individual boss parts, you would also need a BossPartModel:
using UnityEngine;

[RealtimeModel]
public partial class BossPartModel
{
[RealtimeProperty(1, true)]
private int _maxHealth;

[RealtimeProperty(2, true)]
private int _currentHealth;

[RealtimeProperty(3, true)]
private int _partID;
}
using UnityEngine;

[RealtimeModel]
public partial class BossPartModel
{
[RealtimeProperty(1, true)]
private int _maxHealth;

[RealtimeProperty(2, true)]
private int _currentHealth;

[RealtimeProperty(3, true)]
private int _partID;
}
Remember that when using RealtimeModels, you need to: 1. Add the [RealtimeModel] attribute to your class 2. Make the class partial 3. Add [RealtimeProperty] attributes to each property you want to synchronize 4. Compile the model in Unity This approach follows the pattern shown in the Synchronizing Custom Data documentation. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant Which of these 2 models would i use for this script?
using Normal.Realtime;
using System.Collections.Generic;
using UnityEngine;

public class BossPartsManager : RealtimeComponent<BossPartsModel>
{
[SerializeField] private List<BossPartHealth> bossParts = new List<BossPartHealth>();
private SpawnBoss spawner;
protected override void OnRealtimeModelReplaced(BossPartsModel previousModel, BossPartsModel currentModel)
{
// Initialize model if needed
}

void Start()
{
// Find all child parts
bossParts.AddRange(GetComponentsInChildren<BossPartHealth>());
}

void Update()
{
// Check if all parts are destroyed
bool allPartsDestroyed = true;
foreach (var part in bossParts)
{
if (part != null && part.IsAlive())
{
allPartsDestroyed = false;
break;
}
}

if (allPartsDestroyed && spawner != null)
{
// All parts destroyed, destroy the whole boss
Realtime.Destroy(spawner.bossInstance);
}
}

public void SetSpawner(SpawnBoss spawnerRef)
{
spawner = spawnerRef;
}
}
Then create a part-specific health script:
csharp
public class BossPartHealth : RealtimeComponent<BossPartModel>
{
[SerializeField] private int maxHealth;

protected override void OnRealtimeModelReplaced(BossPartModel previousModel, BossPartModel currentModel)
{
if (currentModel != null && currentModel.isFreshModel)
{
currentModel.maxHealth = maxHealth;
currentModel.currentHealth = maxHealth;
}
}

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
model.currentHealth--;

if (model.currentHealth <= 0)
{
// Destroy just this part
Realtime.Destroy(gameObject);
}
}
}

public bool IsAlive()
{
return model != null && model.currentHealth > 0;
}
}
using Normal.Realtime;
using System.Collections.Generic;
using UnityEngine;

public class BossPartsManager : RealtimeComponent<BossPartsModel>
{
[SerializeField] private List<BossPartHealth> bossParts = new List<BossPartHealth>();
private SpawnBoss spawner;
protected override void OnRealtimeModelReplaced(BossPartsModel previousModel, BossPartsModel currentModel)
{
// Initialize model if needed
}

void Start()
{
// Find all child parts
bossParts.AddRange(GetComponentsInChildren<BossPartHealth>());
}

void Update()
{
// Check if all parts are destroyed
bool allPartsDestroyed = true;
foreach (var part in bossParts)
{
if (part != null && part.IsAlive())
{
allPartsDestroyed = false;
break;
}
}

if (allPartsDestroyed && spawner != null)
{
// All parts destroyed, destroy the whole boss
Realtime.Destroy(spawner.bossInstance);
}
}

public void SetSpawner(SpawnBoss spawnerRef)
{
spawner = spawnerRef;
}
}
Then create a part-specific health script:
csharp
public class BossPartHealth : RealtimeComponent<BossPartModel>
{
[SerializeField] private int maxHealth;

protected override void OnRealtimeModelReplaced(BossPartModel previousModel, BossPartModel currentModel)
{
if (currentModel != null && currentModel.isFreshModel)
{
currentModel.maxHealth = maxHealth;
currentModel.currentHealth = maxHealth;
}
}

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
model.currentHealth--;

if (model.currentHealth <= 0)
{
// Destroy just this part
Realtime.Destroy(gameObject);
}
}
}

public bool IsAlive()
{
return model != null && model.currentHealth > 0;
}
}
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- For your BossPartsManager script, you would use the BossPartsModel that I showed you previously:
[RealtimeModel]
public partial class BossPartsModel
{
// A RealtimeArray to track all the part IDs
[RealtimeProperty(1, true)]
private RealtimeArray<int> _partIDs;

// A property to track if all parts are destroyed
[RealtimeProperty(2, true)]
private bool _allPartsDestroyed;

// Optional: track total number of parts for progress indication
[RealtimeProperty(3, true)]
private int _totalParts;

// Optional: track remaining parts
[RealtimeProperty(4, true)]
private int _remainingParts;
}
[RealtimeModel]
public partial class BossPartsModel
{
// A RealtimeArray to track all the part IDs
[RealtimeProperty(1, true)]
private RealtimeArray<int> _partIDs;

// A property to track if all parts are destroyed
[RealtimeProperty(2, true)]
private bool _allPartsDestroyed;

// Optional: track total number of parts for progress indication
[RealtimeProperty(3, true)]
private int _totalParts;

// Optional: track remaining parts
[RealtimeProperty(4, true)]
private int _remainingParts;
}
And for your BossPartHealth script, you would use the BossPartModel:
[RealtimeModel]
public partial class BossPartModel
{
[RealtimeProperty(1, true)]
private int _maxHealth;

[RealtimeProperty(2, true)]
private int _currentHealth;

[RealtimeProperty(3, true)]
private int _partID;
}
[RealtimeModel]
public partial class BossPartModel
{
[RealtimeProperty(1, true)]
private int _maxHealth;

[RealtimeProperty(2, true)]
private int _currentHealth;

[RealtimeProperty(3, true)]
private int _partID;
}
These models follow Normcore's RealtimeModel pattern where: 1. Each model is marked with the [RealtimeModel] attribute 2. Each property you want to synchronize is marked with [RealtimeProperty] 3. The classes are declared as partial to allow Normcore to generate the networking code Remember that after creating these model classes, you'll need to compile them in Unity. In Unity 2021+, this happens automatically. In older versions, you'll need to manually compile the models by highlighting the class and clicking "Compile Model" Room > RealtimeModel. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant I get an Duplicate 'RealtimeModel' attribute error on my BossPartModel
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I can help you with that duplicate 'RealtimeModel' attribute error. This is a common issue when working with Normcore models. The error occurs when you have the [RealtimeModel] attribute applied twice to the same class. This typically happens in one of these scenarios: 1. You've manually added the attribute twice in your code 2. You're using Unity 2021+ where Normcore automatically generates model code, but you're also trying to manually compile the model Here's how to fix it: 1. First, make sure you only have the [RealtimeModel] attribute once at the top of your BossPartModel class:
[RealtimeModel]
public partial class BossPartModel
{
[RealtimeProperty(1, true)]
private int _maxHealth;

[RealtimeProperty(2, true)]
private int _currentHealth;

[RealtimeProperty(3, true)]
private int _partID;
}
[RealtimeModel]
public partial class BossPartModel
{
[RealtimeProperty(1, true)]
private int _maxHealth;

[RealtimeProperty(2, true)]
private int _currentHealth;

[RealtimeProperty(3, true)]
private int _partID;
}
2. If you're using Unity 2021+, Normcore automatically generates the model code for you. You don't need to manually compile the model or add any autogenerated code sections Synchronizing Custom Data. 3. If you're using Unity 2019/2020, make sure you're not adding the autogenerated code manually. The model compiler will add it for you when you click "Compile Model" in the inspector Synchronizing Custom Data. 4. Check if you have any duplicate model files in your project. Sometimes this happens if you've copied a script and forgotten to rename it. After making these changes, try recompiling your project. If you're still having issues, you might need to restart Unity to clear any cached compilation errors. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant my bossParts model gets the following error: The type 'int' cannot be used as type parameter 'TValue' in the generic type or method 'RealtimeArray<TValue>'. There is no boxing conversion from 'int' to 'Normal.Realtime.Serialization.IModel'.
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I can see the issue with your BossPartsModel. The error occurs because RealtimeArray can only store RealtimeModel objects, not primitive types like integers. According to the Normcore documentation, RealtimeArray is "a special model type that represents an unordered collection of models" RealtimeArray<TValue>. This means you can't directly store primitive types like int in a RealtimeArray. Here's how to fix your BossPartsModel: Option 1: Create a wrapper model for your integers
// First, create a model to wrap the integer
[RealtimeModel]
public partial class PartIDModel
{
[RealtimeProperty(1, true)]
private int _partID;
}

// Then use this model in your BossPartsModel
[RealtimeModel]
public partial class BossPartsModel
{
// Use the wrapper model instead of int
[RealtimeProperty(1, true)]
private RealtimeArray<PartIDModel> _partIDs;

[RealtimeProperty(2, true)]
private bool _allPartsDestroyed;

[RealtimeProperty(3, true)]
private int _totalParts;

[RealtimeProperty(4, true)]
private int _remainingParts;
}
// First, create a model to wrap the integer
[RealtimeModel]
public partial class PartIDModel
{
[RealtimeProperty(1, true)]
private int _partID;
}

// Then use this model in your BossPartsModel
[RealtimeModel]
public partial class BossPartsModel
{
// Use the wrapper model instead of int
[RealtimeProperty(1, true)]
private RealtimeArray<PartIDModel> _partIDs;

[RealtimeProperty(2, true)]
private bool _allPartsDestroyed;

[RealtimeProperty(3, true)]
private int _totalParts;

[RealtimeProperty(4, true)]
private int _remainingParts;
}
Option 2: Use a different collection type If you just need to track which parts are alive/dead, you could use a different approach:
[RealtimeModel]
public partial class BossPartsModel
{
// Remove the RealtimeArray of ints

[RealtimeProperty(1, true)]
private bool _allPartsDestroyed;

[RealtimeProperty(2, true)]
private int _totalParts;

[RealtimeProperty(3, true)]
private int _remainingParts;
}
[RealtimeModel]
public partial class BossPartsModel
{
// Remove the RealtimeArray of ints

[RealtimeProperty(1, true)]
private bool _allPartsDestroyed;

[RealtimeProperty(2, true)]
private int _totalParts;

[RealtimeProperty(3, true)]
private int _remainingParts;
}
Then in your BossPartsManager script, you can just track the parts locally and update the model properties:
void Update()
{
// Check if all parts are destroyed
int aliveCount = 0;
foreach (var part in bossParts)
{
if (part != null && part.IsAlive())
{
aliveCount++;
}
}

// Update the model
model.remainingParts = aliveCount;
model.allPartsDestroyed = (aliveCount == 0);

if (model.allPartsDestroyed && spawner != null)
{
// All parts destroyed, destroy the whole boss
Realtime.Destroy(spawner.bossInstance);
}
}
void Update()
{
// Check if all parts are destroyed
int aliveCount = 0;
foreach (var part in bossParts)
{
if (part != null && part.IsAlive())
{
aliveCount++;
}
}

// Update the model
model.remainingParts = aliveCount;
model.allPartsDestroyed = (aliveCount == 0);

if (model.allPartsDestroyed && spawner != null)
{
// All parts destroyed, destroy the whole boss
Realtime.Destroy(spawner.bossInstance);
}
}
This approach is simpler and avoids the need for a RealtimeArray altogether. Remember that according to the Normcore documentation on Supported Primitives, only specific types can be used directly in RealtimeModels, and collections like RealtimeArray can only contain other RealtimeModel objects. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant Im getting the error in the attached image on line 28 of the following script:
using Normal.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PartHealth : RealtimeComponent<PartModel>
{
[SerializeField] private int maxHealth;

protected override void OnRealtimeModelReplaced(PartModel previousModel, PartModel currentModel)
{
if (currentModel != null && currentModel.isFreshModel)
{
currentModel.maxHealth = maxHealth;
currentModel.currentHealth = maxHealth;
}
}

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
model.currentHealth--;

if (model.currentHealth <= 0)
{
// Destroy just this part
Realtime.Destroy(gameObject);
}
}
}

public bool IsAlive()
{
return model != null && model.currentHealth > 0;
}
}
using Normal.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PartHealth : RealtimeComponent<PartModel>
{
[SerializeField] private int maxHealth;

protected override void OnRealtimeModelReplaced(PartModel previousModel, PartModel currentModel)
{
if (currentModel != null && currentModel.isFreshModel)
{
currentModel.maxHealth = maxHealth;
currentModel.currentHealth = maxHealth;
}
}

private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
model.currentHealth--;

if (model.currentHealth <= 0)
{
// Destroy just this part
Realtime.Destroy(gameObject);
}
}
}

public bool IsAlive()
{
return model != null && model.currentHealth > 0;
}
}
No description
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I can help you with this error. Based on the error occurring on line 28 of your PartHealth script, it's likely related to accessing model.currentHealth in your IsAlive() method. The issue is that your model might be null when IsAlive() is called. This commonly happens with RealtimeComponents in Normcore when the model hasn't been initialized yet or has been destroyed. Here's how to fix it:
public bool IsAlive()
{
// Add a more thorough null check
if (model == null)
{
Debug.LogWarning("Model is null in IsAlive() check");
return false;
}
return model.currentHealth > 0;
}
public bool IsAlive()
{
// Add a more thorough null check
if (model == null)
{
Debug.LogWarning("Model is null in IsAlive() check");
return false;
}
return model.currentHealth > 0;
}
This issue is common with RealtimeComponents in Normcore. According to the documentation, the model is set by Realtime when you connect to the room, and it might not be available immediately Common Questions. Also, make sure your PartModel class is properly defined:
[RealtimeModel]
public partial class PartModel
{
[RealtimeProperty(1, true)]
private int _maxHealth;

[RealtimeProperty(2, true)]
private int _currentHealth;
}
[RealtimeModel]
public partial class PartModel
{
[RealtimeProperty(1, true)]
private int _maxHealth;

[RealtimeProperty(2, true)]
private int _currentHealth;
}
And ensure your PartHealth component is properly registered with a RealtimeView on your game object. If it's not showing up in the RealtimeView's components list, try removing and re-adding the PartHealth component Synchronizing Custom Data. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant i am still getting the same error on line 28
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I understand you're still getting the same error on line 28 of your PartHealth script. Let's take a closer look at what might be causing this persistent issue. Line 28 is in your IsAlive() method where you're checking model.currentHealth > 0. The error suggests that either: 1. The model is null, or 2. The currentHealth property doesn't exist or can't be accessed Let's try a more comprehensive approach: 1. First, add more detailed debugging to pinpoint exactly what's happening:
public bool IsAlive()
{
if (model == null)
{
Debug.LogError("PartHealth: model is null in IsAlive()");
return false;
}

try {
int health = model.currentHealth;
Debug.Log($"PartHealth: current health is {health}");
return health > 0;
}
catch (System.Exception e) {
Debug.LogError($"PartHealth: Error accessing currentHealth: {e.Message}");
return false;
}
}
public bool IsAlive()
{
if (model == null)
{
Debug.LogError("PartHealth: model is null in IsAlive()");
return false;
}

try {
int health = model.currentHealth;
Debug.Log($"PartHealth: current health is {health}");
return health > 0;
}
catch (System.Exception e) {
Debug.LogError($"PartHealth: Error accessing currentHealth: {e.Message}");
return false;
}
}
2. Make sure your PartModel is correctly defined and compiled:
[RealtimeModel]
public partial class PartModel
{
[RealtimeProperty(1, true)]
private int _maxHealth;

[RealtimeProperty(2, true)]
private int _currentHealth;
}
[RealtimeModel]
public partial class PartModel
{
[RealtimeProperty(1, true)]
private int _maxHealth;

[RealtimeProperty(2, true)]
private int _currentHealth;
}
3. Check if your RealtimeView is properly set up: - Ensure the PartHealth component is listed in the RealtimeView's components list - Try removing and re-adding the PartHealth component to the RealtimeView 4. Verify that OnRealtimeModelReplaced is being called by adding a debug log:
protected override void OnRealtimeModelReplaced(PartModel previousModel, PartModel currentModel)
{
Debug.Log("OnRealtimeModelReplaced called");

if (currentModel != null && currentModel.isFreshModel)
{
Debug.Log($"Setting initial health to {maxHealth}");
currentModel.maxHealth = maxHealth;
currentModel.currentHealth = maxHealth;
}
}
protected override void OnRealtimeModelReplaced(PartModel previousModel, PartModel currentModel)
{
Debug.Log("OnRealtimeModelReplaced called");

if (currentModel != null && currentModel.isFreshModel)
{
Debug.Log($"Setting initial health to {maxHealth}");
currentModel.maxHealth = maxHealth;
currentModel.currentHealth = maxHealth;
}
}
According to the Normcore documentation, if your custom RealtimeComponent isn't syncing and OnRealtimeModelReplaced doesn't get called, it's likely because the component isn't properly registered with the RealtimeView Synchronizing Custom Data. If you're still experiencing issues after these steps, you might need to temporarily modify your code to avoid the error while you troubleshoot:
// In your BossPartsManager.cs
void Update()
{
// Check if all parts are destroyed
bool allPartsDestroyed = true;
foreach (var part in bossParts)
{
if (part != null)
{
try {
if (part.IsAlive())
{
allPartsDestroyed = false;
break;
}
}
catch (System.Exception e) {
Debug.LogError($"Error checking if part is alive: {e.Message}");
allPartsDestroyed = false; // Assume part is alive if we can't check
break;
}
}
}

// Rest of your code...
}
// In your BossPartsManager.cs
void Update()
{
// Check if all parts are destroyed
bool allPartsDestroyed = true;
foreach (var part in bossParts)
{
if (part != null)
{
try {
if (part.IsAlive())
{
allPartsDestroyed = false;
break;
}
}
catch (System.Exception e) {
Debug.LogError($"Error checking if part is alive: {e.Message}");
allPartsDestroyed = false; // Assume part is alive if we can't check
break;
}
}
}

// Rest of your code...
}
---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant parthealth is reaching 0, but i still get this error on the Realtime.Destroy function
No description
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I understand you're still having issues with the Realtime.Destroy() function in your PartHealth script even though the health is reaching 0. This is a common issue with Normcore's object destruction. The error is likely related to ownership. In Normcore, only the owner of an object can destroy it. According to the documentation, when trying to destroy an object, you should check ownership first:
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
model.currentHealth--;

if (model.currentHealth <= 0)
{
// Check ownership before destroying
RealtimeView view = GetComponent<RealtimeView>();
if (view != null && view.isOwnedLocally)
{
Realtime.Destroy(gameObject);
}
else if (view != null)
{
// Request ownership before destroying
view.RequestOwnership();
}
}
}
}
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Ball") || other.CompareTag("Bullet"))
{
model.currentHealth--;

if (model.currentHealth <= 0)
{
// Check ownership before destroying
RealtimeView view = GetComponent<RealtimeView>();
if (view != null && view.isOwnedLocally)
{
Realtime.Destroy(gameObject);
}
else if (view != null)
{
// Request ownership before destroying
view.RequestOwnership();
}
}
}
}
This approach is recommended in the Normcore documentation to prevent errors when multiple clients try to destroy the same object Common Questions. If you're still encountering issues after implementing this ownership check, you might need to add more debugging to understand exactly what's happening:
if (model.currentHealth <= 0)
{
RealtimeView view = GetComponent<RealtimeView>();
Debug.Log($"Health <= 0, isOwnedLocally: {view?.isOwnedLocally}, ownerID: {view?.ownerID}");

if (view != null && view.isOwnedLocally)
{
try {
Realtime.Destroy(gameObject);
}
catch (System.Exception e) {
Debug.LogError($"Error destroying object: {e.Message}");
}
}
else if (view != null)
{
view.RequestOwnership();
}
}
if (model.currentHealth <= 0)
{
RealtimeView view = GetComponent<RealtimeView>();
Debug.Log($"Health <= 0, isOwnedLocally: {view?.isOwnedLocally}, ownerID: {view?.ownerID}");

if (view != null && view.isOwnedLocally)
{
try {
Realtime.Destroy(gameObject);
}
catch (System.Exception e) {
Debug.LogError($"Error destroying object: {e.Message}");
}
}
else if (view != null)
{
view.RequestOwnership();
}
}
Remember that according to the Normcore documentation on networked physics, ownership is a key concept for controlling objects in a multiplayer environment Networked Physics. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant i am still getting this error:
No description
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I understand you're still encountering an error with your Normcore implementation. Without seeing the specific error message in your latest question, I'll need to make some assumptions based on our previous conversation. It seems you're still having issues with the Realtime.Destroy() function in your PartHealth script. This is likely related to ownership issues in Normcore. Here are some additional troubleshooting steps you can try: 1. Make sure you're checking ownership before destroying objects:
if (model.currentHealth <= 0)
{
// Get the RealtimeView component
RealtimeView view = GetComponent<RealtimeView>();

// Check if we own this object
if (view != null && view.isOwnedLocallyInHierarchy)
{
Realtime.Destroy(gameObject);
}
else if (view != null)
{
// Request ownership first, then destroy in the next frame
view.RequestOwnership();
StartCoroutine(DestroyAfterOwnership());
}
}

private IEnumerator DestroyAfterOwnership()
{
// Wait a frame to ensure ownership transfer completes
yield return null;

// Check if we now have ownership
RealtimeView view = GetComponent<RealtimeView>();
if (view != null && view.isOwnedLocallyInHierarchy)
{
Realtime.Destroy(gameObject);
}
}
if (model.currentHealth <= 0)
{
// Get the RealtimeView component
RealtimeView view = GetComponent<RealtimeView>();

// Check if we own this object
if (view != null && view.isOwnedLocallyInHierarchy)
{
Realtime.Destroy(gameObject);
}
else if (view != null)
{
// Request ownership first, then destroy in the next frame
view.RequestOwnership();
StartCoroutine(DestroyAfterOwnership());
}
}

private IEnumerator DestroyAfterOwnership()
{
// Wait a frame to ensure ownership transfer completes
yield return null;

// Check if we now have ownership
RealtimeView view = GetComponent<RealtimeView>();
if (view != null && view.isOwnedLocallyInHierarchy)
{
Realtime.Destroy(gameObject);
}
}
2. Check if the object has a valid RealtimeView component. According to the Realtime Class Reference, you can only use Realtime.Destroy() with GameObjects that were instantiated with Realtime.Instantiate(). 3. Add more detailed error logging to identify exactly what's happening:
try {
Realtime.Destroy(gameObject);
} catch (System.Exception e) {
Debug.LogError($"Error destroying object: {e.Message}\nStack trace: {e.StackTrace}");
}
try {
Realtime.Destroy(gameObject);
} catch (System.Exception e) {
Debug.LogError($"Error destroying object: {e.Message}\nStack trace: {e.StackTrace}");
}
4. Make sure your RealtimeView hierarchy is properly set up. If a parent object owns a child object, you might need to request ownership of the parent first. If you could share the specific error message you're receiving, I could provide more targeted assistance for your NormalVR implementation. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
✨Mike the Floof Wizard✨
@Normcore Assistant how do i request ownership from a parent object
kapa.ai
kapa.ai3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To request ownership from a parent object in Normcore, you need to understand how ownership hierarchy works in the RealtimeView system. According to the Normcore documentation, ownership settings of a parent are enforced on all children. If you want to request ownership of a child object, you need to make sure the parent is either unowned or owned by you first. Here's how to request ownership from a parent object: Basic Approach
// Get the RealtimeView of the current object
RealtimeView myView = GetComponent<RealtimeView>();

// Get the parent RealtimeView
RealtimeView parentView = myView.GetParentView();

// Request ownership of the parent first
if (parentView != null)
{
parentView.RequestOwnership();

// Now you can request ownership of this object
myView.RequestOwnership();
}
// Get the RealtimeView of the current object
RealtimeView myView = GetComponent<RealtimeView>();

// Get the parent RealtimeView
RealtimeView parentView = myView.GetParentView();

// Request ownership of the parent first
if (parentView != null)
{
parentView.RequestOwnership();

// Now you can request ownership of this object
myView.RequestOwnership();
}
Using the Root View If you need to request ownership of the entire hierarchy, you can use the GetRootView() method:
// Get the RealtimeView of the current object
RealtimeView myView = GetComponent<RealtimeView>();

// Get the root view in the hierarchy
RealtimeView rootView = myView.GetRootView();

// Request ownership of the root
if (rootView != null)
{
rootView.RequestOwnership();
}
// Get the RealtimeView of the current object
RealtimeView myView = GetComponent<RealtimeView>();

// Get the root view in the hierarchy
RealtimeView rootView = myView.GetRootView();

// Request ownership of the root
if (rootView != null)
{
rootView.RequestOwnership();
}
Requesting Ownership of Self and Children Normcore also provides a convenient method to request ownership of a view and all its children:
// Get the parent RealtimeView
RealtimeView parentView = GetComponentInParent<RealtimeView>();

// Request ownership of parent and all children (including this object)
if (parentView != null)
{
parentView.RequestOwnershipOfSelfAndChildren();
}
// Get the parent RealtimeView
RealtimeView parentView = GetComponentInParent<RealtimeView>();

// Request ownership of parent and all children (including this object)
if (parentView != null)
{
parentView.RequestOwnershipOfSelfAndChildren();
}
According to the RealtimeView documentation, these methods are available to help manage ownership within a hierarchy of objects. Remember that ownership is enforced by the server, so if a parent is owned by another client, your request might be rejected. In that case, you'll need to coordinate with the other client to release ownership first. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3w ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?