Punching networked enemies
Gameplay: multiple people facing a wave of enemies. If I punch an enemy, I want it to fly for everyone. Is this the right architecture?
Enemies have kinematic rigidbodies with RealtimeTransform. If I punch an enemy (via OnTriggerEnter), I claim ownership of the enemy RealtimeTransform. I turn the rigidbody (on my client) to kinematic = false, apply my custom punch force with physics. The enemy flies on my client and its transform gets synced to other clients via RealtimeTransform. SO everyone sees it fly. Once the enemy hits the ground and finishes rolling, I release the ownership and set the rigidbody kinematic = true again.
I understand we can just do networked rigidbodies and Unity physics collision and this is handled mostly "automatically" but I want to use OnTriggerEnter to do some custom punch power calculations based on the puncher's "strength level."
68 Replies
Hi @khoi. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
----------------------
Your proposed architecture aligns well with how Normcore’s RealtimeTransform and Rigidbody synchronization works, and your approach is generally correct based on the documentation:
- Claiming Ownership: When you punch an enemy, you should call
Your approach is correct and matches Normcore’s recommended practices for networked physics and ownership transfer. Just ensure you handle ownership requests and kinematic state transitions as described, and avoid runtime component changes or reparenting. If you need more advanced state (like puncher’s strength), you can sync that via a custom RealtimeModel or as part of your punch logic, but the core architecture is sound for your use case. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
RequestOwnership()
on the enemy’s RealtimeTransform. This makes your client the source of truth for that object’s movement, so all other clients will follow your simulation of the enemy’s Rigidbody as it flies through the air. This is the recommended approach for interactive physics in Normcore, as only the owner’s changes are synchronized to other clients RealtimeTransform Networked Physics.
- Switching Kinematic State: Setting the Rigidbody to isKinematic = false
on your client is correct. This allows Unity’s physics engine to simulate the punch. When you’re done (enemy lands), you can set it back to isKinematic = true
and release ownership. This is also in line with the advice to use kinematic rigidbodies for direct control and to avoid automatic ownership clearing while the object is being manipulated Transform vs. Rigidbody mode.
- Custom Punch Force: Using OnTriggerEnter
to calculate and apply a custom force is fine. Since you own the object, your physics simulation (including the custom force) will be the one that is broadcast to all other clients.
- Syncing State: RealtimeTransform will synchronize the world position and rotation of the Rigidbody while you own it, so all clients will see the enemy fly and land in the same way Networked Physics.
- Releasing Ownership: Once the enemy comes to rest, releasing ownership (and optionally setting kinematic = true) is the correct way to allow other clients to take over if needed. You can also use the “Maintain Ownership While Sleeping” option if you want to keep ownership while the object is at rest Reference: RealtimeTransform.
Caveats:
- Do not reparent the enemy GameObject during this process, as RealtimeTransform does not support reparenting and this can cause desyncs RealtimeTransform Extra Notes.
- You cannot add or remove Rigidbody or RealtimeTransform components at runtime; set up your prefabs ahead of time RealtimeTransform Extra Notes.
- Only the owner’s simulation is authoritative. If two players punch the same enemy at the same time, the one who wins the ownership request will control the simulation.
Summary:Your approach is correct and matches Normcore’s recommended practices for networked physics and ownership transfer. Just ensure you handle ownership requests and kinematic state transitions as described, and avoid runtime component changes or reparenting. If you need more advanced state (like puncher’s strength), you can sync that via a custom RealtimeModel or as part of your punch logic, but the core architecture is sound for your use case. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
This is how I would do it
How do I spawn networked enemies? Does one client use Realtime.Instantiate? @Normcore Assistant
Let me check the knowledge base for you again. One moment! :books:
----------------------
To spawn networked enemies in Normcore, you should use
Yes, one client should use
Realtime.Instantiate()
to ensure the enemy prefab is instantiated on all clients and properly synchronized. Any client can call Realtime.Instantiate()
—typically, you would designate one client (such as the host or a game manager) to handle enemy spawning to avoid duplicates, but technically any client can do it.
The prefab you want to spawn must have a RealtimeView component on its root GameObject and should be located in a Resources folder (unless you are using Addressables or a custom loader). When you call Realtime.Instantiate("EnemyPrefabName")
, the enemy will appear on all clients, and all RealtimeViews and RealtimeComponents will be initialized and ready for use immediately. This is the standard and recommended way to spawn networked objects in Normcore Realtime API Reference: Realtime.Instantiate.
If you want to use Addressables instead of Resources, you can implement a custom loader as described in the Using Addressables guide.
Summary:Yes, one client should use
Realtime.Instantiate()
to spawn networked enemies, and this will synchronize the enemy across all clients.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
If I spawn an enemy that has a RealtimeView and RealtimeTransform, and it has "transform.Translate(forward)" in its Update() loop and I call it on each client, what happens? Will each client just act independently? @kapa.ai
Let me check the knowledge base for you again. One moment! :books:
----------------------
If you spawn an enemy with a RealtimeView and RealtimeTransform, and each client runs
transform.Translate(forward)
in its own Update()
loop, then each client will move the enemy independently—except for the client that owns the RealtimeTransform.
Normcore’s architecture makes only the owner’s transform authoritative. The owner’s position, rotation, and other state are synchronized to all other clients. If a non-owner client tries to move the object, its changes will be overwritten by the owner’s state from the datastore. So, only the owner’s movement will be reflected across the network; all other clients’ local movement will be ignored and reset to match the owner.
From the documentation:
In order to move an object with a RealtimeTransform component, your client needs to be the owner of the RealtimeTransform component... RealtimeTransform treats the client that owns the object as the source of truth. All other clients follow along with the owner's copy of the transform or rigidbody. This means that in order to move an object with a RealtimeTransform component on it, you must be the owner of the RealtimeTransform component.RealtimeTransform So, only the owner’s call to
transform.Translate(forward)
will have any effect. All other clients will see the enemy move according to the owner’s updates, not their own local movement code.
If you want the enemy to move in a networked way, you should ensure that only the owner runs the movement logic, or that all movement is driven by the owner and synchronized via RealtimeTransform.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
what if no one owns it initially @kapa.ai
Let me check the knowledge base for you again. One moment! :books:
----------------------
If no one owns the RealtimeTransform initially (i.e., its ownerID is -1), then any client can move it locally, but those changes will not be synchronized to other clients. The object is effectively unowned, and each client will act independently, leading to desynchronized movement across clients. The authoritative state will not be updated in the datastore until a client requests ownership.
From the documentation:
When a model is unowned, it can be modified by anyone in the room. However, when a model is owned by a client, the server only lets the owning client make changes to it or to any of its child models Server Authority + Ownership + Lifetime Flags.And specifically for RealtimeTransform:
In order to move an object with a RealtimeTransform component, your client needs to be the owner of the RealtimeTransform component RealtimeTransform.So, if no one owns the RealtimeTransform, movement is not synchronized and each client will see different results until someone requests ownership. Once a client requests ownership and becomes the owner, their state becomes authoritative and is synchronized to all other clients. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Okay, and if a Client owns a RealtimeModel or RealtimeTRansform and then disconnects, the ownership is automatically wiped right @kapa.ai
Let me check the knowledge base for you again. One moment! :books:
----------------------
Yes, when a client owns a RealtimeModel or RealtimeTransform and then disconnects, the ownership is automatically cleared. The server will remove ownership from any models owned by the disconnected client. This is confirmed in the documentation:
The server pings connected clients every five seconds. If their connection is dropped suddenly, the server will disconnect them after five seconds and any models that they own that are tagged destroyWhenOwnerLeaves will be destroyed automatically. For more information, take a look at the Ownership + Lifetime Flags page.Common Questions: Architecture If the model is not set to be destroyed when the owner leaves, the ownership will simply be cleared, and the model will become unowned (ownerID = -1), allowing other clients to request ownership if needed. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
To Instantiate, the prefab must be in a folder called Resources/ right? Can that Resources/ folder be nested? What is best practice? Top level Resources/ for all Resources or can I do Prefabs/Resources/ to keep it cleaner? @kapa.ai
Let me check the knowledge base for you again. One moment! :books:
----------------------
Yes, for
The Unity Resources system allows Resources folders to be nested anywhere in your Assets directory. Prefabs can be placed in any Resources folder or subfolder, and you can organize your project as you like (e.g.,
The Normcore documentation does not specify a required location or structure beyond being in a Resources folder. You can nest Resources folders to keep your project organized. Just ensure you use the correct relative path when calling
- The prefab must be in a Resources folder (can be nested). - You can organize Resources folders as you prefer for project cleanliness. - Reference the prefab by its path relative to the Resources folder when instantiating. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
Realtime.Instantiate()
to work, the prefab must be located in a Resources folder. According to the Normcore documentation, the prefab must "live in a Resources folder to ensure it can be loaded at runtime" Realtime API.
Regarding nesting:The Unity Resources system allows Resources folders to be nested anywhere in your Assets directory. Prefabs can be placed in any Resources folder or subfolder, and you can organize your project as you like (e.g.,
Assets/Prefabs/Resources/Enemy.prefab
or Assets/Resources/Enemies/Enemy.prefab
). When loading, you reference the prefab by its path relative to the Resources folder (e.g., "Enemies/Enemy"
).
Best practice:The Normcore documentation does not specify a required location or structure beyond being in a Resources folder. You can nest Resources folders to keep your project organized. Just ensure you use the correct relative path when calling
Realtime.Instantiate()
.
If you want to avoid using Resources altogether, you can implement a custom loader using Addressables as described in the Using Addressables guide.
Summary:- The prefab must be in a Resources folder (can be nested). - You can organize Resources folders as you prefer for project cleanliness. - Reference the prefab by its path relative to the Resources folder when instantiating. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
if i spawn with ownedbyclient = true, do i also have to request ownership of the realtimetransform on it or does that automatically happen? @kapa.ai
Let me check the knowledge base for you again. One moment! :books:
----------------------
If you spawn a prefab with
ownedByClient: true
using Realtime.Instantiate()
, the RealtimeView on the root of the prefab will be owned by the local client when instantiated. However, ownership of the RealtimeTransform component is not automatically granted just because the RealtimeView is owned. You still need to explicitly call RequestOwnership()
on the RealtimeTransform if you want to ensure your client is the owner of that component.
From the documentation:
The last thing we'll need to do is call RequestOwnership()
on each RealtimeTransform when the prefab is instantiated to signal that all other clients should use this client's transform as the source of truth.
>
...If you're familiar with our Networked Physics guide, you'll know that RealtimeTransform, when paired with a rigidbody, will attempt to clear ownership automatically when the rigidbody goes to sleep in order to allow other clients to take it over on physics collisions. However, in this case, we want to retain ownership of the Player RealtimeTransform at all times. In order to do that, we'll want to set it to Maintain Ownership While Sleeping.>
Making it multiplayerSo, you should still call
RequestOwnership()
on the RealtimeTransform after instantiating the prefab, even if you used ownedByClient: true
for the RealtimeView.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
If I want to change the values of a Rigidbody across all clients, do I have to make a custom RealtimeModel for that? @Normcore Assistant
Let me check the knowledge base for you again. One moment! :books:
----------------------
No, you do not need to make a custom RealtimeModel to synchronize the values of a Rigidbody (such as position, rotation, and velocity) across all clients. Normcore provides the built-in RealtimeTransform component specifically for this purpose. When you add RealtimeTransform to a GameObject with a Rigidbody, it will automatically synchronize the Rigidbody’s state (position, rotation, etc.) across all clients, as long as your client owns the RealtimeTransform. Other clients will follow the owner’s simulation of the Rigidbody.
From the documentation:
RealtimeTransform is a built-in RealtimeComponent that can be used to synchronize the transform or rigidbody of a GameObject... RealtimeTransform will monitor the GameObject and synchronize the transform or rigidbody state to a model in the datastore.RealtimeTransform If you want to synchronize additional custom data (such as custom properties not covered by RealtimeTransform), then you would create a custom RealtimeModel. But for standard Rigidbody synchronization, RealtimeTransform is all you need. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
will it sync mass ? @Normcore Assistant
Let me check the knowledge base for you again. One moment! :books:
----------------------
I'm sorry, but the knowledge sources do not explicitly mention whether RealtimeTransform synchronizes the
mass
property of a Rigidbody across clients. The documentation specifies that RealtimeTransform synchronizes position, rotation, velocity, and angular velocity, but does not list mass or other Rigidbody properties such as drag or useGravity RealtimeTransform Reference Networked Physics.
If you need to synchronize mass or other custom Rigidbody properties, you may need to implement a custom RealtimeModel for those specific values.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Okay, so if I call transform.position = Vector3.zero; and then ClearOwnership(); it wont set the position to zero because i'm clearing the ownership in the same frame. This is what I am observing. Is this inteded Normcore behavior? I have to ClearOwnership the frame after? @Normcore Assistant
Let me check the knowledge base for you again. One moment! :books:
----------------------
Yes, this is intended Normcore behavior. According to the documentation, Normcore does not send changes to the server immediately when you change ownership or the value on a model. Instead, it synchronizes at 20Hz and sends the latest state. If you change something (like setting
transform.position = Vector3.zero;
) and then immediately call ClearOwnership()
in the same frame, nothing will be sent to the server. As a result, the position change will not be synchronized to other clients.
From the docs:
Normcore doesn’t send changes to the server immediately when you change ownership or the value on a model. Instead, it synchronizes at 20hz and sends the latest state. If you change something and then change it back immediately, nothing will be sent to the server. In this case, skip calling>ClearOwnership()
. As long as the RealtimeView’s owner is None, then any other client in the future can just callRequestOwnership()
to take over the transform and move it.
Realtime > Common QuestionsTo ensure the position is synchronized, you should wait at least one frame after setting the position before calling
ClearOwnership()
.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Will one frame be enough time? @Normcore Assistant
Let me check the knowledge base for you again. One moment! :books:
----------------------
The knowledge sources do not explicitly state whether waiting one frame is guaranteed to be enough time for the position change to be synchronized before calling
ClearOwnership()
. The documentation only specifies that Normcore synchronizes at 20Hz (every 50ms) and that changes are not sent immediately, but it does not provide a recommended minimum delay in frames or time between making a change and clearing ownership Realtime > Common Questions.
If you need to ensure the update is sent, you may want to wait longer than a single frame (e.g., a few frames or until the next network tick), but the documentation does not provide a definitive answer.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Ok. Let's say I (client 0) punch enemy 1, I request ownership on that frame and then apply a physics for. Enemy 1 is now flying on client 0. On client 1, the normal Update loop is running with rigidbody.MovePosition(forward). I assume this does nothing because client 1 does not have ownership?
What happens in this case @Normcore Assistant
Let me check the knowledge base for you again. One moment! :books:
----------------------
In this scenario:
- Client 0 requests ownership of enemy 1's RealtimeTransform and applies a physics force, causing enemy 1 to fly on client 0.
- Client 1 continues to run its normal Update loop, calling
Only the owner’s (client 0’s) physics and movement are synchronized. Non-owners (client 1) cannot move the object, and their local movement code is ignored/overwritten by the networked state. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
rigidbody.MovePosition(forward)
on enemy 1, but does not have ownership of the RealtimeTransform.
What happens:
On client 0 (the owner), the physics simulation and position updates are authoritative. The RealtimeTransform component will synchronize the Rigidbody's state (position, rotation, etc.) to the datastore.
On client 1 (not the owner), any local changes to the Rigidbody (such as rigidbody.MovePosition(forward)
) will be overridden by the state received from the owner (client 0). The documentation states:
RealtimeTransform treats the client that owns the object as the source of truth. All other clients follow along with the owner's copy of the transform or rigidbody. This means that in order to move an object with a RealtimeTransform component on it, you must be the owner of the RealtimeTransform component.RealtimeTransform So, client 1's calls to
rigidbody.MovePosition(forward)
will have no effect—the enemy's position will be continuously updated to match the authoritative state from client 0. Only the owner’s simulation is synchronized and visible to all clients.
Summary:Only the owner’s (client 0’s) physics and movement are synchronized. Non-owners (client 1) cannot move the object, and their local movement code is ignored/overwritten by the networked state. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
It will update until the ownership update is received and then the transform will jump to where the owning client says it is
Okay. And if the Enemy rolls to a stop and then becomes "unowned" due to ClearOwnership() triggering when an Enemy rolls to a stop, and both clients have Update running with rigidbody.MovePosition(forward), what happens? Do they overwrite each other and fight? @Normcore Assistant
Let me check the knowledge base for you again. One moment! :books:
----------------------
If the enemy becomes unowned (ownership cleared) after rolling to a stop, and both clients have their Update loops running with
rigidbody.MovePosition(forward)
, each client will move the enemy independently on their own instance. The movement will not be synchronized, and the enemy will quickly become desynchronized between clients.
From the documentation:
When a model is unowned, it can be modified by anyone in the room. However, when a model is owned by a client, the server only lets the owning client make changes to it or to any of its child models Server Authority + Ownership + Lifetime Flags.And specifically for RealtimeTransform:
In order to move an object with a RealtimeTransform component, your client needs to be the owner of the RealtimeTransform component RealtimeTransform.So, when unowned, there is no authoritative source of truth, and each client’s local movement code will affect only their own copy. There is no "fighting" or overwriting between clients, but rather, each client will see the enemy move differently until one client requests ownership and becomes authoritative again. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
In order to move an object with a RealtimeTransform component, your client needs to be the owner of the RealtimeTransform component RealtimeTransform.
So if neither client owns the Enemy which has a RealtimeTransform, it will just be stuck? @Normcore Assistant
Let me check the knowledge base for you again. One moment! :books:
----------------------
If neither client owns the Enemy's RealtimeTransform, then the object will not be synchronized across the network, and any attempts to move it will not be reflected on other clients. Each client could potentially move the object locally, but those changes would not be shared, leading to desynchronization. In practice, the object will appear "stuck" from a network perspective, because no authoritative updates are being sent or received.
From the documentation:
In order to move an object with a RealtimeTransform component, your client needs to be the owner of the RealtimeTransform component RealtimeTransform.So, if neither client owns the RealtimeTransform, the object will not move in a networked, synchronized way and will appear stuck unless a client requests ownership. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
But @Normcore Assistant will I see it moving locally?
Let me check the knowledge base for you again. One moment! :books:
----------------------
Yes, if neither client owns the Enemy's RealtimeTransform, you can move it locally on your own client (for example, by calling
- You will see it move locally. - Other clients will not see your changes. - The object will not be synchronized until a client requests ownership. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
transform.position = ...
or rigidbody.MovePosition(...)
), and you will see it move on your screen. However, these changes will not be synchronized to other clients, and each client will see their own local version of the object's movement. There is no authoritative state being broadcast, so the object will quickly become desynchronized between clients Server Authority + Ownership + Lifetime Flags RealtimeTransform.
In summary:- You will see it move locally. - Other clients will not see your changes. - The object will not be synchronized until a client requests ownership. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
What is the best way to handle this:
- A single client is chosen as the "game host"
- They spawn the enemies and own the RealtimeTransforms of the enemies, so they control the enemies walking around
- Another player runs up and tries to punch the enemy --> How do I ClearOwnership and let the other player RequestOwnership when they want to punch?
@Normcore Assistant
Let me check the knowledge base for you again. One moment! :books:
----------------------
The best way to handle this scenario in Normcore is as follows:
1. Game Host Spawns and Owns Enemies:
The designated "game host" client uses
When another player wants to punch an enemy, you need to transfer ownership of the enemy's RealtimeTransform to that player. The recommended approach is: - The host (current owner) should call
- The host owns and controls enemies by default. - When another player needs to interact (punch), the host clears ownership, and the punching player requests ownership before applying their force. - Ensure proper timing between clearing and requesting ownership to avoid synchronization issues. If you need more granular control or want to prevent race conditions, you can use the
The designated "game host" client uses
Realtime.Instantiate()
to spawn enemies. When instantiating, set ownedByClient: true
so the host owns the root RealtimeView. After instantiation, the host should also call RequestOwnership()
on each enemy's RealtimeTransform to ensure they control the enemy's movement and state Making it multiplayer.
2. Transferring Ownership for Punching:When another player wants to punch an enemy, you need to transfer ownership of the enemy's RealtimeTransform to that player. The recommended approach is: - The host (current owner) should call
ClearOwnership()
on the enemy's RealtimeTransform when it is appropriate to allow another client to take over (e.g., when the enemy is about to be punched or when a punch is detected).
- The punching player then calls RequestOwnership()
on the enemy's RealtimeTransform. Once they become the owner, they can apply their punch force, and their simulation will be synchronized to all clients RealtimeTransform Ownership and Lifetime Flags.
Important details:
- Timing: Make sure there is a small delay (at least one frame, possibly longer) between clearing ownership and the new client requesting ownership, to ensure the change is synchronized properly. The documentation notes that changes are sent at 20Hz, so immediate back-to-back calls may not be synchronized Realtime > Common Questions.
- Ownership Enforcement: Only the owner can move the object in a way that is synchronized. If no one owns the RealtimeTransform, local changes are not synchronized and each client will see different results RealtimeTransform.
Summary:- The host owns and controls enemies by default. - When another player needs to interact (punch), the host clears ownership, and the punching player requests ownership before applying their force. - Ensure proper timing between clearing and requesting ownership to avoid synchronization issues. If you need more granular control or want to prevent race conditions, you can use the
preventOwnershipTakeover
flag to control when ownership can be transferred Prevent ownership takeover.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
The host (current owner) should call ClearOwnership() on the enemy's RealtimeTransform when it is appropriate to allow another client to take over (e.g., when the enemy is about to be punched or when a punch is detected).
-- How should I do this? Should the punching player send a RPC event if they get very close to the enemy to tell the owner to release it? That seems clunky... @Normcore Assistant
Let me check the knowledge base for you again. One moment! :books:
----------------------
You are correct that having the punching player send an RPC to the host to request ownership transfer can feel clunky, and the Normcore documentation actually recommends avoiding RPCs for state synchronization whenever possible, as they are not reliably synchronized for late joiners and require manual state management Room > RPCs.
The recommended approach in Normcore is to let any client request ownership directly by calling
- Avoid using RPCs for this purpose. - Let the punching player call
RequestOwnership()
on the RealtimeTransform. The server will grant ownership if the object is unowned, or deny it if it is already owned by another client (or if the preventOwnershipTakeover
flag is set) Ownership and Lifetime Flags.
Best practice:
- The host should only clear ownership (call ClearOwnership()
) when it is appropriate for another client to take over (e.g., when the enemy is in a state where it can be punched).
- The punching player should simply call RequestOwnership()
on the enemy’s RealtimeTransform when they want to punch. If the object is unowned, the server will grant ownership and the punch will succeed. If not, the request will be denied and the punch will not be applied.
This avoids the need for RPCs and keeps the logic simple and robust. If you need to coordinate more complex interactions, you can use model properties or flags in a custom RealtimeModel, but for basic ownership transfer, direct calls to RequestOwnership()
are the intended workflow.
By default, anyone can request ownership of a RealtimeView at runtime. The server will approve this request unless a parent RealtimeView is already owned by another client. However, this can be controlled with thePrevent ownership takeover of a RealtimeView Summary:preventOwnershipTakeover
flag.
- Avoid using RPCs for this purpose. - Let the punching player call
RequestOwnership()
directly.
- The host should clear ownership when appropriate, and the server will handle the rest.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@Max if i want to avoid desync, i should have someone own the enemy realtimetransform always, but then how can I design the game to clearownership at the right time?
I've been trying the avatar closest to the enemy always own the realtimetransform, if you aren't the closest avatar and you own it, ClearOwnership(). Presumably if you are the closest avatar, you are probably the next person to punch it.
Would this work?
I don't think I understand. What's desyncing that you're trying to avoid here?
If it's an NPC, I would try to simulate it locally and avoid RealtimeTransform. When someone punches it, they add a collision event to a RealtimeSet that all clients can simulate together.
but technically it's impossible to avoid a desync here. If I punch an NPC, all other players will see my punch after ~100ms delay and so it's already out of sync no matter what you do
but if you're trying to have the NPC have a RealtimeTransform just leave it owned by the last person to punch it? RequestOwnership() takes affect instantly and is rolled back if the server rejects it. So when I punch something, my client would request ownership and I'd see the punch effects instantly as the enemy barrels away.
All other clients would see my punch and the enemy bounce back with the same delay so it would look good to them too
The only issue you'd run into is if two people try to punch the same guy at the same time and I don't know what the solution is there unfortunately
" if you're trying to have the NPC have a RealtimeTransform just leave it owned by the last person to punch it? RequestOwnership() takes affect instantly and is rolled back if the server rejects it. So when I punch something, my client would request ownership and I'd see the punch effects instantly as the enemy barrels away."
This is what I'm trying to do exactly, you got it! My only question is, in my current set up a different client owns the RealtimeTransform at the time that I punch it because I want the positions to be the same for sure across clients. So I wouldn't be able to punch it, since someone else owns the RTT for authoritative NPC movement.
But it sounds like you are saying we should keep the NPC unowned and just simulate locally until a punch event happens. And there shouldnt be much noticeable desyncing if it's just simple movement.
You don’t have to clear ownership for someone else to take it over
why can’t you do exactly what you’re trying to do? that should work
Ok I misunderstood the docs, I thought you could not RequestOwnership if someone else owned it.
I think what we were running into was that the RealtimeView was owned by someone else so the RealtimeTransform was rejecting ownership.
So I think we should be good! Thanks Max.
sweet! glad to hear it
there’s a preventOwnershipTakeover setting but it defaults to false
right -- for some reason I got the impression that only one client could own something and it would lockout other clients until cleared. But there is this snippet that is a big a-ha! "A RequestOwnership() call is only rejected if the RealtimeView or a parent RealtimeView is owned by a different client." from the RTT page.
RealtimeTransform | Normcore Documenation
RealtimeTransform is a built-in RealtimeComponent that can be used to synchronize the transform or rigidbody of a GameObject.
@kapa.ai just to double check -- with a custom unowned RealtimeComponent besides RealtimeTransform, anyone can modify it right? Dont need to RequestOwnership before modifying it? Eg a custom realtime component to synchronize custom data does not need to have ownership flags to modify it
Let me check the knowledge base for you again. One moment! :books:
----------------------
Yes, that is correct. If a custom RealtimeComponent (or its underlying RealtimeModel) is unowned (ownerID = -1), then any client can modify it without needing to call
RequestOwnership()
first. Ownership is only required to restrict modifications—when unowned, all clients are permitted to make changes.
From the documentation:
By default, a model has anSo, for your custom RealtimeComponent used to synchronize custom data, ownership is not required to modify it as long as it is unowned. If you want to restrict who can modify it, you would use ownership flags and require clients to request ownership before making changes. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------ownerID
of-1
, which denotes that it has no owner. When a model is unowned, it can be modified by anyone in the room. However, when a model is owned by a client, the server only lets the owning client make changes to it or to any of its child models Server Authority + Ownership + Lifetime Flags.
<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@kapa.ai so RealtimeTransform is just a special case of RealtimeComponent
Let me check the knowledge base for you again. One moment! :books:
----------------------
Yes, RealtimeTransform is a built-in subclass of RealtimeComponent. It is a special case designed specifically to synchronize the transform or rigidbody of a GameObject across the network. Like other RealtimeComponent subclasses, it keeps a part of your scene in sync by synchronizing its state with a corresponding RealtimeModel in the datastore. The main difference is that RealtimeTransform is purpose-built for handling position, rotation, scale, and Rigidbody synchronization, while you can create custom RealtimeComponent subclasses for your own data types RealtimeComponent RealtimeTransform.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@kapa.ai what's the behavior of Rigidbody.MovePosition in regards to RealtimeTransform? If I call it on my client, which owns it, will it move the rigid body on the remote client? or only the transform? (can those two things even get separated in unity?)
Let me check the knowledge base for you again. One moment! :books:
----------------------
If you call
Rigidbody.MovePosition
on your client and you own the RealtimeTransform, the movement will be synchronized to all other clients. On remote clients, the Rigidbody (not just the Transform) will be updated to match the authoritative state from the owner.
Normcore’s RealtimeTransform in Rigidbody mode synchronizes the world position and rotation of the Rigidbody, not just the Transform. This is because Unity’s physics engine (PhysX) operates in world space, and moving a Rigidbody via the Transform would teleport it and bypass collision detection. Therefore, when you use Rigidbody.MovePosition
as the owner, the resulting Rigidbody state is sent to the datastore and applied on all other clients, keeping both the Rigidbody and Transform in sync across the network RealtimeTransform + Physics Transform mode vs. Rigidbody mode.
So, the Rigidbody will move on remote clients as well, not just the Transform. In Unity, the Transform and Rigidbody can get out of sync if you manipulate the Transform directly, but Normcore’s synchronization ensures that the Rigidbody’s state is authoritative and consistent across all clients when using RealtimeTransform in Rigidbody mode.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@412870643533479936> Kapa.ai is still learning and improving, please let me know how I did by reacting below.