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:
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”. 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:
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:
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.
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, 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.
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.
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 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.
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,
Edit:
At the suggestion of skypillow I’ve modified how often the camera adjusts its zoom and position.
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 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.
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. 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:
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. 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:
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.
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.
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.
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.
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,