Some simple scripts covering different networking needs that you may find useful:
A simple spawn script that Instantiates the Networked Player on a keypress.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnCharacter: MonoBehaviour
{
public KeyCode KeyPress;
public GameObject NetworkedCharacter;
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(KeyPress))
{
Instantiate(NetworkedCharacter);
}
}
}
Simple Input to get your Game Objects moving.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SimpleMovement : MonoBehaviour
{
public float speed = 1f;
void Update()
{
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
transform.position += transform.forward * (v * speed * Time.deltaTime);
transform.position += transform.right * (h * speed * Time.deltaTime);
}
}
An Event System you put on the Prefab with coherenceSync
that can handle Network Commands and transfer Authority.
using Coherence;
using UnityEngine;
using UnityEngine.Events;
using Coherence.Toolkit;
public class CoherenceCommandHandler : MonoBehaviour
{
private CoherenceSync sync;
public UnityEvent onCommandReceived;
private GameObject fxInstance;
private void Awake()
{
sync = GetComponent<CoherenceSync>();
}
public void Adopt()
{
if (!sync || sync.HasStateAuthority)
{
return;
}
sync.RequestAuthority(AuthorityType.Full);
}
public void Orphan()
{
if (!sync || !sync.HasStateAuthority)
{
return;
}
sync.AbandonAuthority();
}
public void ToggleAuthority()
{
if (!sync)
{
return;
}
if (sync.HasStateAuthority)
{
Orphan();
}
else
{
Adopt();
}
}
public void Command(CoherenceSync sender)
{
sync.SendCommand<CoherenceCommandHandler>(nameof(CoherenceCommandHandler.ReceiveCommand), MessageTarget.All);
}
public void ReceiveCommand()
{
onCommandReceived.Invoke();
}
public void InstantiateFX(GameObject fx)
{
if (fxInstance)
{
Destroy(fxInstance);
}
var t = fx.transform;
fxInstance = Instantiate(fx, transform.position + t.position, transform.rotation);
}
}
An Event System you put on an empty Game Object in your
Scene that handles Global Connection/Disconnection.
using UnityEngine;
using UnityEngine.Events;
using Coherence.Connection;
using Coherence.Toolkit;
public class CoherenceConnectionEvents : MonoBehaviour
{
public UnityEvent onConnect;
public UnityEvent onDisconnect;
private CoherenceBridge Bridge;
private void OnEnable()
{
Bridge = FindObjectOfType<CoherenceBridge>();
Bridge.onConnected.AddListener(HandleConnected);
Bridge.onDisconnected.AddListener(HandleDisconnected);
Bridge.OnLiveQuerySynced.AddListener(HandleLiveQuerySynced);
}
private void OnDisable()
{
Bridge.onConnected.RemoveListener(HandleConnected);
Bridge.onDisconnected.RemoveListener(HandleDisconnected);
Bridge.onLiveQuerySynced.RemoveListener(HandleLiveQuerySynced);
}
private void HandleConnected(CoherenceBridge _)
{
onConnect?.Invoke();
}
private void HandleLiveQuerySynced(CoherenceBridge _)
{
onConnect?.Invoke();
}
private void HandleDisconnected(CoherenceBridge _, ConnectionCloseReason __)
{
onDisconnect?.Invoke();
}
Keeps a score that is synced across the Network.
using Coherence.Toolkit;
using Coherence;
using UnityEngine;
// Keeps a score that is synced across the network.
// Put this script on a singleton object in the Editor.
// That Game Object needs a CoherenceSync that doesn't allow duplicates.
// Remember to sync the score variable, and the PointCommand method.
public class ScoreKeeper : MonoBehaviour
{
static ScoreKeeper inst;
CoherenceSync sync;
public int score; // Remember to sync this variable!
void Awake( ) {
if( inst != null ) {
Destroy( gameObject );
return;
}
inst = this;
sync = GetComponent<CoherenceSync>( );
}
// Remember to sync this method!
public void PointCommand( int amount ) { score += amount; }
public static void GivePoint( int amount ) {
inst.sync.SendCommand<ScoreKeeper>(
nameof( ScoreKeeper.PointCommand ),
MessageTarget.AuthorityOnly, amount );
}
}
Display a name tag above all player characters with their name.
Put this script on your player character and populate the nameTagPrefab variable (a basic TextMeshPro Object will do fine).
You also need to sync the playerName variable through the coherence Configuration tab.
using Coherence.UI;
using UnityEngine;
using TMPro;
using Coherence.Toolkit;
// Creates a TextMeshPro Object from a Prefab.
// Finds a canvas in the Scene and attaches the TextMeshPro Object to it.
// Looks for the player name in World or Room ConnectDialog.
// Updates the position of the name tag in update.
// nameTagPrefab can be just a default TextMeshPro Object.
// playerNameField is an InputField in either the World, Room, or Lobby connection dialog. The code below looking for it might fail if your hierarchy is organised in a very different way.
public class NameTag : MonoBehaviour
{
public GameObject nameTagPrefab;
public Vector2 offset = new Vector2( 0, 2 );
[Sync] public string playerName;
CoherenceSync sync;
TextMeshProUGUI text;
InputField playerNameField;
Camera cam;
private void Start( )
{
sync = GetComponent<CoherenceSync>( );
Canvas canvas = FindObjectOfType<Canvas>( );
if( canvas == null ) Debug.Log( "couldn't find a canvas to parent NameTag to" );
if( nameTagPrefab == null ) Debug.Log( "NameTag prefab is null!" );
text = Instantiate( nameTagPrefab ).GetComponent<TextMeshProUGUI>( );
text.transform.SetParent( canvas.transform );
// Only get player name if we have Authority, which gets synced with other Clients.
if( sync.HasStateAuthority )
{
playerNameField = GameObject.Find("Connect Dialog").transform.Find("PlayerName").GetComponentInChildren<InputField>();
if( playerNameField == null ) Debug.Log("Couldn't find the player name input field. Your hierarchy might be organised in a different way than expected.");
// Use the input field value to display the player name
playerName = playerNameField.text;
text.text = playerName;
// Listen to potential changes, in case the user types in a new one
playerNameField.onValueChanged.AddListener(TextFieldChanged);
}
cam = FindObjectOfType<Camera>( );
}
private void Update( )
{
// Here we assume the game is 2D.
// Use the Camera Facing UI script if your game is 3D!
Vector2 pos = transform.position;
text.transform.position = cam.WorldToScreenPoint( pos + offset );
if( text.text != playerName ) text.text = playerName;
}
private void TextFieldChanged(string newName)
{
playerName = newName;
}
private void OnDestroy( )
{
playerNameField.onValueChanged.RemoveListener(TextFieldChanged);
if( text != null ) Destroy( text.gameObject );
}
}
Following the previous example, put this line of code in the Update() function to have the UI always face the camera.
text.transform.LookAt(text.transform.position + cam.rotation * Vector3.forward, cam.rotation * Vector3.up);
This method returns a random position off screen (in 2D!). Very useful for spawning enemies off screen in your prototypes.
Vector2 RandomBorderPosition( ) {
Camera cam = FindObjectOfType<Camera>( ); // you should probably move this line to Awake() or Start()
float unitsOffScreen = 1;
Vector2 campos = cam.transform.position;
float XY_Ratio = (float)cam.scaledPixelWidth / (float)cam.scaledPixelHeight;
Vector2 camdim = new Vector2( cam.orthographicSize * XY_Ratio, cam.orthographicSize );
Vector2 randomDirectionFromCenter = camdim + Vector2.one * unitsOffScreen;
if( Random.Range( 0, 100 ) < 50 ) randomDirectionFromCenter.x *= -1;
if( Random.Range( 0, 100 ) < 50 ) randomDirectionFromCenter.y *= -1;
if( Random.Range( 0, 100 ) < 50 ) {
randomDirectionFromCenter.x = Random.Range( -1f, 1f ) * camdim.x;
} else {
randomDirectionFromCenter.y = Random.Range( -1f, 1f ) * camdim.y;
}
return campos + randomDirectionFromCenter;
}
When set up, this script will display a sprite (an arrow for example) on the edge of the screen, indicating that something is off screen.
To set it up, create a GameObject and attach this script and a SpriteRenderer to it. Put what you want to track into subject, and you're good to go!
using UnityEngine;
public class OffscreenIndicator : MonoBehaviour
{
public Transform subject;
public float radius = .3f; // units outside screen before indicator starts showing.
public float border = -1; // units from screen border to display indicator. positive values will display outside screen.
public bool pointTowardsSubject = true; // if the indicator is an arrow or something, you probably want it to point towards your subject.
Camera cam;
void Awake( ) {
transform.localScale = Vector2.zero;
cam = FindObjectOfType<Camera>( );
}
void Update( ) {
if( subject == null ) return;
bool onscreen = true;
Vector2 pos = subject.position;
Vector2 campos = cam.transform.position;
float heightRatio = (float)cam.pixelWidth / (float)cam.pixelHeight; // viewport returns screen as (1,1), so we gotta factor in the screens height to width ratio
Vector2 dirFromCamera = pos - campos;
if( Mathf.Abs( dirFromCamera.x ) > ( cam.orthographicSize * heightRatio + border ) ) {
dirFromCamera.x = Mathf.Sign( dirFromCamera.x ) * ( cam.orthographicSize * heightRatio + border );
onscreen = false;
}
if( Mathf.Abs( dirFromCamera.y ) > cam.orthographicSize + border ) {
dirFromCamera.y = Mathf.Sign( dirFromCamera.y ) * ( cam.orthographicSize + border );
onscreen = false;
}
if( onscreen ) {
transform.localScale = new Vector2( 0, 0 );
} else {
transform.position = campos + dirFromCamera;
transform.localScale = new Vector2( 1, 1 );
if( pointTowardsSubject ) {
Vector2 direction = pos - ( campos + dirFromCamera );
float atan2 = Mathf.Atan2( direction.y, direction.x );
transform.rotation = Quaternion.Euler( 0, 0, ( atan2 * Mathf.Rad2Deg + 270 ) % 360.0f );
}
}
}
}
c#