In this article you’ll learn how to build the foundation for a multiplayer drawing game. We’ll start with drawing on a texture and add some Unity UNET network components to make it multiplayer. By the end, you’ll see other players drawing in real time and have a good foundation to build your own drawing game in Unity3D.
To start, we’ll need a couple pieces of art.
First is our default/blank canvas.
For this example, I’m using a solid white texture that I put a little border around.
Download this canvas or create your own
The other thing we need is a color wheel.
Download this wheel or find your own
Add both of those textures (or alternate ones you choose) to your project in a Textures folder.
Setup the Canvas
Create a new Quad.
Resize it so it looks similar to this.
Drag the canvas texture you’ve chosen onto the Quad.
Change the material shader on the quad to Unlit/Texture.
Make sure you select a resolution and don’t use free aspect while you’re setting this up. I”m using 1920×1080.
Name it “Paint Canvas”.
Create a script named PaintCanvas.cs and replace the contents with this.
This script is used for two things. First, it makes a copy of the texture in memory so we can easily read and write to it. This texture is created in PrepareTemporaryTexture and assigned to the static property “Texture”.
The second thing it does is set the texture data from a byte array. This is so we can ‘load’ a texture from the host when we join the game as a client. You’ll see this later when we get into the networking code.
Add the new PaintCanvas component to the “Paint Canvas” Quad.
Back to the Inspector
Your PaintCanvas should look similar to this.
It’s time to setup our color picker so the player can choose different colors easily.
Create a Quad
Name it “Color Picker”
Drag the ColorWheel texture onto the Quad.
Change the Shader to “Unlit/Transparent Cutout”
Create a new script named “ColorPicker.cs” and replace the contents with this.
Add the ColorPicker component to your “Color Picker” object in the inspector.
This script starts off with a static “SelectedColor” property. This is where we’ll store the users color selection when they click on the colorpicker.
But the work here is done in Update(). We check to see if the user has clicked this frame, and if so, we do a raycast from the click point into the scene).
If the ray intersects with something (line 17) and that something happens to be the color picker (line 20), then we pull the texture coordinate from the RayCastHit multiply it by the height and width of the texture, and get the actual pixel on the texture that we clicked.
With that pixel, we use the Texture2D method GetPixel to get its color and store it in that “SelectedColor” property (line 32).
Finally, we set the material color of our “preview” object to the selected color (line 34). This is where the user will see the color they’ve picked and have “selected”.
Back to the Inspector
Your color picker should look like this.
Move the wheel so it looks like this.
Selected Color Preview
If you look at the Color Picker code or inspector, you may notice that we have a “Selected Color Preview” field.
Create another Quad and position it just below the wheel like this.
Create a new Material and name it “SelectedColor”
Assign the material to the “Selected Color Preview” Renderer
Change the Shader to “Unlit/Color”
We don’t want light having any impact on how our drawing appears. Unlit/Color makes that not an issue.
It should look like this in the inspector.
Brush Size Slider
Let’s also add a brush size slider.
Create a new Slider from the GameObject->UI menu.
Move it so it looks like this. (I also added a text object at the top, feel free to do that too if you want)
Create a new script named “BrushSizeSlider.cs” and replace the contents with this.
This class is really just wrapping the slider value so we don’t have to reference it anywhere in the editor. Because our player is network instantiated, and we don’t have any objects in the scene that would make sense referencing this slider, we’re just putting the BrushSize into a static global int. This isn’t the best architecture for something like this, but we’re not building out a full game or editor so it’s a quick enough way to get things working.
Add the BrushSizeSlider component to your slider GameObject.
Your slider should look like this.
Save your work to a scene!
Player & Brush Time
Now that we have the board setup, it’s time to create the player & brush.
Create a new script and name it “PlayerBrush.cs“.
Replace the contents with this.
This is the most important part of the project. Here, we’re doing the drawing and networking. (ideally we’d split this into 2 classes soon)
The Start method is only called on the server/host because of the [Server] tag. That’s because the Start method is getting the texture data and sending it to the client. This may be a little confusing, but remember that the “Player” will be spawned on the server, and this method is being called on the player gameobject but only on the host/server.
RpcSendFullTexture is the opposite. It uses the [ClientRpc] tag because we only want that method being called on the clients (it’s also required to send the command to the client). This method calls the SetAllTextureData method we covered earlier, setting the data of the client to match the server.
.Compress() & .Decompress() – These methods are extensions that you’ll see in a while. They’re used to compress the texture data into something much smaller for network transmission. The compression here is extremely important, without it, this wouldn’t work.
When you look at the Update method, it should feel familiar. Like with the color picker, we’re checking for left mouse down, doing a raycast, and checking to see if the click was on what we wanted. In this case, we’re looking to see if they have the mouse down on the PaintCanvas.
If they do, we get the texture coordinate (like before), get the pixel x & y, then we send a command to the server using CmdBrushAreaWithColorOnServer. We also call BrushAreaWithColor on the client (if we don’t do this, the pixel won’t change until the server gets the message, handles it, and sends one back. this would feel laggy and bad, so we need to call the method on the client immediately).
CmdBrushAreaWithColorOnServer doesn’t really do any work itself. It’s a [Command] so clients can call it on the server, but all it really does is tell the clients to brush the area using an Rpc. It also tells the server/host to draw using BrushAreaWithColor.
BrushAreaWithColor is responsible for changing the pixels. It does looping over the brush size to get all the pixels surrounding the clicked one. It does this in a simple square pattern. If you wanted to change the brush to be circular or some other shape, this is the method you’d modify.
Back to the Inspector
Create a new “Empty GameObject“.
Name it “Player”
Add a NetworkIdentity component to it & check the “Local Player Authority” box.
Add the “PlayerBrush” component.
Your Player object should look like this.
Drag the Player object into a folder named Prefabs. This will make your player a prefab so we can instantiate it for network play.
Error! – An Extension Method!
The playerbrush script references some compression methods that we’ll use to sync data to the clients when they first connect.
To add these compression methods, let’s create a new script for our extension methods. If you don’t know what extension methods are, you can read more about them here: Unity Extension Methods
Create a new script named “ByteArrayExtensions.cs” & replace the contents with this.
I won’t go into the details on how this compression works, but this is some standard c# code for doing a quick in memory compression of data. These are extension methods that will ‘extend’ the byte arrays. It’s essentially just a cleaner way so we can write somearray.Compress() instead of ByteArrayExtensions.Compress(somearray).
The last thing we need is a networkmanager.
Create a new gameobject and name it “[NetworkManager]“.
Add the NetworkManager component to it.
Add the NetworkManagerHUD component to it.
Expand the NetworkManager component.
Drag the “Player” prefab from your Prefabs folder (NOT THE SCENE VIEW ONE).. onto the “Player Prefab” area of the component.
The work is done, Save your Scene!
Testing the game
Now that everything’s setup, it’s time to test.
To do this, create an executable build (make sure to add your scene to the build settings).
In one instance, start a LAN host, then the other can join.
If you want to download the full working project sample, it’s available here: https://unity3dcollege.blob.core.windows.net/site/Downloads/MPPainting.zip
If you want to go beyond LAN mode, you’ll need to enable Multiplayer in your Services window. It will take you to the Unity webpage where you’ll need to set a maximum room size. For my test I went with 8 players.
Conclusion & Notes
This project is meant only to be an example for UNET & drawing. To keep things simple, I used quite a few static fields, which I’d highly recommend against in a real project of any size.