PUN Classic (v1), PUN 2 and Bolt are in maintenance mode. PUN 2 will support Unity 2019 to 2022, but no new features will be added. Of course all your PUN & Bolt projects will continue to work and run with the known performance in the future. For any upcoming or new projects: please switch to Photon Fusion or Quantum.

Simple Getting Started Tutorial

This document contains the steps to produce a demo scene from scratch without any coding and using only the supplied components and some Unity Standard Assets. As such, it is not a cosmetically beautiful demonstration. The goal of this demonstration is to show how the assists can be used to quickly produce useful networking.

Contents

A word about Assists

Assists are menu items that automate the tasks of creating networked objects and they are used almost entirely for this demo. They are basically one-click wizards as they have no interface. They will attempt to perform a task on the gameobject that is selected in Unity or will create a new scene object if applicable.

Create New Project and Import Latest PUN2 / Simple

  1. Create a new Project in any version of Unity above 2017.4.
  2. Import the latest PUN2 from the Asset Store.
  3. Download Simple and import.

Create a New Scene

  1. Create new Scene (if not already in one)
  2. Set up a basic floor/walls by using the assist:
    GameObject > Simple > Tutorial > Create Starting Scene


This assist does nothing of any real networking interest. It prepares a scene for this tutorial by adding some walls and a floor and moves the camera to a better position.

We now have an empty scene with a ground plane.

For Unity 2019 if the scene appears dark:
You may need to go to enable AutoGenerate in:
Window > Rendering > Lighting

Create a Player

First we want to add an instance of the BasicKyle prefab to the scene so we can make a variant of it meant for networking.

  1. Use search in Project to find the prefab in Assets.
    A lite version of Kyle/Ethan from Standard Assets is included that you may use.
  2. Drag the prefab into the scene


Convert BasicKyle to Simple Net Object

Controllable objects like a player or vehicle need to be 'netified' to sync anything. The Convert To > Player assist automates most of the work needed to do this. For this example ThirdPersonController from Unity Standard Assets is used but the process will be similar for most player object types. This assist attempts to make a best guess starting point for making single player objects that move/animate into networked objects.

  1. Select the 'BasicKyle' GameObject in the scene

  2. Convert 'BasicKyle' to Net Object using the assist:
    GameObject > Simple > Convert To > Player



    This should add placeholder health bars to the player object, as well as an assortment of networking components.

    • PhotonView
    • NetObject
    • SyncAnimator (if an Animator was found on the object)
    • SyncTransform
    • SyncVitals
    • AutoOwnerComponentEnable
    • ContactTrigger
    • MountManager
    • Mount (The Root Mount)
    • BasicInventory
    • SyncState
    • OnStateChangeKinematic
    • SyncSpawnTimer
    • AutoDestroyUnspawned
    • OnStateChangeToggle on children

    NetObject is the root component that works as an extension of PhotonView, and handles passing callbacks from the NetMaster to child SyncObjects and PackObjects, as well as managing serialization and deserialization. NetObject gets its timings from the NetMaster singleton, which is automatically added at runtime.

    NOTE:AutoDestroyUnspawned is automatically added. For some objects you may want to remove this. Game Objects with this component in the starting scene will be destroyed at runtime. This allows you work on the prefab instances in the editor without having to remove them before playing or building out tests. Objects you intend to spawn as part of the scene will want this removed.

  3. Drag the scene 'BasicKyle' prefab instance into any Resource folder

    You may want to add a new Resources folder rather than dig for an existing one. Newer versions of Unity will ask if you want to make this a Variant. That is fine.

Add PUN2 Networking Code

There is no code yet to set up and join a PUN Room, so let's add that so we can test our progress.

Typically this will all be your own custom code for matchmaking, creating a lobby & room and handling player object instantiation and such. PUN2 has a couple components that will automate this for you and Simple has an assist that will automatically add them to your scene.

Add Components for Connecting and Room Creation

  1. Select 'KyleBasic' in the scene before running the assist

  2. Add Room Launchers using the assist:
    GameObject > Simple > Add To Scene > Auto Room Launchers

    This will create (or modify if it already exists) a scene object with the components:

    • ConnectAndJoinRandom
    • OnJoinedInstantiate (any selected prefab to the Prefabs To Instantiate list)

You can rerun the assist with other prefabs select to add them to the OnJoinedInstantiate prefab list.

Playing the scene should now:

  1. Connect to the Master Server
  2. Create or Join a Room
  3. Spawn our player.

Create Vital and Inventory Pickup Items

Pickups are objects with IContactReactor component(s) that can interact with IContactSystems, such as Inventory or Vitals. These objects can be triggered, picked up, attached or mounted to/by IContactSystem objects. Typically this would be a player (or AI) that would trigger these items.

There are assists for creating prototypes of these objects, with all of the networking components automatically added. These generated objects include placeholder child objects, for different states.

  • Items have the IInventoryable components that can be picked up and mounted to IInventorySystem.
  • Vitals has the IVitalsContactReactor components needed to interact with the IVitalsSystem.

The assist generated items come with a set of placeholders with OnStateChangeToggle components, set up for a few of the possible states. Replace these with actual graphics, sound triggers, particles systems, etc. You can also create your own scripts that implement IOnStateChange to capture callbacks when the state of objects change.

Add a Health Pickup

  1. Add a Pickup Vital using the assist:
    GameObject > Simple > Add To Scene > Pickup > Vital: Static

    Vital: Static is the same as Vital: Dynamic, but no Rigidbody or SyncTransform is added, since this object is not expected to be dropped/thrown.

  2. Move the Item so it is not overlapping the player spawn points
    Set Position X to 3



    Running the game at this point should allow the player to pick up the item. When the player triggers this pickup, it will add to the players health and attach to the player, as the pickup is set to vital type 'Health' and the player SyncVitals has a Health Vital.

Add a Pickup Item (IInventoryable)

We will produce an inventory item using a similar method.

  1. Add a Pickup Item using the assist:
    GameObject > Simple > Add To Scene > Pickup > Item: Dynamic

    Item: Dynamic is the same as Item: Static, but has a Rigidbody added as well as a SyncTransform, so it can be dropped/thrown.

  2. Move the Item so it's not overlapping the player spawn points
    Set Position X to -3



    Running the game at this point should allow the player to pick up the items.

    Currently the only Mount on Kyle is the default 'Root' mount, which was automatically added by 'Convert To > Player'. We can add more Mounts to Kyle and assign them to the Inventory and Vitals systems. The next section shows how to add and use additional mounts.

Add Mounts to Player

Mount components can be added to child GameObjects of any NetObject. Any items that are picked up can be designated to mount to these transforms.

A default Mount is automatically added to the root and is named 'Root', as well as a MountsManager component (which handles automatic mount indexing).

  1. Select the Player instance in the scene

  2. Type 'wrist' into the Hierarchy search box

  3. Select 'Left_Wrist_Joint_01'

  4. Add Mount Component to 'Left_Wrist_Joint_01'

  5. Select the Mount Type 'LeftHand'
    This associates this mount with the named type LeftHand. Each Net Object can only have one mount associated with each named type.

  6. Optional: Modify the global mount types
    If 'LeftHand' doesn't exist in the current list in mount settings, add it in by expanding Mount Settings. This exposes the Settings ScriptableObject for Mounts. MountSettings apply to the entire project, and all Mounts.

  7. Add Mount Throw component to 'Left_Wrist_Joint_01'

  8. Set Throw Key on MountThrow to Alpha 5

    Hitting the '5' key will drop attached Inventory items after they have been picked up. We now have a mount on the left hand, however there is nothing specifically telling objects to use this new mount. We need to do the next step for this mount to get used...

Mounting Assignments

Inventory and Vital items can only be mounted if they have Mountable To named types that match the mount settings for Vitals and Inventory Systems they interact with.

Assign Inventory to a Specific Mount

SyncState handles attaching a Net Object to a Mount on other Net Objects. As such, you can restrict which Mounts a Net Object can attach to, by setting the Mountable To values in SyncState.

  1. Select our 'Pickup Item' created in the previous step.

  2. Enable 'LeftHand' in the SyncState Mountable To selector

    This mask is used to restrict Attach to only happen between compatible types. This object can now only be mounted to an IContactSystem that use the 'LeftHand' mount.


  3. Set BasicInventory to use LeftHand as mount

    On the root of BasicKyle, find BasicInventory, and set the Default Mounting to LeftHand.


    This indicates that any IInventoryable picked up by this IInventorySystem will attach to the Mount associated with 'LeftHand'. IInventoryable items must have 'LeftHand' checked in the SyncState Mountable To. (See the step above)
    BE SURE TO APPLY CHANGES TO THE PREFAB!

Now when the player touches the 'Pickup Item', it should attach to Kyle's hand, as that is now the first valid inventory that will be found. If not, double check that you set the inventory to use LeftHand and that LeftHand is selected as a valid mount on the SyncState of the pickup.

Add Networked Hitscan

  1. Select 'Robot2' or any child gameobject of our 'BasicKyle' player in the scene

  2. Add a Damage Scan using the assist: GameObject > Simple > Add To Object > Remote Contact > Damage Scan

  3. Reposition the created Damage Scan child
    Set the Position to (-0.5, 1.5, 0)




    The weapon assists add child placeholder objects. You can replace these with your own models.

    BE SURE TO APPLY CHANGES TO THE PREFAB!

Hitting Play, you can see the default hitscan fire when you press 'R'.

Note: Contact Scans generate ContactEvents similar to Enter, Stay and Exit.

As such they can be used to trigger the ContactTrigger remotely. This means it is possible to 'Raycast' or 'Overlap' any ContactTriggers.
Hitscans are not just for weapons.

Add Networked Projectile Cannon

  1. Select child 'Robot2' of 'BasicKyle' in the scene

  2. Add the networked Projectile Cannon using the assist:
    GameObject > Simple > Add To Object > Remote Contact > Damage Cannon

  3. Move the created Projectile Cannon child
    (right now he is shooting out of his toes)
    Set the Position to (0.5, 1.5, 0).


    Damage Cannon Added
    The weapon assists add placeholder objects. You can replace these with your own models.

    BE SURE TO APPLY CHANGES TO THE PREFAB!

Hitting Play, you can see the default projectile fire when you press 'F'.

Note: Projectiles act as proxy ContactEvents. As such they can be used to trigger the ContactTrigger as if the shooting object collided with the object. This means it is possible to 'shoot' pickups. Projectiles are not just weapons._

Add a Hitscan Remote Actions

Hitscans are not just weapons, they can also be used as a means of triggering the ContactTrigger component by proxy and become extensions of the ContactTrigger of the objects firing them.

  1. Select child node 'Robot2' of the 'BasicKyle' instance in the scene

  2. Add a ContactProxy hitscan using the assist:
    GameObject > Simple> Add To Object > Remote Contact > Contact Scan


Hitting Play, you should now be able to pick up IInventoryable items (Vital/Item Pickups) by hitting the '5' key. You will also see a visual representation of the hitscan momentarily. The default hitscan is an OverlapSphere but can be changed to any other type of overlap or raycast type. By default, you should also see a yellow visualization object of the hitscan for a moment after triggering. Any ContactTriggers hit by the scan will trigger an OnContactEvent with the ContactType.Hitscan.

The 'Contact Scan' assist adds the same set of components as 'Damage Scan', except it doesn't add a IContactReactor component. This means that all Hitscan Weapons are actually capable of triggering IInventory -> IInventoryable interactions.

Add Moving Terrain

Platforms/Moving Terrain can be added with the ITransformController components derived from SyncMoverBase. For this example we will be using SyncNodeMover, which allows us to create time-based lerping between two points. All of the networking is automatically handled (which is true for any of the components starting with the word 'Sync'). If the moving terrain is a Net Object with a Mount, the AutoMountHitscan component on the player can automatically detect its proximity and re-parent to that platform. This networks the players location relative to the moving platform, which prevents delayed 'floating' that will occur if a player is attached to a moving object that is owned by someone else.

  1. Select the leftmost section of flooring named 'Floor Tile 1'

  2. Convert to a Platform using the assist:
    GameObject > Simple > Convert To > Platform


    This converts the flooring section into a Net Object, and adds Mount and SyncNodeMover components. When the scene is built or played, this section of flooring will now oscillate up and down.

  3. Add AutoMountHitscan to 'BasicKyle' using the assist:
    GameObject > Simple > Add To Object > Auto Mount Hitscan

    This adds a child GameObject named 'AutoMount', which contains the AutoMountHitscan component.


On play, a raycast continuously tests downward from the base of 'BasicKyle' to see if a Net Object is in close proximity. BasicKyle will mount (re-parent) to the closest NetObject with a compatible Mount. The settings for this hitscan test are defined by the Hitscan Definition setting on the AutoMountHitscan component.

Adding an NPC

First we need to make a scene object that moves autonomously.

  1. Create the tutorial NPC using the assist:
    GameObject > Simple > Tutorial > Add NPC
    This produces a basic networked cube that moves in a ring.

  2. Select the Example NPC object if it has been deselected

  3. Convert the NPC to a Net Object using the assist:
    GameObject > Simple > Convert To > Player
    This adds the extra components, similar to what we added to our BasicKyle. Now this object has a health system, inventory system, health bars, etc.

Running the scene now should result in this object moving in a circle. This object will be owned by the Master Client. The NPC has a health system and as such it is able to be damaged and can trigger IVitalsAffector and IInventoryable pickups just like a regular player. The MasterClient will own this object, as is true with all scene objects.

Create a PackObject with SyncVarAttributes

EXPERIMENTAL

Documentation on PackObjects

Basic SyncVarAttribute Implementation

  1. Add a new Cube to the scene:
    GameObject > 3D Object > Cube

  2. Create a script named SyncvarTest.cs and paste in the code below

        using UnityEngine;
     using Photon.Compression;
     using Photon.Pun;
     using Photon.Pun.Simple;
    
     /// The PackObject attribute tells the code gen engine and the NetObject that this class/struct 
     /// contains [SyncVar] attributes that should be serialized and synced.
     [PackObject]
     [RequireComponent(typeof(NetObject))]
     public class SyncvarTest : MonoBehaviour
     {
         /// THIS IS OUR SYNCVAR.
         [SyncVar]
         public float syncedZ;
    
         public void FixedUpdate()
         {
             /// We only want to set the value on the MasterClient.
             if (PhotonNetwork.IsMasterClient)
             {
                 /// Changes to syncvars only are networked if the master changes them.
                 syncedZ = (float)(System.Math.Sin(Time.time) * 720);
             }
    
             transform.localEulerAngles = new Vector3(0, 0, syncedZ);
         }
     }
  3. Add the SyncvarTest script to the Cube

  4. Set the Cube position to (0, 3f, 0)

  5. Build and Run

The values are set on the MasterClient and are synced to other Clients.

Advanced SyncVarAttribute Implementation

The same functionality as above, but some advanced features being used. Interpolation is now enabled, so the remote value automatically interpolates every Update.

using UnityEngine;
using Photon.Compression;
using Photon.Pun;
using Photon.Pun.Simple;

/// The PackObject attribute tells the code gen engine and the NetObject that this class/struct 
/// contains [SyncVar] attributes that should be serialized and synced.
[PackObject]
[RequireComponent(typeof(NetObject))]
public class SyncvarTest : NetComponent
{
    /// THIS IS OUR SYNCVAR.
    [SyncVar(applyCallback = "MyOnApply", snapshotCallback = "MyOnSnapshot", interpolate = true, keyRate = KeyRate.Every10th)]
    public float syncedZ;

    // NOTE: With interpolate enabled, this callback isn't needed for this example.
    // This is just included to show how it would be enabled.
    /// applyCallback fires remotely when the synced value has changed.
    public void MyOnApply(float newValue, float oldValue)
    {
        // This is redundant with the interpolation being true, and values being applied in Update()
        // But is included to show how this callback would normally be used.
        transform.localEulerAngles = new Vector3(0, 0, newValue);
    }

    // NOTE: With interpolate enabled, this callback isn't needed for this example.
    // This is just included to show how it would be enabled.
    /// snapshotCallback fires remotely every simulation tick, whether or not the value changed.
    public void MyOnSnapshot(float snap, float targ)
    {
        /// snapshotCallback gets called every tick, whether the value has changed or not.
        Debug.Log("Applying Syncvar Snapshot " + snap);
    }

    /// We are using NetComponent so we have access to better timing callbacks for networking.
    /// However, this could work in FixedUpdate as well.
    public void FixedUpdate()
    {
        /// We only want to set the value on the MasterClient.
        if (PhotonNetwork.IsMasterClient)
        {
            /// Changes to syncvars only are networked if the master changes them.
            syncedZ = (float)(System.Math.Sin(Time.time) * 720);
        }
    }

    public void Update()
    {
        /// We set interpolate = true for this syncvar, 
        /// so it will get modified remotely before every update
        /// to have interpolation automatically applied, using the Snap and Targ values.
        transform.localEulerAngles = new Vector3(0, 0, syncedZ);
    }
}

To Document Top