top of page

Summary:

Vibe City is a team project made over the better course of half a year in a team of 7. 3 Programmers, 2 Designers and 2 Artists. Among the programmers, we split up the work loaded such that, we had a VFX/Shader programmer, Progression and Customizaion programmer, and Gameplay and networking programmer. I took on Gameplay and networking. For the networking library, we used Photon Unity Networking, PUN for short. The choice was made primarily because of bad experience with Mirror networking and a want to expand my skills.

Aside from programming, I also had to do a bit of animation & modeling work for the player and environment as well as placing the objectives around the map and decorating the main menu. You can read more down the bottom of the page!

There are many things I could talk about this project. And this is but some of the things I have done.

Lobby System:

The lobby system controls how a user creates a lobby for their friends to join and the setup and starting of games. This worked through 4 different managers. The RoomManager, LobbyManager, ModelManager and GameManager. The biggest struggle with creating this system was its requirement to work in both local and networked multiplayer. Yes, Vibe City contains split screen, couch PvP.

The GameManager is used to store data in its public static values for the up coming game. In this way, it is simply a storage device for the Lobby System.

The RoomManager controls the creation and joining of rooms as well as managing passwords. It starts off with the CreateRoom function which does not, in fact create a room. The naming conventions are somewhat broken due to the need to update the systems constantly. Otherwise, the RoomManager prepares the passwords and tells the GameManager if the game is networked or not. If its a local game, then the room manager turn the LobbyManager on instead of going through the room creation process. From here, a few other functions are used to create & join lobbies with and without passwords.

Photon does not have any understanding of passwords, so when joining a lobby, the client must ask the host if there is a password. If there isn't don't leave as the host will set the client up. If there is, wait for the client to send a password. Wrong = kick, Right = Host sets up client. There is a caveat to this system. To send and receive data from the host, you must join the lobby. Thus, if a client joins a passworded lobby, they can hold a slot in the lobby hostage by never entering a password. Please don't actually do this I beg you.

The LobbyManager controls the setup of the game and the joining of new players. It primarily syncronises the player information to clients but most importantly the GameMode, using a Factory system. Factories are basically read-only data that is assigned by a developer in the editor to ensure its consistent between clients. It also has a play button to start the game. The LobbyManager does the most memory manipulation on the GameManager, setting the game mode, sorting teams and setting the number of teams.

The ModelManager has a single purpose, spawn the players characters in the lobby and make sure none overlap. When a player joins, a message is sent to the ModelManager to send the new client a slot to spawn their model and locks that spawn location. In local play, it just spawns the model there. When the client leaves, the slot is freed.

There is 1 extra class for QoL, the RoomBrowser. This class is very simple and just displays already created lobbies with a pretty join button. Its disabled once a Lobby is created, so in a local game since the RoomManager skips strait into a lobby, it never turns on.

So in summary, the RoomManager & RoomBrowser creates or joins a room which launches the LobbyManager & ModelManager when it does. While storing important game information in the static values of the GameManager.

Character Controller:

The player character runs using a custom state machine. The state machine makes use of scriptable objects to store the states & transitions, with a monobehaviour for running the states per instance of player. Because the game needs to be able to run multiple state machines simultaniously, any per instance data must be stored on the CharacterController to avoid players reading and editing each others data. Readonly data is otherwise fine.

These states additionally make great use for dead reckoning. All we have to do is remember to return from the state before we start writing per instance data since that data will be networked.

One of the flaws of the player character is that they use a rigidbody for movement. This was ddecided upon early by the rest of the team. The difficulty with rigidbodies is Unity. Its difficult to clamp velocity since the ApplyForce function does not immediately change the velocity, this happens in Unity's internal FixedUpdate loop, alongside the movement. As such, I need to manually calculate how the velocity of the player will change when a force is applied.

Another flaw is the seams issue. If two colliders are perfectly level and perfectly flat, if a rigidbody moves over that seam, it will bounce. Luckily, the player is almost always airborne with the grapple hook. Unluckily, the objectives are on the ground meaning seams can be a big issue. And there is almost nothing that can be done about it. There is a solution by reducing a value in the Project Settings but all that does is reduce how big the bounce is.

I have preference for character controllers. Even then I prefer my own because Unity's has its own draw backs. 

Grapple Hook:

When we started, it was decided that we were going to use a Spring Joint for the grapple hook. This comes with a few draw backs I had to work around. A spring joint cannot be disabled, so instead we must destroy and create it when we are or are not grappling. For this, it means we must manually store the spring joints data ourself and put it back when its created.

The second issue is an odd one. When creating a spring joint in run time, its local anchor position is automatically set. For a while early on in development, we had an extremely weird bug where the grapple hook was seemingly defying gravity. What we later found out is that this was caused by updating our model. When we changed from a capsule to a model, we deleted the mesh renderer & filter off of the player and parented the new model onto it. For some unknown reason, when the spring joint is created it uses the mesh filter to place the local anchor. If there is no mesh filter, it seems to just pick a random values, placing the anchor 100 units away.

Ok, so add the mesh filter back. The problem persisted. What we found was that the mesh filter and mesh renderer have to both be on the player. And they have to be the top 2 components, specifically the top 2, on the player. Otherwise the anchor would break. Now aint that fun to figure out.

This later came back to cause bad code when networking it. The player is run through a state machine with the grapple hook being fired using a transition that never passes. In order to network the grapple hook, it was easy to send the data over, the problem was creating it. The grapple hook spawning code was in a transition. The serialization function was on the PlayerController. To spawn the grapple hook, a Ctrl+C, Ctrl+V had to go in the serialization.

Networking with Photon:

In the past, I have networking with Mirror and Photon was a nice change of pace. The PunRPC attribute makes setting up networked functions easy. Photon has a few of useful tool to help network stuff. RaiseEvents: A global event that can be called on all or specific clients. Custom Serialization: Photon lets you create custom to and from byte[] for networking custom types or un-supported types.

In Vibe City, RaiseEvents are used to syncronise timers and when clients are joining, to request and send password related information. This is required because the RoomManager creates rooms and handles passwords but itself is not a networked class. However, the RoomManager can hook into Photons RaiseEvent callback to get this information and reply to the client.

Custom Serialization is used vary sparsely in Vibe City as most data types are already covered. I had to create custom serialization for uint, sbyte and PhotonView funnily enough. It did make RPC's much easier to network since we could pass gameObjects much easier.

During Vibe City, I was the network programmer as the other 2 were either scarred by Mirror and/or did not want to do it. In terms of gameplay, the level was primarily static with players in it. The only things that need synchronization were the game manager, players and their abilities.

Networking Abilities used a Factory approach. Every ability exists in the AbilityFactory and thus, every ability can be converted to an integer. When an ability is cast, it is converted into an integer and sent to all other players. Other players then convert that integer into an ability and cast it on/from the casters character. Inside the code for each ability is a local player check. Only the local player, the caster, can run the code beyond that check. This way an ability such as EMP Missile will use Photons instantiate function to spawn it on all clients after the local player check to avoid more missiles spawning.

AbilitiyCastingNetworkewd.PNG

Networking Players was probably the easiest at its simplest. Attatch a PhotonView, PhotonTransformView & PhotonRigidbodyView and we are technically done. This results in somewhat jumpy movement. As such, within the players state machine we add dead reckoning using the local player check from earlier. The code in the states remains almost identical but with a local player check to avoid overriding networked data.

Art & Design:

In the project, I did a little bit of art work. Creating the fancy and not UV'd at all I-Beams! Look at them in all their solid colour and low polly beauty. You can find it in the construction district & in the customization menus background!

Aside from basic modelling, I animated the players grapple hook arm while grappled. This worked using an avatar mask that covered the characters left arm. From here I created a series of 1 frame animations using Unity's inbuilt animator for all the way to the left, right and directly in front, above and behind.

IBeam.PNG

This then got placed into a 2D blend tree. With left and right being X and up and down being Y, with forward being (0, 0). Then, all I had to do was, in code, adjust the X and Y value to represent how right a normalized vector to the grappled location is. Repeat for Y except up. Both of these checks can be performed using the dot product using normalized vectors. And this is what you get.

ArmBlend.PNG

I'm not sure if this would be an art or design task so I'll put it in the middle but I created the background and transitions for the main menu. The Bezier path code was written by our shader programmer, and the customization and achievements menus were created by the other, so don't give me credit for those.

While making it, I wanted to make sure that each menu showed Vibe Tower in its greatness. Each of the menus them self also represented a district within the game. Main Menu was residential, Customization construction with Achievements and Lobby representing the botanical district. I thought it was a nice thematic touch. Though I did my best, if you look carefully during the transitions, you may notice the decorations for the other menus but don't pay heed to it. I'm sure you didn't spot it first time.

For Design work, one of the first things I did was design the original concept for construction. Why did I do this? We had our first big showcase coming up, Alphacon, and we did not have a level yet, so I spent my own home time working and building it. It was a busy, compact place, filled with twists and turns (and a few too many cranes might I add). Now you may ask yourself, if this is so good, where is it? This variant of construction had to be cut due to updates to the islands geometry and required a re-design since my design was designed around flat landscape. Thou bits survived, it was ultimately replaced. Sadly, the cranes have been broken due to changes to their prefab. So if they look jank, its because this is old relative to the cranes.

During the final days, I helped the designer place billboards around the island, the surfaces you tag to get points. I did it for the Botanical District.

bottom of page