Ascension is a multiplayer online first-person shooter game, for which I was the lead programmer. I built the multiplayer framework utilising the features of Photon’s PUN plugin, and did the majority of the game-play programming.

Here is a video explanation I created for our final group game hand-in during my second year at university.

Though I was rather happy with the finished project (other than the lack of art), soon afterwards I came across a better way of handling timers.

We needed a round timer to tell players how long was left in each round (and handle ending the match), so I created a rudimentary one wherein the master client (host), would send a message to every client letting them know what the time is, at regular intervals. Between these intervals however, each client would estimate the time themselves. This approach raised problems. One was that each client’s time would still be slightly different due to differences in ping with the host. I fixed this issue by having each client compensate for their ping. Overall this worked really well.

Extract from RoundTimer.cs

// Every sync seconds sync player timers to host
        if(sync <= 0)
        {
          GetComponent<PhotonView>().RPC("SetTime", PhotonTargets.Others, timeLeft);
          sync = syncEverySecs;
        }

That being said, when I came across another approach to this form of timer, I felt rather daft. This better way of doing it had the master client announce a timestamp, and each client would get a copy of that timestamp when they first connected. They would then simply reduce time themselves. This resulted in perfectly in-sync timers across all clients, and meant only one network message had to be sent per player, instead of the message every few seconds I was previously sending for every player.

Extract from an unreleased game I’m working on

    void MasterClientUpdateTimeStamp()
    {
        // set starttime
        timeStamp = PhotonNetwork.ServerTimestamp;

        ExitGames.Client.Photon.Hashtable ht = new ExitGames.Client.Photon.Hashtable()
        { { "startTime", timeStamp } };
        PhotonNetwork.room.SetCustomProperties(ht);
    }

    public override void OnPhotonCustomRoomPropertiesChanged(ExitGames.Client.Photon.Hashtable propertiesThatChanged)
    {
        if (PhotonNetwork.isMasterClient) { return; }

        if (propertiesThatChanged.ContainsKey("startTime"))
        {
            timeStamp = (int)propertiesThatChanged["startTime"];
        }
    }


/// elsewhere
    void Update()
    {
        elapsedTime = (PhotonNetwork.ServerTimestamp - timeStamp) / 1000;
        timeLeft = beginningTime - elapsedTime;
    }