Skip to main content

Creating Networked Players

Learn how to create a networked Avatar or other type of player for your multiplayer Experience.

Networked Player Manager

The NetworkedPlayerManager is a component in the NetworkManager prefab that manages all the players in the game including Avatars, NPCs, and other types of players.

This component requires the networked player factories references in order for a script to spawn and manage them. It also has key properties to spawn players once initialized and choose the local player factory.

Player Manager

Networked Player Factories

The networked player factories are components that describe what the NetworkedPlayerManager component will spawn. There are two main types for Avatars and NPCs.

Networked Avatar Factory

The NetworkedAvatarFactory component manages each player prefab for the local and remote version. It also has key properties such as the Animator Controller or Empty Avatar Mode which is useful for non-Avatar players like a paddle in Pong.

Avatar Factory

Networked Player Components

In order for a player prefab to be synced with other clients, it requires the prefab to contain these important components:

  • Player Network Transform: Required by both the local and remote player prefabs to sync the players for all clients using the server. It requires a reference to the local or remote controller components.
  • Sync Local Transform Controller: Required by the local player prefab for clients to send important local player data to the server.
  • Remote Transform Controller: Required by the remote player prefab for clients to receive remote player data from the server.

Player Components

Networked Player Prefabs

The NetworkedAvatarFactory component requires two prefabs: Local Player Prefab and Remote Player Prefab. Each prefab requires a Player Network Transform component.

tip

Look at the Assets > GeniesSdk > Prefabs > Player folder for example local and remote player prefabs.

Local Player Prefabs

Local player prefabs require a Sync Local Transform Controller component. They will also have all the necessary client-side components like character controllers or colliders.

Remote Player Prefabs

Remote player prefabs require a Remote Transform Controller component. This is usually less complicated than the local prefab as its usually just trying to sync the visual display of the remote player.

Networked Player API

Network Player Manager Class

The NetworkPlayerManager class includes important events such as creating and updating local and remote players. These events are each passed a NetworkPlayer class object.

import { NetworkPlayerManager } from "Genies.Components.Sdk.External.Multiplayer";

let playerManager: NetworkPlayerManager;

//Player Manager Events
playerManager.OnLocalPlayerCreated;
playerManager.OnLocalPlayerUpdated;
playerManager.OnRemotePlayerAdded;
playerManager.OnRemotePlayerCreated;
playerManager.OnRemotePlayerRemoved;
playerManager.OnRemotePlayerUpdated;

Network Player Class

The NetworkPlayer class manages the networked player entity. It has references for the server SFSUser and local GameObject.

import { NetworkPlayer } from "Genies.Components.Sdk.External.Multiplayer.Player";
import { SFSUser } from "Sfs2X.Entities";
import { GameObject } from "UnityEngine";

let networkPlayer: NetworkPlayer;

//Network Player Properties
let user: SFSUser = networkPlayer.User;
let obj: GameObject = networkPlayer.gameObject;
note

The SFSUser class is important because it has an Id property to indicate which player this is on the server and across clients. See the SFS Client Side API for more information.

TypeScript Example

Tracking Player Amount

Here's a client script that tracks the amount of players in a server:

import { NetworkPlayerManager } from "Genies.Components.Sdk.External.Multiplayer";
import { NetworkPlayer } from "Genies.Components.Sdk.External.Multiplayer.Player";
import { SFSUser } from "Sfs2X.Entities";
import { MonoBehaviour } from "UnityEngine";

export default class MyScript extends MonoBehaviour {

public playerManager: NetworkPlayerManager;

private allPlayers: NetworkPlayer[] = [];

private Awake() : void {
this.playerManager.OnLocalPlayerCreated.AddListener(this.AddPlayer);
this.playerManager.OnRemotePlayerCreated.AddListener(this.AddPlayer);
this.playerManager.OnRemotePlayerRemoved.AddListener(this.RemovePlayer);
}

private AddPlayer(player: NetworkPlayer) {
this.allPlayers.push(player);
this.DisplayPlayers();
}

private RemovePlayer(player: NetworkPlayer) {
this.allPlayers = this.allPlayers.filter(p => p != player);
this.DisplayPlayers();
}

private DisplayPlayers() {
console.log("There are ", this.allPlayers.length, " players");
for(let p of this.allPlayers) {
let user: SFSUser = p.User;
console.log("Player ID: " + user.Id);
}
}
}

Spawning Players on Terrain

This client script that manages the local and remote players position so they are on top of the terrain when they spawn and update:

import { Camera, Collider, GameObject, MonoBehaviour, Ray, Transform, Vector3 } from "UnityEngine";
import { NetworkLoginManager, NetworkPlayerManager, SmartFoxManager, Utility } from "Genies.Components.Sdk.External.Multiplayer";
import { NetworkPlayer, PlayerNetworkTransform } from "Genies.Components.Sdk.External.Multiplayer.Player";
import { RemoteTransformController } from "Genies.Components.Sdk.External.Multiplayer.Sync";
import { NetworkObject } from "Genies.Components.Sdk.External.Multiplayer.Object";
import { MMORoom } from "Sfs2X.Entities";
import { Vec3D } from "Sfs2X.Entities.Data";

export default class MyScript extends MonoBehaviour {

public loginManager: NetworkLoginManager;
public playerManager: NetworkPlayerManager;

public terrainCollider: Collider;
public aoiPrefab: GameObject;

private _localPlayer: NetworkPlayer;
private _aoiTransform: Transform;

private Awake() {
this.playerManager.OnLocalPlayerCreated.AddListener(this.OnLocalPlayerLoaded);
this.playerManager.OnRemotePlayerCreated.AddListener(this.OnRemotePlayerCreated);
this.playerManager.OnRemotePlayerUpdated.AddListener(this.OnRemotePlayerUpdated);
}

public OnLocalPlayerLoaded(player: NetworkPlayer): void {
this._localPlayer = player;

Camera.main.transform.parent = player.transform;

// Instantiate and set scale and position of game object representing the Area of Interest
this._aoiTransform = (GameObject.Instantiate(this.aoiPrefab) as GameObject).transform;

var aoiSize = (this._server.LastJoinedRoom as MMORoom).DefaultAOI.ToVector3();
this._aoiTransform.localScale = new Vector3(aoiSize.x * 2, 10, aoiSize.z * 2);

this._aoiTransform.position = new Vector3(
this._localPlayer.transform.position.x,
-3,
this._localPlayer.transform.position.z);
}

public OnRemotePlayerCreated(player: NetworkPlayer) {

let transformController = player.GetBehaviour<PlayerNetworkTransform>();

// Adjust the Y position on entry to make sure players don't fall through the terrain
let entryPoint = player.User.AOIEntryPoint.ToVector3();

let adjustedY: float = this.GetTerrainHeight(entryPoint);
let newEntryPoint: Vec3D = new Vec3D(entryPoint.x, adjustedY, entryPoint.z);
player.User.AOIEntryPoint = newEntryPoint;
}

public LogRemotePlayerUpdates: bool;

public OnRemotePlayerUpdated(player: NetworkPlayer) {
var transformController = player.GetBehaviour<PlayerNetworkTransform>();

let remoteTransformController: RemoteTransformController = transformController.RemoteController;
let position = remoteTransformController.TargetPosition;

// Adjust the Y position on update to make sure players don't fall through the terrain
position.y = this.GetTerrainHeight(position);
remoteTransformController.SetPosition(position, true);

if (this.LogRemotePlayerUpdates) {
console.log(`[OnRemotePlayerUpdated] ${player.name} NewY: ${position.y} Target: ${remoteTransformController.TargetPosition}`);
}
}

public LogRaycastUpdates: bool;

/**
* Evaluate terrain height at given position.
*/
private GetTerrainHeight(position: Vector3): float {
const maxHeight: float = 10;

const currentPositionY: float = position.y;
position.y = maxHeight;

const ray: Ray = new Ray(position, Vector3.down);
var result = Utility.Raycast(this.terrainCollider, ray, 2.0 * maxHeight);

if (result.Success) {
if (this.LogRaycastUpdates) console.log("Hit at:", JSON.stringify(result.Hit));

return result.Hit.point.y;
} else {
if (this.LogRaycastUpdates) console.log("Failed to hit.");
return currentPositionY;
}
}
}