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.
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.
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.
Networked Player Prefabs
The NetworkedAvatarFactory component requires two prefabs: Local Player Prefab and Remote Player Prefab. Each prefab requires a Player Network Transform component.
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;
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;
}
}
}