Download

[TUTORIAL] Smash Bros-style multiplayer camera system (blueprints)

Hello all! I just started learning Unreal a little over a week ago and I’d like to show you something I’ve cooked up. I thought for my first foray I’d get to know the camera system in Unreal. The goal was to make a system similar in function to Super Smash Bros, wherein the camera actor relocates and zooms to keep all characters in view. I noticed that several other people have had the same idea, but there wasn’t a definitive source on getting it to work with 4 characters. I did finally get it to work last week, and although it was pretty simple in retrospect, I thought I’d share my work in case anybody can get some use out of it.

All credit goes to Praseodyl for getting me started with his/her 2-player tutorial, found here. I will be describing how to use that tutorial as a foundation for a 4-player system, so if there’s anything I haven’t covered it is likely covered in there.

A note: This tutorial is intended for beginners like myself, so please bear with me if I’ve made any obvious mistakes. Corrections and advice are welcome!

Another note: I’m starting with the Side Scroller Template with blueprints, as this is a resource everyone has access to.

So, to get started, we want to remove the default camera from the SideScrollerCharacter. To do this, go to the character’s blueprint and simply delete the camera and camera arm. Once you’re done, the viewport should look something like this:

CharacterNoCamera.png

We’ll be making our own camera blueprint, so there’s no need for an extra one here. Feel free to ignore the capsule around his arm, as that’s not relevant to our current project.

Next, create a new blueprint for the camera. To do this, go to your content browser on the SideScrollerExampleMap tab and click “Add New”. Then select the actor class. Name your new blueprint and choose to edit it. Once you are on the tab for your new actor, add 3 components in the box on the left: A scene component, a spring arm, and a camera. Drag the component names to attach them together like so:

CameraViewport.png

The spring arm we added will control the camera’s zoom level, while the camera itself will move to keep all the characters onscreen. Set the locations of the spring arm and camera components to (0,0,0). I set the spring arm rotation to (0,-10,0) so the camera is angled slightly down, but you can adjust that to your preferences.

Now, select the actor’s Event Graph tab. Below is the first addition we’ll make:

9d5ff9cca74716047b60e3fe84ca4a89166d0cd3.png

The first box, Event Begin Play, executes the blueprints attached to it as soon as play begins. The next box, Set View Target With Blend, is telling the attached player controllers (which is all of them) to use the blueprint for the camera we’re working on. Next we’ll get all the actors of the “Character” class and put them in an array. The following For Loop is simply taking each index in the array (4 total, one for each character) and assigning them to a different variable so they’re easier to reference later on.

Next, we’ll figure out which characters are farthest apart. The next two images show a zoomed out image of part of the next section of the blueprint so you can see the structure and a close-up so you can actually read the different boxes.

8b21d0b975965fff8e1c0e94a2135b3fee04f72d.jpeg

320c6f254745244038f36e573978fa671a33035b.jpeg

So, looking at the close-up, we can see what’s going on here. First, we are getting the variable references to every possible pair of characters, then finding the location of each one with the Get Actor Location box. After that we find the difference between the two characters in that pair, change the vectors into an actual length, and find the absolute value of that length. Finally, we input the distance between each pair into an array and, using the Max Of Float Array box, we figure out which distance is the largest. This certainly isn’t the most elegant way to get our result, but hopefully it will make it easy to understand the process. Next we’ll use that result to select which function we’ll use.

Now that we’ve determined which two characters are farthest apart, let’s move on to determining how we’ll move the camera to keep both characters in sight.

BranchFunctions.png

Looking at the above image, you can see that the all of our executions in this section will come from the Event Tick box. This means that every “tick” (or frame) we will be re-evaluating where the camera should be. The first thing we’ll do on tick is grab the index for the largest distance between two characters and stick it into a variable. You may notice that I also took the distance itself and put it into a variable also, but we won’t be using that today. It’s actually a relic from an old method I was testing that didn’t pan out, so you can ignore it entirely. Once we have that index, we’ll use it to determine which function we’ll call to find the location the camera should be at. For example, if characters 1 and 2 are the furthest distance apart, the index will be 0. The == Boolean boxes are testing which index has been returned and outputting true or false depending on the result. Continuing with our example, our blueprint has tested and determined that the index is in fact 0, so the executable line will continue on from the first branch. For any programmers among us, branches are the alternative to if-else statements. For example, the executable line is routed through every branch and is tested at each one. Since the index is 0, the first branch detects that the attached Boolean is true and executes Function 1-2. If the index was 3, the first 3 branches would all return false and move to the next one until we reach the branch attached to the Boolean returning true, at which point the attached function (2-3) would execute. A bit archaic, but it does work.

Next, let’s look at how each of our functions is set up.

Function1-2.png

Each function is performing a relatively simple task: Taking the two actors that are currently farthest apart and finding the exact midpoint between them. We do this by finding the locations of each character, adding them together, and dividing by 2. This result is then put into the New Pos (New Position) variable, which will tell the camera where to move to next.

Once the correct function has been executed and the New Pos has been found, we can adjust the camera accordingly.

SetCameraLocationAndZoom.png

We’re in the home stretch guys! So now that we’ve found where our camera needs to move to, let’s get it on it’s way. As you can see, all of our functions output to set the Focus variable. This variable is actually another relic from yet another failed method, so you can just connect those executable lines directly to the Set Actor Location box. This box will tell our camera where to go once we’ve input a vector (orange line). Before we get to that, notice that after the actor’s location is set we’ll find that location and set the variable Old Pos (Old Position). Now look back to where we’re getting our camera’s destination vector from. What we’re doing here is taking both the Old Pos and the New Pos and using Vinterp To to smoothly move from old to new. On every tick we’re finding the new destination from our relevant function, setting the old position from the camera’s current position, and moving from old to new. Without Vinterp To we’d be doing the same thing, but the camera would simply teleport from old to new. Vinterp To makes this a smooth process. You can adjust the Interp Speed to change how quickly it will move from the old position to the new, but I’ve found 0.8 to be a happy medium between slow/ inaccurate and fast/jerky. Next, we’ll take the same vector that the Vinterp To box is outputting and convert that to an actual length. The Clamp (Float) box keeps the length between the two settings. I’ve set the minimum distance to 700 and the maximum to 2000 because that will keep all characters in view no matter where they are on my stage, but you may need to adjust this depending on stage size. Finally, we’ll take this clamped length and use the Set Target Arm Length to set the length of the Spring Arm we created way back at the beginning of this tutorial. This will effectively set the camera’s zoom level to keep all characters in view at all times.

And that’s it! Congratulations everybody, you’ve now created your very own camera system in the style of Super Smash Bros! If you have any questions or comments, feel free to post below and I’ll do my best to get back to you. Thanks for sticking with me for this whole thing and you have a wonderful day!

Sincerely,

Kasbane

Edit:

At the suggestion of skypillow I’ve modified how often the camera adjusts its zoom and position.

d2a01cf3b4f4bccb9d1645c1439d1221bffc3567.png

Now, instead of adjusting on every tick using the Event Tick box, I’ve instead set the whole adjustment section of our blueprint to fire based on a timer that is started after we set the view target in the first section. This timer fires our new custom event, CameraAdjust, every 0.03 seconds starting from when the match begins. Make sure to set the looping Boolean to true, otherwise the blueprint will run once at the beginning and then never update. This will help with performance because the camera will only check its position once every 0.03 seconds as opposed to once every frame.

Edit 2:

Upon further testing of this setup I realized that the camera doesn’t zoom out fast enough to keep a character onscreen on a larger map.

SetInterpSpeed.png

To fix this I simply created a variable to determine the interpolation speed of Vinterp To, rather than just having it stuck at 0.8. This variable is created by taking the float we used to set the zoom level of the spring arm and dividing it by 600. The specific number can be changed to your preference, though I found that 600 is a good approximation for my purposes. Then plug that variable back in to Vinterp To and you’ll find that the camera is now zooming in and out at a much more workable speed. You may notice that I also made one other change in that I added a multiplication box in between the Vector Length and Clamp (Float) boxes. This just increases the size of the spring arm so the character doesn’t ever get too close to the edge of the screen.

Edit 3:

Okay, more testing revealed that when the characters were on roughly the same level one of them could move off the edge of the screen if they were going fast enough. To fix this, I had to create the following:

6d323216069fe2fc3b1ebf37ab603a5dbe757475.png

All I did was take the previously unused Distance variable that we created way back towards the beginning of the second part and implemented that in the calculation of the spring arm length. Then take the square root of the distance and multiply it by 50ish. As per usual, this can be adjusted to fit your preferences. In all the tests I’ve run this has kept all characters in view at any velocity unless they go straight up, which is kind of what I’m going for since this is supposed to emulate the Smash style, so I’ll leave it unless someone requests a fix.

Edit 4:

The next step is complete! I have finally finished setting up a system that will automatically detect the number of characters and adjust the calculations accordingly, even if that number changes mid-match. Instructions are as follows:

ArrayAssign.png

The first thing we have to do is change how the system references players. I actually got rid of the variable system completely in favor of using an array, since it can be changed on the fly without risk of missing references. This is implemented towards the beginning right after we set the View Target with Blend. I simply used a for loop and and an Is Valid check to see how many characters are in play before adding them to an array.

Don’t forget that since we’ve removed the player variables, we’ll have to update our functions.

NewFunction.png

Adjusting the functions is as straightforward as replacing the variable references with references to specific elements in the array. The 1st player variable would be replaced with a Get node retrieving the value in the Player array at index 0, the 2nd player variable would be at index 1, and so on. You’ll have to update each of your functions.

Now we move on to the meat of the process.

OuterLoop.png

This new system will replace everything between Event Tick and Set Index on the original version, which is mostly a jumble of comparisons. Instead, we’ll use a pair of for loops to take care of all of our comparisons. The outer loop has an easy job - check to make sure the reference is valid (alive), pass it on to the next loop if it is and add null references to the Distance array if it isn’t. The Distance array will contain the distances between all possible pairs of characters.

Once that’s done, the inner loop takes over.

InnerLoop.png

Our second loop starts the same way the first loop did - checking the validity of the current reference. Once again, invalid references are added to the Distance array as null values. If the reference is valid, however, the inner loop gets the distance between the player from the first loop and the player it just checked and adds it to the Distance array.

Finally, now that we have the distances all added to our array, we need to determine the largest one and adjust how we decide which function to use.

NewBranchFunctions.png

This one should mostly look familiar. One important but easily missable addition is the Clear node for our Distance array. This is crucial because without it our array would never reset which would throw things off very quickly. Finally, since the indexes for the distances aren’t labelled nicely in order from 0 to 5, we have to change them. Specifically, the order should now read as 1, 2, 3, 6, 7, 11.

And with that, our camera will now be able to adjust for variances in the number of players, whether that happens at the beginning of the match or somewhere in the middle. Thanks again for staying with me and have a great day!

Sincerely,

Kasbane

You sir, are a legend.

This will spur me on to go back to my abandoned project hah.

Thank you so much! I’m so glad I could help. Best of luck with your project!

Nice tutorial. What about performance issue, maybe it will make sense to use timer instead of tick? I mean check camera every 0.1 second for example.

Thanks! I’ve added an additional note concerning modifying the blueprint to use a timer instead of Event Tick. Thanks for the suggestion!

Im getting the attached error, any ideas? c8ab33ff3faad170a38297c86ebfe85d748d9f08.jpeg .

Is it because i havent added the other players in yet?

That’s exactly it. I ran into the same issue at one point. In case you need help spawning the characters, this is how I did it:

1151d1d36ed367afa2a12c189c7ce6f0f42bd019.png

This is all in the level blueprint. You’ll need to set the Spawn 0-4 variables to where you want your players to spawn in. I just plugged in the locations of my PlayerStarts as the default value, but you could have the system figure those out for itself too.

That’s great thanks, I’ll give it a go.

Pretty cool!

Thanks 94!

first of all great system and exactly what I am looking for. I can’t seem to get it function right when there are only 2 players though

Thanks Mobfolios! Sorry about the super late response, I haven’t checked this thread in quite a while and I didn’t get a notification for your post for some reason. The problem is most likely because the system I built is for 4 players, so Unreal is looking for the 3rd and 4th players that don’t exist. If you remove all references to players 3 and 4 then you should be set. Also, the tutorial from Praseodyl that I linked to in my original post may be of assistance, since that one was built for only 2 players. Here is the link in case you’re interested. Feel free to PM me if you have any other questions, although it might be best if we keep this thread updated in case someone else can benefit from it later on.

Edit: Also, I’m working on a subsystem to automatically detect how many players are in the match and adjust the calculations accordingly, so I’ll update the tutorial whenever I figure that mess out.

Camera work can be tricky as I’m sure you’re finding out. Good job on this project.

It sure can. Thanks Distul!

Hi! This camera is really great,but I’m having an issue… after I leave the game open for a while,it starts lagging quite a bit, like some sort of memory leak. Anyone else get this?

Thanks!

I found what was causing the lag.

LVrpfHR.png

If you cut off all the nodes except the first one relating to the first player, the camera works fine. But if you allow the camera to be actively searching for all four players when there are not four players in the game, the lag occurs. So in order to fix this you have to figure out a way of having the camera detect how many players are in the game and then adjust its settings accordingly. In my case, I’m probably going to make 4 different cameras, and have them switch around whenever a player drops in and drops out.

Thanks

Hello Kasbane,

First of all I want to say that this is a great tutorial.
i wanted to mention that I have some strange bugs testing this camera system and I wanted to ask you if you know what causes this? I made a short video (which is a bit laggy because of the screen capture program)
https://youtube.com/watch?v=2BiXodEn2Z0

I hope you can help me solving this.

Thanks in advance

Thanks paragon and Qwerty! Glad you figured out what was going on paragon. I’m still working on finishing the player auto-detect system, so I’ll update the tutorial as soon as that’s done.

Qwerty, I’m not sure why that’s happening to you. I haven’t experienced that issue before. Can you post some screenshots of your blueprints so I can take a look at them?

Hi, thanks for the reply :3
I already set up multiple cameras,and the work perfect. Maybe I’ll switch over to your auto detect one,but for now these work greatly :slight_smile:

Sure

54f4d4c1e7823126285508962c388b2851dc4c31.jpeg
ab2c0b2f9889158238fc74e658e6bd689c16b19f.jpeg
cea7518ae4f409aa245d96114bc6bfb700aa7731.jpeg
01fefdccb85547f0430e1a21e380e3bfeac98b9b.jpeg
91458f608c2b84df4969311f1c2cd362f654890e.jpeg
450fdf260b640cc636a5dce10ff47a7fd7b5156e.jpeg
cd4949fe881ab3ebaa8b69b387f913cfa3df37c0.jpeg
f9801478b34711874944d6c9ffb9f62412618a0d.jpeg
94e296818ac5944fe8f37d4ec5d7be72739b5c7d.jpeg
fc43bf2cbd296c7a66491a733e42e920739c6585.jpeg
a4ba0fcbd3ce6acf79ff28188458212b6d3ecfc1.jpeg
e20cc027560be29248a810cdf86c0a3e0f53725f.jpeg
CameraFocus_Viewport.jpg

And I simply dropped the BP in the level. I think the camera somehow rearranges at these moments…maybe I set up something wrong, but I’m relatively sure I did everything like you. I hope the screenshots help you helping me :slight_smile: