(v0.50) DOTS Tutorial - Build a Multiplayer AR app
  • What is the Purpose of this DOTS Tutorial?
  • Important Notes For Using This Gitbook
  • Unity ECS
    • Intro to Unity ECS
    • Create a Unity ECS Project
    • Spawn and Move Prefabs
    • Spawn and Move Players
    • Spawn Bullets and Destroy Player
    • Publish Builds in Unity ECS
  • Unity DOTS Physics
    • Intro to Unity DOTS Physics
    • Use DOTS Physics for Prefabs, Players, and Bullets
    • Use DOTS Physics for Collisions
  • Unity DOTS NetCode
    • Intro to DOTS NetCode
    • Create a Network Connection using DOTS NetCode
    • Load a Game using DOTS NetCode
    • DOTS NetCode and Prefabs
    • DOTS NetCode and Player Prefabs
    • Use DOTS NetCode for Collisions and Destroying Bullet Prefabs
    • Dynamically Changing Ghosts Between Interpolated and Predicted
  • UI Builder and UI Toolkit
    • Intro to UI Toolkit
    • Create a ScreenManager
    • Style a View
    • Create a ListView
    • Responsive Game UI
    • Navigate Between Scenes Using ClientServerBootstrap
  • Multiplayer (NetCode+, UI Toolkit+)
    • Intro to Unity NetCode Multiplayer
    • Host or Join a Multiplayer Session on LAN
    • Broadcast a LAN Multiplayer Game
    • Keep Score and Update Game UI
    • Send Ghosts with NetCode Using Relevancy
  • AR Foundation
    • Intro to AR Foundation
    • Set Up AR Foundation and ARKit
    • Spawn a Player using AR Foundation
    • Update UI using AR Foundation
Powered by GitBook
On this page
  • What you'll develop on this page
  • Updating Asteroids Based on Proximity to Player
  1. Unity DOTS NetCode

Dynamically Changing Ghosts Between Interpolated and Predicted

Code and workflows to update ghosts between interpolated and predicted

PreviousUse DOTS NetCode for Collisions and Destroying Bullet PrefabsNextIntro to UI Toolkit

Last updated 2 years ago

What you'll develop on this page

We will update Asteroids to be "predicted" when within a specified distance from the player. Although difficult to tell from the gif the Asteroids close to the player are moving smoother than those far away.

Github branch link: https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/tree/Changing-Between-Interpolated-and-Predicted

Updating Asteroids Based on Proximity to Player

Currently all the Asteroids are interpolated. That means that the server updates their movement, and then snapshots are sent to the clients to update their position.

You might have noticed that the Asteroids movement is not as "smooth" as it was in our Physics section. The reason is that the client does not get as many update s because of the amount of Asteroids. The server has to send a snapshot for every single Asteroid.

By interpolating all the asteroids, we reduce the computation requirements needed on the client, because they run less physics. So it is a trade-off, do we reduce computation and increase bandwidth?

The answer, like most things in engineering, it depends! For a large scale game, it probably is unnecessary to predicted the physics of everything on the map, especially when the player cannot see/interact with the predicted objects. So in this section we will implement a system that changes which Asteroids are interpolated vs. predicted based on a player's proximity.

  • First let's make a ClientSettingsAuthoringComponent.cs in Authoring/ to create an authoring component where we will store the radius where Asteroids switch form interpolated to predicted

using Unity.Entities;

[GenerateAuthoringComponent]
public struct ClientSettings : IComponentData
{
    public float predictionRadius;
    public float predictionRadiusMargin;

}
  • Let's add this component in ConvertedSubScene on the GameSettings GameObject

  • Let's set predictionRadius to 5 and predictionRadiusMargin to 1

  • Now let's create AsteroidSwitchPredictionSystem.cs in Client/Systems

using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.NetCode;
using Unity.Collections;
using Unity.Burst;

[UpdateInGroup(typeof(ClientSimulationSystemGroup))]
public partial class AsteroidSwitchPredictionSystem : SystemBase
{
    private NativeList<Entity> m_ToPredicted;
    private NativeList<Entity> m_ToInterpolated;
    private GhostSpawnSystem m_GhostSpawnSystem;
    protected override void OnCreate()
    {
        RequireSingletonForUpdate<ClientSettings>();
        m_ToPredicted = new NativeList<Entity>(16, Allocator.Persistent);
        m_ToInterpolated = new NativeList<Entity>(16, Allocator.Persistent);
        m_GhostSpawnSystem = World.GetExistingSystem<GhostSpawnSystem>();
    }
    protected override void OnDestroy()
    {
        m_ToPredicted.Dispose();
        m_ToInterpolated.Dispose();
    }
    protected override void OnUpdate()
    {
        var spawnSystem = m_GhostSpawnSystem;
        var toPredicted = m_ToPredicted;
        var toInterpolated = m_ToInterpolated;
        for (int i = 0; i < toPredicted.Length; ++i)
        {
            if (EntityManager.HasComponent<GhostComponent>(toPredicted[i]))
                spawnSystem.ConvertGhostToPredicted(toPredicted[i], 1.0f);
        }
        for (int i = 0; i < toInterpolated.Length; ++i)
        {
            if (EntityManager.HasComponent<GhostComponent>(toInterpolated[i]))
                spawnSystem.ConvertGhostToInterpolated(toInterpolated[i], 1.0f);
        }
        toPredicted.Clear();
        toInterpolated.Clear();

        var settings = GetSingleton<ClientSettings>();
        if (settings.predictionRadius <= 0)
            return;

        if (!TryGetSingletonEntity<PlayerCommand>(out var playerEnt) || !EntityManager.HasComponent<Translation>(playerEnt))
            return;
        var playerPos = EntityManager.GetComponentData<Translation>(playerEnt).Value;

        var radiusSq = settings.predictionRadius*settings.predictionRadius;
        Entities
            .WithNone<PredictedGhostComponent>()
            .WithAll<AsteroidTag>()
            .ForEach((Entity ent, in Translation position) =>
        {
            if (math.distancesq(playerPos, position.Value) < radiusSq)
            {
                // convert to predicted
                toPredicted.Add(ent);
            }
        }).Schedule();
        radiusSq = settings.predictionRadius + settings.predictionRadiusMargin;
        radiusSq = radiusSq*radiusSq;
        Entities
            .WithAll<PredictedGhostComponent>()
            .WithAll<AsteroidTag>()
            .ForEach((Entity ent, in Translation position) =>
        {
            if (math.distancesq(playerPos, position.Value) > radiusSq)
            {
                // convert to interpolated
                toInterpolated.Add(ent);
            }
        }).Schedule();
    }
}
  • Now let's hit play and check out the difference for Asteroids that are close to the player

  • The Asteroids that are closer to the player are moving in a smoother fashion! (because they are predicted)

  • What is great is that this runs on the client side, so the client can make a decision of how much prediction to do

    • You can imagine you can have settings where lower powered devices run less prediction

We now predict Asteroids that are within our prediction radius

  • We created the ClientSettingsAuthoringComponent and added it to the GameSettings GameObject in ConvertedSubScene

  • We created AsteroidSwitchPredictionSystem to use these settings to change nearby Asteroids to predicted from interpolated (and switch back)

Github branch link:

git clone https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/ git checkout 'Changing-Between-Interpolated-and-Predicted'

Join our Discord for more info
Join our Discord for more info
Interpolated Asteroids being changed to predicted Asteroids when near Player
Adding Client Settings component on the GameSettings object in the SubScene