Boss size and rotation not syncing (I think)

In my game i have a boos fight. We have the following script that spawns the boss and handles movement when a player steps into an invisible collider. The boss spawns and rotates and works, but for the player that doesnt spawn the boss the size and rotation are different. this is the script:
using Normal.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

public class BossMove : RealtimeComponent<MovingTargetModel>
{
#region Variables
[SerializeField] private float rotationSpeed;

private Vector3 startPosition;
#endregion

void Start()
{
startPosition = transform.position;
}

void Update()
{
MoveTarget();
}

private void MoveTarget()
{
transform.Rotate(Vector3.up * rotationSpeed * Time.deltaTime);

if (model != null)
{
model.rotation = transform.rotation;
}
}

protected override void OnRealtimeModelReplaced(MovingTargetModel previousModel, MovingTargetModel currentModel)
{
if (previousModel != null)
{
previousModel.rotationDidChange -= RotationDidChange;
}
if (currentModel != null)
{
if (currentModel.isFreshModel)
{
currentModel.position = transform.position;
}
UpdateParent();

UpdateRotation(currentModel.rotation);
UpdatePosition(currentModel.position);
UpdateScale(currentModel.scale);

currentModel.rotationDidChange += RotationDidChange;
currentModel.positionDidChange += PositionDidChange;
currentModel.scaleDidChange += ScaleDidChange;
}
}

private void RotationDidChange(MovingTargetModel _model, Quaternion value)
{
UpdateRotation(value);
}

private void PositionDidChange(MovingTargetModel _model, Vector3 value)
{
UpdatePosition(value);
}

private void ScaleDidChange(MovingTargetModel _model, Vector3 value)
{
UpdateScale(value);
}

private void UpdateRotation(Quaternion newRotation)
{
transform.rotation = newRotation;
}

private void UpdatePosition(Vector3 newPosition)
{
transform.position = newPosition;
}

private void UpdateScale(Vector3 newScale)
{
transform.localScale = newScale;
}

private void UpdateParent()
{
string parentString = model.parentNameString;

if (parentString == null) return;

List<string> parents = new List<string>();

foreach (var parent in parentString.Split(" : "))
{
if (parent.Length > 0)
{
parents.Add(parent);
}
}

Transform currParent = null;

foreach (string name in parents)
{
Transform child = FindChildByName(name, currParent);

if (child != null)
{
currParent = child;
}
else
{
Debug.LogWarning("Could not find child: " + name);
return;
}
}

transform.SetParent(currParent);
}

Transform FindChildByName(string name, Transform parent)
{
// If no parent specified, search from the root (scene hierarchy)
if (parent == null)
{
return GameObject.FindGameObjectWithTag("MapOrigin").transform;
}

// Search for the child in the hierarchy
return parent.Find(name);
}

public void SetNewParentString(string newParentString)
{
model.parentNameString = newParentString;

UpdateParent();
}
}
using Normal.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

public class BossMove : RealtimeComponent<MovingTargetModel>
{
#region Variables
[SerializeField] private float rotationSpeed;

private Vector3 startPosition;
#endregion

void Start()
{
startPosition = transform.position;
}

void Update()
{
MoveTarget();
}

private void MoveTarget()
{
transform.Rotate(Vector3.up * rotationSpeed * Time.deltaTime);

if (model != null)
{
model.rotation = transform.rotation;
}
}

protected override void OnRealtimeModelReplaced(MovingTargetModel previousModel, MovingTargetModel currentModel)
{
if (previousModel != null)
{
previousModel.rotationDidChange -= RotationDidChange;
}
if (currentModel != null)
{
if (currentModel.isFreshModel)
{
currentModel.position = transform.position;
}
UpdateParent();

UpdateRotation(currentModel.rotation);
UpdatePosition(currentModel.position);
UpdateScale(currentModel.scale);

currentModel.rotationDidChange += RotationDidChange;
currentModel.positionDidChange += PositionDidChange;
currentModel.scaleDidChange += ScaleDidChange;
}
}

private void RotationDidChange(MovingTargetModel _model, Quaternion value)
{
UpdateRotation(value);
}

private void PositionDidChange(MovingTargetModel _model, Vector3 value)
{
UpdatePosition(value);
}

private void ScaleDidChange(MovingTargetModel _model, Vector3 value)
{
UpdateScale(value);
}

private void UpdateRotation(Quaternion newRotation)
{
transform.rotation = newRotation;
}

private void UpdatePosition(Vector3 newPosition)
{
transform.position = newPosition;
}

private void UpdateScale(Vector3 newScale)
{
transform.localScale = newScale;
}

private void UpdateParent()
{
string parentString = model.parentNameString;

if (parentString == null) return;

List<string> parents = new List<string>();

foreach (var parent in parentString.Split(" : "))
{
if (parent.Length > 0)
{
parents.Add(parent);
}
}

Transform currParent = null;

foreach (string name in parents)
{
Transform child = FindChildByName(name, currParent);

if (child != null)
{
currParent = child;
}
else
{
Debug.LogWarning("Could not find child: " + name);
return;
}
}

transform.SetParent(currParent);
}

Transform FindChildByName(string name, Transform parent)
{
// If no parent specified, search from the root (scene hierarchy)
if (parent == null)
{
return GameObject.FindGameObjectWithTag("MapOrigin").transform;
}

// Search for the child in the hierarchy
return parent.Find(name);
}

public void SetNewParentString(string newParentString)
{
model.parentNameString = newParentString;

UpdateParent();
}
}
3 Replies
kapa.ai
kapa.ai3mo 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: ---------------------- Your issue—where the boss's size and rotation are not syncing correctly for all players—likely stems from how and when you are synchronizing these properties with Normcore's datastore. Key Points from Normcore Documentation 1. Synchronization Must Use the Model
For properties to sync across clients, you must update the model, not just the transform. In your Update() method, you only set model.rotation = transform.rotation; but you do not update the model's scale or position there. If the scale is changed locally but not written to the model, it will not sync to other clients. 2. Ownership Is Required for Syncing
Only the owner of a RealtimeComponent can update the model. If the local client is not the owner, changes to the transform will not be propagated. Make sure the client moving/rotating/scaling the boss has ownership of the component. You can request ownership using RequestOwnership() on the component or its RealtimeView as appropriate.
See: RealtimeTransform and Ownership 3. Model Updates Should Be Consistent
You should update all properties you want to synchronize (position, rotation, scale) in the same way. If you only update rotation in Update(), but not scale, scale changes will not be sent to the model and thus not synced. How to Fix - Update the Model for All Synced Properties
In your Update() or wherever you change the boss's transform, set all relevant model properties:
if (model != null) {
model.rotation = transform.rotation;
model.position = transform.position;
model.scale = transform.localScale;
}

if (model != null) {
model.rotation = transform.rotation;
model.position = transform.position;
model.scale = transform.localScale;
}

- Ensure Ownership
Before making changes, ensure the local client owns the component:
if (!isOwnedLocallySelf) {
RequestOwnership();
}

if (!isOwnedLocallySelf) {
RequestOwnership();
}

(You may want to do this in Start() or before you start moving the boss.) - Check Model Initialization
Make sure you are not trying to sync before the model is ready. Use OnRealtimeModelReplaced for initialization logic, as described in the docs:
Synchronizing custom data Additional Notes - Reparenting: Normcore does not synchronize parent relationships. If you reparent the boss, this will not be synced to other clients and can cause desyncs.
See: Can I reparent a game object with a RealtimeView or RealtimeComponent on it? - Prefab Instantiation: Make sure you are using Realtime.Instantiate() to spawn the boss, not Unity's Instantiate(), so that all clients instantiate the object in sync.
See: Synchronizing a GameObject Summary - Update all properties you want to sync (rotation, position, scale) on the model, not just the transform. - Ensure the local client has ownership before making changes. - Do not rely on reparenting for synchronization. - Use Realtime.Instantiate() for spawning networked objects. If you follow these steps, the boss's size and rotation should sync correctly for all players. If issues persist, double-check that your model events (rotationDidChange, scaleDidChange, etc.) are firing and being handled on all clients. --- References:
- RealtimeComponent
- Synchronizing custom data
- Realtime common questions
- RealtimeTransform ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@372679700885012485> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Mechabit
Mechabit3mo ago
Is that script running on all the clients? looks like MoveTraget would override whatever's in the data store if the boss rotates at a fixed rate you can just sync the start time and the players can calculate the current rotation from the room time

Did you find this page helpful?