Transcripts
1. 00 Intro: This is what you'll be building a complete shoot them up in the Unreal engine five
entirely in blueprint. I'm Rob, and over
these 27 topics, this series will take you
from an empty code base to a finished playable game. We'll be creating our own
fully customed player movement with framerate independence, using the enhanced
input system and covering things like
collision detection in depth. We'll be implementing the
pawn classes for the enemies and the player planes with
a focus on inheritance, so one base class, which
powers every ship type, adding a projectile system
that will work for both sides, a single class, which
is fully customizable. Even the spawning classes that
we create are set up with an architecture which will handle enemies and backgrounds, and you can extend
this however you like. We'll also be taking a
small look at things like design and just juicing up the game with
particle effects, audio, camera shake,
and even knock backck. We'll be adding a
complete game loop, so you have a playable
game by the end of this, including your user interface. You can play, die,
restart, or quit. The focus is going to be
on doing things properly. We're going to be looking at clean architecture,
reusable systems, and actually understanding
why something works or doesn't instead
of just copying the notes. Every decision gets explained. I'll purposely be skipping built in features
inside of Unreal, and then tell you why
we take that choice. When something is counterintuitive,
I call it out and all of the assets that
you need to follow along and get this final
results are provided, meaning that you can just focus on the programming and getting that good project structure down. I've handled
the art for you. Your class project
is going to be this finished game
fully polished, juiced up and ready to expand
into a portfolio piece, along with any new features
that you decide to add. There could be new
enemies, weapons, a scoring system,
whatever you wanted. The architecture is
going to be laid out in a way for you
to build and extend. For example, I've taken
the project to look something like this
over the space of a couple of months
and adding a bunch of new features and assets
to an extended project. The first video is
going to be covering the project setup. I look
forward to seeing you there.
2. 01 - Setup: Most unreal projects become impossible to navigate
within a month. Assets are going to be scattered everywhere, no
naming conventions, and you end up spending most of your time searching rather
than building anything. I've created this series with an intention to fix
that from the start. I'll be helping you
build a complete shoot them up from scratch, where every topic
will be focusing on getting things done properly. This is good structure,
good naming, good architecture,
skills that will transfer into any project
that you work on. We'll be working towards
building the game that you can see in the
background here based on official content created by Epic in their content
examples package. If this is something that
grabs your interest, then let's get going and
set up that foundation. First, I'd recommend
that you navigate to the download link
in the description. You'll see two
options here. We have shoot them up underscore assets. This contains the rule
meshes and textures in case you wanted to modify anything
in blender or Photoshop. Then you have the shoot
them up, underscore tutorial underscore start, and this is the
complete project. And this will be the one
that you want to follow along with the topics
that we run through. By default, this
will be provided as a zipped folder for you. So you need to make sure to extract this somewhere sensible, and starting from
the same project simply means that we're working
with identical settings, the same assets, working at
the same scale and so on, which will make
troubleshooting a little bit easier if
you do run into issues. The first thing to look at inside of the extracted folder, go into the shoot them up
underscore tutorial folder. By default, the project is
in Unreal Engine 4.27 for maximum compatibility if you wanted to work with a slightly
older engine version. If like me, you're
going to be upgrading this to work with the
Unreal Engine five. The upgrade takes
about 10 seconds. On Windows, you can simply right click on this dot U
project just here, and you have the
option to select switch Unreal Engine version. From the drop down, pick
the installed version that you want to work
with and then click Okay. You're ready to go. I'm
currently working on Linux, so I'll show you an
alternative approach. This is useful, anyway. This will work on Windows,
and it's actually a really useful thing to
know about because this can help you fix projects that
wouldn't otherwise open due to things like missing
plugins, plug in issues. So to do this, I'm just going to right click on the
dot U project. I'm going to go to open W, and you can pick any text editor. This could be Notepad VS code. I'll be working with Zed, and we can see here on
the left hand side, if we look at the
Engine association, it's telling us that
this was made with 4.27. We can just change this. I'm going to be
working with 5.6, which is probably
outdated by the time that you're watching this,
but it will be close enough. With that done, like
with any text editor, we can just save this. We can close the window,
and that's now ready to go. To get the project started, we simply double click
the dot U project, and this will begin
loading in the background ,atic on Linux, double clicking a U project by default doesn't actually
launch the Unrel engine. If you wanted that kind
of integration along with my custom
UnreLEngine installer, this will do things
like finding all of the locally downloaded or
installed versions of Unreal, adding them to an
automatic launcher here, and we can just go
and Impress Okay, and we can load the
fairly standard launcher that you'd normally get
with the Unreal engine. It's not really the main topic of what we're going
to be going through, so I'll provide an
additional link to this. You can check that in
the description below. I'll be providing the scripts
completely free of charge, so you can download
the scripts as well. And that will just go
more in depth than what I needed to do to get
Linux up and running, ready and working pretty seamlessly with
the UnreL engine. Now that we're inside of Unreal, your interface may look a
little bit different to mine depending on which version
you've decided to work with. Epic tends to reorganize things roughly every time someone
gets a little bit bored. But we can just focus
on the concepts, not the exact buttons
and the locations. They definitely will exist, and you'll be up and
running quickly enough. The first thing to
check out is the content browser or
the content drawer, which is in the bottom
left hand side. This is where everything
in your project lives. We can click this and we can
see our project structure. Inside of the content folder, there's a subfolder that
matches the project name, which is shoot them up
underscore tutorial, and this is Epic's
recommended naming convention for projects and the
folder structures. Inside then, we have an
assets folder which holds things like our meshes,
textures, and materials. We have a blueprint folder
for our code and logic, and we have a Maps
folder for our levels. This is a simple structure, but it scales to
any project size. The other thing you may notice if you're browsing
through some of the folders are things like the naming conventions
which have been chosen. We have things like MM
underscore texture, which stands for
master material. We then have MI underscore, whatever the material is for, and that is material instance. If we go to the textures,
we have things like our T underscore plane hero,
and that's T for texture. The place I'm
guessing all of these naming conventions
from are not random. These are all based
on the AA style. You want to Google that,
you can find that here, so we can just search
for Style Guide, and we're looking
for the Github page, the first one just here. A link for this will be in the
description below as well. Naming things is
supposedly one of the too hard problems
in computer science, and this solves one of
those problems for you. I'd recommend bookmarking
this for reference. And then whenever you're
creating a new type of asset, if you're not sure what
to call it, check here and through repetition,
this will become automated. It's a really good
resource, though. It provides pretty much
everything you could want. You don't need to remember
everything at once. There's quite a lot
of things in here. But if we look down at the
general naming conventions, we've got things like skeleton
meshes, would be SK Bob. The material for Bob
would be M nscore Bob, texture for Bob would be T
underscore Bob, and so on. If you look at blueprints, we've got things like BP underscore, and then we can
also check things like the file and
folder structure. So exactly as I've
mentioned here, the name of the project, and then all of the folders in a somewhat logical,
meaningful setup. So this really is the holy grow of how to structure
your projects, how to keep things tidy, and
how to organize as you go. To get started, we need a
main level to work with him. Before I dive into
that, there's something I want to do and introduce you to just to make
things a little bit easier for you
to see my screen, but this may be useful for
you to know about, as well. So I'm going to go up to the
edit settings just here at the top of the screen and
go to editor preferences. This is where we can
change different features about how the editor
is presented, the shortcuts, key bindings, and things like that can
all be found in here. The thing that I
want to change is the application scale here. I'm just going to
change this to 1.25. This is purely because
I've got quite a large screen set up, so it may look a little bit
stretched and the buttons may look a little bit small
for you to follow along with. So as I've said,
this is optional, but it's useful to
know this exists. And there are many more
things that you can do inside of the editor
preferences to tweak the general look and feel of your editor if something isn't quite right for
you to work with. This saves automatically though, so I'm just going to close
the window, and we can now get into creating
our first level. This is nice and simple. We can go to the
top left hand side. We have our file button, and we've got the option
here to create a new level. One thing to look out
for are the shortcuts. We can see all of
the shortcut keys. New levels are something we tend to play with quite a lot. So this might be
one that's worth remembering early on to save
you a little bit of time. Inside of the new window, we're just going to
select the basic level, not the open world. That's going to be far too complex for what
we need right now. And it also takes longer to load in the editor every
time you start backup. So we'll double click on Basic, and we're now inside of
our new basic level. Then let's make sure that we've saved this
correctly, as well. By default, this is
called Untitled, meaning that we haven't yet
saved it. This isn't stored. If we close the project and come back, we'll lose our progress. So we're going to press
Control in S inside of the Viewport here,
Controlling S for save. We're going to drop down into the Shootm up tutorial folder, go to the Maps folder, and
we'll call this one main. We can give the name
just down here. Hit Save or Enter, and
that's automatically saved. Inside of the content drawer, we can go to our Maps folder, and we can see our
map is just here. Quick thing to note. UnreL uses the word map and level
interchangeably. Same thing, two different names because of course, it does. We can see that here, in
fact, this is called a level, but when we make
use of this later, Unreal will ask for us
to set a specific map. Another thing to note and something else
we'll be working with quite often is the
content drawer. So rather than pressing
the button down here on the bottom left hand
side constantly, we can press Control in space, and this will toggle the
content drawer open close. And this bit is
really important. This is where beginners
always lose their work, or at least they
think they do until they come back and
get some help. Without this step, you'll close your project, you'll reopen it, and you'll find yourself
staring at an empty, open world map or something
you weren't expecting to see. Your main label
does still exist, but Unreal doesn't know that it should be opening by default. So to change this
behavior, we're going to go to the
project settings. We'll go to edit and
then project settings. On the left hand side,
in the top category, we want to find the
maps and modes option. And then in the right hand side, you see the editor startup map. Change this dropdown from template default to our main
map that we've just created, and this tells the
Unrel engine which level to open when you
first launch the project. We could also set
the game default map here to be main as well. This determines what
the player sees if you actually package the game and hand this to somebody
else to play. These settings all autosave,
so we can close this. We can reopen the project, and this means that
you'll return exactly where you left off as
long as you've had a chance to save the
map you're working in. So this may seem like a lot of extra work and a lot of talking
just for the first topic. But these fundamentals
really compound. An organized project will stay manageable at the
10 hours of work, 100 hours, even 1,000 hours, whereas a messy
project just doesn't. And that's really
the goal and focus of this entire project
based approach. Things may take a little
bit longer, but by the end, you'll understand
what you're doing and hopefully take away
some good practices and make your future
projects much more intuitive and
easy to work with. But with that said, the
foundations are nice set. The project structure,
naming convention, and settings are all
configured correctly. The next topic will be
creating our player pawn, which is something you
can actually control. And as I've alluded
to, I want you to actually learn what you're
doing as you go through. One of the best
ways to learn isn't just by following
along step by step. Once you feel that
you understand something, try extending it. Even for something
as simple as we've done now, add your own folders, rename some things, and
just prove to yourself that you understand the
pattern, not just the steps. I'll be guiding you
on that throughout the course of what
we're doing anyway, but there's only
so much I can do, and a lot of the work will
come from self learning. If you make changes
that work well, drop those in the comments
and discussions below. And if you think you understand something and see somebody posting a question
that you think you can answer,
try answering it. Explaining something
that you've just learned is one of the quickest
and best ways to actually lock the
new information in and ensure that you don't
forget that in the future.
3. 02 - PlayerPawn: The projects now set up, and we need something to actually
begin controlling. By the end of this
topic, we'll have a player pawn with vigils.
It won't move yet. That will be coming next, but
we will understand exactly how and why Unreal decides
to spawn things and where. One of the core things in
development is planning ahead and really thinking
about what your current goal is and
how to achieve that. So quite simply,
from the videos, we know that we want
to be controlling this small character in
a little blue plane, to ensure that nobody's
getting left behind. If you're completely
new to everything, I don't want to throw
complete jargon out there. So I'm going to show some
really basic things. The plane that we're looking at is something called
a static mesh. So if we go to the
assets meshes, we can see here this
is a static mesh of our little character
in the blue plane. So we want to be
controlling that. If we threw that
straight into the level, we wouldn't be able to
control a static mesh. We need to make this
code based and use a type of blueprint class at the very least
to get this going. Inside of Unreal, this is
referred to as a pawn, something that we can
possess or take control of, essentially, just a
special type of actor. So let's go ahead
and create our pawn properly with our good
folder structure. So I'm going to go into
our Blueprints folder. Inside of here, we want
to create a new folder. We can right click and search for the new folder option here. I tend to use the
Control Shift and N shortcut to create a new
folder there. So N for new. I'm going to rename
this folder to CR. This will be our core gameplay mechanics or gameplay features. And then inside of this
big folder structure, if we right click, we want to go up to the blueprint
class option just here, and this will allow us to
create a new blueprint, and we can see a range of
different options here. And this is where
understanding Unreal's class hierarchy
can begin to help. In short, everything inside of UnreL inherits from
something called an object. This is just a data container, the most simplified class
that we have access to. We quite often then see
something called an actor. Everything in the
level at the moment is at the very root an actor class. We'll have static mesh actors, camera actors, light actors, but they're all
actors at their root. Adds a physical presence
on top of the object data, which is a location,
rotation and scale referred to
as a transform. And then the pawns that we're going to be working with are just an actor with the
functionality to be possessed. You'll see in some tutorials, they will use something called
a character class instead, and that would be
overkill for what we're looking to make
with this shoot them up. Characters include things like walking mechanics, gravity, capsule collision,
stuff really designed for humanoid characters
walking around on foot. We're going to be
making a spaceship, so we don't really
need any of that. And it comes with a
lot of extra overhead that we'd need to actively work. So you may see other tutorials
creating things like a tank controller using
a character class, and this is just where
being aware of things like this would allow
you to consider that that may not be the best solution for what
you're trying to create. Because pawns are
quite a commonly used actor or type of class, we can see that's actually up
here in the common section, so we can click on this and this will create a new
pawn class for us. And we want to call this one
BP underscore pawn base, because this will be
the base class for any pawn related classes that
we create in the future, allowing us to reuse some of the logic for
our enemies, as well, naming it for what
it is in general, not just what it's meant
to do first of all, which is going to be working
as the player class. Before we go any further,
another quick aside here, quick, quality of life fix. UnreLEngine has some
strong opinions about window management,
and they're mostly wrong. So to fix one of the things
which is about to happen, if we double click to
open our blueprint now, this will just create
a floating window pretty much anywhere
on the screen. This can be quite annoying. It means that
windows can get lost behind other applications
and things like that. So if we go back up to edit
and then editor preferences. Same place that I showed
you previously where I changed the application scale. As I've mentioned,
we can do a lot of quality of life
fixes and changes. I want to find is under
asset editor open location. We want to change
this from default, which is open wherever, and we're going to change
this to main window. You can try different
things and see which may work best for you. I
quite like main Window. It's kind of like working
in a web browser, then, where each new file, blueprint, asset, whatever
it is you're working with. Whenever you open a new window, it will create a new tab and dock them kind of like
bookmarks at the top. So again, this automatically
saves, we can close this, go back into the content drawer, and then double click again
to open the pan base. You can see that opens up here, which is exactly what I wanted. If you've done the same as me, where you may have
opened this twice, you may be greeted with this
simplified data only view. And that's perfectly fine.
We don't need to panic. This is all of the class details presented to us essentially
in text or drop down form. To get back into the
full blueprint editor, we just want this
blue line of texture, the open four Blueprint editor, and that will bring us back
to where we want to be. Okay, so there's a lot of information that I could front
load you with right now, but you'd probably
forget most of it. So we're going to take a kind of more organic and natural way
to learn the unreal engine. We're just going to
work with new features, and I'll introduce them as we go through and as
they become relevant. The first thing that we can
see at the moment is we're in the event graph in
this tab just here. This is where we
place our logic and code. We're going to
come back here later. For now, if we click on
over to the viewpoint, this is just a similar thing
where we can look around and see what our blueprint
class will look like. Blueprint classes are normally made up of different components, each with their own
features and functionality. In the left hand side, we can
see the components panel, and we want to add our visual representation,
that static mesh. So if we press ad and search for static mesh, we can
see that just here. That's going to be
the first part of the puzzle that we're
trying to solve. With the static mesh selected, in the right hand
side of the window, we can see our details panel. This tells us everything about the component that we
currently have selected. We can see we have a drop down
here for the static mesh, and this is the
visual representation that we want to set. We know that we want this
to be the blue plane, so we'll find the plane hero
and we'll select that one. Inside of this viewpot, I'm just using the mouse wheel
to scroll in and out. If we press Alt and left click, we can drag around
and view the model, and if we press right click
and drag around the mouse, we can change what
we're looking at. Middle mouse click and move Will Dolly or Pan, I
think that's calling. And this is the
basics of kind of navigating around
this viewpot and also the world viewport later when we come to level building and
things like that. Something else which is useful
to know is if you press F when you have something
selected, you focus in. So that's F for focus. Now, what we've just
created is our static mesh, and we can see back in
the components panel, we have something called
a default scene root. This is showing the
actor's structure, and every actor needs at
least a default scene root, a point in space where
everything else is attached. The default scene root that we have is just a placeholder, so we can grab our static mesh, and we can drag our static mesh onto the default scene
root and let go. So left leg and we've now made our static mesh what is referred to as
the root component, so the base element of our
player class or our pawn. One other thing, when
we get to a stage where we think we're quite
happy with the progress, we need to make sure we
get into a good habit of hitting compile and save. This just make sure
that if anything crashes, then we
don't lose our work. And compiling we just check for any errors and
catch things early. So we've now got our
pawn with visuals. But of course, if we
go and press play, we're not going to be seeing
this just yet because we haven't told the
Unreal engine to use it. And we can use some standard first person game input here. So WAS and D will
fly you around. Q and E will move
you up and down, and of course, the
mouse will look around. But, of course, that's not quite the functionality
we're looking for. So in the next topic,
we'll be fixing this by looking at the Unreal
engine gameplay framework. We'll be telling the
Unreal engine exactly what to spawn and where
we want it to spawn. Once again, if you're
experimenting on your own, which I'd always recommend, try adding in other components
to the pawn class. Something as simple
as a point light, a default particle system, just to see what
happens when you attach things to different
parts of the hierarchy. That kind of experimentation is really how you'll get
this stuff to stick.
4. 03 - Gameplay Framework: With the pawn built,
we've pressed play, and we've already seen
that in play mode, we actually end up flying
around what is called the default pawn. So we
need to change this. Unreal spawns is by default and has completely ignored the
class that we've made. To fix this properly, we need
to actually understand how Unreal'sGame framework
decides what to spawn, where, and then we can tell
it what to do instead. So to begin, a little bit
of a concept overview, again, let me show you what's actually happening
behind the scenes. First of all, I'm
going to go back into the main window and press play. Inside of Playode, this is
where we can fly around. Something which is really
useful to know is you can press F eight to eject from play. So I'm still in play mode. But I'm no longer controlling
the default porn. I'm now essentially
back in editor mode so I can use WAS and D, Left Mouse click to look around, and I can see what's
happening during play. If we look at the outliner
on the right hand side, we can see something
called default porn zero. That's what we've actually
been controlling in the level. And in fact, everything
highlighted in yellow over here has been spawned by UnreLEngine
gameplay framework. We didn't place any of
these in the level. So when we press Play, UnreL
follows a specific chain. First, it will check
for a game mode, and if you haven't set one,
it will use a default option. The game mode will then
decide what pawn to spawn. And again, if you
haven't specified one, it uses the default, which is literally
called default pawn. And that's currently what
we're controlling instead of our plane because we've never told Unreal to use
that plane class. So let's press Escape
to exit Play Mode, and we can begin taking
control of our project. Back in the core folder. So if you're not really
here, blueprints core inside of the
content drawer, we want to create a
new Blueprint class. Same way as before, right click and go up to Blueprint class. And this time we want to create one off type game mode base. I'll give this one the name of BP Underscore Game Mode base. And a game mode is essentially something we can think
of as a rule holder. It tells nrLEngine what
classes to use for everything, what pan to spawn, which
controller to use, which Hud to display, and so on. If we double click to
open our game mode, we can see this looks somewhat
like a normal blueprint. We could go into the code Event graph section
if we wanted, but everything that we
want is actually on the right hand side
in the details panel. We can see here those
things I've just mentioned, the default Hud type of controller are all
exposed just here. The thing that we're
looking for is our default porn
class just here. We can drop this
down and change this to BP Underscore porn base. That's pretty much all we
need to do right here. We can hit Compile and save to make sure all
of that's updated. And we can actually close out
of the game mode for now. I will keep this open to give a bit of a visual example of
something in just a moment, but that's pretty
much all we need to do inside of our game mode. As you'd expect, just creating the game
mode isn't enough. We now need to tell the project to actually
make use of it. So if we go back into the edit option in the
top left hand side, we're going to go to
project settings again. And we're actually looking
back to where we were previously, maps
and mode section. You can see on the
right hand side, we have the default game mode is just one called
Game Mode base. And if we drop this down here, we can actually see
what it was using. So it was using a
default Hod class, the default pawn class, the thing which we've literally seen ourselves flying around, the player controller
by default, and so on. We can't change
these, which is why we needed a custom
class to work with. If we drop this option down, we can change this to BP
underscore Game Mode base. We can see this is now freed up. And the important thing
is it's now changed this to be our BP
underscore pawn base. The one thing I wanted to
show you very quickly is that if I change this here,
I could drop this one down. I'll change it to something
completely unique. We changed this to
the Arc Vz character. We can actually see that
the game mode class that we've created
in the background, so we've got our BP
Underscore Game Mode base was updated in the background
to be Arc Vz as well. If we click this in
here and change this one back to BP
underscore pawn base, I just wanted to
highlight that these are completely connected now. So if we change something
here, that will update in our Blueprint class. And if we change something
in our Blueprint class, that will change in
our project settings if we have it hooked
up just here. With that done, we can
close the project settings, make sure that we compile
and save the game mode. If you're playing
around with things, go back into the main level, press play, which is O and P, the shortcut to enter play mode. And we're kind of
in the plane now. In fact, we're quite
literally in the plane. So another problem to solve,
but we're getting closer. If you've been placing things manually earlier in between, if you're playing
around with things, you may have other planes
in the level as. Make sure you get rid of those just so that there
isn't any confusion, but we can kind of
see what's happening. We press play we
go into play mode, and then if I press F
eight to jump back out, you can see it has now actually spawned a plane
for us to control. The main problem is that
we're inside of it. I just wanted to highlight
another thing which is happening with the
gameplay framework, and that is the use
of our player start. So you can see that
just here in the level. And in the outliner, we can see it's called here
the player Start. This is what the framework
will be looking for to confirm where you
want to actually spawn the pawn that
you want to control. So if I move this
somewhere really far back, I move this all the way into
the open space over here. If I press play, we're
still spawning the plane, but we're now spawning
out into the open. So again, something
useful to know about. If we don't have one of
these, we can delete these, but Unreal now needs to kind of guess where we want
to spawn our thing. And it's not really
guessing just to kind of show what happens
with this scenario. If I move around in the viewpot, I'm just again using Right
Mice click to look around and then W AS and D to fly
based on where I'm looking. If I press play, we can see that the plane
is now just going to spawn the last position my
camera was in the viewpot. So again, something
I've just seen trip up new developers because it may not be super clear what the
player start was doing, especially if you're working in a project where you might
have something like a third person character or a first person
character with gravity, you'll be working in
the level, you'll design a few things,
you'll press play, and then you'll suddenly
fall through space, completely avoiding the level and be a little bit confused. And that's just because
you've probably gotten rid of your player start. Unreal didn't know where to
spawn your default character, so it's chosen the last place
you were in the editor. If you followed
along here, we can go to this Add
button in the top. We can search for a
player start just here, and we can just drop one of
these back in the level. Nice and easy, we're
back to where we were. So that just leaves one problem is that we're looking
through the wrong place. This just means that
we need a camera and we need to tell Unreal
which one to use. To do this, we need
to go back into the BP underscore porn base. If you don't really
have this open, remember that in
the content drawer, so control in space and double
click the porn base class. As I've mentioned, most
classes are just made up of numerous amounts
of components, each component trying to fill a specific role or use case. In this case, we want
a camera component to look at our static match. Before we add a camera,
I'm going to add something called a Spring arm just
to introduce those. So if we press ad over here, we can search for spring arm.
We'll click to add this. And this is basically
a stick which will hold the camera at
a fixed distance. It can also handle
things like collision, pushing the camera
closer when we hit walls and trying
to avoid things. We won't need that level of
complexity for our game, but it's useful to know
that these features exist. Another really handy use of
the spring arm is it avoids us needing to directly
affect the offset, the position, and
rotation of the camera. So we're going to keep it
just for those reasons. With the spring arm
still selected, we're going to go back
to the Add button. We're going to
search for a camera. And we want to select the
default camera here at the top. You can see that this
is just placed at the camera at the end
of the spring um. This gives us a little
bit of time to focus on Unreal engine and the way
that it treats hierarchy. We've already looked
at the fact that the static mesh is now
classed as the seam root. So wherever the seam root goes, everything else
follows, including rotation, location, and scale. The spring um is then classed as a child of the static mesh, and the camera is a
child of the spring um. So what this means is if I grab the spring um component and then change something
in the rotation, for example, I know that
I want the camera to look 90 degrees down on
the static mesh. That will be -90 degrees. We can see that the
camera inherits that rotation that I've
given the spring um. I make the spring um bigger, so I'm going to
lock the location, and I'll make this
the size of ten, we can see that the camera
also gets ten times bigger. Now, we don't scale the camera
and spring um this way, but I just wanted to
show how hierarchy works inside of unreal
with the components. We do want the camera
rotation, though, so I'd recommend a rotation
of something like -90, so we have the camera looking straight
down on the player. If we press play, we can see
what this will look like. So maybe a little bit too close, that could be somewhat
difficult to see what's happening
with the enemies and projectiles during gameplay. The Spring hum has
another feature, which will help us with this. It's called the
target arm length, and you can see that again
in the details panel. So, as I've mentioned,
with the static mesh, when we've got
something selected, the details panel is just all of the information about the components you currently
have selected. The Spring hum has
this value called target arm length,
which is this red line, and we can set this to
something like 1,200, and that would be roughly
where we want this to be. So we can press
Play, and we can see we can see a little bit
more of the screen. We'll be coming
back and refining things like this a
little bit later anyway. But for now, that will
be perfectly fine. Once again, when you've made
changes you're happy with, make sure that we hit
Save, and we have this looking somewhat more
like a top down shooter. So if you've been following
along and paying attention, we've now seen that
if we don't tell the Unreal engine
which map to use, it uses a somewhat random map. If we don't tell it
which game mode to use, it uses a default game mode. And if you don't tell
that which pawn to use, it uses a default pawn. But you've probably
noticed that we didn't tell it which camera to use, but it knows to use the
camera that we've just added. So it kind of breaks that rule that I've
been highlighting. In this works, though,
is it still comes down to the gameplay
framework behavior. When the framework
first possesses a pawn, it will look inside
of that class and check for any
camera components. I'm simplifying
this a little bit, but it's essentially the
process which is taken. If it finds a camera, then
it will use it by default. If it doesn't find a
camera in your hierarchy, then it will create
a default camera, and that position
it puts it at just happens to be zero,
which in our case, is directly inside
of the plane mesh, which is why we were looking
at the inside of the body. So again, we can now see why we were staring at the inside of the plane and how
we've overridden that to now look
down on the plane. Same kind of pattern as
everything else, though. The framework has its
default processes, and the important thing is that we are now more aware that we either have the
option to override them or live with them. With that done, we now
control what spawns, where it spawns, and which
camera we look through. The framework is working for
us instead of against us. And again, you may be
surprised how many people end up working against the framework
for quite a long time. Something else I've become
quite familiar with when students just dive
headfirst into projects. By the time I get
to see the project after they've worked
on it for a while, they'll have workarounds for viewing through
specific cameras, forcing pawns to be
triggered at certain points, basically breaking the framework apart and fighting it
rather than working. Where possible, we can use a lot of these framework basics, and they actually help us
more than they hinder us. Of course, we still
don't have movement. We currently just have a pawn that sits there and a
camera to look at it. So that would be the next
thing, adding movement. I'm actually going
to show you the wrong way to add movement first because it's what you'll see in a lot of different tutorials. After we've seen that, I'm
going to show you how to fix those problems, so you
know what to look out for. If you wanted to experiment again, the small recommendation, whilst we're still quite early
into the topics would be to try adjusting the
camera distance or angle, maybe go for a slightly
different setup for the angle at which you're
looking down at the plane. Can add a second camera and see what happens. Which
one does it pick? And if you are doing things
like that, start trying to describe what you're noticing
the framework is doing, what it's doing that
you don't expect, or what you're kind of
understanding about it.
5. 04 - LegacyInput: Before the player
can move, Unreal needs to know what
the players pressing. That could be keyboard keys,
controller sticks, triggers. All of this flows through
the input system. Unre takes those bindings and converts them into input values, and we read that for
our movement system. So the first step
will be understanding how those values actually work. To get started, we'll
be doing this through the legacy input system
going kind of old school. To find these, we
want to go back to our project settings. So we're going to go up to
edit and project settings. We're going to go just a little bit further down
than we had before. And we can see here we
have the input section. As I've mentioned, this
is kind of old school, and there's even a
warning here about the legacy input system now being deprecated.
This is fine. It's still useful to know
because you're likely to come across projects
which still use this. Many examples and projects out there are still
using this system. Have two different
types of input here. We have our action
mappings, which are binary. These are either
pressed or not pressed, perfect for things like fire, interact, jump, and so on. We then have our axis mappings, and these convert
the player input into continuous
floating point values. Perfect for movement.
So for that reason, we're going to go and add a
new axis mapping just here, and we'll name this
one move right. Because we have
this scaling value here that we can change for the different inputs that we provide, we'll call
this one move right. It will also control
moving left by making the other input
a negative value. From this dropdown, we can
find any input that we want. This could be your
keyboard presses, mouse, gamepad, whatever
the case may be. Something that I didn't
know for far too many years when I was using the
unrel engine is this. Is actually a button.
If we click on this and press the D key, for example, that will
track the next input as D, and that would be perfect
for moving to the right. If we press the plus button,
we can add another binding. I'm going to press this
again and then press A, so we now have our
leftwards movement. The only thing as I've
mentioned is that by default, this is
a positive value. If we change this to a
negative value, so minus one, that will allow the input to move us in a
leftwards direction. If you wanted to
bind different keys to the same input binding, we can just add two
more as an example. We can add one for
the right arrow and one for the left arrow. Again, when we're moving
left, we just want to make sure that we add the
negative value to this one. Simple system and flexible enough for what we
need to do right now. Like with everything
before, this automatically saves so we can close
the project settings, go back into the content drawer, using control in space
again as a recap on the shortcut and go back
into the porn base. If you're still inside
of the viewpot, we're going to want
to move on over to the event graph tab now. So that one that
I mentioned, this is where our logic is
going to be going, and that's what we'll
be doing straightaway. In here, we can see a few different nodes
already in the graph, and I'm just right clicking
and dragging to move around. First, we can see we have
our begin play node, and this fis just once
when we press Start. Below, we have our event
tick at the very bottom, and this event fires continuously while
the game is running. You may hear people
say things like we avoid using event
tick at all costs, but that is slightly
oversimplifying things. For functionality like movement, where we want that
to be smooth and responsive or camera movement, physics, things like that, this is exactly where the
logic needs to lie. We just need to
make sure that we keep it clean and efficient. Then finally, we have the
event act to begin overlap. This is an overlap check, a collision check for
the entire class. In our case, that could be
anything from the static mesh, the spring um component
or the camera component. It's a little bit vague, so I tend to not use this and we'll handle collisions on a per
component basis later. So we can grab this one, press delete, and
we'll get rid of that. To demonstrate what the
axis mapping is doing, we're going to move
down a little bit, so I'm right click and
dragging to move the graph. Want to right, click in here,
and we're going to search for that input binding we
made just a moment ago. I've called mine Move Right. We have two different
options here. We have the axis event
and the axis value. The event would create something that looks a lot
like the eventic providing this execution pin so we can drive future
logic from this. The difference with
the axis value is if we click this one. This provides just
this green pin. That green pin is a float value
or a decimal point value, and this will provide a value depending on
what we're pressing. If we're not pressing
anything, we get zero. If we're pressing left,
we get minus one, and if we're pressing
right, we get positive one. I can actually show you that in a really simple demonstration, and I recommend this to anyone learning new parts of
the Unreal engine. We're going to pull from
this execution pin here, drag this into the
graph and let go, and then we're going to search
for the word print string. So this is just going to provide a debug message on our screen, on the left hand side
if we press play. So we can see that
over here is currently spamming the window
with the word hello. Now, to avoid that being
spammed because this is running 120 times per second, we want to drop this down, change the duration
to 2 seconds. This is currently
printing for 2 seconds, and we'll turn this to zero. This then becomes more of a
constant print over here, so it's now just
printing the word hello. Obviously, we don't
want this to say hello. We want this to show us
what is being pressed. So I'm going to take
the float value here, pull from this pin, and drop
this into the instring. The Unreal engine
is smart enough to automatically convert our float to a string of text
so this is readable bias. So if we hit compile
and play again, you can see now this
is printing zero, which is because I'm not
pressing anything at all. If I press the left arrow, it prints minus one,
the right arrow, I get positive one and
the same for A and D. So we can see that's working. We have
something happening here. So printing the value
shows the data, but it's still just a number
as far as we're concerned. So let's just do something very quickly to make this
effect something visibly. We're not going to add
movement just yet. We're going to get there
on a separate topic. If we pull from our
print string, though, so this execution pin here and search for set Act
scale three D. We want this node just here, and all we're going to do
is we're going to affect the overall scale of our actor based on the
direction we're pressing. To do this, we're
just going to need a tiny bit of space in between. So I'm just moving some
nodes around here. We're going to keep the
maths really, really simple. This is completely
throwaway and just to visualize parts
of the concept here. We can take our set
actor scale three D, though, and we can control this by the direction
we're pressing. So we can make the scale of the actor zero minus one or one. And if we add some analog input, we could make that value
this is really simple. We're going to take our
get move we value here, so I'm going to grab this, press Control in D to duplicate. And we're just going
to plug this in here. So as I said, really,
really simple. This isn't going to be useful for any kind of real
use case scenario. I just wanted to get you
somewhat familiar with a little bit of blueprinting before we dive in with movement. But if we hit compile,
this just ensures that everything's been checked
for errors and working, hit save so we don't lose
any of the progress. And one thing I think
I will do is go back into edit and then
project settings. I've connected my gamepad, and if we scroll back down
to the input section, just going to add
a new input here, click this button, and I'm just moving the analog
stick to the left. So the left analog stick, I've moved on the X axis,
which is left and right. We can leave this as a
positive number if we wanted to invert the
analog movement. So, for example, if I pull
left, then I'd move right. Then that's why we may want
to add a negative value. But because the analog has
a full range of movement, and real smart enough to know to take the right
direction as positive, and the left direction
will automatically be given this negative value. So we can close this. We
can go into play moode. We've kind of broken the
plane at the moment. So at the moment, we're
printing zero at the top. Which means we're passing
the number zero into the X, Y, and Z of the scale of our plane, which is
why we can't see it. If I press A, then we're
inverting the size, so we're seeing the
bottom because we're now making plane the scale
of this negative one. And if I press D, we're making the scale of the
plane positive one. The reason I want to
use the analog stick is that we can kind
of visualize this a little bit more so I can get some smooth movements by just
moving the analog stick. If you see the number at
the top, this is going between zero and minus one because I'm
moving to the left. And if I move it to the right, we're going between
zero and positive one. So just a kind of visualization
of how we can make use of these values and plug this into some
really simple logic. Again, we're getting
another conversion. This is taking our movement
direction, so minus one, zero or one, multiplying that against all of
these scale properties. So the scale
properties and unreal. You've got the X, Y, and Z. Just to visualize this for
something simple like a cube. I'll come in here just
to show you this. If we wanted to
change the properties or the scale of this object, we could take the X axis alone, and you can see that's
getting longer. We could scale on the y
axis to make it wider. And again, we can change the
z axis to make it taller. So nice and simple, and that's
really all we were doing here in the code, but we're
just normalizing this. So all of them are either zero, either minus one or one,
based on the input. So as I said, completely useless code,
complete throw away, but hopefully that makes
the input and how we can utilize that value a little
bit more understandable. With that done, we've confirmed that our input is working. Porn itself still doesn't move. That part's going
to be next to come. And in the next
video, we'll turn this input into some
actual player movement.
6. 05 - PlayerMovement: But set up and tested, it's time to make the
plane actually move. Fair warning again,
we're still going to be doing things
the wrong way first framework dependent
movement with a kind of snappy movement,
fill, but it will work. And you may be wondering why I'm knowingly teaching
the wrong thing. This is because this exact code will show up in a lot of places. This is from the epic
official content examples, which means you'll see it
in other tutorials that reference that older
projects you may inherit, and I'm sure in
many other places. When I realized that, I
decided this would be a perfect opportunity
to get you to a point by the end of this video where
you'll see the bug, you'll understand why it happens and you'll
know how to fix it. So we need to still be inside of our BP underscore pawn base. We can actually get rid of all of the code that
we have at the moment. We'll get rid of this
scaling and print string. That was just for
demonstration and debugging. For the movement, this
is really simple. We just need to consider this as taking our move direction. We'll add in some kind of variable speed a
little bit later, and we want to
offset the location of our plane every single tick. We can do this by
pulling from the eventi, the execution pin just here. We're going to
search for something called set actor location. And this will be
our node, which is directly driving and
controlling where we move. To get this to work, we need
to calculate an offset, taking into account the
plane's current location and then where we want it to be. We can do that using
another built in function. We can right click
in the event graph, and I'm going to search
for Get actor location. If we hover over these when
we're finding them as well, in case you weren't
aware, you can see tool tips on what
these functions do. So this returns to the location
of the root components, which we know is
our static mesh, essentially telling us where
we are at any given frame. To the side of this,
we're going to pull from this vector pin the
yellow pin just here, and we're going to search
for the plus symbol, or you can type add and
we want the AD node. We can do some really
simple vector maths. So what we have
here is our current position plus an offset will equal where we want to be so we can plug
this straight in. One problem we're faced
with at the moment is we want to directly
control the Y axis. So when you're
working with Unreal, I'm going to come back into the level just for a
quick example here. When it comes to
directions and movement, if you're ever not sure
which way you want to move, we can just do something
like grabbing our mesh. Drop this in the level, and
we get this widget here, which will show us the
directions that you can move inside of Unreal. The main thing to try and
remember early on, red, which is the X axis is
forward in the world. Y, which is green is right and left in the
world or sideways, and Z, which is blue
is up and down. That's quite handy because Unreal Engine does
color code these. So if we look in
the details panel to the right, we can
see here we have X, Y, and Z, and that
has a small pill icon showing red, green, and blue. We have the same for
rotation and scale. And as I've mentioned briefly before, these three variables, the location rotation and scale are just a
structure that make up the trans and each of these
variables like the location, and it's just a structure of individual floating
point values. In the example that I've shown that this
project is based on, the plane obviously moves side to side and only side to side, kind of like old space invaders, and we're trying to
recreate that, so we know that we need to
move on the Y axis. So back inside of our code, that means we need to somehow
expose this variable here. Super simple to do. We can
right click on the yellow pin, and we've got this option
to split the structure pin. And that's why I
mentioned that these are referred to as structures. They come in many
different forms, but they're normally just a combination of
different variables, making one specific type of variable with a
different use case. In this case, it's the location for the three
dimensional movement. If we split the structure pin, you can see that this
is then reverted into its individual
floating point values, the X, the Y and the z, and we already have our
movement set up. That is our move right, which we can just plug in to the Y axis. So what we're doing
now is we're taking our current location,
and every frame, we're either adding one, zero or minus one to the direction. So if we press play, we can see we now have some
kind of movement. So A and D will make me
move left and right, as do the left and right arokis. So we're a step closer to
where we want it to be. Now this does put us in a small situation
here where we now have an issue where this is
a framerate dependent game. So I'm going to show
you a few things on the repercussions
of this and how you can kind of test and
see this for yourself. So at the moment,
remember we're moving one unit per frame if
we're pressing a button. So if I'm running at
120 frames per second, that's 120 units per second. An rL engine calculates
units in centimeters. So this is currently
moving around about 120 centimeters/second. Now, if I go back
into play mode, I'm going to go into
the main window here, and I'm going to introduce
you to something called the console commands, where we can run simple
commands to test things. I'm also going to quickly get rid of this test plane here. So back inside a Playmode, click into the
viewpot to make sure you have full
control of Playmode. And you want to
find the Tilda key. On most keyboards, this is
next to the row of numbers. If you press the Tilda key, you can see a small console appears at the bottom of
the screen over here. I'm going to enable a really
simple console command, which is stated space FPS. If I press Enter, we can see to the right hand
side of the screen. I'm now displaying the number of frames I'm running per second. I'm going to press
Tilda key again, and I'm going to
type T dot max FPS or One Word and then space, and then I'm going
to set this to 30. We can now see on the right
hand side that's capped my frames down to 30
frames per second. Now just considering what
I mentioned a moment ago, if at 120 frames per second, I was moving 120
units per second, this now means I'm
obviously moving at 30 units per second. So we're playing the same game, we've got the same
movement code, but the movement is
drastically different. If I put this back up
I'll uncap this to 300. I'm running at about 300 FPS. We can see we're now
actually moving considerably faster because I'm now
moving 300 units per second. I'll put this back to 30 and
you can see the contrast. Now, you can probably begin
seeing why this isn't ideal. And this is what we refer to as frame rate dependency when a game is running specifically
based on the hardware. So players with faster
systems will move faster and players with slower
systems will move slower. It's a bug that ships in a lot more games
than you'd expect. So before we get to
moving and looking at making what is called
frame rate independence, we're going to tweak the rest of our movement code for now so that we have the full
thing to work with. At the moment, moving
just one unit per frame is obviously
not too notable, so we're going to want to
multiply our speed by a value. We can do that really
easily. Again, working with maths inside of blueprints
is relatively simple. We can pull from our float
in here what we want to multiply against. Drag
that into the graph. Search for the asterisk, the multiply symbol
or type multiply, and we can plug this
result into the Y. This means that will now
take our move direction multiplied by a movement speed, which we can set to something
random for now like ten, and this will be our new offset that will apply
every single frame. So we're now moving faster,
but we're still going to have that frame rate
independence issue where if I change the
frame rate I'm running at, I'm now moving a lot faster. So that's becoming
even more noticeable. I just wanted to focus
in here as well. What I've just created here is what we refer to
as a magic number. This number that context, when I come back to this
in a month or two's time, you're quite likely to
forget exactly what this number was representing without looking back
through the code. Give you the question,
why was this ten? Why was it not 100? Why was
it not 1,000 and so on. To alleviate this a
little bit, and again, focusing on that good programming
practice and trying to keep our code tidy right
from the very beginning. We're going to pull from
this green pin here, and I'm going to do
something called promoting this to a variable, and you can see the option just here to promote
this to a variable. So we'll click on this. On the left hand side is
where the variables live. We can grab this name, and I'll rename this one to
movement speed. So now at a glance,
at the very least, this gives us some idea
what this value is doing. So this is the speed at which
we're expecting to move, multiplied by a direction being added to our active location. It begins to read a little bit more like a novel on a page, and you get an idea of what
the code will be doing, even without fully
investigating it, which is what we
want our code to do, become human readable. So we have our movement
speed variable. We can hit compile up here, just to double
check that this has the number ten
filled in because we already had that value
as a magic number. Back to the frame
rate bug, then. We need to ensure
that regardless of the speed our
system is running, that we get the same
type of movement. This isn't just for
the player's outcome, but as a designer,
when we're making our game, we're fine
tuning everything. We want everything to
feel as smooth and reactive and responsive
as we'd expect. We fine tune the whole game
to play in a specific way. And obviously, that's
going to be completely ruined if somebody
who's playing it on a system which is twice as fast as ours gets a
different result. What we need for this is
something called Delta seconds, sometimes referred to as Delta simply the number
of seconds that have passed since
the last frame. Rather than using
this pin here for our calculation,
because, of course, it would have overlapping wires and begin to introduce
spaghetti code. We're going to right
click in the graph, and we're going to search
for Delta seconds. You can see here
we get the option, get World Delta seconds, and we can plug this in
and make use of this. Quite simply, we
want to take all of the values that
we have here and multiply this against our
Delta seconds. So same again. We'll search for
multiply, plug this in, plug the calculation
into the result, and we're getting
closer to what we want. Now, allow me to quickly
demonstrate what Delta seconds is actually doing and what the
value represents. A really quick way to
do this. I want to throw in a print string
like we've seen before. I'll set this to zero
for the duration, grab the Delta seconds node, duplicate this, plug this in just so we can
see what's printing. In my 300 FPS, we're not
going to use this number, so I'm going to cap
this back down to 120. At 120 FPS, we're getting
a value around about 0.008 seconds in
between each frame. If I cap this back down to 60, we'll see this drops
down to 0.016. And then if we drop this
down further to 30, we're going to get
a number of 0.033. So roughly, not exactly, but roughly half the number each time we drop
the frame rate by half. And here's how this is
useful and how this fixes our frame rate
dependent issues. If you're running at 30 FPS, each frame moves you further, but these are bigger steps, but you're taking fewer
steps per second. Whereas at 120 FPS, each frame is moving you less. These are smaller
steps, but you're taking four times as
many steps within that. And what we end up with
is the total distance per second stays
roughly the same. And this is whether you're
running at 30 FPS or 120 FPS. We get frame rate independence, and this is the difference
between a game that feels fair on all hardware, compared against people having different experiences on
higher or lower end systems. One thing to note, because
we're now multiplying our values by this
incredibly small number, the speed we're
moving is obviously going to feel a lot slower, very sluggish, almost
not noticeable. So we need to come in and account for this in
the movement speed. I'm going to increase
this to something like maybe 500 rather than ten, hit compile and play, and it will feel
roughly the same. The main thing, again, is
what we're looking at is a I jump 30-120 FBS. It looks smoother, but we're not noticeably going
faster or slower. The only difference
we're seeing is how smooth the movement feels due to the
frame rate that we're looking at. So that's
really the goal here. So with that, the
movement works. We have the framework
problem fixed, but it's really not
quite polished. We can notice that it's quite
snappy when we move it. If we press a direction, we're
instantly at full speed. Once we release, we
are instantly stopped. There's no acceleration,
no momentum. Real vehicles don't
work this way. Even our gady ones will
have some weight to them, so we'll need to consider adding some smooth acceleration. And this whole approach that
we're taking at the moment, the set actor location. Doing this every frame has
no physics integration, no proper collision response,
and it's perfectly fine. This is why it's provided
in Epics content examples. The pattern works
because it's simple, not because it's best practice. So we have movements.
We understand the framework bug that we
had and how to fix it, and we know why this approach is common and why it's not ideal. Next, we'll move the sites. We'll add some acceleration,
rotational banking, just generally
aiming for something that feels more
like an aircraft. If you wanted to
try experimenting, you could always try
adding vertical movement. It's the same pattern. Just use the different axis, the X axis instead of
the Y or add to it. You'll likely need to
introduce a new input binding, as well, a small tip there.
7. 06 - SmoothMovement: Moment, our movement
technically works, but it feels very robotic. Instant start, instant stop. We'll address that in
this video by adding some acceleration and
banking rotation, so it feels like you're actually flying some kind of
Arcade aircraft. We'll be using something
called interpolation, which is the smoothing
between two points over time. This will also be the
next step to inheriting the bugs inside of the epic
official learning content. We'll find it, we'll
acknowledge it, and we'll fix it a
little bit later. So right now, the problem
is that our speed is being directly
taken from the input. Just here, we have move right multiplied
by movement speed. So if we're pressing right,
that's one multiplied by 500. We're going straight to the
speed of 500 units per frame. If we're pressing
left, that's negative one multiplied by 500, so we're getting negative 500 being applied straight
to our movement speed. That's why we get that
instant start instant stop. Of course, as soon
as we release a key, then we're multiplying
the value by zero, so we go straight
500-0 movement speed. For smooth movement, we're going to need two different values. We want a target speed, which is where we want
to be, and we need our current speed, which is
where we are at the moment. Or the use these to
gradually move between our current speed and
our target speed. To get started, we can
very easily create a new float or the current speed that we're
going to be tracking. We can grab our movement
speed over here, right click on this or press
Control in D to duplicate. We'll call this
one current speed. And what we'll do is
we'll take this value that we're calculating
just here, essentially our movement speed, and that will be the target that we'll use a little bit later on. If we wanted to tidy this up, we could definitely make
this more visually readable. If I move this over, we can grab this calculation just here. I'm going to press
Control in X to cut this, control in V to paste it above, we'll pull from this here and we'll promote this
to a new variable. We'll promote this
one to something which we'll call target speed. And we can plug
this in just here. You'll see why in a moment, this isn't really changing
anything right now, but it will make things just
a little bit more readable. Now, as well as just
hiding up things so we've got a little bit
more space to work with, do remember that we need to rehok our target speed that
we're now calculating. The only difference is
that we've essentially given this pin a name. So again, it's just a
little bit more readable. We're going to grab
our target speed. And if we just drop
this straight onto the Y flow pin which
is now exposed, that will automatically
hook that up for us. Something you'll see
me do quite often, as well, is what I've
done just there. So these pins we
kind of the wires were floating around
a little bit. If you grab a bunch of nodes, press a cue that will automatically align
the wires for you. So we can keep things a
little bit tidier as we go. Another thing which is useful
to know is that if you hover over the pin just
here, for example, and press a cue without
actually grabbing the notes, it will take the node
which is connected, and it will line
those up as well. So not something that you
absolutely have to do, but I'm just mentioning
it because I realize that I do this kind
of with muscle memory, so I'll do this whilst
I'm talking sometimes. And I just wanted you to know the shortcuts
that I'm using. Okay, so back on
to the main topic. We want to get this movement smoothed out with a
float interpolation. If we right click in the
event graph over here, we're going to search
for F interp two. This is a float based
interpolation to a certain value. We can see here a few
things that were given. So we start with
our current value, which is, as I mentioned,
where we are now. The target is where we want to be to fill these straight in. We already have these values and they're named accordingly. So we can grab our current speed and drop this over
here on current. So we're going to go
from our current and we're going to reach
our target speed. So we just drag and
drop that on, as well. You can see that we then need
to plug in the Delta time. So we have the
Delta seconds here. I'm going to select that
one, press control, indeed, duplicate with my
mouse down here, and we can plug this
into the Delta seconds. The Delta time here keeps the E interpolation frame rate independent kind of
with some quirks. But again, we'll come
back to that when we see that problem, and we'll
address it later. And then finally, we
have the interp this is controlling how
quickly we accelerate toward the target speed
that we're given. Higher values here mean
a snappier response. Lower values mean it will feel
a little bit more floaty. I'm going to start with
the value of four, which has worked perfectly
fine in testing. And then, of course, we
don't want any magic numbers in our code. So we're
going to pull from here. We're going to promote
this to a variable. I'll give this one
a sensible name something like movement
interpret speed. So we now have the
four F interp two. The result of this
interpollation will become our new
current speed value. Very important here.
We're going to need to give ourselves a
little bit more space. I'll remove the print
string over here. We're not going to
need that anymore, and we'll move this right
on over here for nine. We're going to avoid
any spaghetti code, and we're going to keep
things tidy as we go. This will need to be
calculated up here. We need our target speed first, so the value that we know that
we're going to be hitting. And we also need to now set. So using a node
like this setting the value of our current speed. Something I don't think I've covered just yet is that we have alternative ways to bring our
variables into the graph. If we hold the control key
on the keyboard and grab, for example, the
current speed and drag this into the graph,
this provides a node. This slim node is
called a getter. It basically provides the
value. We can read this. It's getting the value for us. If we hold Alt and do
the same thing again, grab current speed and drag this into the graph,
but now holding Alt. This is providing
something called a setter. So this
is the opposite. This allows us to write to
the value that we have here. So this allows us
to read the value. This allows us to
write to the value. We want to keep the writable version here
at the current speed, so I'm going to get
rid of the getter. And we want to take
our value here. As I've just said, this is what our current
speed will be tracked as. So we're going to plug
this into current speed, and we just need to make
sure that we hook up the execution pins just here. And just to try and wrap this up and ensure this makes sense, because I appreciate this could look a little
bit confusing. We need to know, first
of all, what we're pressing and what speed
we're trying to get to, which is why we're currently setting the target speed first. Then if we imagine we're calculating this from
the very first frame, we already now know that our
target speed is movement multiplied by or more accurately our
direction by movement speed, multiplied by Delta, so
we'll get a value of 500 multiplied by whatever our small value is based
on the frame rate. So we have a clear
target to reach. The first time we
run this, obviously, current speed will
likely be zero. So we're going to go from
our current speed of zero, so we're not moving
at all to let's just say 100 units of movement to
provide a nice clear number. We're going to take Dw
time into account again to keep this frame
independent, kind of, and we have our movement speed, the interpllation
movement speed to provide different level
of smoothing here. Then the next frame, obviously,
current speed would have been calculated based on this
to be a little bit higher. So we're going to go from
that slightly higher speed. Let's just say two or three, and then we're going to go
from that two or three value, still trying to get to that 100 units of speed that
we need to get to. And we're going to be doing
this continually over time, which will provide
that smoothing. So the final thing is we don't want to use target
speed directly. That's just our we want to use whatever the
speed is at the moment. So we'll delete this one. We're going to grab
our current speed. And again, we're just
going to drag this onto our green pin here, and that will automatically
hook that up for us. And that's it. That's
pretty much ready to go and test the smooth movement. So you can see here, as I'm
moving back and forwards, the moment I release the key, we have a little
bit of smoothing happening as we go down
to no speed at all. So we have some deceleration and acceleration coming in here. Now because we're
doing that over time, the movement may feel a
little bit more sluggish. So one of the other really
useful things from promoting our magic numbers to variables is I can just grab
the movement speed. I can maybe double this, so
we'll just set this to 1,000 because it's feeling
a little bit slow. Didn't need to go
through the code. I didn't need to find out
where this was happening. This is really well named,
so I know that this is controlling the speed at
which I'm going to be moving. I can press play. I
can test this again, and we've got a completely different
feeling plane going on. So this is feeling much
better. This really kind of exaggerates the acceleration and deceleration a little bit more. And again, all of that was done changing a complete property of the overall movement
system simply by grabbing the movement speed
and plugging this in here. Of course, we've added
the smooth movement, which is why we needed
to make that amendment. If you wanted to play around with things you could
do a similar thing, grab the movement interpolation speed and see if you wanted that to take more or less time
to reach that final value, that current speed
that we're targeting. So these are just kind
of variable tweaks here based on how you want
your game, your plane to fill. One design principle that I
heard very early on and has always stuck with me is always try to exaggerate the variable. So if you're not sure what
they're doing, or you're not sure whether you
wanted it to be much faster or much slower is double or triple the
value that you're working. I could set this 4-12 because you want to
hit an extreme first, find out if that works,
if it's too high, too low, and then
refine it in between. So I can see that is almost
feeling quite snappy again. There is some acceleration and deceleration, but not very much. So what I might want to
do is tone this back down to six because 12
is definitely too high, and we're feeling a
little bit better here. Now, as I said, for me, I quite like the feeling
of this at four, but just a general
design principle is to take the value
to an extreme, double, triple, half something, whatever the case might be, and you'll quite quickly
be able to hone in on what you wanted your
property to feel like. For my purposes, I'm going to keep the movement
interp speed at four, and we can move on
to the next feature. So now adding the
banking rotation. This is actually going
to be very simple. It's essentially a repeat
of what we've just done with some slightly
different functions. Essentially, as we're
moving side to side, we want the plane to tilt
a little bit to make it feel a little bit more
like an actual aircraft. A quick refresher
on the axis here. I don't think I've touched
this in fall just yet. I'm going to grab a
static mesh very quickly. I don't need to follow along. This is just to visalize
what we're doing. I'm going to press E inside of the viewpoint to go
into rotation mode, and we can see our axes here. So I've mentioned before, we have this transform,
the location, rotation and scale that
makes up the position in worldspace for our actor.
The locations very simple. We have X, Y, and Z, which is forward, sideways,
and up and down. Rotation, it's
relatively simple. Same thing. We
have three colors. We have the red, the green, and the blue, so the
X, the Y, and the z. But if we hover over
these, we can see here they actually have
their own unique names. We have the roll, the
pitch, and the le. Now, this doesn't
matter too much, but if you ever trying
to tweak these features, add your own functionality,
and you're not even sure which axes should I be
rotating or something around. I just wanted to show
you a slight visual here that's actually
really simple to find out. So, in this case, I
want the plane to tilt in a direction based
on how it's moving. So if I try just a
few things at random, if I try the green one
and see what this does, we can grab the green
value over here and I'll just slide this
to get this rotating. And we can see this
gives us a kind of up and down rotation
of the plane here. This would probably be good if you're moving
forward and backward. You could maybe add a
little bit of this to exaggerate the movement of
going forwards and backwards. If I grab the z and
do the same thing, I just grabbing the
ones I know don't work to begin with, get
these out of the way. We can see that this is rotating
around the plane's axes. So again, not going
to be very useful unless we're doing four
tones or something. So the one that
we want to use is going to be the X axis here. So as we're moving
left and right, we're going to want
to slightly bank and roll just like this, based on
the direction we're going. So this is how you could
very easily come in, play around to the
different widgets here, rotate in a certain
direction, and see which axis you wanted to
apply the rotation to. As I said, that was just
for visual demonstration. I'll delete the static mesh. We'll go back into the pan base. Move this over a
little bit and we can get back on with the rotation. For the rotation here, we have a couple of very similar
functions we've already used. We're going to pull from
the set actor location, and you can probably guess we're going to
search for something called set actor rotation. That's a very similar thing,
updates to the rotation, and this will be
every single frame because we're throwing
it on the eventi. Now, we're going to want to use a very similar function as well, we want to use an
interpolation to. Now for rotation, we don't
use the F interpret, we're going to use
something called interpret two. So we're
right clicking here. Search for interp two, y for rotation, and we'll
take this one just here. The other reason we're
doing it this way, we could potentially grab just a single floating
value because we only want to
do this on the X. We could do a very similar thing here and use an F interpret. Now, I'm keeping this
on parity line for line with the Unreal Engine
content examples project. So this is exactly
how they've done it. They've used an F interpret for the location and an R
interpret for the rotation. So I wanted to show again,
the code that exists and how we can fix it just in case questions were arising, why are we doing this
slightly differently. But the main things here, this is very useful
still because we can see we get the same properties that
we need to feed in. We have a current rotation, so where we are at the moment, we have a target rotation where we want to rotate towards. And we have the Delta time to make this frame
rate independent, again, in kind of. And then we have
the interp speed, which is the smoothing between. So current is actually
really simple. We have another
very similar helper to what we've used before. If we right click
in the event graph and search for Get
actor rotation. This will provide, like the get actor location we've used, the rotation of the actor
at this given frame. The target is kind of a
random arbitrary number, and I say that just because
all the epic example has done is it's taken
the current speed, so I'm going to control drag the current speed into the graph to get the current speed that we're moving and multiply
this by a value. So we're going to pull from
here, select multiply. And I think theirs was
something like 1.5. So we're going to take
whatever speed we're moving, whether that's a positive
or a negative value, multiply that by 1.5. And that will be the
target that we want to rotate towards on the x axis. Something I wanted
to introduce you to. We've already seen here. The use of the split
structure pin to take our three point vector into its individual floating points. The rotator is exactly the same. We could just right click split structure pin and take
this into the X Y and Z. This can get somewhat
messy when you have so many different
floats altogether. So one thing that
can be quite useful, and more than anything, this is really just for code
tidiness is we'll pull from the target here and we're
going to search for something called make rotator. We want this bottom
option just here, and it does just as
the name suggests. It's making a rotator from
an individual X Y and Z. It keeps these two floats kind of by themselves,
so easier to read, and it allows us to just plug
our value here into the X, just a different way to get to the same goal as
we've done before. Neither of them are
less or more efficient, and it can actually come down to what is more readable
in a certain instance. This I wouldn't say
is too unreadable, because it's just one vector that we've split. Where's this? There are already
a bunch of floats, and splitting more
floats into that just became a little
bit unwelding. Like we've seen
previously, we want our final result to be the
new rotation we have here, so I'm going to take
that interpolation and plug that into
the new result. And that just leaves us with
a couple of things to fill. So we want our Delta time, so I'm going to come over here, grab a get world delta seconds, press Control ID over here to duplicate that and plug that in. And then we just need
our interpolation speed. So again, that's
moving in between. I'm testing, I think, a value of around about ten
worked very well, and this is one of those values
you'll just come back and tweak to get the rotation
feeling exactly as you want. Again, no magic numbers, so we'll pull from here,
promote a variable. Give this one the name of
rotation interp speed. And with that we're
pretty much ready to go and get this tested. Although one things
just stuck out to me. This is essentially acting as a tilt factor or tilt strength, rather than having this
magic number as well we'll pull from here,
remote to variable. I'll just go with Tilt factor, and that is now
actually ready to test. So we can hit
compile, press play, and we can see we do
have some banking. Now we do have another
issue, which I completely forgot
was going to happen. So we will fix that in a moment, but the banking, the
rotation is indeed working. So now that I've potentially given everybody motion sickness, the camera is banking
with the plane. We've confirmed that,
at the very least. The reason that camera
issues happening is the camera inherits
rotation from the parent. We've really seen this
in the hierarchy. The cameras attached
to the spring um, the spring arm to
the static mesh, and these are following all
of the rotation and location applied to the static mash.
This is really simple to fix. And one of the reasons I
wanted to use the spring um, I just forgot to tick this
on with the spring um selected in the details panel
to the right hand side, this unique component
provides us with a solution. Epic knew this kind of thing
would be a potential issue. So they've given us
some tick boxes here. We can untick the inherit
pitch, U and rotation. Remember the names for the
X Y and Z of rotation. So if we untick those,
this is basically saying, don't allow the
spring arm to follow the rotation of the
parent component. And because the cameras a
child of the spring arm, that will apply to the child component of
the camera as well. So if we hit
compile, press play, we can go in and we can see we can see the rotation nicely. And we're not getting that
potentially nauseating camera movement that we
had just a moment ago. One other thing I just realized with the spring um selected, I still had this ticked, as
well. Do collision test. If you've had any problem
where the camera seems to be moving around or getting shoved into the
floor or something, that's potentially
because you have the collision test happening. What's happening here
just to visualize this? If anything is detected between the camera and the
thing it's looking at here, so something with
collision on it at this point in
the red spring arm, that's where the collision
test is happening. If anything passes through that, that's where the
camera's going to try to automatically move out of the way or duck behind something
to avoid that collision. Really useful for things like
third person games where you may have the camera
brushing against a wall, and it will get a
little bit closer to the player. So that's
quite a nice feature. In a game like a shoot them up like we're creating, though, we can have issues
where the camera looks twitchy if projectiles or traces and things
like that are being passed through the
camera, the line of the camera. And again, this is
something else you see in beginner projects
where the camera's kind of twitchy or glitching at. It's probably
because they've got some kind of collision test happening between the camera
and what you're looking at. So we're going to turn this off. We won't need that for
this type of game. With that done, the
camera stays level. The plane banks naturally,
feels much better. So now we finally
get to look at where Epic's example breaks
down a little bit. Just wanted to mention this
is no shade being thrown at the example project or
Epic or anything like that. I'm purely bringing
this up because I realize this is a perfect
teaching opportunity, something which is
so widely covered used by so many different
tutorial channels, myself included, you're
going to see code which is broken even from
official sources. So knowing that and
being able to look out for that is going to
be super valuable. So the problem we're getting at the moment is if we're
going to play mode. At high frame rate, I'm going to put my framerate stats back on. So 120 FPS, this is
looking kind of okay. If I put this back down
to 30 FPS, though, focus on how much the
rotation is being applied. So again, you can see this isn't just a case of one of
them looking slightly smoother and one of
them looking a little bit less smooth due
to the frame rate. This is literally a
difference in the mechanic, same code, same input,
completely different rotation. At 30 FPS, we're almost rotating the wing
through the floor, depending on how far away
the plane is from the floor. And this is one of those things. It's not even gameplay breaking. But if you've set up a
system to look super smooth, and it looks exactly as you want it to look to be
the most appealing to the player at but
then someone on a lower spec system plays and is greeted with
the second result. You're obviously going
to be disappointed, they're not getting the
experience that you intended. And this is why this is
really important to focus on. And this is what I
meant by this bug exists in Epics official
content examples, same code, same problem, shipped in the actual documentation. And this is why blindly trusting
even official examples, even first piety
documentation can have bugs. So always test edge cases and look out for
things like this. That's what I'm really
trying to get across. Touched on previously,
it's going to be a little bit too long
for this one topic. So this is something
we're going to fix a little bit later, and we'll be rebuilding the
movement system entirely. But for now, we're just going to acknowledge that the bug exists. This is still perfectly
fine to work with. It's a perfect foundation
for building further. We'll come back and
we're going to rework the movement system entirely, and we'll fix all of the
bugs when we go through. And now that we've seen
the payoff for not using magic numbers, we have
our movement speed, controlling how fast you can go, movement interp speed, which is how quickly you
reach that speed, our tilt factor on how much the plane banks at the
different speeds, and then even the rotation
interpolation speed, as well, which is how quickly we get
to those tilting values. All of this is tunable
without touching the logic. If you want snappy
arcade action, we can increase
the interp speed. If you want a slow and floaty
space, you can lower it. The feel of the game is
then all coming from the numbers. So we
can leave that there. We have a smooth, somewhat satisfying movement
with some nice banking. And yes, there's the frame rate bug that we're going
to come back to, but at least we know about
it, and we'll fix that later. Next, we can upgrade to
the enhanced input system. This is Unreal's
modern replacement for the legacy input system
that we're currently using. If you wanted to
experiment, as I've said, you now have all of those
values ready to play with. Try adding some more
pitch to the rotation and potentially adding that
with the forward movements. The same pattern,
different axis for both the rotation
and the movement if you still only have this
going in one direction.
8. 07 - EnhancedInput: Movement setup and working
to a testable point. Remember the legacy input
system that we've been using that as I've
mentioned, is deprecated. The enhanced input is
the modern replacement. There's a little bit
more upfront setup, but it's significantly
more flexible. We get things like hot
swappable control schemes, built in dead zones, runtime rebinding, all the
things that you'd have to code yourself using
the legacy system. So let's begin migrating over. The first main difference is
the enhanced input system actually has its
own set of classes. So navigate back to
the blueprints folder, right click in here or press Control Shift in n to
create a new folder, and we'll call this one input. Enhanced input splits
things into two parts. We have our input actions, which defines what can happen things like fire, move, restart. And then we have something
called input mapping contexts, which define which buttons trigger the previously
mentioned actions, providing a separation
of concerns. And the reason this
separation proves useful, this allows the control
schemes to change without needing to rewire
the entire gameplay logic. In code, we can simply call the input system to switch
between menu controls, gameplay controls, vehicle
controls, whatever it may be. They can share the
same action mappings to the specific buttons, but they can run under
different contexts. One line of code, and we
have everything switched. Inside of the input folder,
we're going to right click, and I'm going to go to the
Input category just here. We can see we have the
input action section, so we'll create one of these. Give this the name of
IA Underscore Fire. We'll select this one Breast control, indeed, to duplicate. We need two more of these, so
we'll duplicate this twice, and we'll call one
IA underscore move, and the other IA
underscore restart. Next, we want to open up our
IA underscore move class. We'll double click to open
this. And the main thing we want to look at is just here, we have the value type. By default, this is set to
digital Boolean or ball, and this is very similar
to what we set up in the project settings with
the action mappings. We need to delete these anyway. We're going to go back into
Edit, project settings. We'll go down to input again. And just for context, the
digital boolean that either on or off would have been if we created an action
mapping here. For example, we could have
created something called fire, bound this to a button, and had this firing every time we
wanted to call a projectile. Whilst we're in here, I'm
going to also get rid of the action mappings just so that we don't have anything leftover. This will also cause a little
bit of a bug in our code, but this will make
things easy to find and hone in on when we're
doing our code update. So we'll just press Delete to get rid of our access
mappings, as well. Again, that automatically
saves we can close this and go back into
the IA underscore. Both our fire and
restart input actions. These both need to
digital bullions, which is why I
haven't opened these. They're perfectly
fine by default. We're either pressing the
fire button or we're not. It's the same for
the restart button. For the movement, though,
we want to drop this down, and we want to use
this axis one D float, essentially providing the same
thing as an axis mapping, providing a value
from negative one to positive one or zero if
nothing is being pressed. So that's the only change
that we need to make inside of our input action. We can press Save. We
can close this one, and we can go back
into our input folder. So now we want to get
to our mapping context. This is going to be which
buttons trigger, which actions. If we right clicking
the folder here, we're going to go back
into the input section, and this time we'll create
an input mapping context. Naming convention
is going to be IMC. Underscore, and then the name of the thing this will
be responsible for. We'll only have one type of input in this more
simple project. So I'll call this one
main for our main input. Double click to open this, and we can see the way it works. Now, this kind of
is representing the project settings that
we were editing earlier. In the top left hand
side, we want to click this plus button
and drop this down. We need to make each of
these entries unique, which is why this is
currently being graded out, so we can't add
any more mappings until we have some unique
properties in this one. If we drop this down, we can set this to Ia underscore fire. And then if we drop down
the category just here, this is where we can begin doing the same type of mapping
that we saw earlier. So if I wanted the
fire functionality to be called on Spacebar, I can press the button here, hit Spacebar, and we
have one binding. If I also wanted it to react
to the left mouse click, I can click here and
do left mouse click. I'll also do the same thing one final time for the gamepad. I'm going to press the button again and press the
lower face button, the A button on the gamepad,
and we have that bound. So we now have three different buttons or
three different inputs, which can all call the fire function when we
get to implementing that. We want to do the
same thing again for our other input actions. So we'll press the plus. We want to bind something
to the restart. This will be nice and simple,
so I'll do this one first. Same thing again, we could have potentially R for restart, and perhaps I'll add
one for the gamepad, as well, and I'll make
this the start button. So that is the special
right face button. Just to note, I'm using the left shift and pressing the arrows, and that will collapse
everything back up. If you wanted some
shortcuts here, hold Shift, press left click,
and you can see that unfolds every
individual element. So a nice quick way to have
things either unfolded or collapsed neatly when we're going through these
different mappings. Final mapping that I wanted to create is going to
be the movement. So we'll add one more
of these. We'll bind this to I A underscore move. We'll drop this down. And again, we're going to want to
add some keys here. If we get started the same
way we did previously, we're going to click
the button here, press D to have a
default right movement, and then click
another one, press A to get our left movement. Now this is where things
change a little bit, and this is unique to the
enhanced input settings. Even though we have these set
as a one directional axis, a floating point value for
our IA underscore move, you can see we
don't actually have any floating values to change here to invert the A to
make things move left. Instead, this is where modifiers come in with the
enhanced input system. So if we click on the plus
sign on the modifier, can drop this down,
and you can see a bunch of useful
functionality pre built in. We can add things like dead
zones for analog sticks, automatic scalars and
things like that, which can be applied
to the input. The One we want is just here, so we want to negate the value. It's going to default to
one. If we negate that. This is what gives
us a negative one. So nice and simple to use just a slightly
different approach. I'm going to do the
same thing again. I'm just going to add two more. One for the right arrow.
One for the left arrow. We'll add that same modifier
onto the left arrow. And then finally, I'm going to add one final input per move, and I'll move the left
analog stick again. Now, the same thing
happens here. We don't need to add in negates here because Unreal knows that an analog stick has a left and a right potential on the x axis, it will automatically return negative one when I
push this to the left. That's everything set
up and ready to go inside of the IMC
underscore move. So we'll hit Save and
we can close this one. We won't need to edit
anything in there again. So, this may have looked
a little bit tedious. As I've mentioned, it
does have a little bit more of an upfront setup, but you only need to
set this up once, and these assets can
then be copied to two other projects very easily if you find you're
using similar patterns. And the main thing is
that in larger projects, this will begin saving you
a lot more time as it's much more flexible than
the legacy input system. Now we want to jump back
into our porn based class. This is another one
I really need you to focus on because
this is the step that everybody often forgets with the enhanced input system. The mapping context exists, but nothing is
using it just yet. And unlike the
legacy input system, our enhanced input
system needs to be explicitly bound to a class. We can do this really
easily, and this is the step that everybody quite
often forgets to do. From the begin play, we can right click
somewhere near here, and we're going to
search for the G player controller
built in function. The player controller is
what is receiving the input directly from our
physical devices, feeding that into unreal
and deciding how to manage that across the pan
that it may be controlling. This can also apply to AI, so this would be
the input that AI is giving to the
controlled AI pawn. From the controller,
we're going to pull from the pin here and
we're going to search for something called G enhanced
input local player subsystem. Really snappy name
there. We want this one just here
with the pink F, so this is indicating
this is a function. It's just going to be
returning a certain value, which is that enhanced input,
local player subsystem. And from here, this is the
function we really need. We're going to call the
Ad mapping context. Nice and clear. We can see exactly what
this will be doing. This will be adding a
specific mapping context, the one that we've
just created to be bound to the class
that we're currently in. So we'll make sure that we hook these up with the execution pin, drop this down here the mapping context
that we want to use, and we only have the one we
have IMC underscore Man. The way that you
may be using this, though, as I've mentioned, is you may have a more
advanced specific controller, which is tracking what you might be doing during the play
through of your game. During gameplay moments, this
would be making sure that the mapping context is
using the gameplay IMC, and you may have something
like a main menu IMC, as well, so you
can navigate with the keyboard in your main menu. When the player presses
pause or escape, the player controller would
be responsible for changing the mapping context to go
from gameplay to menu. And this is where this becomes
really, really flexible. As I've mentioned, a
little bit overkill for the kind of project
we're creating, but it's useful to know,
and you can really build upon this into
any of your projects. And the thing I really
want to drive home again, this step is critical. I've seen so many people
configure everything perfectly. They have all of
their input set up. All of the movement
functionality would be working correctly. They press play and
nothing happens. You spend an hour debugging, and then you finally
realize that you forgot to bind the
mapping context. So don't be that person.
So the final thing we want to do here is replacing the legacy
input that we had. If we hit Compile, because we've removed the legacy input
from the project settings, this is what I meant
that we'll actually get a very useful error
or warning down here. It's now telling us
that it's trying to reference something which
is an unknown axis. If we press this
underlined text here, that will actually drop
us straight down to where that problems
occurring. So nice and easy. And all we need to do
is we'll grab this node that no longer exists in
the project settings. Press delete. We can
right click in here, and we want to search for
our IA underscore move. Same thing, very similar to the legacy input.
We get two options. We get the action events, which would create a node
like this where we have an execution pin and
the float variable, or we can use the axis action
here, the action value. The same thing as
the axis value we had previously. So
we'll select this one. This will give us just
the floating point value, and we can plug that back in. So the code itself is pretty much identical. Nothing's
really changed. It's just the way that we're
going to be setting up the enhanced input to feed
that information to us. That makes the transition
relatively seamless, and all of our movement logic
will stay exactly the same. If we press play,
we can test that. So A and D are doing
the same thing, left and right, and this
is the analog stick again. So all working exactly
as we would expect. All we've had to do is
upgrade the input layer underneath our core
code. And that's it. We now have the
enhanced input system in our project, fully setup, which did take a little bit more than the legacy input setup, but it's much more flexible
and definitely worthwhile. We'll be using more of
the features later, such as the fire
events for shooting, triggering held
inputs on a loop, and then also the single
fire restart button. The next topic we
want to address, though is going to be
our gameplay boundaries. Right now, we can fly
up into affinity, which is not really great
for any shooting up. And, of course, if you
wanted to experiment, you can try adding
in things like dead zone modifiers for
the thumbstick input, see how timers or pulses might work using the
built in functionality. No code required and just
something to play around with.
9. 08 - Boundaries: New input system, we still have this issue where we can
currently fly off into infinity. Eventually, you'd hit some kind of floating point
precision issue, but long before that,
the game would become unplayable and more
importantly, quite boring. For this type of game,
we're going to want a fixed camera and some boundary walls,
some invisible walls. The camera we have is currently
attached to the pawn, and for a shoot them up, a level camera would
just make more sense. So if we go back into the
BP underscore pan base, we can grab the spring
arm control select, the camera, and we'll
just delete these. If we press play, we're now
back inside of the ship, so we have that problem
back, but we're about to fix that pretty
much straight away. The main thing is
that you're now aware of the gameplay framework and you know why we are looking at the inside of our ship. If not, then go
back a few topics. If we go back into
the main level, we just want to add a camera. To the top, just
above the viewpot there's a small cube,
which is our add button. If we drop this down, we want to search for the option camera. Many of the options
here tend to move categories every few
unreal versions, so searching can be
a little bit faster because life's just too short
to be looking for cameras. One that we want is
our camera actor. This will be dropped directly into the world if
we select this. The camera is going
to need to look at where the player
will be spawning, and we already know again from the gameplay framework how we're managing that and
where that's going to be. So a nice quick,
shortcut here for you. If you grab the player start, which is where the
player spawns, we're going to go to the details panel on
the right hand side, right click on the
location property, and we can copy the actual
location of the player start. If we grab the camera,
we'll do the same thing, but we're going to right
click here and we're going to paste the property
that we've just provided. So we now have the
camera directly at the point of
the player start. We also want to give
this some rotation. So inside of the viewpot we can press F to focus
in on the camera. If you wanted, you can go into rotation mode and you can
use the widgets just here. Now I know for our needs,
we just want to rotate the camera -90 degrees
on the Y axis. So I'll do this from
the details panel, and we're now looking
directly at the floor. This may be a little
bit too close. So the next thing we're
going to want to do is move the camera
up in the Z axis. So again, if we grab
the Z value here, I'll place a value of 2,400. I think this is similar to
what we had previously. We can see now we're going to be looking directly down at the player start and where the
plane will be spawning. You may not be tempted to press play and see what happens. The reason being quite simply, what if we had five, seven,
20 cameras in the scene? How would it know
which one to pick? Like with the other rules
that we are setting, we want to be in control of
this and set this manually. So with the camera
actor selected, we just want to search
in this panel just here, in the details panel,
we want to search for something called
Autoplayer activation. We can see we have
this option here. Auto activate for player is
currently set to disable. If we drop this down
and select player zero, this will be the
first and only player in our single player game. So now the framework knows to use the camera that
we've just selected. If we press play,
we can see that. We'll need to do
some adjustment to get everything
centered properly, but we're now looking
roughly where we want to be. So with the camera
placement, we're going to want the player to be closer
to the bottom of the screen, kind of like space
invaders and again, somewhat recreating the setup in the content examples
that we're going from. The problem with the
editor and usability here is that the player start doesn't have any visualization. So it will be a
little bit difficult to see where the
player ships going to actually be spawning and
position the camera accordingly. So a quick trick here that
I wanted to introduce, we can add a temporary
sphere or shape, something like that to show
where the player start is. If we go to the Add
button up here, we can go to our shapes option. I know that I still have
the location stored from the player start
earlier when I copied that. I'll undo the search
for Autoplayer here, right click and paste the location of the
player starting. And I looking at this sphere, which is directly on
the players start. If we grab the
camera actor again, this has helped
because we can now see exactly what the camera
will be looking at. Pressing F, I'll snap
back up to look exactly at the camera and we can
un drag this into place. So if we bring the
camera forward or we could bring the
players start forward, either one will be
perfectly fine. The main thing is that
we want the player ship to be roughly down here. So that's a nice,
easy way to get things in position
nice and quickly. If I grab the sphere,
I can delete this. Obviously, we don't want
that to stay in the lab. Can press play and the players closer to where we
wanted it to be. Another tip of the camera
that I just wanted to show very quickly is if
we grab the camera again, whilst you're playing
around of things. If you didn't want this
demonstration window to automatically toggle off when
you select something else, we can choose this little
pin icon down here, and that will pin this
window to your viewpot. So if you needed to come in, move the players
start to something around to see what's happening through the
view of the camera, another quick tip there that we can keep this on
screen temporarily. When you want this
to go away, we can just press the pin again, and that will unpin
from the viewpot. Just a couple of things
to begin getting comfortable with
and familiar with. I'll grab another
shape just here. I think I've mentioned
it a couple of times, but if you press F with
an object selected, you're focusing on the
object you want to look at. You can click to then rotate
directly around the object. And another thing
which can be quite useful is if we're placing
something like this, we can hold a shift, left click on the widget direction,
and then drag this. So if we begin
dragging an object here and then press Shift, was this moving, we can
actually have the camera in the viewpot keep tracking the
object that we're moving. Quite handy if you're
trying to play something and you didn't
want to keep moving it, moving the camera, moving
it again, and so on. So just some quick tips there, getting familiar
with the viewpot. Now we're going to want to begin looking at the boundaries. Once again, from the Add button, we're going to
search for something here called a blocking volume. We can see how we have the
option for blocking volume. We'll select that
one, and what we get given is this invisible cube. This is going to represent
our invisible wall, and we just want to
lay this out across the level to block where the player or the enemies may hit. To get this in place, I'm going to do the
same thing again. I'm going to right
click and paste the location of the player start. That will
put us down here. For this one, we can put this on the left side of our play zone, which I know from
testing would be a good spot of negative 1,240. That will place this just over. Ready perfectly
fine on the Z axis. This is high enough that the plane will indeed
collide with this. If we put this at the
X location of zero, that will put this
kind of middle of the level on the Ford axis. And then what we want
to do here is scale the box to cover
the entire extent. Now, one way that you
could do this is to make the X axis
here much longer. That will change the box. That is kind of stretching
the actual actor. When we're working with
things like volumes, we have another option. So I'm going to set this back to the ideal way that we'll be working with these is actually changing the brush
settings here. So this is the size of the box that we currently
have at the moment, which is 200 centimeters around. So if we set this to something
much larger instead, I'll just add a
couple of zeros here. So we'll have a 20,000 unit
box just to ensure that we're encapsulating the
entire play zone and a little bit more,
probably a lot more. But that's perfectly fine. The reason that we want
to do this is that this is changing the scale of the actual component inside, this brush box component. Whereas this value up here is the scale of the
actual entire actor. So if we can keep this
uniform and clean at one, then if we ever needed
to check things in code, it's a little bit
easier to work with. And again, just keeps our
structure a little bit tidier. Whilst we're down
here, I'll just make the height a little
bit bigger asll. So we may want to set this
to something like 1,000 on the Z just to make
sure that we're definitely blocking
absolutely everything. Something else that may get
a little bit confusing is, if it's a little
bit hard to see, we do have options to
change the opacity, we can add a display
shaded volume, and then we can start seeing
where the actual cube is. Again, this doesn't
show in game mode, but it does show
through the camera, which makes it a little
bit easier to see. And the main thing
is being able to gauge where the blocking
volume will actually occur. With that done, I'm just
going to press Control in D after selecting the blocking
volume we've set up. I'll set this to 1,240, so we have this on the right
side of the playfield. So on the YX is here. And just to make it visible,
like the left side bound, I'm just going to
scroll back down. Turn on the display
shaded volume, and these will both and look the same. Do be aware as well. I think when we
duplicate things, Unreal tries to move the
duplicated actor several units either way so they're
not fully overlapping. So you can see that on
the right side volume, we have some offsets on the X. This actually wouldn't
cause a problem. But just to soothe the
voices and my brain, I'm going to set this back to
zero and everything again, nice and tidy and
well maintained. With a number of
changes like that made, you may want to
press Control Shift and S to save everything
that we've been working on. So now we can go into playode. We're going to be looking
through our camera. It's going to be a little
bit difficult to see because we can't see the
actual bonding volume here. So if we did want to test
things and make sure we know exactly when we might
be going through a wall, another option, a little
bit lengthy here is we could go back into shapes.
We could add a cube. Want to go down to the
collision settings in the details panel on
the right hand side, and just to ensure that
we're only going to be checking against
our blocking volume, we can select the
collision preset just here and turn this
to no collision. We'll look more at collision
a little bit later, but this is just to
get something visual. Then again, I'm going
to go back to details, paste this in the location, and put this -1,240. For speed and
simplicity, I'll just keep this roughly the same size. It doesn't need to be exact, so we're going to grab the cube. On the Y axis, we need to make
this two units on the Y to get roughly the size of our
blocking volume and again, we'll make this a lot
larger on the X so that it spans everything and
larger again on the Z. We don't need to be
quite so careful. Here. The main thing,
it's just something visual so we can see if and when the player would be going through the wall, so
we can see that here. The main reason is
that I knew that the collision
wouldn't be working, and I just wanted to
immediately eliminate the fact that we are indeed going through
the bonding area, which should be
colliding with us. The reason I wanted
to introduce this, as I knew this was
going to happen, this gives us a bit
of an example on how we may go about debugging. This is really important
in development, actually understanding
how to problem solve, work things back, and
find out what's going on. Already kind of answered
one of the problems. The first thing is that we wouldn't have known
if we're just flying around whether we had actually gone through
the wall or not. So we've already discarded the fact that we're
not reaching the wall. We can get to the
point of the wall and go further and no
collision is happening. So that's one option from the checklist
completely ticked up. If I grab the blocking volume on the left here, we'll try
and fix this one first. The first suspect might be the collision properties that
I've just shown you. So on the right hand side
in the details panel, we'll go back down into
the collision properties. Under collision, we can see that this preset is set
to invisible wall. If we drop this down, we can have a look
at what this means. And this is going to be a very brief overview
of collision. Again, there will
be much more depth on this coming in future topics. But for now, all we need to know we have three
different types. We have Ignore,
overlap, and block. Ignore does what
you would think. It ignores all other
colliders and interaction. Overlap is looking
for two things which are in the
same physical space, but not physically touching with things like physics
and blocking each other. And then we have
block, which would be two physical objects
impacting each other and trying to push each
other away so that they don't inhabit the
same physical space. In short, invisible wall is
set up exactly as we want it. It's set to block absolutely
everything in the world. So that's our second
suspect, by the way. We know that we're
reaching the wall and we know that the
wall has collision. The next thing is we're going to look at the pans collision. So if we're going
to the pawn class, we'll go to the static mesh, and the collision at
the moment is being handled on the static mesh. So in the details panel, again, we're going to want to scroll down and find the collision. We'll drop down the
collision presets, and we can see that this is
set to block all dynamic. Now, this actually
doesn't matter. Again, the important thing
of these tick boxes here. This is ticked to block
absolutely everything as well. And if we go back into
the world very quickly, we'll get a very quick crash
course on collision here, we can see that the
invisible wall is set to be its object type is
set to world static. Porn itself is set
to world dynamic. So this is looking to block
world static objects, and this is looking to block
world dynamic objects. So as long as those two
are looking to interact, we should see some
physical blocking. So that's the third
suspect, by the way. The porn collision is
actually set up correctly. And that really leaves the
only thing is that we've probably done something
wrong in our code. And spoiler alert, I already
know that's the problem. So if we go down into the
event graph on the event, we're going to move over to our set actor location
function here. And this is one of those
things where it would be quite difficult to debug back and actually know
this is the issue without really having
experienced this. Two things to look out
for here are the sweep and the teleport booleans
on the function call. If we hover over the sweep, we can see here the description that we get is whether we sweep the destination
location triggering overlaps along the way.
This is really important. Also controlling
whether we stop short of the target if something
is blocked by something. This is the real important part here is that if we don't
have this ticked enabled, then we won't be checking for having our position blocked, which means we're not
going to hit anything. In short, having sweep
unticked is technically kind of teleporting us to the
location that we want to be. So if we tick this on,
hit compile and save, we'll go back and test again. We can now see when we've flying over, we're
now being blocked. So we haven't needed to change any of the
collision presets. We'll get into those
a little bit later. That means, as
well, that the wall on the right hand
side will be working because it's set up in
exactly the same way, so that's perfectly fine. It was just a really
simple thing, very easy to overlook
in our code. So I wanted to include
that just because, like I've mentioned, things
don't always go perfectly, and you do need to
be comfortable just somewhat debugging and trying to work back through
the things that you logically think could
be part of the problem. This is one of those things that ends up being a
really common gotcha. It's really easy to
miss. Really hard to debug if you don't know
what you're looking for. But had that failed, even myself as an experienced developer, I would have been kind of out of potential thought paths I
could have gone through, and I would have had
to have just gone over to Google or RDI or something like that
and tried finding there if anyone had
a similar issue. So just a quick tidy up
with the boundaries now. I'm going to go back in.
I'm going to get rid of this white cube here. That
was just for visualization. We now know that we are
spawning in the right place, and we're getting
blocked by both of the walls over here.
So with that done. The camera is fixed. It's now
in place where we want it. The boundaries are set, and
when we begin adding enemies, they'll detect the walls
and bounce between them. Obviously, once we've
implemented that code. Before that, though, we're going to start looking at materials, the more kind of visual
elements of our project. We'll be able to customize
the look of our ships so that all of the enemies don't look identical to the player. As always, if you
wanted to experiment, at the moment, just
try something simple. Try adding a top and
bottom boundary, especially if you
already have some forward and backwards movement. Once again, same
process, different axes, just to get you familiar and
trying out different things.
10. 09 - Materials Instances: Moment, if we jump straight
into creating our enemies, they would look exactly
like our player. Same blue plane, no way to
really tell them apart. That's why our
materials come in, allowing us to wrap different
colors and images around our three D models
to differentiate them and add some variety. We won't be creating
materials from scratch, as that would be its
own rabbit hole. And as I've mentioned,
we're focused primarily on programming, practices and projects,
the three Ps. But I will show you
how we can create color variations from what
I've really provided. Whilst we won't be creating
new materials from scratch, I do want to show you the
materials that we already have. If we go into the assets
folder materials, as I've touched on previously with the
naming conventions, we have a few things
here to look at. We have two MM underscores
the water and the texture, and we have four MI underscores, which are the
material instances. Mm, which is a prefix
for master material. This is where the actual
shader logic happens. We open the texture base, we won't be editing
anything here, but I just wanted
to show you some of the terminology and details
to be familiar with. If we move over to the left
hand side of the graph here, this is very much like the
blueprint graph we can just right click and drag
around to move. The main thing that we have are three texture sample inputs. We've got the base color,
which is our color map, the blues, the wood
grain, the paint. And this is all taken from the Ubi map on the three D model. We then have our
normal texture or the normal map which adds surface detail without geometry, those bumps, ribbts,
panel lines, and so on, all faked by manipulating how
light bounces off of the. The final important one
is the ORM texture. In this case, this is something
I've created personally, and I have a slightly
different workflow. I've created this as an ORME
specifically this time. This is essentially
four grayscale images crammed into one texture
files, RGBA channels. The main reason for this is that all of the data within them can be extrapolated
through grayscale. This saves memory, and it's just a little bit
easier to work with. In this case, the red channel
is the ambient occlusion. This is where the light
will struggle to reach. The green channel is roughness, creating a mirror smooth versus matte surface in
different areas. The blue channel is
metallic. This is binary. This is either metallic
or not metallic, and the Alpha
channel is emissive, showing which parts would glow. Something which is useful to be aware of when you're
working with materials is that rather than just having this spherical
representation here, we can change what
shows in this viewpot with a handy editor trick. If we open the content draw and go into the meshes folder, grab a mesh that you'd like to visualize with your material. In this case, it's going
to be the plain hero. We don't need to do
anything with this. We just need this
to be selected. We can go back into
the material and unil remembering what
we currently have selected inside of
the content drawer. Can change the representation, the visualization
of our material here to be things like a
cylinder, a plane, a cube. And then specifically
this brick icon here. If you have a static mesh,
selected, and we click that, it will give us a
representation, visualization of our plane or the mesh that we currently have selected in the content draw. So this can be quite
handy to visualize things and see exactly what
is responsible. But what when you're changing
different properties and getting that
immediate feedback? Next up, I want you to focus on these named nodes everywhere. These are called
named reroot nodes. Intead of wires stretching
across our graph, somewhat like a conspiracy
theory evidence board. We can instead choose to create named connections just once, and then we can reference
these absolutely anywhere inside of our
Shader. Completely optional. You won't always see these used in other examples,
but it does again, allow me to keep all
of my Shader logic on the left hand side and then just call things when I need them
on the right hand side. One of those things
where the tool exists, so why not use it. As I've mentioned, though, I
wouldn't recommend changing anything inside of the master material unless you
know what you're doing, or at the very least
make a duplicate and make some tweaks
in that one so you always have something
to fall back on. The main reason being the
next topic we'll look at are the material instances
and the way that the instance works is driving the information directly
from this master material. So if something breaks
in here, then you're potentially going
to break all of the instances that rely on it. So back inside of the
materials folder, if you've moved around, to save a little bit
of time, we can use a fairly logical starting point. We already have one plane. It has most of the
textures and everything set up exactly as we need it, and that is the MI
underscore plane player. So go ahead and grab this
one, press control, indeed, to duplicate and rename
this one to plane enemy, keeping the MI at the front. If we double click
and open this, we can now start looking
at material instances. We can see that this
is much different from our master material. One thing to keep an
eye on here is on the right hand side
in the details panel. Something I should mention, you're probably
noticing patterns here. Every time we're in a panel,
whether that is materials, blueprints, components, view pot, when you grab something, there's normally a details
panel lingering around somewhere to give you information on what
you're looking at, which is definitely one of
the nice things about Unreal. When you begin learning
parts of the feature sets, that transfers over to different features inside of the engine. But what I was about to say is if we look at the details panel, we have the parent
being denoted here, and that is showing that this is the MM underscore texture base. So we can double
click into this, and this just takes us back into the material I've just
been showing you around. So, as I've mentioned,
all of the information is coming directly from
this master material. One other thing, it'd be nice to visualize the plane again. So just to recap, go back
into the meshes folder, grab the plane and press the brick icon over here inside
of the material instance. The main things that we want
to change are the textures. We can see that this
is very different. We don't have any
of the graph or that shader information here. All of that is specifically
used in the master material. Inside of the instance,
what we have instead are things that I've
specifically chosen to expose. All of these named
big notes here, the textures, the texture color. These are parameterized
variables that I've exposed so that we can see
those in the instance. So what we want to
look out for if we drop Dan's categories, these are all of the
things I've just been showing the exposed properties. By exposing these
inside of instances, we get a really pleasant work
environment where we can just make a change and we can see that update immediately. So we can do that to get
our first enemy type. If we drop down this
texture base color, so the color information, and we're going to search
for the green enemy. I think I'll make
the enemies green. I have provided a lot of
different texture colors here if you wanted to expand upon this and make your own enemy types. I think I've included yellow, purple, maybe orange or red. I'm going to go for the
green base color texture, and we can see that
will immediately change our information
of the plain. Most of the normals
and the OIME textures are probably about the same, but I did export
them individually per texture when I
was creating them. So we may as well go ahead
and grab the normal. So we're going to
search for green again, and we're going to find
the plain green hero. But this time, we want
the normal texture. And then finally, we're going
to drop the OIME slot down, and we're going to
search for green again, and we'll just replace this
to be the hero green OI ME. Okay. So just to make
sure that that lines up exactly the models I've made inside of blender in
case anyone was curious, and I've textured them and baked all of this information
in substance painter. So as I mentioned, you may see similar but slightly
different workflows. It comes down to the artist and the person
who's making them. The main thing to
note there, though, is that we got that
immediate feedback. As soon as we change something,
there's no recompiling, we saw the new texture
applied to the material, which is one of the key
benefits to material instances. Definitely not exclusive
to that benefit, and we should always try and use material instances
where possible, where things are sharing
similar properties, features and values
that we can expose. So beyond textures,
this would be a perfectly fine ending point if you're happy with this
looking as it is. I just wanted to
provide a little bit more information about
material instances, what they do and how
we can use them. So some of the other properties
that I've exposed here are floating point values
or colors that we can directly change and override
the general color or tint being applied
to the material or things like how
specular the value is. So specularity is
really easy to look at. If I tick on specularity here, this allows
us to edit this. And at the moment, we're at 0.5, roughly average specularity. This is how much light is
bounced back from a surface, making things look a little
bit more plasticy or more like rubber the higher
the specularity goes. Generally, we don't have anything that we
interact with on a daily basis that
has zero specularity. So starting somewhere
around about 0.3 or 0.5 is
somewhat realistic. If we turn this down to zero, we get that immediate feedback. I mean, I have a
completely matted, completely non
reflective surface. If we put this all
the way up to one, then the light is bouncing back around this
much, much more. So again, you can tweak
features like this and try and get an idea of what you'd want
yours to look like. You could also
play around things like a color mask
or a color tint. So if we take on the
color mask here, essentially a percentage
normalized 0-1. So zero being no masking
and one being full masking. If we turn on the texture
color, these two work together. So the color mask is masking the texture
color override here. If I make this something
really obvious, like a red color and then turn the mask off by making
it 100% effective, so allowing 100% of the
color to come through. So it changes to the value one. We now have a fully pink plane. So we can control
this very easily. And the really nice thing
about this is we get that immediate feedback and the immediate update when
we're making these changes. If you wanted this to be a
little bit more subtle we could change the color mask
to something like 0.5, and we're only allowing half of that color
to come through. Take it back down to
0.2, and we're getting just a very light blue tint
on top of our green plane. Turn it all the
way down to zero, and we're going to have no
color coming through at all. So just another thing for
you to play end with, along with the different
textures that you can use, and then the emissive
properties work the same way. Now, I didn't have anything on here to actually make glow, like headlamps or
anything like that. So the emission was more of just a way to give you different
properties to play with. If I turn on emissive strength, we need to give this a
value more than zero. And then if we turn on
the emissive color, at the moment, it's black. Now obviously,
black is just zero, zero, zero in the RGB value. So if we multiply
black by one or 100, we're still going to get
black, which is nothing. So we need to give this a
color if we change this to something like a
yellow or an orange, and we maybe need to increase the strength a little bit more. And this is one of
those things actually where as I was exploiting
different textures, some of them, I realized
wouldn't have any use for a texture packed
emissive channel. So if you wanted to play
around the emissive property, you can want to go
into the OME texture here and we'll change this
to the blue plane again. Plane here are blue.
We're going to have to tone this down
just a little bit. Maybe but there's something
more like one or 0.5, so it's not completely
overriding the color. And you can make the whole
plane glow a little bit here. So, you do have control over the emissive property as well. As I've said, unfortunately,
I didn't think of modeling in some
headlights ahead of time, but I wanted to shove
the emissive properties so you can make the whole plane glow
and have fun with that. Might be useful if you wanted to animate it blowing
up or something. You could have it flashing between its normal
color and, like, a red before the explosion
happens, an idea. I just to kind of help visualize what's happening here
and how these textures, especially the OIME is working. I realize that could
be quite confusing. I'm just going to turn
this off, and I'll set these back because I don't
want to override any of these. And I'm going to
turn the texture back to green for
the OIME as I know, that one's been
baked specifically for this color property. I just wanted to very
quickly show you through the textures
and what's happening. So when I say these have been baked into different channels, if we go into the
texture folder, and we'll find doesn't really matter which
one to be honest. We can just find
the plain blue hero and I'm going to open
up the OIME channel. So normally, in most textures, colors and everything that we're seeing here
are coming from the RGBA channels all being
combined together at once. If you take some away, all
you're going to end up with is kind of
muddy weird colors like this where red and blue added together will
obviously give us purple. Red and green will give us a kind of orange
color and so on. So we wouldn't use it this way. With the ORME process, it's
a little bit different. And this is because what I've
done, in the red channel, this is our ambient occlusion. So anything which is
white is essentially not blocked by any other
elements of the model. So the light is
impacting us fully, and then anything which
is going towards gray or black has some kind of occlusion from another
part of the model, kind of like the underweel
bearing and things like that. And it will apply
a little bit of fake shadowing there to
emphasize the shadows. In the green channel,
this was our roughness. So again, going from essentially
a value of zero to one, different areas appear
to be more rough like these areas here are providing
some kind of rough detail, whereas these are going
to be much more smooth. The blue was the metallic, so again, that was
either metallic or not. So these would be the
front of the propeller. There are some small
metal parts on the wheels and other
small features like that. And then the Alpha channel has been baked, and as
you can see here, this is essentially
the entire body of the plane has been baked
into the emissive channel. So by taking this information
out and multiplying this against our
color properties and things like that
in the material, we can manipulate
specific elements of the model through
our texture channels. So it's a clever
way to do things. It saves on memory,
and it gives us a little bit more flexibility
within our materials. Just a couple of other things before wrapping on this topic. If you wanted to play
around with more materials, just to show you a
few different ways to create different
material instances. I've shown you how to duplicate our original player material. Control in D will
duplicate that. If you know that
you just want to make something completely new, a brand new material to
play around with those, we just go to material
and we'll select this. If you already have
a master material to work with and you wanted
to make a fresh instance, you could right click on
the master material here, navigate up to the top here and create a new
material instance. It would give you
something very similar to the player plane or
whatever I had previously, and it would just have some
default pre filled values, essentially the same result. So I'll get rid of this one.
I won't need to keep that. Just wanted to show you the way that you could
work with these. Saying that because
it's probably one of the easier topics to
get on board with, and we're not going to go much more in depth than we have here. I definitely recommend
taking some time between topics to pause the videos
and take a step back. Maybe create a few material instances with different colors. This would give you the option later to have a green enemy, a purple enemy, a red enemy, whatever fits your
vision for the game. You could also try
giving different chips, different secularity values, adding a little bit of a missive property
to some of them, or giving them a color tint. So if you really wanted to
start learning more about the engine and really
understand what's happening, take this as a small piece of homework between
this topic and the next and try to create a few material
instances in between. But with that done, that is our material stuff kind of covered, and we're ready to go
with an enemy material so we can start creating
our new classes. Using material instances,
we've been able to create that new variation without
touching any coat, using the one parent material
class I've provided with a whole range of potential for material instances
to come from that.
11. 10 - EnemyMovement: Time to make some
enemies. Fair warning, we'll be doing this the
sub optimal way first. You might question some of the choices if you know unreal. Once again, this is
completely intentional, and this time it's to allow
us to refactor our work. Refactoring is part of
any real development. It's taking the code base or
project you already have, improving the code which
is working that you want to keep and removing
that which you don't need. I want to make sure
I have the chance to run through this with you so that I can show you how to make the process a little
bit more painless. But of course, first,
we're going to need something to allow
us to refactor. To do this, we're going
to create our enemy pawn. So if we navigate to
the content drawer inside of the blueprint
and the core folder, we're going to right
click here and go to the blueprint class option
and create a new pawn class. We'll give this one the name
of BP Underscore pawn Enemy. Enter to open this, and
this is going to be almost identical to what we've already set up for our player. And that's essentially part
of the initial problem here. We're going to be duplicating a bunch of work that
we've already done. We'll be going through the
better solution later, but for now, let's
just build up to what we had before
with our player. So in the components panel, we know we want to add
a new static mesh. We're going to grab
our static mesh and drop this onto the
default scene root. With the static mesh selected, we're going to go
to the right hand side in the Details panel, and we'll select to use the
SM underscore plane hero. Of course, we don't want
this to be the same color. And if you can't
see your materials in the details panel
at the moment, we simply need to click off of the static mesh, so find
the other component. Select the static mesh again, and you can see we now have
our material selected. Element zero is the
body of the plane. This is the main
color and the detail. Element one is a
separate material slot created exclusively for
the wooden propeller. This allows us to do things like vertex based animation through the shader to make the propeller look as
though it's moving. A little bit complex
for this project, but the features there if you
wanted to extend it later. If we drop down element zero, we want to search for
the enemy material. And of course, if
you took those extra recommended steps
between the topics, you may have a few different
materials to choose from. We'll select this
one, and you can see that my plane has turned green, so this should stand out against the default player plane
model and material. Next, we're going to want to add some movement to our enemy. So if we go over to
the event graph, we do the same thing as before, remove the Acta begin overlap. We won't be using that, and we can focus in on the event ti. Something which is always useful getting
into the habits of is really planning
ahead with the features that you're
going to implement. We could do that, for
example, by looking at the content examples
project we're basing this off of and break down the core functionality and features the enemies will have. The first is that the movement isn't the same as the players. It doesn't have
that fake momentum or kind of
pseudophysics going on. It's more snappy,
I just moves left, right, and down the screen.
So we have diagonal movement. This allows us to plan ahead a little bit because
we can also see things when the enemy hits the wall, it
changes direction. So we know that we need
to do some sideways movement and forward movement, and when they hit
walls, we need to do something to account for
a directional switch. Structuring our projects
this way allows us to somewhat
logically break down a larger problem into sub problems which are going
to be easier to tackle, and we can hit those
one step at a time. So to get started, the first
thing we're going to want to do is actually add
the actor offset, the same kind of positional
offset we had before. So from the
eventicEecution pin here, we're going to search
for set actor location. So again, very similar to what
we've done in the player. We'll also want to use the same approach
to find out where the actor is at the moment and then add an offset to this. So we'll use the
get actor location, and we'll do a very
similar things before. We'll pull from here
and we'll search for add or just press
the plus button. We'll figure out the calculation first before plugging
anything in, and then once we have this
finished and ready to go, we can hook that up and test to make sure we're starting
on the right foot. We already know that we
don't want magic numbers, so we may as well
create a couple of variables that I can already see that
we're going to need. The first one will be a floating point value for movement speed. Just a quick tip here. If you create a new variable over here, by default, this
will be a boolean. We can keep this one
in fact. The way that we're driving
movement will be checking whether
we're moving left or right. So whilst we have this? Let's go ahead and call
this one B move left. So this was just to
say that whenever you create a new variable, you
will be given a boolean. A shortcut that I like
to take, knowing that I need a floating point value
for the movement speed, similar to what
we've used before. We can pull from
this green pin here. The green indicates that the Delta seconds is
a floating value. Drop this into the graph and then click Promote to variable. We'll give this one the
name of movement speed. And that's just a quick way
to get a variable of the type that you're looking for
without needing to use the dropdown and finding
it in the graph. You can do it either
way, but it's just nice if we needed a
vector, for example, we can find a random vector, drop this into the graph,
promote the variable, and then give it a name like so. The only thing is we
need to do some cleanup, so we'll delete the nodes. We won't need those just yet. But I just wanted to introduce different
ways you can go about working with the Unreal
engine graph editor. The vector was just an example, so select this and delete that variable as well. And
we're ready to carry on. When it comes to the
movement, we've already covered the frame rate
independence and why we need it, so we can build that
in from the start. If we right click and search for Get Wild Delta seconds we can use this in our
calculations coming up. For the movement, we're going to want to take our movement speed. And if we hit Compile and just make sure that this
has a variable, we'll set this to
something like 250. That would be perfectly
fine for testing. And like I've mentioned,
we're going to need two separate calculations. We're going to want one
for sideways movement and one for the forward
movement down the screen. For the forward movement, we
can take our movement speed. We can multiply this by
a value to invert that, so we'll take this as negative one because down the screen, as I've mentioned,
is a negative value, and if you wanted to
move up the screen, that would be a positive value. So the forward movement for the enemy is actually
relatively simple. And, of course, we want to
multiply this result by Delta seconds to keep things
framerate independent. So that would essentially be our forward movement
ready to go and test. For the sideways movement,
we're going to want to do something with
our Boolean value. So I'm going to grab the movement speed
and duplicate this. We're going to want to do
something very similar here where we're going to take
a multiplication node, but we're obviously not
going to want to multiply either only one or negative one. We need to turn this into some kind of
conditional operation. The way we're going to use the boolean is a true
or false stick. We would say we're
either moving left or we're not moving left,
so we're moving right. Now, if you tried
this, nr will convert a Boolean to a floating
point multiplication. Unfortunately, booleians
only read as zero or one, zero when it's false,
one when it's true. So obviously, that
would mean this won't work for our current use case, because if we're multiplying by zero rather than moving left, that would just take
the movement speed, multiply that by
zero, which would cancel out any
movement whatsoever. What we could do though, if we press Alt and
left click here, we can use something
called a select node. So we're going to
pull from here, we'll search for Select. And we want this option at the bottom. This gives
us some options. If we're working
with things like integers as the wildcard here, you can see we add any
number of integer. Whatever the integer that you place in here, so
any whole number, if you place in a
whole number of one, for example, we could give
this a negative value here. What we want to do instead is we're going to take our boolean. We're going to plug this
into the index here, the wildcard, and Unreal will switch that to
be a boolean for us. And we can see that
this is now set to either be true or false. So the way that we
can read this to make this more kind of intuitive
and easy to understand, is we can say that if we want to move left, are
we moving left? If that's true, then
the value needs to be negative one because we want
to move in a negative value. If moving left is
equal to false, so we're not moving left, then we want the value to be one. So essentially, this
is moving right. This is moving left, and this is going to be positive and
this will be negative. So a nice simple
way that we can use a Boolean value to essentially toggle
on move left or not. And then when you do
the same thing again, we'll take our Delta seconds. We'll take our calculation here, multiply that against
Delta seconds to make sure that both directions are
frame rate independent, and we could essentially
begin plugging this in. So just to keep things tidy, I'm going to pull
from the vector pin here and search for make vector. So we've seen these before.
This will just give us the three floating
point values exposed. Forward in unreil
is on the X axis. Sideways is on the y axis,
and that's pretty much it. We can hook this in.
We could test this, and we would have
a moving enemy. So if we hit compile, save, we'll go back in into the main level, we can
just drop our enemy. Doesn't really matter where this is going to
be as long as it's just slightly up the
screen from the player. I'm going to give
this a 180 degree rotation on the z axis, so it's facing
towards the player. And then we can hit play, and we should see this moving
down the screen. We have this moving in
a diagonal direction, which is the initial
part of the problem. So that's one of
those steps solved. I think I noticed one
small issue here. It looks as though
we're going to be going through the invisible wall. So yes, we've definitely had the enemy pass the invisible wall. I think it's just
within the bounds. As I mentioned, something which
is really easy to forget, and I've just done
it again is we want to ensure that we
hit the sweep toggle, so make sure that
we're sweeping, and we shouldn't have the same
functionality as the player, but we'll just hit the
invisible wall and stop. Perfect. So that's the
next part of the problem. That's the next step
that we need to solve. When the enemy hits the wall, we want to take that information and toggle the
movement direction. And we already kind
of have that set up. So we have our move left.
We'll change that to true and have them moving left relative to the
enemy's direction. Right before we go
through that, I wanted to go through
a little bit of information about general
kind of code tidiness, things that we could
improve here, even before doing a big refactor. I've purposely laid
this out to make it more readable
and understandable. But this is actually some
pretty bad code already before we even get into what
we need to refactor later. Now, the first thing
is I just want to introduce people
to commenting, which is something I
haven't mentioned yet. If we grab a node, for example, and press C,
we can make a comment. Kind of a self
documentation thing intended to remind us
what something was, why calculation is
being done, how it was. And it also makes things
kind of more readable at a distance. You don't need
to comment everything. You may have seen
certain developers share some of their code base, and there may have
just been comments littered absolutely everywhere. You may have seen things
like this where they have a variable or something
really simple like this called movement speed, and they've given it a comment just saying
movement speed. This is completely arbitrary. It doesn't do anything.
It's not useful. And as long as you're
naming your variables correctly or with
some good intent, we already know that
this is movement speed because that's the
name of the variable. So we don't need to
comment like this. However, if we grab this and expand it to cover
all of these nodes, what we could do is we
could change this to say something like move
direction, move forward. And we could even
make this more useful because if we press F two to
go back into renaming this, we could also explain
why we're using negative one as a multiply because this doesn't
have a value. It doesn't have a real
use of having a variable. So it is kind of a valid point to have a magic number
here, but at a glance, we might forget why negative
one is being applied, so we could leave a comment just describing
what's happening. So we can say here, we're
multiplying this by negative one to move enemies specifically down the screen. Then do a very similar thing.
We could grab these nodes, press C, and we'll give
this one a similar comment. This one doesn't really
require any explanation. I think this is quite clear. If we're moving left,
then take the value related to which direction
we want to move. The main thing we get from
doing this is that if we start zooming out of the graph, we can now see that this section without actually
looking at the code. This section is for
forward movement. This is for sideways movement. If you ever needed to
quickly hone in and find a specific chunk of code to fix something or
double check it, we know exactly
where these this is just the use of comments.
These can be useful. You don't have to
use them everywhere, but definitely places where you think you might forget what you're doing if you come back
in a month or two's time. Now, the main issue that
we want to fix now though, I'm going to delete
these is that we're currently doing a
bunch of duplication. We have G World Delta seconds multiplied twice
here individually. So one thing that we could
do, and this may not have been completely
obvious to begin with, but rather than multiplying the individual float
point values here, we can multiply
against the result. So if we take the results of this make vector
that we've created, pull from here and
search for multiply. We can grab one of our Get
world to Delta seconds, press Control X to cut this and control B to paste and
then plug this in here. And RL converts that to a
vector multiplied by a float. We can plug that
calculation in here. And then we can get
rid of this node here, so we don't need this multiple
anymore and plug that in. And we can get rid
of these two nodes. We don't need these at all, and then plug this back into the Y. So we now have something
a little bit tidier. We don't have quite so many
multiplications going on. We're taking the
entire vector now, which will take the
multiplication for the X and the Y at the same time
against our Delta seconds, ensuring that everything
is framewright independent for the
entire calculation. So that was just a really
quick code tidy there. So if we hit compile and save, we should see we get exactly the same response so
nothing's changed. It looks and moves the
same as it did before. Final thing before leaving this topic for
night before doing the Ru factor is that
every enemy is going to be starting moving in the same direction
and the same speed, which will be a
little bit boring. If we add it in a
couple of enemies, so we can just hold Alt and drag on a widget here to
duplicate immediately. So I'm just holding the lt key, grabbing the widget
direction and dragging in a direction which will duplicate
what we have. If we press play, it's going to look like some kind of
synchronized swimming. They're all going at exactly the same direction, the same speed. It's going to be a
little bit dull. So if we back up
to the begin play, I'm going to show you
how to randomize a boolean first to at least change the direction that the
individual enemies might be moving. This is
actually really simple. We're going to hold Alt and drag the boom move left next
to the event begin play. Or you could just put it on
the execution pin just here. And then from the variable, we want to drag from the pin here. I'm going to search for
something called random ball. So we're going to
get a random boolean and we'll set this
on the bignPlay. So super simple. We have a 50 50 chance of
moving left or right. So if we press play,
they all chose to go the same direction and
they've done it again. We can see there's some
randomization, so that's good. They're not always going to go exactly the same direction. So a small learning
exercise for you. The approach to doing a random float is
slightly different, but take a few minutes, pause
the video and see if you can implement something similar
with the movement speed. So bring the movement
speed in and see if you can find some
built in functionality that UNR might have
to allow us to pull a random value into
the floating point. You could randomize this
between something like 250, which is currently our
default and maybe 500, so that each enemy will move at a slightly
different speed. Pause the video, give
that go and begin testing how comfortable
you're getting with the blueprint system. Okay, so I'll run through the way that I
would have done this. If we plug this in and hopefully you have tried that
out because again, these will be really valuable
to actually learning how to use blueprints rather than
just following my steps. But so that we have
the same code, at the very least or
something to go from. What I would do is I'd grab
my flight variable here. We're going to get
a random flat. This is the only thing which
may have thrown you off, and you may have wanted
to come through and try a few different options. But rather than just
using random float, which would just
give you any number, this could be really
fast or really, really slow. So not too usable. You could have also tried things like random float from stream. But the one that I
would use would be this random float in range. So if we select this
one, it just gives us two points to pick
from our minimum, which I would say
would be the 250, and we'll double that
to 500 as the maximum. So some planes might
get that minimum 250, some might get 500 or a value in between. And there we go. We can see they all
start at completely different speeds, which again, will just make
things a little bit more interesting when we get to actually adding
these and they're working properly in the level, bouncing off the walls
and things like that. With that, though, the enemies are moving and they're
colliding with the walls. They don't bounce yet, and they also wouldn't
interact with the player. So before the big Ruf factor, we're going to next look
at collision detection, making things actually happen when objects touch each other.
12. 11 - EnemyCollision: Have enemies that move, but
they don't react to anything. Ideally, when an enemy hits something, we need
to check what it is. Is it a wall, and if so,
then toggle direction. If it's the player, we can actually see from the
example project again, it applied damage to
itself and gets destroyed. So what we'll do is
we'll automatically destroy the enemy if
they touch the player. Projectiles are going
to handle themselves, so we'll think about
that a little bit later. This isn't going to be a
problem to consider here. One thing to mention is that
the unreal collision system is flexible and powerful, which unfortunately means
it's somewhat complex. At least when you start
working with it after trying a few different iterations of different types of things
you want to do with it, I think it clicks quite quickly, and it's quite a comfortable
system to work with. So let's navigate back
into the pan enemy. We can use this for
testing. If we grab the static mesh and open the collision section
on the right hand side, just scroll down here and
find our collision category. If we drop this down, we can see the collision preset here.
We'll drop this one down. We can see it set to
block or dynamic. So this is our current
collision setup. The object type is also
set to world dynamic here, so that's the
collision channel that this object identifies as. That's why when we press play, the enemy stops at the walls. This is a physics or physical collision where they're both blocking each other
and trying not to inhabit the same physical space. Now, if we drop
this one down and change this to
overlap or dynamic, so the equivalent,
but for overlaps, we'll go and hit compile
and quickly play. What we'll see is that they now pass back through the walls. Now, under the hood,
something is being fired off. There's a message
being sent saying that these three enemies have
just overlapped the wall. But because it's not a
physical interaction, it's just a notification and essentially an
observation that they were passing through
the same space. You can kind of
think of blocking versus overlap like
a racing game. We want the walls and surroundings
to block the vehicles. You crash into them,
you get stopped. We want the checkpoints
to overlap. We obviously want to
know if a vehicle has passed through a checkpoint
and what time that happened, but we wouldn't want those
checkpoints to start physically blocking the
cars from proceeding. That would be just a
little bit annoying. So if we go back
into the pawn enemy, that was just to show
how they work different. We have the third
option as well. We have the ignore option. Now, that one's
probably quite obvious. That will set this to ignore
all types of collision. Could do that, for example, by going to the no
collision option, and even though it
looks as though this is saying
it's set to block, this is actually in the code, reading the Ignore option. Now, there won't be
any messages fired. Nothing really knows that the
plane is going through it. And this is good for objects that don't need to be added to the call stack and the collision checking stack. So
good for performance. But generally, we're going to
be working with overlap or block depending on what
we need them to do. In this case, we can
set this back to its default, which
is block dynamic. I've done that by
just pressing here. You can see the small arrow. If you ever wanted to reset
properties to their default, we can just press this,
and this goes back to what it was before we
were tweaking things. With the static mesh
still selected, we want to scroll
down even further in the details panel
right to the bottom. And we have these options
here, the events. Now, we're not going to
go through all of them. The things that we're again,
most often going to be interested in will be
the on component hit. This happens when two
things physically block each other and on
component begin overlap, and sometimes on
component end overlap. These are called when
two overlapping objects, either enter or leave each. So we can get the end overlap
because as we've seen, overlapping collisions
allow the objects to inhabit the same physical space. We want to bind
our wall check to the on component begin
hit function here. This creates a new customer
event for us, so function, which called every single time a collision is detected
on our static mesh. So if you remember,
every time we've created our class and we had that default function that
we really didn't need the actor hit function check
that we've always deleted. That was essentially
a similar thing, but like I've mentioned,
for the entire class. Whereas now we can hone
in and specifically check I just the static
mesh has been hit, then we can do
something afterwards. These nodes actually provide
quite a lot of information. For example, if
we right click on the hit structure pin here and
select the option to split the structure we can see all of the information
we can get back every time something
hits us or we hit something. We can
find out the time. We can find out if the
blocking was successful. We can see the actual hit
location and the impact point. We can get things like
the rotation from this, the type of physical
material, and so on. We don't need all of this
right now, but it's useful, again, as you start getting more advanced and comfortable
with unreal, if you want to do fancy things with things like
bullet penetration or the type of results that happen
when two things collide, you can definitely dive in
here and see what's available. Just right, click on any
of these pins, though, recombine the structure pin
just to tidy this node back. The main things that we tend to work with when working
with collisions, we're going to want to
know a little bit of information about
the other actor, because at the moment, we
don't know what's hit us. We just know that we have been
hit when this gets called. It could be the
wall, it could be a player, it could
be another enemy. So this is going to become
useful to read later, so we may as well promote
this to a variable now, and we'll call this
one hit actor. And this is why I just wanted
to take a quick moment to go through different
ways that you could even just consider
approaching solving this problem, finding
out what hit us. In a lot of student projects
that I review and work with, I see a lot of casting because a lot of very basic
tutorials that you may find online tend to just fall
back to showing how to cast to other objects because it's a quick and simple way
to get information. In our project, this is relatively small and
simple what we're doing, so it wouldn't
actually look too bad. But if you were
to go that route, you might have something that
ends up looking like this. I'm just going to do a quick
cutaway. Quick cutaway. As I said, this is essentially
what you'd end up with. The way that a cast works, and I want to introduce you
to casting anyway, the way that casting
works is that when we're working with
our other actor, if we highlight
over the pin here, we can actually see
what's being returned. So I'll hit compile. And I hover over the other
actor information. This is showing us
that this is providing an actor reference. The reason being, if
we remember back to UnrelEngins hierarchy
of inheritance, everything that we
can interact with in the world that has
a physical presence, that transform needs to
be of actor or higher. So by providing a pin here, which is the most basic object that we can find in
the world, an actor, we can ensure that we can
check against everything and then hone in specifically
on what type of actor, what extra functionality
and features that holds. Now, the way that you might
do this, or I've seen this a lot of time in many
tutorials is that they'll show you to
take something like the actor that you've hit
and we cast against that. And what this is this is
essentially a question. It's saying this generic actor, I can see the generic
actor exists. Is it specifically the
type of pawn base, which for us is the player. If it is, so this is
the true, the yes, it is a pan base, then
do this thing here. So we'd call the function
specific to the player class, which as I've mentioned, is applying damage to the player
and then self distracting. If it's not, so if
we've done that check, and it comes back as not being
the player that we've hit, then we've hit something else.
So it would go to cast f. And then you'll see
people do a second cast. So we get like a cascading
waterfall of casting, which is messy wildy. And if we get into
much larger projects, you can see we've only got
three things to consider. But if we've got different
things like pickups and obstacles and many other things that we
might need to consider, we're going to get a much
larger cascading waterfall. But the next thing you might
do again is we're going to come back in here and
say that generic object, the generic actor, is it
specifically an enemy? If it is, then we'll
do the enemy stuff, which at the moment, I haven't decided how
we'll handle enemies. I think they'll just
phase through each other, but we need to do that function. If not, then we'll check again. Then we might cast
to something like the blocking volume if the
things are blocking volume, if that's true, then yeah, we've hit a wall, and
we'll change direction. So this is one way that
you may see it done. It may have been how
you consider doing it if you're aware of
what casting does. And I just wanted to run through a general kind of rundown of why we won't be
doing this and why we should try to avoid
this where possible. It's another thing which
gets repeated very easily like the general blanket
statement of never use tick. You'll hear people say
never use casting. Again, that's actually
really simplified. There's a bunch of reasons
that we would want to use casting where it can be completely safe and
very cheap to do. It's a little bit outside of
the context for this topic. So at the moment, we're just going to say,
I'm going to delete these. We won't be doing this
purely because we don't want this cascading
waterfall effect. Where we're checking per object. So to keep things simplified, I'm going to break this
into two different things for this project that we
pretty much want to check for. We want to check for the wall, and we want to check
for the human player. I think, just for game design
and to make it feel better, I think I'm going
to let the enemies phase through each other
so that the player only has to consider
which way they're going between the
impact with the walls. So this actually becomes
them really, really simple. We can do a really simple
check to begin with. We can right click
in the event graph, and we can search
for Get player pawn. So this is a built in function. The Unreal engine is always tracking what's classed
as the player pawn, which is the thing that the
human player is controlling. So when we press
play, that would be the blue ship. So
what we can do here? This avoids casting, which
is one useful thing. We don't need to get all of the information out
of that other class. This also means
that if we had more features like letting the player pick between five different
blueprint classes with completely unique ships, again, we wouldn't need to check against each unique ship. We just need to know that
the thing that we've hit is a player ship, and we would know that
from this check here. So we can pull from this bin, and we can search for equal two, so just two equal signs. And I'm just going
to unhook this to make it a little
bit more readable. Hook this back up.
We can just say, Is the hit actor? So the thing that we've
just collided with, is that also specifically the thing the player
is controlling? We can then pull from
our boolean here. We can search for a branch.
We can plug this into our execution pin and a nice simplifying the code that
we need to check against. So if the thing that
we've hit is the player, if that's true, then we're
going to do our damage thing. So we may just want a print string here to
remind us to come back. So I'll throw on a print
string because we don't have damage functions and
stuff like that just yet, but I'll make this big and bold so that every
time this happens, this will remind us to come
back and change the code. So this would be where we
apply damage to the player. And then the destruction of ourself is actually
really simple. We just need to call the
function destroy actor. We can throw that
in here. And that's actually part of the
feature set, already done. So now if I did let the
enemy planes fly down, they would be ready
to apply damage, although we don't have health
and everything yet anyway, and then they'll
destroy themselves. Now, off of the false branch, again, actually
really quite simple, that now tells us that
if we're going to allow the enemies to
phase through each other, then we're not worried
about that, which means if we hit something else, that's going to
have to be a wall. That's the only
other thing that we can collide with
in this project. So we can do that from
the false branch here. And what we want to
do is we're going to set the move let. So I'm going to plug this
into the execution pin. Move this down here. And again, just to consider the way that
you might go about this. When you're getting used
to the flow of code, you might be tempted
to throw a Banchon so I'm just grabbing
from the execution pin. This will connect these
up automatically, and you might do
something like this. So check the value of move left. Are we currently
moving left? If yes. So if left is true, then
we want this to be false, and if left is not true, then we want this to be
flipped and turned to true. So that's definitely one
way you could do it. But again, I just want to how a nice kind of clean
way that we can do this. So if we hook this back up here, we can move these out the way. I'm going to delete
these, in fact, from our boolean here,
I'm going to pull, and I'm going to search
for something called a knot ball or a not boolean. The way that this
works is basically, if this is true,
this returns false. If this is false,
this returns true. So we can take this value, and it's essentially
always going to flip whatever the
current boolean is on. So if we're moving left, it
will make us move right, and if we're moving right,
it will make us move left. So a nice, simple, one liner
version of what could have been an extra branch
structure that we didn't need. And
that's pretty much it. That is our health
being accounted for when we get to that
topic a little bit later, the destruction on
contact with the player, and then flipping the
direction when we hit a wall. So if we go back in, press play, we can see they're currently
bouncing off each other, which I guess would be a
perfectly fine implementation, completely up to you. And then if they
hit the player, we can see on the top
left hand side, the print string
was being called, and they're being removed. So it's going to come
down to again, design. If you wanted them to
bounce off each other, we can leave that
functionality in. If you want them to phase
through each other, I'll show that when
we get more into the collision topics
a little bit later. But that's it. We now have
a working implementation. And as I mentioned, the reason
that this is working is every single time a collision is detected in this class now, this will automatically
be fired off. It's tracking all
of that information about what's been hit
and what's happened, and then we're
extrapolating from that information what's
relevant just to our project. We're going to do
something quite clever with the projectiles, so we won't need to account for projectiles when they
hit the enemy either. That's going to be handled
in the projectile code. So all the enemy needs to know about is the player
and the walls, which is actually another
programming principle, which is really
important, especially in languages like C plus plus, and blueprint because it's essentially still based on that workflow in the real engem. The general concept is
that we try to make a class as specific
and small as possible. So it's meant to do one
thing and one thing well. An example of bad code
in an enemy class would be tracking for what pickups
the player may have held, checking for things like what
projectile has just hit it, how much damage that
projectile should apply, and things like
that, because that is the logic in reverse. All of the stuff to do with damage from a
projectile should be stored in the projectile and that information should
be passed along. The enemy should only
be considering and responsible for
enemy related stuff. And in this case, that is moving and hitting walls,
very simple entity. So just another quick look
at what would be classed as clean coding is
that encapsulation of making sure that your class, in general, the rule is, as I've said, to do one
thing and do one thing well. With that set up
and ready, though, we now have a full class
ready to restructure. So next, we're going to look
at the topic of inheritance. This will be a better
way to handle a lot of shared code and
features that we're currently seeing in the
enemy and the player. We've been duplicating
a lot of work, so it's going to be
time to fix that.
13. 12 - Inheritance BaseClass: Time to fix the mess
that we've made. We've duplicated the mesh setup. We're handling
movement differently, but following the
same structural logic twice. This seems
small at the moment. But when we get to adding
health, destruction, effects, this will all
begin to compound. Every shared feature means
twice the work for us, but also potentially
means twice the bugs. Inside of the pawn
class as well, we're currently using the
entire mesh as a collider, which really isn't ideal
for this type of game. Most kind of ICD action
games like this actually use a trick to make
the player feel a little bit smarter than
they may actually be. When you think that you've
dodged something skillfully, but you actually just
scraped by that feels good, but that's because your
collider is probably a little bit smaller than
the enemy's collider. We're going to want to implement something
like that so that our game starts to actually feel interesting
to interact with. Just a minor form of
psychological manipulation, but it makes the game feel fair, even when the math
would say otherwise. The way that we'll solve
this is by using a sphere collider as the new route
instead of the mesh. The mesh will just act as completely collision
free decoration. Both classes will need this, so we may as well build
this just once and share components and features like that across
all of the classes. The way that we do this is with something
called inheritance. I've mentioned before,
the UnrelEngine is a very heavily inheritance
based piece of software, so we'll be following the
same kind of conventions. It basically means that we
can code something once in what's called a
parent or a base class. We can then make child
classes of that, which will inherit all
of the components, the features, the
systems that we input. For example, we've seen the
actors provide a transform, a location rotation
scale in the world, and then pawns take
that information and build upon it
with being given the mechanics to be
possessed and many other things.
That's enough talk. Let's see this in action.
So let's get to creating our new base class that will help us see the
system in practice. If we go back into the
blueprints and the core folder, we're going to create
something completely fresh. A right click in the
window, we'll go to Blueprint class again and
create a brand new pawn. I'm going to switch this from being called Pawn underscore something to BP underscore
plane base instead of pawn. They're all going to be
planes, so we may as well be clear with our
naming convention here. Inside of our new class, we'll
go up to the components, and we're going to add that
sphere component I mentioned. We're not looking for
this one here. This is a solid three D visual sphere. We just want a colliding bond. If we search for
the word collision, we can see here we have
a few different options. We want our sphere collider, which will apply this just here, and we can do the same
thing we've done before. We can drag this onto
the default scene root to make this our
new root component. We can then go back
up to the Add button. We still want our static mesh. This just won't be
handling collision anymore. We'll do the
same thing again. We're going to go to our static
mesh over here and we'll select our SM
underscore plane hero. With the static mesh
still selected, we can go down to the
collision section over here, and we can actually just
use this drop down and tell it to do no collision
detection whatsoever. We can also turn off the
overlap generate events. This just make sure that
nothing is being checked against the static
mesh at any point. Perfect because we want
this to be visual. It means it doesn't need to get added to the collision stack. It's just kind of sat
there being rendered. If we select our
sphere collider, as well, we can make a
couple of changes here. If we drop this down
in the right hand side in the collision section again, we want to change this
from overlap or dynamic, and we can actually make this
more distinct than that, and we'll tell this to be
classed as a pawn collision. So we'll see that
this changes the collision preset to pawn. The object type is now
tracked as a pawn, and it's done a few
things over here. Not going to leave it at this if we wanted full flexibility. We'll take this as
the default preset, but we can customize
this by dropping this down again and
selecting custom. So we still have
this set as a pawn. We still have the collision
enabled settings as we want, and we have all of
this set by default. We may find that we want to come back in though
and make this not overlap other pawns
or block other pawns, do something different
with world static, and we now have the
option to change these if we wanted. We
won't do that right now. We'll do that based
on testing and seeing how the project unfolds. With that done, this is putting us in a really good
position, though. Every child class will
now inherit this set. So this means we've
configured this once, and we never really need
to think about it again. So now we can start looking at our shared functionality
as well in the event grab, the kind of things
that we want to share between the two classes. As always, we're going to grab the event to begin overlap
and delete this one. And for the movement, there
are a few things to consider. Once again, purely just based on the recreation of
the example project we're trying to hit some level of parity there's
a small feature if you really look
at what's happening where the planes and
the player don't move or align movement at the
start of them spawning him. They play a small
animation. They animate up the screen for
maybe a second or two, and then they allow
movement to go sideways. So we're going to do
something very similar. We're going to freeze or pause any movement input until that
animation is rolled out. We'll get to the animations
in the polish section, but we just need to again, think ahead of what
we're going to need to account for and we can
program that in now. So let's start with
that. We're going to want to do some very simple, logical if and check. So if we pull from
our execution pin, we're going to
search for a branch. And from the branch, we can
take the condition here. We'll promote this to a
variable straightaway. Call this one B,
movement enabled. We'll hit Compile and make sure that this is
defaulting to true. So if movement is enabled, which we're going to
want to do whilst we're just debugging before we
have that animation set up, I movement is enabled,
then we'll allow the enemy to fly side to side
and move down the screen, and we'll allow
the player to fly side to side based on the input. If not, then that means we're
probably in the animation, and we'll call the false
function here and stop any movement from happening.
So this just sets us up. Later on, when we get
to those features, by thinking ahead,
we don't need to completely refactor
the code again. Next thing is both classes will need some type of movement. But like I mentioned,
we are programming the movement slightly
differently, so we can't share the
actual movement code because there's not the same
type of movement being used. But what we can do is share logic flow and the concepts of the
functionality that we will need. We can do this by having
a look at functions now. So on the left hand side, we have this function
category just here. Kind of like events,
we can create our own custom functions, and then we'll just
have logic called when that function is
called the same way that we've been doing
with events in the past. So if we press plus here,
and I'm going to call this new function we've
made handle movement. And you can see one of
the differences here and actually one of
the core benefits of using functions over events is that they do very
much the same thing. They respond to being called, but here we get our
own function graph. So it's kind of tucked
away, and again, keeping things nice and tidy. Now, we're not going
to actually handle any of the movement
function in here. The way that this will be
used is we're going to override this function in
our child classes later, but we need this to exist
to have something to call. What we do want to do is we're going to go back into
the event graph. We want to take our
handle movement function. And we're just going to
drop that on the true pin. So if a movement is enabled, then we're going to call the
handle movement function. So again, it becomes
nice and readable. And when we do get to
actually implementing this, we can get an idea of exactly what's going
to be happening. So this is kind of
simple for now. You'll see this unfold
and make a lot more sense when we actually begin
implementing things. This is one of those things
where I could try and spend five to 10 minutes describing
what we'll do later, but I think it'll just
make much more sense simply implementing it as we go. Another thing that
both plain classes are going to need the enemy and the player and bosses
or whatever you want to start inheriting
from this class, they're all going to need to be destroyed or killed
off at some point. So whilst we're here, we may as well go and
add another function, and I'll call this
one handle death. Again, we're not going to put
anything in here just now, but one thing that we can
do with this function, the generic things like everything is going
to need to play a sound effect or a particle
effect when they die. We can do that in
this class here because that is a
universal function. That's something that regardless
of the type of plane, they all need to show some kind of feedback
when that happens. So we can do the universal
functionality here. And then if there's
something unique to the individual plane, then we can override again the functionality that's
happening here and then do something
on top of that. So just a kind of rough
idea of how inheritance works and how we can make
use of that reusable code, saving us time and
effort as we go. Now, the final thing that
I can imagine that both of the plane classes will need is the movement speed. And we
kind of already know that. If we look at our own code, one of the few things
that both of them have is if we open both
their event graphs, pan base, which is the player has a lot more functionality. There's a lot more
variables here, but it does have movement speed. And then the pan is slightly
different, simplified. Whereas we're not
doing the move left check in the player version, we are still using
movement speed. So things like this
are what we're looking for in our base classes. Things that we've had to
implement more than once is something that we
could just dump straight in to the base class. So in here, what I'll
do is I'll create a new variable called
this one movement speed. And we changed this
one to a float, it and pile, save. And any time that we might need to use those
between the classes, that's available
and ready to go. Again, we don't actually
need to make use of it yet, but this is just setting
the groundwork for us to have a more smooth and
pleasant transition. And really, that
is our base class fleshed out as much
as we need right now. We can come back and
add things later, but we have the shared
components that we need the unified collision, the movement ready to implement in the individual child classes. Same for the death
and then the speed variable ready to use as well. So just to have a
very quick look at how we can make use
of this base class, we'll add the full functionality
a little bit later, but a very quick demonstration. If we go back into
our content draw, we can right click
on the plain Base. And up here, we
have the option to create child Blueprint class. If we click this one, this will create a new
blueprint for us. This will inherit
everything from plain Base. I'll call this one BP
underscore plane player. And then if we double
click to open this one, we can see that this
now comes pre built with our sphere and
our static mesh. We also have a few other
things here as well. If we click the override option, we can see actually
the functions that we've created manually, handle death and
handle movement, specifically from
our base class. If we click these, this will give us a
function that we can then start using the parents
version of the function, plus, as I've mentioned, doing our own thing on top of that. Our press controls to get
rid of that one for nine. We don't want to do any
overriding just yet, and we don't need to
change anything just yet, but I wanted to show you another way as well
that we can go about adding classes based
on those that we've. If you go back into the content draw one more time,
right click here, and this time, we're going
to go to a blueprint class, something that may
not be super obvious. But a lot of people think
that this is restricted to unreal engine classes and those created by the
unrelengin developers. But in fact, as soon as
we've created a class, we can actually find those here. So if I look for plain base, we can see here plain
Base is a child of a pawn and a pawn as I've
said, is a child of an actor. And our plain player is
on a child of plain base. So we can actually now select our own classes from our class
list here in the editor. So we'll select plain base
again. Select this one. And, of course, we're
going to call this one BP Underscore plane enemy. So both of these are now ready to go as we start
fleshing things out. Just wanted to
mention, as well, that there are multiple
ways that you can make use of your base classes when you have them
ready and set up. With that done, the base
class is ready, though. As I've said, we have
the shared components, the shared functionality, and the unified collision setup. Next, we're going to
refactor the player first. We'll take the big
one, go through the chunky code that we're
going to move across, and we'll focus on
the enemy afterwards.
14. 13 - RefactoringPlayer: Class is now ready to go.
So now we're going to need to convert our player
to make use of this. This is what's referred
to as refactoring, essentially restructuring
existing code without changing what it does. We're aiming for
the same behavior but better organization and reducing overhead or reused
variables components and things like that
where possible. Realized in the previous topic
we've just gone through, I gave you instructions to
create two new plane classes. There's one other thing which I realized in between
that could be useful to be aware
of and actually would save us a
little bit more time. So what I'm going to
recommend is we actually delete plain enemy
and plain player. So we can grab these Shift
Select and press Delete. We haven't actually done
anything in those anyway, so we're not losing
anything, and at least you're aware that those
options are available. What I would suggest instead is if we go to our plane base, and I'll just close these
to not confuse things, we want to double click and open the plane base class
that we have been working on to make
sure that you haven't deleted the plane base class. So with plane Base still here, we didn't want to
get rid of that one. What we want to do is
we'll actually make use of the existing classes and we'll just move things
around where we need. So the first thing is
to focus on our naming. If we focus on the
player first of all, I'm going to rename
this one to BP, underscore plain player. And that's why we
needed to delete those other classes so we
don't have naming conflicts. The class name was fine, but we can just do
something quite smart here to make use of what we already
have in this class. And we'll do the
same for our enemy. So we'll grab this one and call this one BP underscore
plain enemy. And then if we
double click to open the BP underscore plane player, we can start making
our changes in here. So the thing that I thought
would be useful to know about is there's actually
a really cool way that we can change what this class
is classing as its parent. So at the moment, we can see
the parent class as pawn. If we come over here
to the class settings and then back in
the details panel, we can see we have the
parent class here. The dropdown is set to pawn. If we search for plane,
we can change that to now be inheriting from BP
underscore plane base. And this is actually quite a
nice way to refactor things. One reason is that it
will give us errors and warnings about code
that may be duplicated. And we can kind of
already see that. So if we look to
the left hand side, we can see we have
two static messhes. Anything here which says Edit in Blueprint or Edit
in C plus plus, if you're working in a
C plus plus project is an indication that this is
inherited from a base class. So when you see this,
we cannot come in. We can't grab the inherited ones and delete those, so
they're stuck here. So what we want to
do is we want to grab our static mesh
underscore zero. We don't need two different
meshes for our plane, and we can delete this one, so we'll just delete that here. The other thing that we can see here is we have something called movement speed underscore
zero in the variables. If we write, click on this, we can go to the find references, and we can find all of the
current class members of this. If we double click
this one, that will take us directly
to where this is. So as I've mentioned,
it's a nice quick way to be able to tidy
things up because we can just use these shortcuts
to jump around and find the remaining things that we might need
to get rid of. And again, the reason
this has been given an underscore zero is
because we're aware that we have something called
movement speed in the base class and we can't
have the same variable, even if it's in a child
version of that class. So we'll do the same thing
again. We'll delete this one. We'll just delete
movement speed. This is just telling us that
we're currently using this, but that's perfectly fine
because we know where that is. And then what we want to
do here is we're going to pull from here and we'll
search for movement speed. And we can see here
we want to get the movement speed variable, and this is the one in our
base class, the pairing class. And there we go. So
that is pretty much it. That's the class kind of
fixed up and ready to go. So we could definitely
leave it here, but there's a few more
things that we can make use of from the parent class. So the first thing
we always want to account for is on
the begin play, and we need to do on all of
our functions essentially. But to begin on our begin play, we're going to right
click on the node, and we want to find
this option here to add call to parent function. And we want to hook
this up in between our current execution and the functionality
we're already calling. This just ensures that
the parents begin play logic is also called
when we start playing, meaning that if we had any
universal logic again that we want to do on
the parent class, that will also get called. Otherwise, this will only
be called by itself, and we'll ignore the
parents version. We want to do the same
thing for the eventic. We're going to write click
on this, add call to parent function, and then
just hook everything up. Including the Delta seconds, we might need to move
some things around here. And then we can just
plug this in over here. And again, tidy things
up if you wanted. Try to help visualize
this for you, the parent functionality
will be called first. So, for example, when our
player first gets created, it will automatically
have its event begin play get called once, and then we're going
to immediately send a message to actually look into the parent version and see if anything's happening
on that begin play. Navigate over here. We can open the parent blueprint
with this shortcut here, and we can go to the
begin play over here, and we can see there's no
actual code at the moment. If we were to do
something like pull a print string off
of here, though, we would just say parent begin and keep that on
screen for a moment. What would happen now
is when we press Play. We're going to see that parent begin is printed at the top. So we can confirm that
our parent logic, the universal logic is,
in fact, being called. Now the problem,
of course, is if we added this to the enemies, so if we go into the
plain enemy class, we come into the begin play. We're going to want
to do the same thing. We're going to right click, add a parent call, and
we'll plug this in here. Now, because we've got three
enemies and one player, we're going to see that
message four times because they're all calling
that same function. Of course, we need to
make the same change in the enemy class
class settings. Change this from
pawn to plain base. Okay. So we're doing the
same thing. We're just reparenting what this
class is inheriting. Hit compile. So all four of them are taking
that same process. Now, this is fine, in this case, but this is where you want
to be careful and start making sure that you're considering what
you're implementing. So this and wouldn't make sense if this message was
saying something like, if this was specific to what
the player should be doing, like shouting, I'm the player. If we hit compile, that
means the plane player, we'll call that, which
is perfectly fine. But so all of the enemies. So all three enemies
are also saying, I'm the player, which is
a little bit confusing. So this is where you need to
be careful with inheritance. And this is why I
keep saying that we want our universal
logic in here. Anything which is
just related to being a general plane
is perfectly fine. So if this was every plane had to just announce
itself and just say, I'm a plane, this would
be perfectly fine because all four of
them are indeed planes. As we can see, but we
wouldn't want any code in the base class that was related specifically
to the player. So hopefully, that makes sense, and we can kind of
start seeing how we can use inheritance and how we
can share our features, components, and logic
between multiple classes. And this is how we'll be handling the things
like destruction. We can have the
particle effects, the sound effects all play in the parent class
because that's not specific to any one plane. They all need to be
destroyed in some way. But the things like the movement
we just slightly unique, we're going to handle that in
the child classes instead. Back into plain player. We're going to finish this
one off first of all. A really quick way
to take the code, which we know is
already working. Like I've mentioned, all
we really want to do is make sure that we don't
have this littered around, and we can make use
of that function that we created a small while ago. So if we grab everything
related to movement, so that's going to be these
nodes here, calculation here, I'm just holding Shift and dragging and then the
actor location here. This is only related
to rotation, so we're going to leave
this one for now. What we want to do is
we're going to press Control and X to cut, and we're going to click here to override the handle
movement function. So we want the function that we made earlier, that empty space. And then we're going
to press control V in here and we're going to
hook everything back up. So we can ensure our
movement function will always be called because
we're calling our eventic. And if movement is
enabled, then handle movement will also be
called from that eventic. So again, we only need to
call this function once here, and we can make use
of that function call in our child classes. We've moved this, and I'm just going to come back
into the event graph. We'll move this over for now, the rotation, and we'll
hook this back up. And just testing as we go
through, like I've mentioned, this is more to show
you how you might refactor code on your
own if you needed to. And this is a very
common process. We just want to test these
little steps as we go, and we should still have
the movement being enabled. So if something's broken, let me just double check
what that could be. If movement's enabled, if that's true, we're calling our eventi. We have our handle movement
here, movement speed. So I just realized we've got our new movement speed
variable in the base class, but we haven't
given that a value. So if we grab our class
settings just here, this is actually
going to give us access to the class
settings when we select this topmost element
here or the class default. We can see the class defaults
have the variables here, and we can see our
movement speed, and I think I was
setting this to 1,000 in the player. So
we'll set that back. And then we should
be able to go and press play and move around. So we get the same movement,
we get the same rotation. It all feels the same. We haven't changed
the functionality. We've just tidied up our graph and where
things are handled. So we're ensuring that this
is called if and when we want to and another
really cool benefit of dis now is we could come in. We could disable
movement enabled, so movement are no
longer enabled. We can press play, and now
we can't move the plane. But of course, it breaks
the rotation because we're now multiplying against a value that doesn't really do anything. But you can see the
logic is that we're kind of handling things as
we want this to work. And all of this is now
actually handled in the base class regarding the movement enabled
and disabled states, and we're just leveraging
that inside of the player version
of that class. So make sure that we click this. We're going to make sure that movement is enabled
for the moment. We'll be toggling that
through the code a little bit later when we
get to the animation. Remember that I've said that we won't be adding things like the rotation or faking physics and things
in the enemy class, which is why we're not re using the movement code exactly. Another thing the
enemies won't have is the actual rotation logic we
have here for that reason. Something we could
do, though, to keep the naming conventions and
the general styling the same. There's another tip
I wanted to show. We can grab all
of these nodes in a press shift select
to grab these nodes. And then if we right click on any one of the nodes
we have selected here, we can choose to collapse this
to a function of its own. And we're just going
to call this one. You can probably guess. We're going to call this
one handle rotation. Nice and obvious. If we have to dive through this and start
reading through our code, we'd come from the
plane base festival, and we can see that if
the event ticks running, if we have movement enabled, handle movement,
and then this will jump out of the base class. So once this is done,
this will then return us to our child class. So this has now been
handled. The execution pin will be called, and then
we'll start calling this. So we're handling movement
if movement's enabled, and then we're
handling rotation, again, if movement's enabled. Exactly the same results if we go and press play,
nothing's changed. We're just making this much more kind of readable, maintainable. We're getting rid of
that spaghetti code or even the potential
of that happening here because we're in full control of our code base and how
we wanted this to run. We're making use of inheritance, so we're saving a lot of time when we get to
adding new features. At the moment, this might
just feel like busy work, but it would
definitely save you a lot of time in the long run, understanding what this is doing and how to make use of it. Oce we're here, if you're
inside of the plane base, if you're following
along exactly, we can get rid of this message describing
what the class is. We'll just get rid of
that one. It compile in. Enemy will leave for now
and just double check that everything is saved
in the player class, and that is the
player refactor done. So you can see it wasn't
really that painful. I took a little bit longer
just because I wanted to explain these concepts and
make them clear as we go. If I was doing that
by myself, that probably would have
been a 1 minute task. So even refactoring and
completely reconsidering how the structure of classes are handled really doesn't
need to take that long, especially if you're thinking
early in development, which is why I'm trying to
emphasize this as we go. Try and make these
choices upfront, and it's the
difference of having a 1 minute refactor
to a 1 hour refactor. If you're testing your code and something's broken
or not working, just double check that all of your parent calls are connected. So these parent calls here, the orange nodes are
connected up and being called on the begin play,
the event tick, and so on. Likewise, make sure that
things are the movement enabled is ticked in both the base class here
and more importantly, through this value up here,
like I've just shown, just a couple of
gotchas there, which I can imagine some people
might fall into. So just double check the things that we
have been changing. For the next topic, of course, we're going to jump
into the same thing, but for the enemy
and it should take a little bit less time. Now
we've seen how to do it here.
15. 14 - Refactoring Enemy: Refactoring the enemy. We've
already done this once. Should go a little bit faster. Obviously, if you don't
already have this open, we're going to make sure we
open the plain enemy class. Double check up here if you haven't made the change
already that we have this reset the parent class here
to BP underscore plane base. Remember, we can do
that through the class settings and the
drop down just here. Same things again
as with the player. We're going to delete
the static mesh, which will give us a little
bit of a problem here. We'll just say, This is
fine, so we'll click yes. This has dropped me straight down to
where the problem is, and that is that we've bound the previous component
hit function, that collision check to
the original static mesh. That's actually not a problem because we wanted
to fix this anyway. We don't want to use the
mesh as the collider instead we're going to take
the sphere component here, so we'll grab this one, and we want to do the
same thing again. So in the right hand side
in the details panel, we just drop this. We're going to find the
on component hip function and create a binding to this. And that's all this is doing. It's just binding
whenever action occurs, it's binding this function
to then be called. So we can delete this one,
hook the same things up, so execution pin and actor pin, and that's the initial part
of that problem solved. Then I'm just going to jump
into the viewport quickly. We're going to grab
the static mesh, and we want to make sure that we're updating the material. So again, in element zero here, this is the one controlling
the body of the plane. We'll drop this down,
and we can now find the other material that you
may have made for your enemy, which for me is just MI
underscore plane enemy. Select that one, and we're pretty much back where we were. Same thing with the
movement speed as well. We want to right
click on this, find the references by
the class member just to make sure get
rid of all of these. We can double click on one of these and see where
they're being used. We can see that we have a few of these to ensure that we don't actually miss any when we're trying to hook
things back up. I'm just going to right
click in the event graph down here, search
for movement speed, and we're going to get the
movement speed variable here, the one which was created
in our parent class. Hook this in here, duplicate
this, hook this down here, remove these just to tidy
things up a little bit, and double click on the final
one, which is over here. And again, we're just
going to right click and search for movement speed. We're going to set
the movement speed here, and then
we'll plug this in. Get rid of that, and we
are back to where we are. Now, there is an option, which I didn't want to mention because I found it to be a
little bit fiddlly. In fact, on the current version, it just doesn't seem to work. But something that
might be useful if they fix this in the future, is that in the past,
we've been able to right click on the variable. We can select here to
replace references. What I'm missing
at the moment, I don't know where
that features gone. You can normally drop this down, and we could have found the
movement speed variable created in the parent class. For whatever reason,
I can only access variables from the actor
and the pawn directly, not the actual BP
underscore claim base, even though I should be able to. But just to say,
for those reasons, we may just need to go through and do it
the slightly more manual way that we've
just those removed, remember to get rid of the movement speed
underscore zero. We shouldn't be using
that at all anymore, and then just hit compile to make sure that
everything's gone through. Same with the player. Now
that we have these in place in the enemy class, we
just wanted to double check. I've already taken
this path whilst we were doing the player content if you decided not to jump
into the enemy class I'm focused just on the
plain player class. Then just double check that
you have the begin play, parent call and the
eventI override of parent call as well. So we're going to
R click on here. We're going to do the add
call to parent function, and again, just make
sure that we're hooking these up as we go. For the movement logic, we're going to do the
same thing again. So we actually want the same
movement logic to happen. We can grab all of this. Nothing's really
going to change. We can grab these, so just
Shift select to grab these. We want to press control in X to cut all of the
nodes out of here. We're going to go
to override, and we'll override the handle
movement function. After we do anything
that might need to happen in the parent function, we're going to paste
these here and then make sure that our
movement is called. With that done, that's
just reminded me that we could also improve
another thing over here. So we want to make sure that with our overlap
that we were doing, we were previously just calling destroy actor and
leaving a message here. We can bring in our
handle death function, drop this in here and replace that with the destroy function. So we'll get rid of Destroy.
We've got our handled. What we may want to do is
actually come in here. We will leave ourself a
comment. So this is taken. If we double click on this, that will take us into
the plane base. So we're now in
the parent class. We can pull from here, and something we're
always going to want to do at the end is called
the destroy actor. One thing that we might want to do another use of comments, I can make things easier
to track things down. So if we press C to
place a comment here, I like to leave
myself these kind of to do postic notes, almost. We can say something like
implement the pre death effect. So what happens
before we destroy our actor? We're going
to do that just here. This makes it really
easy because you can press Control in F in any class and you can search for Do. Double click on this. And wherever you are
inside of your class, if you have a bunch of
to do comments left, you can quickly jump to them and see what
you can work on. We can also come to
the right hand side. We can give this
a different color just to make this stand
out a little bit. Completely up to you how
you wanted to handle this. But as I mentioned, it's
just a nice way for me to quickly find the features that may not fully be
ready to implement yet because we're not touching
those parts of the system, but I know that I
will need to come back here and do
that at some point. So when we come back to
update this later, and again, this is how you should really be focusing on your workflow. This hasn't really done
anything for us right now. What this means is that
when we do come back to add effects and
things like that later, we don't need to come back
and change our enemy code. This is now ready to go. We can keep developing around
the entire project, and this will just
automatically be accounted for when we've
updated the features in here. The rest of the collision
is still the same. This is still going to be pretty much what we
need to do here. You could replace
this with a comment, another to do comment if
you wanted to, but again, we haven't got to
the health system, so we need something
to remind us to come back here, and that
print string is fine. We just want to double check
that all of this is working, so we'll go back into
play mode, make sure that the enemies and the player
are still moving around. Intended, and that's
all pretty good. They're still bouncing
off the walls. They're still taking
their random direction, random speed, bouncing off each other, and we can again
come back and change that depending on how we feel
the game should play out. But I think with
that done, we now have the parity of behavior. Everything works
as it did before, but making use of
proper inheritance. It's just a quick recap now that we've finished our refactor, moving over to
using inheritance, find out what this
has brought us. The core things are
going to be that we have shared collision setup. We can configure
that sphere once, and it will be working in both of the classes
that make use of that. We have the shared
death handling. So again, when we get to
adding things like explosives, sounds, both of the classes
will be able to use that. We can override the
effect if needed, but we don't need
to make any changes to our base code now. We have the shared
movement logic toggle, so when we get to
adding the animations, that movement enabled
will be valid for cut scenes for both
the player and the enemy. We've got the shared variables
like the movement speed. It exists once we
don't have duplication or multiple versions
of those across class. Might see my overkill
at the moment for just these two
different classes, but we're about to add
things like health, projectiles, spornos
effects, and everything. We'll now have a shared feature
pool to pull from where things are relevant
to make use of the same thing rather
than duplicating it. One thing that did just
come to mind is that we could make those
sphere updates now. Like I've mentioned, the
general kind of approach that people take with this type
of game is for the player. We want the sphere to maybe be a little bit smaller than
what you'd expect. So this might actually
be fine. We could potentially increase the
sphere radius a little bit. We can see at the moment it's actually inside of the body. It's so small, we wouldn't
be able to see it. So projectile is going
to completely fly through the entire wingspan
and most of the body. This, as I mentioned,
is just to make the player feel like they're a little bit better
than they are. If they think in
their eyes that they a bullet just about missed them, but in fact, it hit them. They're going to feel
like they've dodged it. And what we don't want is
for it to go the other way, where they feel like they
should have dodged something, even though they clearly
wouldn't have done, and they feel cheated by taking that extra damage in their mind. With the sphere
selected, though, we want to find the
sphere radius here, and we can set this
to something like 50, and you'll see where
that comes into play. So now, this is the area that the player would
be taking damage. So maybe I'll just reduce
that a little bit, so it's just purely the body, something like 45, and I think that's probably as much leeway
as we want to give them. If we hit compile and then go back over to the enemy
class, same thing. We're going to go into the
viewpot, grab the sphere, but I think this time,
what I might do is make this something like
64 or double it. And in fact, that might be maybe even that's a little
bit too small, so let's make something like 70. So again, it's just
going to feel like the player will feel
as though they're making their shots much better that every time
they take a shot, they're actually
hitting the enemy exactly as they planned, where, in fact, they're going
to be missing the body, but we're going to pick that up for them and make them just feel as though they're doing better at the game
than they might be. Obviously, we want
to balance this. We don't want to
make it too easy, but you can see it's nice and easy to tweak these
things anyway. So that's how inheritance setup. Both classes are working, both inherit from
the plane base. Next, we can move on to
adding the health and damage. And this will be where
you really start seeing the benefits of taking this time ahead of the implementation to
get inheritance set up.
16. 15 - HealthSystem: Time to add health and damage. We already have our base class set up to share the
important data, so let's jump straight into
BP underscore plane base. In here, we're going to need
two different variables. We want to track
our current health, the health that we
currently have, and our maximum health. We can do this with
the use of floats, so I'll take the movement
speed, press control, indeed, duplicate this once and call this one
current health. Duplicate it a second time, and we'll name this one Max Health. If we hit compile, I'm going
to grab the maximum health, and I'll set this to
a default of 100. We can leave current
health empty. And what we're doing instead
is on the begin play, drop our current health onto
the execution pin just here, and we want to take our
current health and set this to be our maximum
health. No human errors. Every time we start
playing, we will always make sure that we
start at full health. So a nice, simple way
to just ensure that we don't get anything wrong
with our health system. Once again, just a reminder. This is why it's
really important. When we hit compile and save, go back into the plain player and just double
check that you have the override ce to begin play, and the
same for the enemy. So this is where it
becomes important, because we want to make sure
that both of these classes initialize their health to whatever their maximum
health is meant to be. With that confirmed,
we can implement our damage handler as well.
This is really simple. RL Engine has a
built in messaging system specifically
for handling damage. If we we click in
the event graph, we need to search for an
event called any damage. So it's this one here,
the event, any damage. And this will be called
whenever we send a message from another actor, whether that be a projectile, an explosive impacting the
plane or the enemy class, we can send a message to say,
Hey, you've been damaged. When that message is sent
and this is received here, this event or function
call will be made. And when that happens,
all we want to do is we want to set
our current health, so we're going to drag this
onto the execution pentagon. We'll make that
our current hell, so we'll take the current
health a second time, control drag that into the
graph to read the value, and that will be
negative our damage. So current hell
minus a value here, which will be our damage
that we're passing in, so this will be something we place in the call
from the other class. The result of that calculation
will be our health value. One thing which is normally recommended for this
type of calculation is to avoid things from getting a little bit hacky or weird. We don't want to allow damage or if we implemented
healing, as well, we don't want to allow
those values to take us past our maximum or
minimum threshold. So we can use something
called a clamp. We'll pull from this pin here
and we'll search for clamp. We want the clamp float option, which is just up here a
little bit. So clamp float. And we're going to plug
the result of this clamping into our pin just here. So we can see what
this allows us to do. We can take our minimum value. So the minimum amount of
health that we should be zero. And the maximum amounts of
health that we can have will be our max health value.
So nice and simple. This just means if we add in
something like health kits, when you pick one of
those up, you cannot go past the maximum health
that you started with. So it will always
keep the results of that calculation
within that range. Once we've confirmed the
setting of the health is all done correctly and we
can't get any of this wrong, we're going to do
our check to see if the damage we've just taken
is enough to destroy us. So we'll pull from our
execution pin just here. We're going to
search for a branch. Our true false
flow control here. And all we want to
do is we're going to get our current
health value again, so control drag that in.
See if this is less than. So we're going to look for
the less than operator here. And what I'll do is I'll
actually check whether this is less than or equal
to a certain number. Plug this into the branch
just here, the Boolean, and we now have a
check saying that if our current health is less
than or equal to zero, then we can handle
our calling of the death function or the
handle death function. Be asking why we're using
less down at equal to, especially since
we've just clamped this to make sure that it
never goes below zero. And this is just kind
of a habit of safety. If something applies more
damage than the remaining hell, or we get some floating
point weirdness, or even if we just
refactor the code again later and decide
not to clamp this, I want to make sure
that this check will always be relevant to
ensuring that we're dead. So, for example, if we
do take this clamp, if we're at five health and we take another ten
damage, obviously, we're going to be at
minus five, which means that if we just
checked only zero, then something which has taken more damage than they should would avoid being killed off. With that done, though,
from our branch here, we can take our handle death. And if this is true,
we can just plug this in just this
is what will now start handling all of
the death functionality for any of the planes that
you may be tempted to make, and, of course, the two
that we already have. Handle Death already has
the destroyed function in it, and like I've mentioned, we're going to
return here later to add things like explosions, sound effects, and all of the fun juicy things that we
want to put into our game. So to actually start making
use of the damage system, we already have something
in place to test at least part of
how this structure is going to work
inside of our enemy. So if we go back into the
enemy class, this bit here, which has just been doing
our print string for the longest time, we can
finally remove this. What we want instead is we're going to take our hit actor, so we'll control drag
this into the graph. We'll pull from the other actor, and we're looking for
that global message that I mentioned a little bit
earlier, the apply damage. So we can see here we
have apply damage. This just makes a call to any actor that it can
communicate with. In this case, is
going to be the one that we've just bounced into. We're going to hook
this up just here, so we'll take our
execution pins, make sure they're all hooked
up, move this out the way, and we want to apply
a certain amount of damage to the player. I think for the enemies,
I'm going to give this half of the players health. I know the player is
going to have 100 heal. We'll set this to 50, and this
means the player can take two hits from an enemy so they don't want to bounce
into them too often. It's not going to be a
quick and clean way to gain extra points by just flying into all of the enemies. Now,
that does remind me. Another thing that we can do
with our inherited variables is if we go and select
the enemy, for example, and get the class details just here, in the
right hand side, we want to make sure
that the enemy health is probably going to be different
to the player's health. So we could set the
enemy health to 50, and in the player, we'll
just double check, there should be
defaulting to 100. Make sure that the player
health is set to 100. So again, a nice
easy way we can set the health value just
once and handle this in the parent class for both the damage taken
and the health given here because they're
based on variables which will exist in
all of the classes. And then in the child classes, all we need to do are just
tweak these variables in the editor if we wanted to change the general kind
of markup of our game. And like I've mentioned,
because we've already set this up
in a certain way, it now means that we've
got the handle death. We don't need to come
back and touch this. We needed to make one
small change side of the enemy class
didn't need to be accounted for and everything
will keep working as it was. One thing I want to show
you really quickly, just because this
is another one of those really clear places where you might have
been tempted to take a different approach
to handling death for the enemy is we could
unplug this just for now. Another thing that you
could do is we could duplicate our apply
damage function. We could plug this
in here because the main thing is that when
we hit the player plane, we know that the enemy class needs to damage
itself, essentially. What we could do is we could
take our MAX health value. So we can search for MaxEL
and plug this in here. And then the damaged
actor, we could basically just apply
damage to our self. So if we pull from
the damaged actor in here and search for a cell, we get this option built in functionality that I
wanted to introduce, get a reference to self. So this will basically
it's a bit of a weird one. This will basically
find a reference to itself, which is found here. It will call the apply
damage message on itself, which will obviously
be picked up in the base class because
that's where we have the event any
damage being fired off. And because we're passing
in a maximum health, and this is just a safety thing. I later, I decided
that, in fact, the enemy health
at 50 was too low, and I came in and
changed this to 75. Obviously, what we don't
want to do is have this hard coded 50 because
any changes that we make on a wider base than just this one node here would be kind of forgotten and missed. Just another way, again,
just trying to get to think about how you
structure your code, the use of variables,
mainly just kind of future proofing so you don't have to
do more work in the future. The main thing with programmers
is that we're quite lazy. The least amount of work that
we have to do the better, avoiding coming back to our code and changing
variables everywhere. If we can set something
up like this, I can change the maximum
health to anything. I could lower it, I
could increase it. This function will
always. Like I mentioned, though, it just doesn't
read very cleanly. It takes a little bit
longer to work out why we've got one
applied damage, another one here, and what
these different actors are. I think it makes more sense
if we're just going to do a full kind of
kamikaze style enemy, we can just plug these in,
and it makes it much clearer. So we're going to apply
damage to the thing that we've just once we've done that, we're going to apply
damage to our self. And remember, this is really safe because we're
looking down here, we're checking if
the hit actor is the thing that the human player
is currently controlling, and only if that's true,
are we going to do this. So this won't be applying
damage to the walls, the projectiles, other enemies. All of that will be
accounted for down here. So this is a nice, easy, safe way to structure impact damage. Something I haven't
really mentioned, but also keep in mind that order of operation
is really important. As soon as you think about it, if you haven't thought
about it before, it becomes really obvious. It is something
that people miss. If we just handle death first, so we could throw this
the other way as well. If we plug this in here,
unhook these, move this. Just a very quick example,
plug this back in. And this is just because I've
seen this happen before. Students weren't quite sure why certain functions weren't being called when they
expected them to. It can sometimes be down
into the system as well. Some systems it might work, some systems it might
not depending on how quick the ticket iteration and things like that
will be happening. But basically, if you think
about what's happening here, if we hit the player and
call handle death first, this will be jumping into
the parent function, calling destroy, and
potentially if this gets immediately added to the garbage collection and
is cleared from memory, this class no longer exists. That means it's not going to be stepping back out of here, back into this function, and applying damage to the player. So you might end up with a game where the
player just never receives impact damage. So be careful with
that type of thing. And again, it's very
common for people to have already handled the death
side of functionalities, and then they'll come
back and they'll add the particle effects and the
sound afterwards and then not be immediately clear on why the particles and the
sound effects aren't playing. It's because your
class is probably being cleared out of
memory and doesn't exist. That function being
called. So order of operation is also important. Flow control is
another word for that. This was a really
simple kind of example, but it's an easy trap to fall into if you're not
paying enough attention. Even if you do
understand the concept, it's a simple mistake to make. So with that, if we just
hit compile and save, we can go back in and
test or hit play. The thing that we're looking
for here is making sure that they still destroy themselves
when they hit the player. And most importantly, after
two hits like we saw there, they're also
destroying the player. So neither player and the
enemy have a health system, keeping in mind that
we're not implementing that health system
into each class. We're doing it just
once in the base class, and all of this functionality
to calculate the health, check if their class is dead, and then called the function
is all being handled there. As I look at this, I've just realized I've
probably made a bit of a coding faux par
my this is actually going to cause us
a bit of a problem because we are calling
handle death when we've calculated
that death would be appropriate, and
we're calling it here. There's a good chance
we're going to get two sets of particles
playing off. We're going to get one
from dysfunction call, and then one from
dysfunction call, which is happening
based on health. So now that I'm looking
back through my own code, what I think we should be
doing is actually the way that I was trying to use as
just a learning example. But if we duplicate this again, I think we will have to
do it the other way. So we're going to apply
damage to ourselves. So again, we'll pull
from the damage actor. We'll say sell, and
we'll plug this in here. This just ensures really easy to overlook
things like this. Lucky that we went back and
had a quick look at the code. This just ensures that damage
is applied to ourself. We're going to use that
same trick I mentioned a moment ago, so Maxel. And again, I realize I could
edit this out of the video. But since I've created
this whole project with the intention to try to teach
how to think about coding, I think this is a pretty
good learning example of the kind of things that
you can overlook and how you can easily get yourself into a little bit of a situation that you need to come
back and fix later. So this will ensure that we
definitely kill ourself. We're always passing
in the maximum amount of health we
could possibly have. That will trigger
this to be called, which ensures that our handle death is only ever called once, meaning that when we do get
to the particle effects, the sound effects,
going to have it called once here and then
also played once here. So that's the main
thing we want to avoid, so I can delete that one. It
doesn't read quite as well. This is where you
might want to bring in a comment just to make it
clear. This is for the player. This is for yourself to
make sure that we always remove ourselves from play
after hitting the player. Really simple thing,
really small thing. Turned it into a bigger topic
just because it's more of a learning moment that just accidentally
cropped up there. Final small thing
we can do whilst we're still in the enemy class. We can remove the use
of magic numbers. So at the moment, we're just applying 50
damage to the player. We can promote this
to a variable, and we'll call this
one impact damage. Just double check for any others that we
might have missed. I think it was just that one.
We do have these, in fact, if you wanted to
create a minimum and the maximum movement
speed because the enemy has the chance
of that randomization. It compile save, go back to the player,
double check here. I don't think we've
got as many variables that we've been
creating recently, so that's fine in
the player class and then in the
base class as well. Something else that you
might want to do if you're really trying
to keep all of these variables tidy as you start getting a lot
more variables, it can become a
little bit harder to find what they're related to. So you can give variables
categories, which may help. So, for example, we've
got here current speed, target speed, interp speed. So these three are all
related to movement, and then these two are
related to rotation. So what we can do
is if we select one of these in
the details panel, we get this option over
here for a category, and we can set this to
something like movement. And then we can just
simply drag and drop the other movements
into the movement category. We can grab the rotation,
and we could maybe give this a category
of rotation. Same again, drop these in, and then we can
collapse these. Makes it a little bit easier
especially when you've got a long list of variables that start cropping up
in larger classes. That means you can
just navigate very quickly to find all of
the movement related stuff if you're looking for
one very specific value to tweak and the
same for rotation. You can have combat. You can
have a category of Booleans, vectors, whatever you
want to set up here. Very much how your work process is going to best fit your
project, but just a quick idea. If you wanted to do
something in between topics, feel free to pause, have a little break
from following along and maybe go back
through the other classes and see how you might want
to categorize things in the base class and maybe
even the enemy class. I won't do that on screen,
but it's just an idea to get those concepts going and let you know that those
features exist. With that done though, that is our health
system complete. We've implemented that once,
and it's working everywhere. It means we can move
on to the projectiles. And again, we can
now just build upon the foundation that
we've already created. That's going to be when
the combat gets more interesting and when we
can actually fire back.
17. 16 - ImprovedMovement: Remember that frame
rate bug that we acknowledged and
just moved past. It's time to return
to that and fix it. We'll be replacing the
problematic movement code with something production ready. Proper frame rate independence, cleaner structure,
and a better fill. I realize that before
we start adding fancy things like
actor spawners, projectiles and fleshing
out the damage system, whilst we're doing this factor and improvement
of the base code, we should probably return
and make sure we don't forget about the movement and the bug that we're
already aware of. The offending code
that we need to fix is in our player class. So if you don't already
have that open, go ahead and open BP
Underscore plain player. In my case I'm just going to
pres middle mouse button, close the tabs that
we won't be using. So base class and
the enemy class and navigate into the handle movement function
just over here. So this is the code that we
want to replace and improve. Now, the first thing is after
the parent handle movement, we want to click and
unhook this code. Some people have
different opinions on this type of thing. Again, I want to keep
focus on code structure, good practices, and
things like that. What you may see, especially
in C plus plus code is some developers might keep the old code that we're
trying to replace. Maybe put this in a comment
just in case you wanted to see it later in case you think you might need
to fall back on this. That ends up with a
cluttered workspace. You have maybe a lot more code in your C plus plus
than you need. In Blueprints
obviously we can have a lot more stuff just in
the graph than we need. And ideally we'd
be using something like Github for source
control anyway. So if we ever did need to
look at old code, ideally, we'd just roll back
our repository and view that through Git
or Perforce or something. Now, in this case
I'm going to keep this just for a moment
because I think there's a few things we can simply
copy and paste for quickness, but we will be getting rid
of this code entirely. So we're going to
start this one fresh. The first thing we
want to do is we're going to pull from our execution pin up here and we're going to create something
called a sequence. We want this option just here. And if you haven't
seen these before, sequences are heavily
underutilized. They make our blueprint
read like traditional code, meaning from top to
bottom, left to right. Each of the pins handles
a specific task, or at least that's the way that we should think
about using these. It's much cleaner than logic stretching across
all three monitors, just because you have
this long line of logic. Just to point out,
I'm not showing off. That was an exaggeration. I do not actually
have three monitors. There are other
benefits and reasons that you might want to use
this, but the main thing, going back to sequences
is that they are just a really nice
way to help structure our code and help us think logically breaking things
down essentially into steps. You're saying, do this,
then do this, then do this, and you try to keep everything somewhat unique to a given task. So the first thing we actually want in our given task is that we need to calculate our
target movement speed. We're already doing
that down here, so this is one of the things
that we can just grab. If we grab the code up to the
target movement speed node, press Control in X, and then come on over
and paste this over. So this will be the
very first task that we give ourselves
on our sequence. One other thing that can help
us keep the code a little bit readable is if we
double click on a wire, this will give us a reroot node. We can move this up
here because we want to make sure we're going
to have enough space, essentially for the different then path
that we're following. Now one thing we are
going to change is we're going to be creating
our own interpllation. We won't be using F interp two. For that reason,
we're going to change a little bit of the Get
World Delta seconds. Man get rid of this node
here, the multiply. Hook this straight up
into set target speed, and we're going to
make this frame rate independent a little bit
later. We'll keep this around. We will need this
in just a moment. The get world Delta seconds. But that's pretty much it.
That's our first step. Our first thing we need to do
is have a target to reach. That then allies us. We
can go down to then one. And what we're going to
do here is calculate our own custom
interpellation Alpha. So this is going to get a
little bit mathematical, but it's going to be the key to proper famerate independence. We'll go through
we'll add this in, and then I'll step back
through and try to explain why we end up with
a calculation we have. So the very first
thing that we want is our movement interp speed here, so we're going to
control drag this in and get the
movement interp speed. We'll multiply this
by our Delta seconds, which is why it's available
just a moment ago, so we're going to
have our Get world Delta seconds to start making our calculation frame
rate independence again. We then want to negate or invert the calculation that we create, so we're going to multiply here. We'll get to multiply node and multiply this by negative one. From our calculation here,
we're going to pull again. I'm going to search for
something called EXP. This is the exponential
value, so it says here, this will return the
exponential E to the power of the value.
So we'll use this. And then finally, after
our exponential node, we're going to pull from here, and we're going to subtract, so just press the minus key, find subtract and subtract one from the final
calculation here. The important thing
before we forget we obviously don't want
any magic numbers, we're going to pull
from the final pin. We're going to promote
this to a variable, and we'll call
this one MV Alpha. If you've been
creating categories, you may want to move this
into the movement category. As, of course, this is going
to be movement specific. We're going to hook this
up to our execution pin, then one, and we now have
our Alpha code ready to go. So this is our smooth
interpolation that we'll be using in a more kind of manual and
intentional way in our code. So just to focus back
in, what this is doing, this is giving us one minus E to the negative
speed times Delta. Essentially, this is providing
a smooth decay curve. That's the main reason that
we need to use the subtract one at the end as we want
this to decay over time. And the reason that we
need to use a formula like this is going back to the main source of the
problem we're having between different frame
rates and keeping things frame rate independent. The F interp function is actually providing a
linear approximation, and that's what's breaking
at low frame rates. In comparison, if we
create a function like this that we can use with
a standard larp function, this exponential
approach gives us mathematically correct
interpolation regardless of frame. So that was the main
goal here is we actually just want to
completely stop using this. Finally, keeping in mind
that we want to try and do very unique specific
things per line. We're going to add another pin, and we want the then
two pin just here. We're going to pull from here, and we want to do
that final thing, which is actually using
the current speed, providing interpolation to it. We still want to
use interpolation, but just the more
rudimentary basic version with our own calculation on top. So we can pull from
the execution pin. We can find the
set current speed. So setting the value that
we've been tracking, and this is where we want
to pull from this pin here, and we're going to
search for loop. And this time, we
just want to use the standard float loop. So you can see it's
slightly more simplified. Instead of having the
current to the target at Delta time and
an interp speed, if we wanted to change
the interp speed, we need to add some
kind of offset here. All we're doing is providing
that exponential curve, which is going to be our Alpha. So if we pull in the move Alpha, we can drop this in here. But besides that we're essentially still
doing the same thing. We're going to be
moving from A to B. And I mentioned
this when we first looked at the F in top two, we kind of have 0.1 in 0.2 or point A and point B. So we're
still doing the same thing. We're going from A, which
is current speed to B, which is our target speed
over a duration of time, and we're now basing
this off of our own custom moving Alpha. So that's the main change
here is the way that we're setting current
speed is being updated. So if we go through, we can
actually delete this now. So we can see we're
sort of most of the way through updating our code base. I'm going to double
click here again to make another reroot node just to
keep this somewhat tidy, move things out the way, just give ourselves
enough space. So the final thing is we want
to set our location again. We're going to do something slightly different here as well. So after we've set
the current speed, once we're now
tracking the speed, we want to pull from
our execution pin, and we want to
search for something called add actor world offset. So we can see the
option just here at the top, at Actor world offset. So for our use case, this is
actually the better solution compared to set actor location for physics
aware movement. The only thing that
we really need to change is we're going to right click on the Delta
location vector here, split the structure pin so we have the same results as before. We want to take
our current speed, so we can plug that
in if you wanted, but in this case,
I'm just going to Control drag current speedom. We're going to multiply
this by Delta seconds. So we're going to get another
G world Delta seconds, and we'll plug this in
to our result here. And then, of course,
we're going to make this the Y because we're
moving side to side. So we want that on the Y
axis. That's pretty much it. So we've now updated. We can
get rid of this as well. We've now updated
our movement code. We are tracking the target speed a little bit differently. We've got our completely
custom movement alpha based on a decay curve, and we've changed a little bit the way
that we're handling the current speed and how we're plugging that into
the world location. Now one thing that we
could do here, just to improve things a little bit, is we could go back
into commenting things. So again, this is a nice use of comments when we've got
these nice big splits of specific logic in our so what we could do is I'll
grab these nodes up here, press C. And if I wanted
to give this a comment, I'd maybe say something like set speed for the plane to reach. And so that I didn't need to
dive into the code later, I could just specify
that this is specifically the target
speed that we're looking at. Again, I might give these kind of more unique
colors just so that the comments stand out
against other things like to do that we
created earlier. We don't need to do this and make everything
look really pretty. You can definitely get
carried away with that. But when I change
the color of things, it's more so that at
a glance, I know that I use a specific
gray for comments. I use a specific red for potentially broken code or something that needs
to move all together. I normally use, like,
a green for to do. So again, just at
a glance, I can see as I'm going
through my code. These are just comments
where I need a refresh this is something I really
need to get rid of later, and things like that
can become useful. So I give these
like a very blue, grayish color just
to remind myself. Just going to press
Control C and Control V to get another
comment over here, kind of duplicate this one, and we'll move this roughly in place just so I
didn't need to set the color again. And this
is one of those things. Once you get used to shortcuts
and things like that, the tiny bit of extra
time it takes to give yourself a custom
color and sort of theme things a little
bit doesn't actually add that much overhead to
your development time. So this, if I was leaving
myself a comment for later, is helping us track Alpha for use in our
interpllation function. Once again, just reminding
myself that specifically, this is updating the
move Alpha function. And then we want maybe
one more comment. I've mentioned, I won't do
this for every line of code, and I won't do this for the
rest of the project and the videos as the videos
would get too long. But just to show the
concept, as we go through some of these bigger
refactors and changes, which might be a little
bit more confusing, I think this is definitely
valuable to see. So we're just going to
move these into place. And what I might call this
one is we're updating our current speed and then setting the
location of our plane. And as I say that,
this is actually another really useful thing is I'm actually now questioning my own code because we're
doing two things per line. And if you remember, I said, the general rule is
with a sequence, we kind of treat them
a little bit like functions where
functions should be doing one thing specifically
and one thing really well. So at the moment, we're
mostly following that. This is setting specifically
the target speed. This is calculating and setting specifically the move Alpha, but this is now actually
kind of doing two things. It's setting the current speed, and it's then moving the plane. So what I might be
tempted to do is we'll add another then pin down here, breast control in X,
control in V. Hook this up. And again, more than anything,
I could have left it, but just to show how
easy it is to keep your code clean and sort of follow specific rules
and structures. So it's now we're actually sticking to the
rules that we gave ourself, and I just need to
move things around just a little bit and maybe
add another reroot node here. Tidy things a little bit. Again, we're not trying
to make this artwork, but the easier it
is to work with, our future selves
will be grateful. What we're doing now is if we
kind of think of the code. We can come and give
this its final comment. So this is setting the
current speed based on a simple interpllation,
so simple alert. So that's the one thing that this line is doing specifically. And then, again, we
need one final comment, so Control C control
V put this in place. I mean, I have a really
nice logical brick, so this is now simply
updating the plane location. So then if we were
to read this, we can see specifically
what's happening. We're setting the target speed. We're updating the move Alpha, which is used for the
interpret function later. We're using that
interpret function and setting the current
speed based on that. And then we're taking
those variables, and we're updating the
plane's final location. So this is now probably better than what I had
just a moment ago. It's only one small
change, but again, just sticking to those rules, we kind of think of these as small functions, essentially, and they're all
doing that one job very specifically, and
they're doing it well. Keep things tidy, keep
things easy to read. And if we ever needed
to come back and remove something, add to it, then it's going to
be much easier to update and fix our
code in the future. So it's like side tangent there. I wasn't expecting
to go that much in depth on code cleanliness, but I just again saw the
opportunity because I kind of went against what I mentioned at the
beginning of the topic. That done. That is the
movement now ready to go. What we need to look at next
is the rotation system. It's going to be the same
problem because we're making use of the
same type of thing. If we go to handle rotation. We have a really simple
function, which is, again, the really nice thing
that Unreal does for us. It provides a lot of
these helper functions, this built in R interp two, nice and simple, one node, a few nodes next to it
for the calculation, and then set the
active rotation, and we're kind of
good to go for test. But something that
people need to be aware of is that generally, it's only kind of
good for testing. We probably want
to start rolling our own code when we get more serious and
indeed with things. Before moving on to
fixing the rotation, just a couple of things to
check in the handle movement. Because we're going to be
driving the current speed a little bit more
manually this way, we probably want to make sure I just noticed when I
clicked it earlier, current speed has
a default value. Default value here was
essentially when this was originally our general
movement speed. So we're going to want
to reset this and make sure current speed, target speed movement interp speed is going to have a value, but mob target speed and current speed are all
set to zero by default, and these will only be
calculated at runtime. The other thing, same problem,
really easy to make again, is I almost forgot to
tick the sweep function, so this has the same function as the set actual location
function we used previously, so we need to make sure
that the sweep is enabled. That should be pretty
much good to go. This won't work
fully at the moment. And I just realized we did
this the wrong way around. I think I was telling you to subtract the
right thing earlier, but during implementation, I
hook this up the wrong way. So if we alt click
after the exponential, really important
here, we want to subtract our
calculation from one. So we're working back from
one. We're making that decay curve from one based on
this update over time. So yeah, it's just testing quickly there to make
sure this still works. As I mentioned, the rotation is going to
be a little bit worked, but we still have
the same movement, so we have some nice
smooth movement, the rotation because it's based on the speed that we have, that's completely broken, but this is something
we can work with, and we kind of
improve that as we go through and
refactor the rotation. So just a couple of
final recaps there. Make sure that we're
doing our calculation for our exponential value, and then we're taking
that away from one. So it's one minus our
exponential calculation here, and that is the result that
we want for our move Alpha. Current speed, target speed, ideally you're going
to be set to zero. We're calculating that here, multiplying those back together, and we get a nice frame rate
independent movement here. So this is still
kind of feeling and working very similar to
what we had previously. You may find you need
to tweak the speed a little bit now, but we're
very close to what we had. Rotations broken,
which as I mentioned, is expected because we're using a very simple calculation here based on target speed and just multiplying that
against tilt factor, and now the target
speed or current speed, sorry, is being handled
in a very different way. With that done, though, we
have the movement improved. It's a really good
groundwork to get started, and then we can jump
straight into rotation. I appreciate these
quite heavy topics as we go through this, but hope taking some time
in between the topics, pause again whenever
you feel you need to and really try to understand what
we've been doing and why these changes are
going to be working. I tried explaining the math
and stuff behind them. Some of the stuff is
really hard to get across without actually having hands on experience with
the source code. So that's another thing
is you could jump into Unreal Engine source code, find the Ler function
in C plus plus. And again, even if
you don't fully understand C plus
plus, that's fine. Most of it is very
much human readable. The comments are all there. It's explaining what's happening on the F interp
function step by step. And sometimes just diving into
the source code that way, since it's all
readily available. Be really useful and you'll
start understanding some of the limitations of just
using the rebuilt, predefined functionality
provided by the unreal engine. Not to say that it's bad, as I mentioned, it
has its use cases, but we do find these fringe cases where
it just doesn't work, and we need to
roll our own code.
18. 17 - ImprovedRotation: Movement set up. We now need to do the same thing
for our rotation. We still want to be inside
of the plane player. We're going to go into the
handle rotation function. I'm going to make a very
similar update here. This is another
nice thing that we can start making
the code a little bit more uniform because the logic behind it will
be very similar. So, in fact, we can take
our initial steps here. We're going to move this down. We'll unhook this,
and we'll keep anything that we might come
back to a little bit later, but like you said,
previously, we will just be completely getting rid
of that by the end. Now, the first thing is we
want to get our sequence. We can do the same thing again. So we'll take a sequence node, and we'll start keeping
this nice and tidy because we're going to need to do quite a lot of
calculation for this. So we'll make sure that we keep our code lined out
nice and neat. Now, there is a little bit
extra we need to do for the rotation because
like I said, previously, it was somewhat of an
arbitrary value just being multiplied
against current speed with nothing really
else accounted for. So we're going to do a
little bit of clamping and tidying to make this a little
bit more refined as well. What we want is we're going
to take our current speed. We'll get to the current
speed that we have, so we're still going to base it roughly off the same thing, but with some modifications. The way we're going to do
this is from current speed. We want to make use
of something called a map range clamped. This is going to map one
range of values to another, perfect for converting speed
to a specific tilt angle. In range A is going to be
our movement speed negated. So we'll right click, we'll
search for movement speed because this is being accessed
from our parent class, the actual target speed, the maximum speed we can move, so we're going to get
the movement speed. We're going to multiply
this by negative one. Feel free to give yourself
space as you're going through. So that's going to
be our in range A. In range B is simply going to be the movement speed,
so unaltered there. And again, just giving myself
a little bit more space, as we're going to need
some more stuff down here. Then we're going to
need a new value for our maximum and minimum
tilt that we want to reach. So this is where we actually not getting a little
bit of control. So we can say we either want
this to be able to tilt a maximum of 15 degrees
at any one time or 25 or whatever you wanted
in the rotation category. And again, if you've got
categories, that's fine. If not, just grab any
float that you have. I'm going to grab
my tilt factor, duplicate this, and I'll
call this one Max tilt. Hit compile, and I
think by default, I will give this a
maximum value of 15, so this will clampas to
rotating 15 degrees either way. You could create a minimum tilt, which would be negative 15, but similar to what we've
done here, instead, we're just going to
grab our Max tilt and we'll multiply
this by negative one. And that will be
our out range A, and you'll start seeing
how this all works. Hopefully, as you see
these come together. And I'm going to
grab our max tilt and then this will
be our out range B. So we're going to take
our current speed, whatever our current speed is, as this has been calculated, if it's somewhere
between zero and negative our maximum
movement speed. I think that was 1,000. Then our maximum tilt
is going to be ranged from zero to negative 15 based
on a kind of a percentage, a uniform value that would match roughly where
that speed is. And then vice versa,
if we're moving right, then we're going to take
our maximum movement speed, zero to positive 1,000, and I'm going to pick a
tilting value 0-15 degrees. So this is how we can actually
clamp rather than just having completely arbitrary
rotation either way. We're actually basing that
specifically between whatever our current speed
is and then mapped between the actual
speeds we can. This makes it really
easy to update. If we ever wanted to say
have more of a tilt, but keep the speed the same, then we can just increase this. I could say you
can now tilt up to 25 degrees instead of just 15. For testing, I think
I'll keep it at 15, but we can play around
with that very easily. We don't want to promote
this to a variable. So whatever this
calculates, we're going to promote to another variable. I'll call this one target tilt. It's very similar to how
the first thing we did in movement was calculate
our target speed. And plug this into then
zero, move this up. And we're probably going to
have to do a fair amount of tiding here to keep this more readable just because there's a lot more kind of math
going on in this function. Rotation is just
always a little bit more intricate and fiddly than calculating movement
or general kind of location or even
scale offsets. Now, the target
Alpha is actually so similar I'm going to
be a little bit lazy. I'm going to go back
into handle movement. I'm going to grab all
of the code here, press control C, so I'm going to get all of these nodes here. The only thing we don't
want is the move Alpha. Going to come back in
here into the rotation, and we're going to plug this
in to a value in a moment. The only thing that we may
want to do is I'm going to move our target tilt into
the rotation category. I'm actually going to rename
this because we've got some slightly different
naming conventions going on. So I'm going to grab the
rotation interp speed and change this to tilt because we've decided
that everything else is kind of being classified as tilting rather than rotating. I'm going to control drag
the tilt interp speed. And plug that in there. So we can get rid of movement interp speed
at the beginning. But other than that, we're
doing exactly the same thing. We're taking our
tilt interp speed multiplied by the G
world Delta seconds, multiplied by negative one, getting the exponential return, and then we're
creating that again, our decaying curve over time. So we're taking all of
these values away from one. So the results of this will
then be another new variable, so we'll promote this
to another variable, and we'll call this
one Tilt Alpha. Because we're going to want
to do the same thing again. We'll be plugging this
into our own interpolation or lop node that we're going
to be in full control of. So we'll just move this up again trying to keep things
somewhat tidy. I think just for cleanliness and space, I'm just going
to delete this now. We don't need to go through step by step like
we did earlier. We just won't be using anything
from the old code here. So we're going to create
another sequence pin. We want to do our
next stage of logic. This one is very
simple. So very similar again to the third stage
in our previous code. We're going to set
our current tilt. And I think just
looking at it now, we're probably going to
start mixing up values here. So Max til we want
and we're currently using Tilt factor, I think
we're going to miss. So I'm going to
rename this one tilt factor to current tilt. And that word is slowly
losing all meaning. I've said that a
few too many times. We're going to drag this in
and plug the execution pin. And again, the value that
we want to fill in here, making sure if we hit compile
that this doesn't have a default value anymore,
so we'll set that to zero. This value will be calculated
based on another loop. So same thing again,
a floating loop. And same thing again.
So we're going to take our current tilt, our target tilt, and our
Alpha. So nice and simple. Pretty much, as I mentioned,
like the movement, but now all rotation
or tilt related. So you can see the
pattern. We've actually been able to use
basically the same code. So aren't you
understand this once. Very easy to reuse
it and remake it. We needed to get a little
bit more intricate with the initial part of
the rotation up here, but otherwise, very similar. So we can do the
same thing again. We want one more pin,
and this is going to be actually using these values
and setting the rotation. So same thing again.
This time we do want to use the set actor
rotation function call. So we'll plug this down here, and we'll come back up and do a little bit of tidy with
some reroots a bit later. We'll split the structure
pin like we did previously. We're going to take our
current tilt value, and we'll plug that into the we're doing
this on the roll. And that's pretty much it, but we've got the
same steps again. So set the target, set the Alpha, set the current, and then use the
current as the value. Now, I'm just going to
come in, as I mentioned, we will do a little bit
tidy up because this is getting somewhat cramped
and difficult to read. I think that's fine if
I'm not going to spend too much time pritting
up the nodes, but that's enough just to
keep things separated. And like I mentioned,
I don't want to use huge chunks of time just
commenting my code. Not the most useful thing to
see on stream or on a video. But if you wanted to
do a similar thing, just a small thing
to take away in between topics, maybe again, pause the content and
just say it's off a task to make sure you
understand what's happening. Read the code and the rotation
and see if you can give yourself some good comments to describe exactly
what's happening here, why we're using the range
clamp and things like that. And the core thing is that if
you needed to come back and look at the code in a month
or two's time, at a glance, this is much easier to see
what's happening than this, currently, as I'd need to come back through and read in depth. But let's just go ahead and check that everything's working. I think the main thing we've got our Max tilt is set to 15, so we should only see a 15
degree tilt either way. And obviously, we're just negating that based on
the movement speed. Movement speed is still 1,000, which I think is perfectly fine. If it does feel a
bit more sluggish, we can come back and
increase that later. The interp speed, I think we want to slow this down
a little bit nice. I think we're going to set
this down to three rather than ten because our decay
curve is going to work a little bit differently
than what we're doing with F interp two and then target current and Alpha should all be getting
set at runtime, so they should start at
zero, which is perfect. And then we're just using
that here. So this should, I think, now work. So if we come back in, press
play. Yeah, there we go. It looks I think it
looks different, but we can see it's
very much the same. I think I kind of prefer
the way this looks. It's a lot more sort of it just feels a bit more refined.
So that's pretty good. And like I've mentioned, this is now where the
flexibility comes in. If you wanted to change
the speed, so maybe that was interplating too slow. We can increase
this. So we'll go back into play mode over here. So we're getting
back into straighten you out a little bit
faster. So that's fine. And then if you wanted,
like I've mentioned before, if you're not sure
what something is doing or which way
a value should go, if you make it really drastic, to make a really big change. And you can see
that's almost like flicking strike back
when we let go. So decay is happening much faster and we're
flicking strike back. So that might look a
bit nicer, actually. So maybe I was wrong, maybe going to a higher
value rather than lower would have been
better for that nice and easy to test, but
this is the main thing. Try to instill into the way that you think
about your own code base, making things easy for you
to update and tweak things later will make it
more likely that you'll refine the game
and make it better. If your project is really
hard to work with, you're much less likely to
want to make refinement. Spend that time to
improve the game. So just more of a design thing there, which is always
fun to think about. The final thing is maximum tilt. So if we grab Max tilt, if I make something much bigger, like 45 degrees just to show off the difference we can
get, if we ne float, you can see that when we start reaching the maximum speed, we're hitting that higher
maximum tilt angle. So this is how that
comes into play. Probably don't want it to
rotate quite that much, but you may want to
up something maybe more like 25 to make it
more visually noticeable. But the main thing that
we have over the previous implementation is, as you saw. It wasn't really doing any
specific calculation to reach a certain angle of rotation based on
speed or anything. It was just going as far as possible within a
given amount of time. Whereas now, we've
got a bit of control. Depending on our current speed, we can control exactly
how much we rotate. And then the really
important thing to check is I'm going to throw in some console commands
to get rid of the enemies. We don't need those. Just
keep flying into us. I'm going to go into play
mode. I'm going to give myself the stat FPS
console command, so we can see I'm
running at 120 FPS. I put myself
straight down to 30. And again, we should
see we're getting a much less pleasant
framework to look at, but we're still getting
only 25 degrees rotation, and we're hitting it
at what we should hopefully see in a moment,
roughly the same speed. So if I now increase that to 60, we're only reaching
the same rotation, the same sort of pitch
tilt or roll tilt, sorry. I'm doing that in roughly
the same amount of time. And then if we go up to 120 or actually, we'll
just uncap this. I'll say like 600, so I can
get around about 300 FPS. Look smoother, but the movements happening
at the same speed, and the rotation is
taking the same number of frames or seconds
to get there. So we've now completely fixed the frame rate independence
issue that we had previously. So the movement, remember, wasn't really that
big of an issue, but the rotation had
an issue where even though we were multiplying by down to seconds because of that, just the type of logic the F
inter functions are using, remember that we were able
to essentially rotate the wing into the floor because the rotation
wasn't clamped. The different frame rates were
affecting it differently. So we've now completely
solved that problem. We had to get a
little bit more kind of knee deep in some
of the mathematics, made use of the more rudimentary simplified lurk function. But by taking the rains a
little bit with our own code, we've now solved
a whole bunch of problems which have
been shipped in you'll probably find a lot of games because a lot of
games are released based on example
content example, product code, and
stuff like that. That's the test complete.
Same movement speed. We have the same
rotation limits. There's no wing clipping
through the floor. The exponential formula
is what gives us the mathematically
correct interpretation at any frame rate whatsoever. I hope that more in depth
content like this hasn't completely frozen your brain and stopped you from keeping up. That's really been the
goal of the topics when I was creating
these is to try and get people thinking more
about their code base and what's happening under the hood and not just following along. So hopefully this is now getting to the
point where you can see the benefits of what we've been I mentioned right
from the beginning, this is taking a lot longer
than it could have done, and I could have just
edited out a lot of the trivial information or what may have seemed like
trivial information, but I think you would
have missed a lot of the learning potential and actually understanding
what you're doing. Goal here is that hopefully you can watch something like this, save yourself
watching another two or three other tutorial series or courses or whatever the
case may be because you're actually getting much more hands on with the systems and fixing things that even exist in the official learning
content by Epic. So hopefully, there's
value in that. So if you wanted to really start refining your project now, this is where you
have probably got the general kind of understanding of what
we're going to be doing. We've got our project organized to a pretty rigid
and solid place. So if you wanted to start
experimenting between topics, this is where you may
want to put a little bit of the brakes on. Again,
this isn't a race. We're not aiming
to get the project finished as quickly as possible. We're aiming to get you leaving, understanding what
you're doing, and how to expand upon what
you've already learned. So maybe do something simple, even if it's just going
into your own code, seeing if you can now comment and describe what your
code is doing back to yourself using
some good comments or categorize the variables. If you've noticed you've got
this long list of variables, and you can see a way
to improve that then start adding categories
to your variables, simple things to keep
your project structured, tidy, easy to work with. Remember, the main goal is sequences are your flow control, making it more readable, like normal scripted
code or written code. Good comments,
explain the intent rather than just
describing the variable. Things like you could
just have a comment saying interpolation alpha,
but a good comment would be, calculate the frame rate
independence values based on interpolation alpha, things like that to kind
of give you a descriptor, at a glance of what
you're doing, and why. With that done, though, the movement system
is production ready. We've got smooth,
controlled frame rate independent movement and
rotation for the player, which is the heaviest
and sort of most complex class we're going
to have in the project. Next, we'll be
looking at spawning enemies dynamically
during gameplay, rather than just
placing them manually, which is obviously
going to be a little bit restrictive for a full game.
19. 18 - EnemySpawner: Time to look at automating
our enemy spawning. We'll be building this in a
flexible way from the start. This will work for
enemies to begin with, but we're going to future
proof it just a little bit, so this will work with things
like background objects or anything else that may
need to appear over time. No more manually
placing enemies, which will be the
first important step. So to get started,
we're going to go into our Blueprint folder, and we'll just
create this inside of a new folder structure. So I'm going to press
Control shift and N for a new folder, call
this one spawners. Right click, we'll go
to Blueprint class, and we want to create our
bog standard actor class. As I've mentioned, this
is just a container. It has a transform, a
place in the world, and then we can drop
our logic in here. We'll give this one the name of BP Underscore Spawner base. So as I've mentioned, forward
planning a little bit. We're not going to specify that this will be just for enemies, and we may have other
spawners that will be a child of this class
with inheritance. Inside of this, we
just want to go straight to our event graph. There's nothing that we need to visually add to represent this. I think in this
case, we can get rid of the actor being an overlap, and most things in here
should be responsive, so we shouldn't be
doing any checks and updates constantly
on the eventi, so we can get rid of both
of these function calls. We can drive our core logic from a custom function.
I just move this over. We'll create a new
function here, and I'll call this
one spawn actor. So again, everything that
we're doing as we go through and plan is trying to
keep this very generic. We're not saying spawn
background piece, spawn pickup, spawn enemy. All we need to pass
in is an argument of the type of actor
that we want to spawn, and we'll probably need
maybe some random rotation, an offset, location,
and things like that. We can make this
nice and generic and make everything
very reusable. So always trying to plan ahead. Remember, think lazy,
plan program once, and use as many
times as possible. Inside of this function, we'll pull from the execution pin, and we actually have a built in function inside of Unreal, named spawn actor from class. When we're using this function, the main thing that we want, as I've mentioned,
is a transform, so where we want
this to spawn and specifically what type of
class we want to spawn in. So at the moment, that could
be things like our enemy, but again, that would
be too specific. So if we highlight and hover
over the purple pin here, we can see that this
is just specifically referencing the type of actor. So these purple pins are the actual class
references themselves, not an instance in the world. When we have these blue pins, this is an actual instance
that exists in the world. We can kind of think of the
purple pins as a reference to the location inside
of our folder structure. So we're going to make use
of this generic typing. We're going to pull
from here and we'll actually promote
this to a variable. We'll give this one the
name of actor type. Make use of this now
that we can fill this with any type of
actor in our project, whether that is an
enemy, a pickup, a background piece when we
add them, whatever we wanted, we can now tell this function to spawn that at a given point. The main things that we're
going to want to expose are the spawn transform details. So if we write,
click on this, we'll split the structure pi. We're going to leave the spawn transform scale to 11 and one. Generally, we try not
to override these. If everything's been
set up correctly, as with this project, all of the assets should be the
correct scale to begin with. So we'll keep this
and just assume that the assets are imported
and working as intended. You do have the option to randomize or offset
the rotation, but I'm going to keep this
nice and clean for now, and we can always come back
and rework this later. What I'll do instead,
I'm going to right click and search for Get
actor rotation. The reason for this is this means that we can plug
this straight in. And wherever we
rotate our spawner, we have full control
in the editor, so this is more of
an editor tool now, where if I want this to
spawn things sideways, I just need to remember to rotate the actual
spawner sideways. Alternative would be
to expose this to a variable and manually
plug in the rotation, but I find this can be
quite a nice way to work. It's just a little bit
more intuitive for me, as long as it's not
too restrictive, which I think, in this
case, we should be fine. For the location, we're going to pull from here and
we'll add a plus pin. This one may need a little
bit of randomization, some variance and
offset supplied. If we right click, we can
search for G actor location. So same thing again,
we're going to use the general properties of
wherever the spawner is, so that will be our
base starting point. Now for this type of game,
because we know that we're only going from a
top down perspective, we don't need any
randomization on the Z, and we probably want to do all of our spawning off screen, which means we
also will not need any randomization on the X axis. So what we could do is
we could right click on this second vector pin
and split the structure. We'll pull from our
Y Pin just here, and we want to use that select node that we've seen previously. We want not select float. We want this one here
with the unique icon. This gives us the wildcard
that we've used previously. We'll change this to a Boolean, so we're either going
to randomize or not, and I'll promote the index
here to a new variable, and we'll name this
one add random offset. Again, remembering to
give that a lowercase B. If we decide to add
a random offset, we want to provide some kind of floating value
within a range. And again, we've
seen how to do this, so we're now just layering upon the functionalities
and approaches that we've already
taken in the past. So if this is true, if we're
adding a random offset, we'll pull from here
and we're going to use that random float in range function that
we've seen before. And from this, we
want just a distance, so a minimum and a maximum sideways distance
that we can apply. So if we get our
maximum float here, and we'll just promote
this one to a variable, and we'll just give this one
the name of offset distance. The minimum is as
we've seen before, with speeds and
things like that, is just going to be the
inverse negative, so we can control drag
in the offset distance, multiply this by negative one, and then plug the
result in here. So if we set a distance
of let's say 100 units, and we want that
to happen either side from the center
of the screen, we'd place this spawning actor in the center
of the screen, plug in the value 100
in the offset distance. That means it can
either spawn it -100, positive 100 or some
value in between. If we decide not to expose the variable and we don't have
the random offset enabled, then we're just
going to add false. So we're going to add zero
to our overall transform, which means the actor will spawn exactly where we've placed it. So relatively simple, but it's still kind of a developer tool, something we can build upon
if more complexity is needed, but it gives us some flexibility and variation in our project, which will just
generally make things look more interesting
and fun to play. Thing I haven't shown yet,
and I want to introduce here because we are kind of making a very rudimentary developer. This is something which is really useful inside of Unreal. Other engines do similar things, and that is allowing
people to see the variables outside
of the class. At the moment, for
example, if we grab one of our spawner classes and just
drop this into the world, making sure that we've
compiled and saved everything, we can see here that
the objects selected, none of those
variables that we've just created are
actually exposed. Wouldn't be able
to manually come in on a per instance basis. So if we had three
or four of these, we wouldn't be able to
pick that this one had some randomization and
a unique distance, and this one had no
randomization, for example. We currently only have one
place we can change that, and that is just
here in the graph. So it would either get this
boolean and turn it off, and then that's on or
off for all of them. And likewise, for the distance. The really useful way
that we can override this are these icons here. You may not have
touched them just yet, but if we click these on, it
provides a small eye icon. By default, they start closed,
and we can open the eye. In other languages,
this is referred to as making something public. It's exposed to
other classes and readable and writable
outside of just this class. Out of unreal blueprint,
this is just making the variable
editable essentially outside of the class here. So if we go into our viewpoint again with the same
objects selected, you can see that in
the default dropdown, we have access to the type of actor that we
want this to spawn. So this is where we
could start doing things like this could be
an enemy spawner. We're not going to do this, but this could be
a player spawner. So we already have
some flexibility in what these are
going to spawn. And then because the enemies might want to be more random, we could turn this
one on to be true to add a random offset
in either direction, and we could set that
to be, as I've said, maybe 100 units to the left and to the
right of the spawner. So nice and simple,
as you've seen, it's not a very complex setup, but we already have
a relative amount of flexibility and freedom
with how we use this. So at the moment, if
we were to add this on begin play and get this
to be called once, one enemy isn't going to be a whole lot of a
challenge for our player. So what we want to do is add
some continuous spawning. We're going to avoid
using things like delays and loops as this becomes
messy and inflexible. So you may have seen
something like this before, where on begin play, again, I just want to visualize some of the things that I've seen
in plenty of projects. So you may have something like, we'll come in the first time and we'll spawn an actor once.
We'll then add a delay. So a delay is just a timed
kind of waiting process. We'll wait, let's
say, 3 seconds. Before we do the next thing, and then we'll spawn actor again. And you may be tempted
to then come into here, and maybe after each
time this is done, try and find a way
to recall itself. It's going to become messy. You end up seeing
people do things like this, we'll
loop background, and you end up with
this infinite loop where people say that
after we've spawned this, we're going to wait
another 3 seconds and call the spawn actor again. So we're going to avoid
this beginner style code. What we want to use instead
is something called timers. These are much cleaner,
and it gives us a lot more control over if
and when they get called. So, off of our begin play, the execution pin, we want to find a function called I valid. We want this option with the
question mark at the bottom. This is checking
if another object or actor is currently
valid, meaning, does it exist, and is
it not being sent to garbage collection for cleaning if it's being destroyed
or something like that? From the input object, so the thing we want
to check if it exists, we're going to pull from here and use that built in function, the get player pawn. So this is, again, just one
of those things to consider. It happens in a lot
of new developers games where you've
made a game like this, and the enemies have
destroyed the player. But even when you're at
the game over screen or you're just waiting
for the credits or whatever's happening afterwards, you can see the
enemy still spawning in or shooting at the player, kind of dog piling
on a dead body. It just looks a
little bit intense, and it's usually because
there's no checking going on midgame to see if the players actually classed as dead or
no longer available. What we're going to do is if the player has been
removed from play, there's no more need for enemies to spawn in, so
we're going to stop. If this is true, so if the
player is still valid, we're going to call
a function here, which is the set timer
by function name. We want this option here, and the first thing that we can
fill in is the time here. So this is how long we're
going to wait until this function that we'll
create in a moment is called. So for the time itself,
we can pull from here, and we can search for another
random float in range. And a small trick, which can be quite useful when
you're not looking to just have the minimum and maximum or something just be
the inverse of each other. We can make use of
something called a vector two D. So if we create
a new variable, we'll name this one
spawn intervals. And we're going to
drop this down, and we're going to search
here rather than a vector, which is a three
point floating value, we're going to look
for vector, too. We want this one down
here, the vector two D. And if we hit Compile, we can see all this is just two floating point
values instead, where the X value is going to be our minimum and the Y
value will be a maximum. So we could set this to
something like a minimum of 3 seconds before
something spawns in and a maximum of six. And we can come back
and tweak this later. We can control drag
this into the graph, right click on structure pin, split the structure
pin here, and we can say minimum, maximum. So a nice kind of clean, tidy way to set up the
timing function. For the function name, we have
our spawn actor over here. One thing I always
tend to do, again, partly because I'm lazy and
don't want to type things, but also to avoid making
spelling errors or typing error. I'm going to press F
two to rename this, press Control N C to
copy and then control NV over here to paste this in ensuring that we
have the same spelling, the same capitalization,
avoiding any errors in the name because this is case sensitive and needs to be
spelled exactly the same. We're not going to
set this to looping, but if you did have
a function where you want it to keep
going constantly, you could set this to looping, and it would just
keep going round and round and
essentially calling it. To want a little bit
more manual control. I'll make sure that
this isn't ticked on. And what we can do
is if we grab all of this code, including
the is valid, because if you think about this, when we first begin play, there's no reason
that the player pawn wouldn't actually
be a valid object. It's always going to
exist when we press play. So what we're going to
do instead is we'll right click on any
of these nodes, and we'll collapse
this into a function. We'll give this the name
of call spawn Aca timer. So we're just kind
of obfuscating out the logic a little bit here. So, again, the steps are
a little bit more clear and easy to understand as
we go through our code. And the way that we
actually want to handle this is we're probably going to want to create
some enemies straightaway. So rather than spawning
based on a timer, we can bypass this first of all. So we'll start by spawning
an actor straight in. We'll drop this onto
our execution pin. And again, just
thinking about the order of logic in our code. So we can always
rework things like. We're going to begin
play, and we're going to spawn an actor straightaway. What this will do, it
will drop in here. We're going to then find pick our actor type if
we have one set, pick a location and an offset, if there's an offset
to be applied, and select the rotation and
spawn that in at a point. And after this, this
is where we can start doing an almost
automatic looping here. So if we wanted
this to keep going, we could take our core
spawn actor timer, drop this on just here. And what this will now
do is this will go in, and this is more important
here because at runtime, this is where something after
the first or second spawn, we may have had the
player get destroyed. So this one I do
our is valid check, a really simple,
cheap check to make sure that the player
is still in the game. And if they are,
we're going to allow it to spawn another actor, so using the spawn actor
function, basically, calling this
function again after a set period or
interval of time. Now, it's something that
I quite like doing, and this is completely optional. But because when we're using the set time by function
name function, we can't double click and find what this is
actually calling, we need to look through
our function list and find the name which matches. What I quite like to do is
I'll the function over here and just drop this below the
set timer by function name. So this means if I'm ever looking through my
code in the future, if I get to this point
and realizing to fix or change something
in this function here, I can just double
click this node, and that will take me to what
that function is calling. So just a really
small tip there, but I find that can
be really useful, especially when
you're coming back to tweak things if this ended up
not being flexible enough, we needed to remove
something, just a nice way to navigate around. But that's pretty much
everything that we needed. That is a nice kind
of self encapsulated, self controlled spawner with some flexibility on the different actor types
that we can spawn in, where they're going
to spawn and if there's any
randomization applied. We could also come down and expose the spawn
intervals as well. So we could have each
different actor, each different spawner, having different timed intervals on when things should spawn him. So make sure that we
compile and save. One thing that I do want to
add before we actually go and test this is if we go to
the component section, we're going to add something
here, I'm going to search for the arrow component. So there's actually
a component here we can use which is
just a visual arrow. We can change the
color if you wanted to change the color
for different reasons. But the main thing is,
again, at a glance, this will just allow us to
know which way is forward, so it's always defaulting
to point forward. So if we hit compile and save go back into
the main level, we can see, in fact,
here, the enemies will be facing the wrong way. So I'm going to
get rid of some of these we'll just test
with one at a time. Make sure this isn't
overlapping with the floor, otherwise the enemies
won't be able to move. And then we can just rotate
this 180 degrees on the Z. And you can see that
that is now pointing towards our player start, which means if we set
this to our enemy class, we could give this a random
distance to either side, and a spawn intervals
fine there. We can press play, and that will spawn
one in straightaway. Then within three to 6 seconds, we should see another one
spawn in, and there we go. So we have our random spawning
actor working correctly. By providing the rotation
that I mentioned, we can see exactly where this is going to spawn in which
direction you'll be facing. The main issue that we
have now is, of course, that we can see them popping in, so we want to move
this back a little bit so that they're
spawning off screen. They'll do that animation, and then they'll spawn in
with that given interval. If you wanted to mit
slightly different position, or if you wanted more of them, you could stagger it this way, so we could have
multiple spawners. You could provide different
offset distances, different spawn
intervals, and you could kind of stagger them
that way a little bit. Again, to make the gameplay and interaction that
much more interesting. I think at the moment, we
could probably get away with just one spawner
for our enemies. So if we find out
or decide there's not quite enough being
spawned at runtime, we can come back and
add more or play around with these
spawn intervals. So another little bit
of experimentation or homework you
can give yourself. You can do some simple
things here like duplicating the spawner and try
different configurations, as I've just mentioned. Try different spawn intervals, different offset distances, enable or disable
the randomization. And ideally, if you've
gone outside of the content so far and created your own
different enemy types, you may have enemies that
only move forward or backwards or side to side,
whatever the case might be. Maybe try adding some
different spawners for different
enemies just to see how you can really
easily make use of this somewhat
flexible system. And that's the main thing here. We're looking at and thinking about how we
engineer our system. Could have created
one actor class specifically for
spawning only enemies. We could have came into
the class and hard coded the logic here to
specifically spawn enemies. We would have then needed to
have done the same thing for a different enemy type or the backgrounds
when we get to that. Essentially, a lot of
duplicate work again, more things to go wrong
and more bugs to fix. Whereas with this
approach, we can reuse this for the
backgrounds later, different enemy types, bosses, whatever you wanted to
make use of this for.
20. 19 - ProjectileBase: Going into one of
the fun parts now, it's time to add
some fire power. This is going to be one
projectile class again that will work for the
players and the enemies. As with before, we're going
to be thinking ahead earlier, making this customizable
for the colors, the damage, focusing on that smart
design up front, saving on duplication later. So we can leave the BP underscore Sponerbse
if you're still here. We're going to go into
our blueprints folder. I'm going to go back into the blueprints main folder here. We'll create a new folder,
so control shift in N, and we'll call this
one projectiles. Now at the moment, there's a good chance
that this project will only ever end up with
one type of projectile. Like I've mentioned,
we're going to try and make this as flexible
as possible. But again, in more advanced or flashed light
versions of this project, you may want things
like home missiles, lasers, shotgun style, shell
blasts and things like that. So we may not
necessarily always be able to reuse the
projectile that we have. So we're still going
to assume that we might want to
build on this later, even if we don't for
this smaller project. So we're going to
right clicking here again inside of the new folder. We'll create a new
blueprint class. Once again, this will
be a simple actor, and we'll give this
one the name of BP Underscore projectile. We'll throw the word
base on the end just again in case
we wanted to expand this a little bit later with similar but slightly
unique child versions. Inside of our new actor
class, we just want to, first of all, create
a new static mesh. So we're going to
add a new component, static mesh, and we can drag this directly onto the
default scene root. So again, making this
the new root component. With the static mesh selected, we're going to go to
the right hand side. I provided here the SM
underscore projectile, so we'll select this one. And this will be our
visual of the projectile. On the left hand side, we go
back into the components. We can drop this line
again, and we can see here if we search for the
word projectile movement. We have our projectile
movement component. Unreal has a built in system for this to simplify
some of the setup. Now, we won't actually be
using this because again, it's nice to be in full
control of our own logic. Let me just show
you how this works. So if we hit compile and
save with this set on, we want to go into the
right hand side here in the Details panel with the
projectile movement selected. We want to turn the
gravity scale to zero. Otherwise we're going to get
some fall off over time. And we'll give this the
initial movement speed of 300. If we come in, hit compile, we're going to go
into our viewpot and we'll just drop
this into the level. Now, I'm not going to press play because we want to actually
see this moving around. But the way that this basically works is we can hit simulate, and we'll see that fires
off at roughly 300 units. So it works. It's nice
and simple to set up. But as I mentioned, there's a reason we
won't be using this. And that's mainly
because the component includes network replication, things that we
really don't need. A lot of physics
calculations, again, that aren't needed for what
we're going to be doing, handling of gravity, which we've explicitly disabled,
but it's still there. It's also a little
bit inflexible. We wanted to add things like
acceleration, over time, homing, all of this is kind of locked into Epix implementation. For the longest time, I think homing didn't
work properly. There are some work around
you can force on that, and acceleration
definitely doesn't work. It's very difficult
if you wanted to emphasize speed and
make something look a little bit cartoony and go
from 100 units of speed and then ramp up to 600 over time,
and then ramp back down. It's very difficult to control the speed of these
components at runtime. Essentially, if you spawn
this in at moving 100 units, that's going to be
the acceleration, the value of speed
for that projectile until you reset
it or destroy it. So for that reason, we're
going to get rid of this actor and for
that reason alone. Rather show you how
we can implement some very similar and much
more simplistic logic for our own projectile movement. So just to let you know
that I'm aware that this exists, we're going
to delete it though. We're going to get rid
of that component, and we're essentially going
to roll our own custom built version specifically to do what we need in our project. So if we jump on over
to the event graph, we're going to do
the standard here. We're going to get rid of
our Act to begin overlap. And remember what I've said
before, we can use venti, and this is one of
those instances again, where we want this to
move smoothly over time with some simple
movement logic. So eventk is where
we want this to run. Get interested and
wanted to look into the Unreal
engine source code, that's even how the projectile movement component
is working there. That is running on venti to keep things updated constantly, framework independent
and nice and smooth. The logic that we need for
this is really simple. So if we pull from
our execution pin, we're going to search for
the add actor local offset. This ensures that
the location of the projectile will be moving forward relative to
its own rotation. So this is another really nice
thing is that we just need to plug in which way to face
when we start spawning it. And by using this functionality, it ensures it will always be moving in the correct direction. We'll split the
structure in here, purely because we know
that we only want this to move on the X axis,
forwards and backwards. But you definitely could
come back in and do some sideways movement
if you wanted to make it a homing projectile or
something like that. All from the Delta X. We'll promote this to a
variable so we get a float, and we'll call this
one movement speed. Hit compile, and we'll give
this a default value of something like 2000
units, or maybe 2,500. We can come back and
change that later if that turns out
to be too fast. Then, of course, we just
need to come in here. We'll take our movement speed. We'll multiply this
by our Delta seconds, making as with the
planes movement, everything frame
rate independent, and then plug that result in here. And that's pretty much it. We have a framerate independent
projectile movement, which will move in the
direction that it's spawned, making it nice and
easy to use for either the player or the enemy, regardless of the
way that the initial plane is facing a simple, fast implementation, but it's customizable if we needed
to extend on this later. So in the previous topics
on Looking at enemies, I mentioned that the
projectile would be handling their own collision
and damage system, and that's exactly what
we'll implement now. So with the static
mesh selected, we're going to go down into
the collision presets. And we want to change the
preset here from block all to overlap or dynamic.
So we'll drop this down. We'll find the
overlap or dynamic, and we can see what that does
to the collision presets. The main reason is
that projectile shouldn't physically
push things around. That can end up in situations
where things look glitchy, where all we really
need to do is be aware that they've
touched something. And if you start
being pushed back around or the enemies start getting moved with projectiles, it can look a little bit
strange to the player. If we want to add knock
backck as a feature, that's perfectly fine, but we'll do that through code so
that we're in full control. Generally, when two
things physically overlap with the
blocking function, it doesn't look like
knock back anyway. It looks like two things are fighting to inhabit
the same space, and it's just not
pleasant to look at. All we need the projectile to do is detect and hit something, and when that happens,
apply damage. No physics simulation
or things like that. We'll do the same thing
as we've done before. We're going to scroll
down to the bottom. We'll find the event section, and we want to add
the on component to begin overlap
function call here. So when this overlap something else, same as we've done before, we need to find out
some information and see if damage
will be applied. We'll take the actor here,
so the other actor, again, same as before, we'll
promote this to a variable, and we'll call this
one overlap to actor. And this is because the same
reason as with the enemies. We're going to want to check some information
against this. We don't want to keep
pulling from this pin here, so we'll use a variable that
we've stored for reference. So the first thing I
think we want to account for is if the thing that we've overlapped is referenced as the thing that's sponda
so the owning actor. So, for example, if
the enemy fires, and this is classed as
an enemy projectile, we don't want it to apply
damage to that enemy's plane. And likewise, if the
player fires a projectile, this is classed as the
layers projectile. We obviously don't want to
apply damage to our self. So the first thing
we can do is we can get our overlapped actor, and we'll check to
see whether this is equal to a specific
type of actor again. So if this is equal to, and we can search for
another built in tracked variable
provided and tracked by the Unreal engine by default, so we may as well
make use of it. We're going to
search for G owner. So whenever something is
spawned into the world, it's immediately given the
concept of what is owning it, what spawned it into
existence. So we can use this. So we can say that if
the thing that we've overlapped is also the owner, then we're just going to
skip applying damage. The main reason for this is
that we may want to pick our spawn point maybe directly in the
middle of the plane. So there's going to be
a very brief moment where the projectile
that the player spawns is actually inside of
the plane for just a moment. If that's the case, then
we're just going to ignore the first overlap
here, we won't do anything. So we'll pull a branch here, which is our conditional flow, so we'll check if we should do something based
on this result. And if that's true, then we can essentially just skip we
won't do anything at all. Now, the way that I generally
like to read these, though, is I like to do things off of the troop in where possible. This is a personal preference. Again, different programmers
will always have different preferences for
how they approach things. But I would rather my
actual finalized code, the thing that goes ahead to do the main functionality
come off true. So what I'm going
to do is actually do a slightly different checkout I just wanted to show the more
readable way first of all, but with one very small change, we can actually search
for isn't equal to. So an exclamation mark
and equal will give us a not equal check
to the overlap factor. We'll plug this
in here, and this now does the inverted check. So this is checking if
the thing is the owner, and this is now checking
that the thing isn't then. So if we plug this in instead, we'll get this result here. So we're saying if the overlap
dactor isn't the owner, and this is how I
like to run my code, is that if that's not
the owning object, then we can do our
damage application. So once again, some of
this is really just to show the different way
that you can think about your code and the different
way that you can have multiple approaches to
solve the same problem, even if it just comes down
to how the code is read. And this is nice and simple now. We can get our
overlap actor again, Control drag this in, pull it from this pin, and
we've done this before. We're going to call
the apply damage that global universal function. The one that can be applied
to any type of actor. So we can pour that
into the true pin. If the thing that we
hit isn't our owner, then we can apply damage to it. I think by default,
I'll give this a default value of 20, so
we'll plug 20 in here. We'll promote this
to a variable. Again, remember, if you place a value in here before you
promote it to a variable, that variable will
already have that stored. And the main thing is that
we don't want magic numbers. Just going to rename this
one to projectile damage. Hit compile and just
double check that we should have the number
20 filled in here. Now, ideally, once
we've had this done, we may want to start
playing some sound effects and particle effects
specific to the projectile. I provided some
really small kind of sparks rather than
a full explosion, just to indicate that
something has hit a surface rather than actually
causing an explosion. So to do that, I'm going to use the same concept that I
mentioned previously. I'm going to press C inside of the event graph
with nothing selected. I'm just going to press C
to create a new comment, and I'm going to give this
a big to do sign here, and then we'll just say something
like implement effects. This will be something
that we do in our polish pass a bit later. That's fine. Ni this will just remind
us to come back here. As I've mentioned, I
normally give this a kind of yellow green color just to remind me and really standite against other things that might appear in the graph. But the thing that we
definitely want to do is once we've
played our effects, we want to make sure that the projectile is
removed from play. So we're going to pull
from the execution pin, we'll search for the destroy actor and we'll get
rid of ourselves. The projectile's done its job so it can go away. And
that's pretty much it. So we're going to check
what we've overlapped. If it isn't the owner, then we're going
to apply damage. Now, this is a really safe call. You may be wondering what
if I hit the side bounds or something like a floor that I may place in or a pickup
or something like that. Now, if that happens
and they don't have the on any damage function
that we saw earlier, so we can see here
the event any damage. They don't have this implemented inside of their code base,
then that's perfectly fine. They'll receive
this information, but they don't have a health system or
anything to deal with. So it's kind of like a very, very cheap throwaway message. We're not going to get performance issues
unless we do something really drastically
bad with this, and it's not going
to do anything. I won't be destroying the floors or the walls
or anything like that. So it's a nice safe function. We don't need to know anything
about the other actor. As long as we've
touched something, we can find out some generic
information about it. And if we think that
we may want to try applying damage, then
that's perfectly fine. We can just send this message. So the final thing is
to make this reusable. That is the damage implemented
in a fairly universal way. We want to make sure
that our projectiles match the shooter's color, so the player will fire blue projectiles and the enemies will fire green projectiles or whatever you want
to customize it to. So we're going to go back up to our begin play function here. We just want to do this once when the projectile is spawned. We want to actually
grab our component. We can do some code on the components that
we already have, so we'll drag that
into the event graph, and we're going to pull
from here and search for create dynamic
material instance. So we only have this
one option here. And this is one of
the benefits of using material instances over
our master material, the base shader code is
with the material instance, we can turn them into
a dynamic version, and we can actually update
their properties at runtime. We want to take the
return value here, so that dynamic version
which gets created, we want to promote
this to a variable, and we'll call this
one material R. So it's just a reference
to our material. From this, we can do our code to change some of
the core properties. The main thing if you wanted to do more on this is
you just need to know the names of the
variables that you can work with. And
this is really simple. If we grab the static mesh, just very quickly show you
some of the other things that you can do once you've learned one I'm about to run
through with you. If we double click on the
MI underscore projectile, we know that this is
our material instance. This will open the asset for us. And basically, anything I've exposed for you over
here, remember, I've said that I've
created this big, somewhat customizable material. Anything which is named in our section here can
be accessed in code. So you can change the
specularity at runtime, if you wanted to make
things look more glossy and potentially wet. You can change the texture
at runtime, the colors, the override colors,
emissive property, if you wanted to make it
glow. You wanted to do. As long as you know the naming
and the specific spelling, you can alter and
affect any of these. So the way that we can
do something really simple to show this kind of inaction is we want to get
our material reference. And again, personal preference, I always get a new node
to pull from here, but you could just find
the information from this material
reference you've just promoted to a variable. I'm going to grab our variable reference here, and from this, I want to find the set
vector parameter value. So it's this option here. We see this comes under
the material category, so we're looking for
the right thing. This will let us set the color
of a vector, in this case. So we're going to plug this
into the execution pin. The vector that we want to alter is going to be the color,
so the emissive color. And again, we can always
just cs check here. This is called emissive color, so that's the one
I'm looking for. And the new color that I
want this to be will be based on whichever
actor has spawned this. So if it's green enemy, I
might turn this to be green. If it's our player, I
turn it to be blue. If you have a red enemy, you may want to
turn it to be red. So let's just check
that this is working. If we change this to
something quite obvious, we'll just make this yellow
for now, hit compile, save. We're not going to see this
happen until we begin play. So we can drop this into the graph and we can
hit simulate here. We should see that
begin glowing. Another one problem
I think I have here is for the MI
underscore projectile. I want to make sure
that this is ticked on. But also, I need
to make sure this has some emissive property. So I'm going to come
in and make sure that the default value for the
emissive property here is ten. Because again, we're
multiplying it at the moment, any color by zero will
make it not glow. So if we press Play
again or simulate, in fact, we can see that
has now turned yellow. So if I just zoom back
in a little bit closer, I'll hit simulate so we can see the whole thing and we have
a glowing yellow projectile. So the one change you
want to make is open the material and just make sure you have this glowing
as much as you want. You can make it a lot
more exaggerated, so we can make this
a very bright, glowing projectile
completely up to you. So I'm going to set that to 100 just so it's very, very visible. And that's pretty
much what we do here. So we're grabbing that
property by its name, and we're setting it to a
new value, nice and simple. Now something else
you may want to do is have some glow
more than others. So I've just needed
to come in manually edit the emissive strength here. So what we could do
instead is just to kind of show the way that we
can use this system. We can duplicate the
material reference, and this time, I'm going
to set a scalar value. So the values that
we're working with, although they're
known as floats, when we're working
with materials, unfortunately, they're
called scalars. This is because they're
made a scalable float. We can change them at runtime. So just try and remember that. It's a slightly new terminology, but the one that you
need to learn for working with materials
specifically. So we're going to take
our scalar value. This one I've called
emissive strength. So we're going to plug
this in. We're going to find our emissive strength. And then you can change this to whatever value you
wanted this to be. So we could turn this
to something really silly by default like 1,000. So 1,000 may have been
a little bit extreme, but it showed the
general concept. I'm going to set
this back to 100. I think that looks
pretty cool as a somewhat emissive
property there for a projectile that
moves quite fast. We may need to emphasize that
to make it more visible. As with everything,
we don't want these to be magic variables again. We would only be able to change this here directly in the code. So we're going to
promote our color to a variable that we
can track and use later. We're naming this one
projectile color for our value. So the floating
point value here. We'll promote this to
a variable and call this one projectile
emissive strength. So that will give us
some flexibility when we start spawning these
in our parent classes, the enemy or the player. Now, some of you may
have realized there's another problem we haven't
really touched on yet. And that is that the
projectiles can potentially fly in space forever if they
don't hit anything at all. I've considered if things
hit the floor and the walls, I've said they'll just
clean themselves up. Fire off a very cheap
throwaway function, which is perfectly fine to
apply damage to the wall, which will essentially
be ignored, but we haven't considered what happens if they don't
hit anything at all. What they'll do at the moment
is just fly into eternity, eating up memory. So there are two things
that we could do. The first one is if we grab
our main property over here, we can actually give any actor that we work with a lifetime. So I'm just going to find
this in the details panel. We can search here for lifetime, and we can give this
an initial life span. If we know that this definitely won't be on screen
for more than, let's say, 10 seconds, then we can give this
a ten second lifespan. And after that time, if it
doesn't touch anything, if we don't call the
destroy function here, it will just be
destroyed automatically. The problem here is
that we may end up with some projectiles which move
slightly slower than others, or we're going to use them
and maybe recycle them later, which means we may just hide them and bring them
back into play later. And obviously, we don't want
our projectiles to just pop off of screen
randomly at 10 seconds. So I'm actually not going
to use this approach. Another thing you might
see is on the eventi, a constant check to see if it's currently out of
camera or out of zone. And we're going to do
something slightly similar, but I wanted to focus again on good programming practices. As I've said, some
things definitely can and should be on Tick, but this isn't one of what
we're going to do is we'll create a new function
called out of bounds check. And this is just going to be
some very, very simple math. So we're going to
right click and search for the G actor location, the location of the projectile
at its current point. We're going to pull
from this node, and we're going to take
away another vector. So we're going to
subtract a node here, and we can use something called the player camera manager. So if we get player
camera manager, we can find the premade always kind of referenced and
in Unreal memory anyway, so we may as well make
use of these values that already exist for us. And from the player
manager, the thing which is currently tracking the
current active camera, we can get the camera location. So this will be the
current location of the camera that
we're looking at. So we can take these two away, and this provides a new
vector, a distance for us. From this, we can pull
from the vector pin here, and we want to take
the vector length. So if we search for length,
we can get the vector length. This will convert this into a floating point value for us, essentially giving us a
straight line distance. So we know exactly how many
units in a straight line our current projectile
is away from our camera. This works regardless
of direction, which is another
really useful thing of this kind of approach, meaning that this will
work for projectiles flying in any
direction, any angle. And then what we want to do
is from our execution pin, we're going to pull from here and we're going to get a branch. Yourself enough room
to work with this. And we're basically
going to say, if this vector length is greater than or equal to
a certain value, then we're going to
destroy this actor. So I say more than or
equal to to account for, again, if it goes
over this value. If that's true, we're going
to plug this in here, dy things up a little bit here. And I'm going to give
this, I think a value of 3,500 units works. So that's roughly outside
of the camera zones. We can be a little bit more scientific and try
and test that later. If you find that things are
popping off screen too early, then you just want to
increase this value, and if things are taking
too long, then you can decrease it. But I think
that will definitely work. It doesn't matter
if they exist for a few seconds more
than they should. The main thing is we don't
want to see them just magically popping off
screen and disappearing. And we don't want
hundreds and hundreds of projectiles all existing
for minutes at a time. That's more of the problem
we're trying to solve. If this is true, then I'm
going to pull from here, I'm going to say destroy
actor just so that we can clear that projectile
out of memory. So nice and simple. As always, no magic numbers,
so I'm going to come down and promote
this to variable. We'll call this something
like out of bounds distance. And again, this
makes it nice and simple that we can always
just come straight back here. And if we are having problems where we
needed to change this, increase it or decrease
it, we now have a nice easy to find a place
to make that change. At the moment, we haven't
called this function, so a very simple
problem to make or easy thing to overlook is we can't just
make the function. We need to call this as well. We don't want to
just call this once. Obviously, we wouldn't want
to do this on begin play. And as I've mentioned,
we don't want to do it constantly on eventi. So instead, what
we're going to do is from the execution pin, after our set scalar
parameter here, we're going to once again,
make use of timers. Tis are very, very flexible. So we'll search for
that same function, which is set timer
by function name. The function we want
is obviously going to be our out of bounds check. So same again, I'm
going to rename this actually rename it
but press Control C, Control V to place this in, and we could set this to loop
maybe once every second. So remember that if
we did this on Tick, this would be checking
potentially 120 times every second based
on my current frame rate. So that's a little bit
overkill. We don't need it to be that precise. Doing this once every
second isn't going to add a massive overhead to
the game's performance, and it means that we're
still checking at a relatively quick iteration or interval for the projectiles that don't need to be on screen. As I've mentioned,
having a projectile around for an extra several
seconds isn't going to be a potentially having hundreds of projectiles checking
120 times every second, if they should still exist
would be more of an issue. We want to make sure that
this is called continuously, so more than once, so we
will set this to looping. So once every second,
this will now be called. We have a looping
iteration of this timer, which will keep double checking the location of the projectile against the camera and whether it's technically out of view. So we can go and test this.
If we plug one of these in, we'll make maybe a
couple of these, and we'll hit simulate just so that we can see
that firing off. And you can see
one of them there actually hit the enemy plane, so that got destroyed
by hitting the plane. But the other one fires
off, and after it's a certain distance away,
we no longer see it. So if I press the
plane from here, the main thing is that
we didn't see them just disappear through the camera, that's what we want to avoid. But we know from the previous
tests that they are being wiped out when we're testing
this in simulation mode. Of course, they're
flying off and they're popping out outside
of the camera view. So that's a nice kind of
clean way to do this. It's not going to
provide a big overhead. This definitely wouldn't
cause any performance issues. It's a relatively simple and lightweight
calculation here. I think it's much safer than using lifetime
because we don't know how long or how fast
all projectiles are going to move and how
long they might be on screen. And this is cheap enough to run this calculation that it won't provide any performance issues. And I wanted to demonstrate just a simple
comparison of where we might use tick and where
we avoid using Tick. Remember, when we're
playing with these things, always come back in and delete the actors that you may
not be using anymore. We don't want to
keep those around. We're going to spawn those in from some fire functionality
a little bit later. So once again, if you are still playing
around these things, feel free to try
different speeds, different colors,
see if you want to affect any of the
other properties in the material instance. See if you can figure out
how to change some of the other scalar values
or vector parameters. Again, the main thing is you
just need their name and the exact spelling, and
you'll be perfectly fine. You can change the values as you see fit. Another
cool challenge. No you're hopefully getting more comfortable with a little
bit of the program. To add acceleration overtime with maybe some type
of interpolation. You can see those approaches from the other
code that we have. And it's one of the
nice things as I mentioned about
rolling our own code. It's just something that
is almost impossible to do with the projectile
movement component. So if you wanted it to go from a slower speed and speed up
over time or in reverse, you could start it
flying really fast and then slow down
losing momentum. You could also have
look at things like homing projectiles using
similar techniques, interplating towards a
specific goal overtime. I've mentioned, I would
recommend keeping the current class as it is. If you're making these tests and trying some of
this experimentation. I'd always recommend
duplicating what you have and trying to roll
your own version from that. It's a great way
to learn though. I don't want to stop you from trying new things,
but, of course, in the future topics,
we're going to be using these projectiles
exactly as they are. So I'd recommend
having that version as well so that we can complete
the project together.
21. 20 - ProjectileFire Enemy: To get things actually
firing their projectiles. We're going to do this
again, nice and tidy. One fire function in the base class customized
by the children if needed. Same pattern that we've
been using before, so we're going to end up with
the same functionality as our example project that we're working to somewhat recreate, but we're taking a slightly
different approach. So first, just a reminder, if you did have any
of the projectiles lying around, I'd
recommend deleting those. Before we get started,
we'll be spawning all of these in dynamically at
runtime when needed. We're going to get started
in our base plane class. So if we go to the core
plane base and we'll do our initial
implementation just here, we'll create a function
in here named fire. We'll do a very similar
thing to what we've done with our enemy spawner class. We're going to pull
from the execution pin and search for the
spawn actor from class. We can once again,
we'll pull from here and we'll promote
this to a variable. And in case we did want to change this later at the moment, I think we're just going to have a single projectile class. But in case we did want
different projectiles, we'll promote this to
make this reusable. We'll rename this one
to projectile class. Leave this as the default
actor type hit compile, and that one's fine. Now, when we do hit compile, we get this error
saying that we need to provide some
extra information. We need the transponPin. We can do two things. We can
split the structure pin. And if you wanted to leave
this completely empty, we can hit compile and that
will make the error go away. Now, I'm going to
press Control in Dead. There's another thing
that we can do here. For the spawn location,
we need to know exactly where we want our projectiles to
come into existence. And one way we can do
this and make this somewhat visual is
adding a new component. I'm going to go
into the viewpot. I'm going to add
a new component, and I'll select the scene
component just here. One thing I should have
done is had this already selecting the mesh, but
that's perfectly fine. I want these scene components to always follow whatever's
happening with the mesh. So I'm just going
to grab this and drop this again focusing on hierarchy so that the
new scene component is a child of the static mesh. To make this clear, if
you really wanted to, we could rename
this, and I'll call this one project
our spawn location. So when we're looking back
through our hierarchy, we have a rap idea on
what these different components are responsible for. Now, we don't need
this to be visual, but what we can do with the use of this is
we can now move this around and have direct control of where we want the
projectile to spawn. We could have that slightly
in front of the plane. Well, like I've
mentioned, we may want this to be just slightly
inside of the plane, so it doesn't look
as though it's spawning magically from nowhere. When you're happy
with the position of your projectile
spawn location, again, if we hit compile,
we'll actually get this bad blueprint result in the viewpot but
that's perfectly fine. We just want to go back
into our fire function. We're going to control drag the projectile spawn
location into the ground. I'm going to pull from here
and I'm going to search for something called get
world transform. So we want this option just
here with the green function? Get World transform is going
to provide the transform of this specific
component in the world relative to the rotation and
everything that's happened. So we can ensure that the projectile will
be facing forward. We'll plug that in,
we'll hit Compile, and we can see the issues
are now gone away. I just wanted to
show another thing, and I think it might actually be worthwhile changing
this here from an actor to specifically
the projectile based class. If we just go back into
our projectile class here, we'll double click to
open the base class and just ensure that we have
two things exposed here. So we want the projectile
color to be exposed, the projectile
emissive strength, and maybe the projectile movement speed could
be useful as well. Making sure that these
three are public. You can come back and again, I'm trying to lay the
foundation for you. You can come back and once you've seen what
we're about to do, make any other changes
that you wanted. Are completely optional, but I think these three will
be good for testing. And if we hit compile and save, we'll go back into
our plane base class and into the fire function. Now, by default, because this
is set to a generic actor, there's no exposed data. Now what we can do by making
use of specific typing is we can search for our
BP underscore projectile. We want to use the base option, we need to drop this
down and select the class reference type,
not the object type. This will just tell us that some things are going
to need to change. We might have some essentially
broken references or links to different class types, and that's perfectly fine. We'll say change that variable. We'll let this do its thing. It's really not going
to break anything because we're not using anything specific at this point anyway. We only had a generic actor.
So we can close that. And what I wanted to show is
that if we right click on the spawn actor and choose the refresh node option here or make sure that we
hit compile first, what we should be seeing are those properties that
we've just exposed, and I've just realized
what the problem is. There's one other step which
is really easy to overlook. And we're going to go back
into the projectile base. As well as making these public, there's another tick
box that we need. So just some of that stuff you
need to know about Unreal, and you'll pick this
up as you're going. So for the movement speed and the other two that
we have exposed, we also want to choose
this option here, expose on spawn in
the details panel. So we need to do this
for each of them, so grab your variable and then tick on the expose on span. I would suggest it just exposes this property whenever we
spawn this class somewhere. It's also really important
we need to make sure that we compile and save in
the projectile class. Otherwise, if we go back into our plane base, we're
not going to see this. And you can see now the
main reason that we wanted this is by
specifying that we're specifically going to be using the BP underscore
projectile base rather than a generic actor. We now have these
properties exposed, which is really useful. This gives us the
power to change the color based on the class that we'll
be spawning this in, as well as the movement speed, the emissive strength,
and things like that, depending on how you wanted to. With your projectile classes. Now, this is all done
once in the base class. We want this to be
easily reusable without needing to
dive into the code. So, of course, we're not going to leave these magic numbers. Instead, we're going to promote each of
these to a variable. We'll promote the first
one, and we'll call this one projectile
movement speed. And then we'll of course,
do the same thing. We're going to promote
projectile color and projectile
emissive strength to their own properties. And
that's all good to go. That means that we can now have this one reusable function in both player plane
and the player enemy, and all we need to tweak on a per class basis is how fast we want the
projectiles to move. Because I remember in
the example project, the enemy is fired
slower than the player, and we can change
the color depending on who is spawning
the projectile. It means we don't need
loads of different types of specific projectiles. We can just take our
projectile base, and we can make some really
simple rudimentary changes on a per plane basis. The one really important
thing before we leave here, remember that functionality on the collision that we're doing
where we're saying here, if we are overlapping
at the moment, our own owner, then we don't
want to apply damage to any. We need to make sure that the
thing spawning the class, which we're now doing in
the plane base is actually passing along the information
of who the owner is. So one other thing
that we can drop down here in the spawn actor in the plane base is
the owner just here. So, nice and simple, we
can pull from this pin, and we're going to search
for G reference to sell. Or you can just type
the word cell and you'll see get
reference to self here. So this is now
just ensuring that whichever plane spawns this projectile when
this function is called, it's tagging itself as the
owner of the projectile, ensuring that it won't
cause damage to itself. So nice and clean,
we can hit and pile, save, and that
one's ready to go. So we're now at a good
point where we can add the more simplistic
version of firing, which is going to
be our enemy class. Before doing that, I think
there's one more variable that both classes will
be able to make use of, and that is the fire
rate, how often the plane is going
to be able to fire. So we'll create a new
variable down here. The first one is going
to be of type of flight, so press Control ID on
any of the other flights, and I'll call this one
fire rate. It compile. I think just to
make sure that we don't forget to set this later, I'm just going to set
this down to zero, and we'll make sure
that we modify this on a per plane basis. And then the other thing
which I think will be quite useful in both of our classes later will be a vector two D for the minimum and
maximum fire rate. So we have some randomization. So I'll create a new
variable. We want this to be a vector two D again. But for now, we'll just call
this one min max fire rate. We want that vector two
D we had previously, and again, we can leave
these at zero for now. We'll come back and
modify these later. And this is one of those cases
where we're getting a lot of similar variables
for one system. So we may want to put
these into a category, specifically for projectiles or combats or whatever
you may want it to be. We could also have a health category,
something like that. As I mentioned, I won't
keep doing this on screen, as it's just some busy
work in the background, but it could definitely
help as we get many more variables as we flash the classes
out individually. This is fine,
though. This is good for a base working point, so we can now go into
our enemy class, so we're going into core. Play enemy, and we want to create a sequence
on our begin play. The reason for this
is we're now getting some kind of muddy logic. We're doing a few
different things on the initialization of our class, which is perfectly fine, but we can just keep things tidy again. So a quick trick here
is we can pull off of the execution pin after the parent call and in between
what we're already doing, and we'll call a
sequence node here. And this will
automatically hook up the logic that we have
without breaking any of the so then zero is essentially our initialization for
generic randomization. So we could have a comment
saying, randomize values here. Then the next thing that
we're going to want to do is set up our firing details. So, in fact, this is
still going to be based on some level
of randomization. So I'm going to right
click, and I'm going to get the fire rate property. So we want to set
the fire rate here, and this will be once again set on a random float in range. We'll plug this in, and
that random float is, of course, going to be the minmax that
we've just created. So we'll get the
min max fire rate, split the structure
pin like we've done before, and plug these in here. So minimum, maximum
for the X and the Y. So another nice simple
randomization so that every enemy isn't firing
at exactly the same rate, and it's going to look very repetitive and dull, otherwise. If we hit compile, we
just want to make sure, as well, that for
the enemy class, we set a Minimax fire rate, which I think maybe somewhere
between 0.4 and 0.8. Again, you can tweak all of
these values as you like, and then the fire
rate will be set specifically between that range. Then from the second output and now we've done our
randomization, we can actually start setting up the fire functionality
to be called. So we're going to make
use of the set timer by function name again,
we'll pull from here. We're going to use the set
timer by function name. Of course, we're going to
call the fire function. That we've created
in our base class, and the time is going
to be our fire rate. So we're going to get the fire rate that
we've just randomized, and we'll make sure that
this is set to looping. So this will keep being called for as long as the
enemy is in play. The randomization ensures that each enemy fires at its
own randomized rate. Some will be a bit
more aggressive and some more leisurely. Another thing for the enemies is that when they
fly off screen, if the player misses them, like with the projectiles, they should also be
cleaning themselves up. So we're going to go into
the projectile class, and I'm actually going
to take the exact out of bounds check logic here. We can copy all of this, press control NC, go back
into the enemy class. We'll create a new
function. Again, we're going to just call this
one out of bounds check. And we just paste this
into the graph here. So a little bit of duplication. And again, this is where you could start considering things like the use of components would be a
valuable thing here. You may see when you're getting more into programming
the concept often referred to inheritance
versus composition, making use of
components like this. A little bit out of scope for this really introductory system. So some of the things we will be taking the slight shortcut, but this still isn't a terrible
approach to doing this, just a tiny bit of duplication. But the out of band check
variable here that we've copied from the other class obviously it doesn't exist here, so we're going to
right click and we're going to create a variable. And this one we
just hit Compile. We're going to check the enemy's location
from the camera. For the out of bounds distance, we can use a similar value. I think 3,500 was
working pretty well. We just want to make
sure that we're keeping an eye on enemies that
leave the play area, and if we notice them just
popping out of existence, we may want to tweak
this in some way. Now with that ready
to go, we want to go back into our event graph. We might want to start tiding
things up a little bit and just moving things around
to give ourselves space. The main thing here, we could
set up another time here, so this could be our logic to set up the reoccurring
functions. So we'll create another TO by
function name for this one. This is going to be our
out of bounds check, and we might want to do
this once every second. So we'll set a time here
but once every second. We'll promote this to
variable. And I'll set this one to be called out
of bounds check duration. Like before, we'll hit Compile. We want to make sure
we give this a value, so that's got the 1 second, and we'll set this to
looping, so it's going continuously until it
gets removed from play. Like I've done in
the past, as well. I think I'm going
to write click here and find the fire function. So we're just going
to callFirefunction. We're not actually
going to use it. It's just so that, again, if
we wanted to double check what's happening inside
of this function, we can just double click here. I'm going to get the out of
bounds check, get the name, and make sure that we
fill this in over here in our time and then just drag
this in same reason again. Just in case I want to
see what's happening, I can just double click in
and see the code exactly. As I've mentioned, just
a personal preference, but something I always
tend to do in my own code. One final thing I think we've
forgotten to do is because we had a little bit of a
swapping of projectile classes, if we go back to the base class where we implemented
the fire function, if we grab our
projectile class here, we can see that the
class is set to none. So if we tried this now, we wouldn't be getting any
of the enemies actually firing anything
because they're not given the specific
type of class to fire. So at the moment, we only have one, which
is the base class. But if we made some
child classes from this, we could use those
unique versions as well. So we sent this to be a default of projectile base
because at the moment, it's the only thing
we have to work with. Hit compile and save.
And then in the enemy, what you'll see is this will automatically propagate dine. So we have the projectile
class set here as well. Hit compile and save, and we
can nest the enemy firing. So nothing super fancy,
but they are indeed. Firing and we can
see we get that a different rates,
different speeds. So we have some slightly sort of unique gameplay from
the different enemies. We should as well see that after a certain number of hits, the player should be
removed from play, so the damage is
working automatically. So we have everything kind
of working as intended, at least for the enemy
class, which is great. The final thing I notice
there, that's probably going to be a little bit
fast, and that's fine. This is why we've set all of these values to be exposed so they're easy to tweak and
change based on play testing, feedback, and the general game field
that you might be aiming for. So I think in the enemy,
we're going to slow their projectiles
down just a little bit because there's going
to be a lot of them. The player probably
wants to fire faster, and the enemies can be a
little bit more relaxed. So we'll set this to
something like 1,500. And if we feel this is too slow. So I'm just selecting up
here in the enemy class, grabbing the projectile
movement speed and setting this down to 1,500. We can also change the color. So my enemies are green.
Going to get a color, which is a little bit closer
to the enemy style here. You should be able to
use the color picker so we can go into the color,
get the color picker, and you can click on the part of the plane that you wanted
to match the color too. For me, this isn't working. This is selecting
the static match. So I'm just going to
grab a color which is roughly similar to
the plane body. That would be perfectly
fine, I think. So now, again, if
we hit compile, we have projectiles moving
at different speeds, and they're actually
representing the color of the ship, which
is firing them. You can also change things like emissive strength
if you didn't want them to glow or if you wanted
them to glow more or less. But that's it. So that is
the enemies set up and done. Just a small side task for you, pause the content again
between the topics, and maybe start looking at different variables
that you think might work for the minimum
and maximum fire rate. Maybe you're not feeling
enough difference between the different
enemies as they spawn. Maybe change the color, the different types
of movement speed, the damage provided,
expose other variables, play around with
all of the values that you now know that
you have access to. Next, we'll be taking a look at the player fire functionality. It's going to be
slightly different, a little bit more hands on, but you can see we
already have a really good foundation to build upon.
22. 21 - ProjectileFire Player: Can now move straight
into getting the player firing in a
similar way to the enemy. So we're going to reuse
some of that base logic, the exposed projectile
properties, and have a little bit
more refined control over if and when the
player should be firing. So if we go in and open up the BP underscore plane player, we've got two different
options here. Now, one is that a lot of
people might just drop the core functionality outside of functions into their
main event graph. Something else I wanted
to introduce, though is that we can make our
own custom graphs. So the default event graph comes with things
like the begin play, the eventi, those
things built in to the engine by
default. Perfectly fine. One thing I quite like
doing is we can press this plus button here and we can create an entire new graph. I'm going to give the name
of this new graph input. And this will be where
we put all of our logic specifically based on receiving
input for the player. So again, we started
adding things later, like a restart button or a menu button or different
types of power ups and things, all of those inputs
could be tracked here in one nice
convenient place. To get the fire
input registered, we're going to finally make
use of an input event. So we've previously
just been using the variables which are
stored in things like the IA underscore in our handle movement
we've seen over here. So this is our example of using just the float
value returned. Inside of the input graph,
we're going to right click and we're going to search
for IA underscore Fire. And we see we have
the option here, we actually want to make use of this event which is returned. So whenever the fire
button is pressed, we can have something
happen off of that triggered result of
the input being received. Now, the first thing
we always want to consider is we want that
immediate feedback, something which is more
of the game design and theory type side of things, is that when the player
presses something, they have this expectation of
seeing immediate feedback. There's no lag in between them pressing something
and something happening. So for that reason, as soon
as the input is received, we want to make sure that
something begins happening. So we have a bunch of different events
which get fired here, and we want to make use
first of all of started. So we want to make sure
that the fire function is called immediately on Start. We're going to
call here the fire function from our parent class. We can see this is
the base class. Started is called
immediately as soon as that button press is recognized and then completed
would be fired off as soon as the
button is released. So we can make use
of these different stages of the button press, but the important
thing for now is that fire is called immediately. Then after that first firing
function has been called, we're going to pull
from the execution pin, and we're going to
make another set timer by function name. The function, of course,
is going to be fire. Mainly just out of habit here. I'm just going to duplicate the fire function we
already have, and again, I'll just drop that
below the timer because we know that's the
one that we're using here. The amount of time
between, we're going to pull from here and I'm going
to find our fire rate. So we're going to get
the fire rate value. And I think for the
player, although I said, we may want to look
at randomization, this is another one of
those things now when thinking about things from
more of a design principle. We probably want
to set this rather than having a
minimum and maximum, the player's going to
have an expectation that every time they fire and every
time they play the game, their fire rate
should be the same. Unless you're making
something like a roguec where
you're specifically telling the player that based on your character or random stats, the fire rate may be different. In this type of game,
they're going to have an expectation that it always feels the same so
they can get used to the mechanics and
learn how to play. So if we hit compile,
we'll grab the fire rate, and we're going to
set this to 0.2. We'll make sure that we
set this to be looping. So essentially, what we're
saying is we'll press fire, and then once you're
holding the button, we're going to call
this function so that when you're holding,
you'll keep firing. Then we can go back
over here and we can now make use of this
completed function. So as I mentioned,
this is called when you release the button
that you are holding. So from completed, we
can actually make use of another part of the built
in time of functionality, and this is a really nice way of keeping your code
again clean and tidy. You may have seen in other
examples this thrown onto the eventic
with some delays, which can be very confusing
if you think about running an eventic and a delay together, essentially
combining logic, which is meant to run
constantly at a given interval, and then also trying
to delay that whilst running it constantly. So we're going to avoid
sloppy code like that. We're going to do instead
is we're going to find a function named clear
Tir by function name. So we'll use this function here, and this is really
nice and simple. We're going to grab
our fire name here, so we'll grab the text here, and then we'll
paste that in here. So we now have
complete control over when this has started
and when this ends. So the first time that we
press the fire button, we're going to call
the fire function. We're then also going
to make sure that we keep recalling this for as long as we're
holding the button. But if we ever
want this to stop, as soon as we recognize the
button has been released, we're going to clear
this function name, so this timer function. So it's going to find this because they share
the same name. It's going to cancel
the looping here, and it means that we
won't be firing anymore. So we don't have wires
going everywhere, things trying to cancel
out on the event tick. It's just a single fire event, and we're only adding a
stack of every 0.2 seconds. Call a really simple function. So again, very low overhead, very good for
performance, very clean to read, and much
easier to manage. And really for the player, that only leaves a
couple of things here. So the firing functionality
is relatively simple. We just want to go
into the class details here and make some
similar changes to what we've done before. So the projectile
movement speed, I think, 2,500 for the player is fine. Leave the emissive
is perfectly fine. And we're just going
to change the color. My players are kind
of bluish color, so we're going to go.
With that just here. Maybe try and match
that a little bit more. As I mentioned, on your system, you may be able to get away
with the color picker. But for me, that's
just selecting black. So I'm just going to set
this in a more manual way. We can then hit compile, save, and then we can go in
and play test this. There you go. So the player
has continuous feedback on the button press. This is looping continually until we release
the fire button. So as I mentioned,
really nice and clean. If you haven't seen it
before, I'm not going to find specific tutorials
to call them out, but have a look on YouTube before something
like Unreal engine, blueprint fire functionality
or gunfire logic, something generic like that
and see the result you get. Have a lookout for
those specifically, which will bring you
into the eventi, and have some delays
thrown off based on a fire rate and see
the way they'll use things like gates to
cancel out of calling the fire function when a button is either
pressed or released. And then compare that to what
we've just done here with two really simple calls to start the function and end
the function overtime. Again, as I mentioned, with the epic examples
and stuff like that, this is never to call out
other people or projects. Explicitly. Mcde is definitely
not always the best, but it's just to get
you thinking and actually critiquing
what you're seeing as you're learning because
one of the worst things that you can do is just follow everything blindly and just assume that it's always
the best or only approach. There will always be different ways to
approach a problem, and I just want to
get you thinking about whether what you're
doing in your projects or what you're seeing online is a way that you want
to implement into your own projects to make them manageable and
easy to work with. That's pretty much
it for projectiles. Again, just make
sure that you double checking everything
has the precise values filled if you wanted
those extra steps that you could take into
improving your project. I'm going to leave this on parity with the example project, but we do have problems
that may not be readily noticeable, but
they're definitely there. For example, we can
actually spam click the fire button faster
than the fire rate. It's really hard to
do because I've set the fire rate relatively
quick anyway, but you can see
I'm clicking here faster than if I
would just hold. So maybe consider how you
might fix that yourself. I'm not going to go
through that process. It would essentially
be a case of tracking a value on maybe a separate timer when you
first press the button. The next time that you
press the button if that is lower than whatever
the set fire rate is, then you would just cancel of
calling the fire function. In this case, it's not
such a complex project that I really think that
makes a big difference. And as I mentioned, this
same functionality is in the example project we're trying to hit parity with anyway. But it's just a nice kind of thought practice and see if you can think about how you would
incorporate similar logic, but stopping the player
from clicking and firing faster than
the given fire rate. Our main next steps, though,
we have the ships are armed. The player and the
enemies are both firing, both using different colors,
different fire rates. You can tweak those
values again as well. If you're finding that the
game is too hard or too easy, change things like the damage, which is applied based on where the projectiles
fired from, the fire rates,
things like that. Next, we're going to
go back to making use of our generic Actor spawner. And I'm going to start adding in some scrolling backgrounds to make things look us that a little bit more interesting.
23. 22 - ScrollingBackgronds: It's time to take this
project a little bit further and actually make
it look like a game. We're going to add some water,
some scrolling islands, and we'll do this by
reusing our Spanus system, which is why we've built that to have such a generic setup. We can close out of any of the classes we have
at the moment. Right click on the main tab and just select to close other tabs, and that will get rid of
everything that you're not currently in, and then we
can just close the player. Back in the main view boot, we want to use the Add
button just here, and we're going to
add a simple plane. So if we go to shapes, and we can find a plane, we just want this to
be a flat surface. We're going to
reset the location, so we'll set this to zero by clicking the undo
button just here. Press the lock button to make sure that this
scales uniformly, and then we're just going to set the value to something like 80 to ensure that we cover
the entire play zone, so everything that we can see. We can also get
rid of the floor. We're previously
using that just for context so that we can see exactly where and
when we're moving. We're using that grid
essentially to detect movement. We won't need that anymore,
so we can get rid of the actual location
of the plane itself. We want this to be
negative 4,000 on the z. And again, this is just based on how I've got my camera set up, the plane and enemy locations. You may find your level has
a slightly different setup, but if we're going from a
roughly similar project for now, you can
tweak this later. Having this far down keeps the shadows a little bit more
subtle and gives us room for the floating
islands because some of the objects I've provided
are intentionally quite big. From the folder, we're going
to go to our assets folder. We'll find the materials,
and we can find the MI underscore water and just drag this
onto the plane. So this will give
us a nice, simple, basic animated water material. As I mentioned,
some of the details and properties may need
to be changed here, like the size of the
plane or the location. We do a full kind of
sweep and refine once we got to the end of the topic based on how things
are looking, though. If you wanted to change
up your water, again, we can double click the MI
underscore water material. Drag this into the window here. This is a nice way
to once again, make use of the
material instance. If you find that flowing water was too fast or you
didn't like the color, we can get that live feedback
even whilst we're playing to see exactly
what you wanted to do with things like
the depth fade, the distance, the different color properties and
things like that. So you have full control over how you want
your water to look. This I'm going to
keep this as it is, but just to say
that these values are here if you
wanted to tweak them. Next, we want to
consider our islands. So we're going to go back
into our Blueprints folder. We'll create a new
folder in here. We'll name this one Islands. Inside of the islands folder, we're going to
Right click, go to Blueprint class,
create a new actor. We just need something with a presence in the world again, and we'll call this one
BP Underscore Island. Inside of the class,
very standard setup now. We're going to add
a new static mesh. We'll set this as the
default scene root, so we'll override the
default scene root here. And with the static
mesh selected, we're going to go to
the details panel. We'll find the SM
underscore island round, so the large one to
get started with. We can press F in the viewpoint, so we can see what that
island would look like. And then we just want to go down to the collision properties, and we're going to take the
collision details here. We're going to drop this down,
set this to no collision. We'll turn off the
generate overlap events. Just making sure
this has no physics, it has no overlap checks. This is going to be a very
cheap throwaway object, which is just there for visuals. Now, before we even get into using our spawner to bring
these into the world, we know that this
is going to look very repetitive, very boring. We're going to get the same
static mesh at the same size, the same rotation every
time that one is spawned. Let's move to the begin play, and we'll solve
that straightaway. Likaways, we're
going to get rid of the Actor begin
overlap function call. We want the begin play
here, and we can do some simple things from
the execution pin here. So first of all,
we can randomize the scale of the
actor a little bit. So we'll call the set
Actor scale three D. And we're going to do
this a slightly weird way. But if we right click and search for random float in range, we can set that range
once again based on a vector two D. So we'll
create a new variable. We'll call this
one Min Max scale. Drop this down, find
vector two D. It compile and provide
some values here for maybe something like 0.8 to 1.2. Drop these in as
we've done before. So you can see, again we're
getting to the point where a lot of code is going
to become very familiar. And this is that
idea of repetition. We'll get you more
comfortable with the project and working with Unreal. So
we'll split the structure in. We'll plug that into the
minimum and the maximum. And we can actually just
take our float value here and convert
that to a vector. The way that this work,
it may look a little bit weird how we're using
a float or a vector. Basically just going
to ensure that the X, the Y, and the z take
whatever this result is. So if this picks
1.1, it means that the X Y and Z will
all be set to 1.1. Now if you wanted
to stretch things out, have more refined control. Of course, you know that you can split the structure pin for the vector and maybe do a
randomized float on the X, the Y, and the z individually. Problem I find with that is
that you don't really have much refined control over stopping the mesh from
being completely. You may end up with something
like really weird and squashed or really
odd and stretched. So for that reason, I'm just going to set
this to be uniform, so it's just scaling all
of the axes together. Then we can also grab
our static mesh. We'll drop this into
the event graph, and we're going to pull from
our mesh reference here, so a reference to our component, we're going to search for the
set static mesh function. We want this one
here, the one which will give us the mesh component, just to make sure you saw that we want to go to components static mesh and set static
mesh. Not this one here. This is actually updating
the variable itself, whereas this is taking the
component we have and it's accessing the details through
our details panel here. So we want to maybe randomize the static mesh that
we use, as well. So we can pull from
here. You've probably got an idea where this
is going because again, we've done this plenty
of times before. We only have two different statics
options for the islands. So I'm going to
use a select node. And because we only have
two, the easiest way I can see this is
if we had more, we might want to use some
random integer within a range, and if we had five,
we would pick a range 0-4 or one, five, whatever. And depending on what was picked between that integer range, it would pick one of four
or five different models. Now, because we only have two, I'm just going to use a boolean. I'm going to pick a
random ball here. We don't even need
this to be a variable. And then we basically
have a 50 50 chance of spawning in the large
island or the small island. So, nice and simple, we don't need to make
this too complex, at least we're now getting
some randomization. We might spawn the same big
island three times in a row, but at least the scale will be slightly different
on each one. If not, then we
may be randomizing between the big and
the small island. And again, they're still
going to have some nice randomized scaling applied. And then finally,
they're all still going to be looking
the same way. So we can grab our
rotation details, and we can randomize
that as well. So we'll pull from
the static mesh, and I'm going to find
the set active rotation. This one, we only want to rotate around the z axis
because remember, that's going to be up
or down in the world. So we're going to right click
and split the structure in. I'm actually just going
to be a little bit lazy and grab the random
float in range. Duplicate that over here,
we're just going to rotate this 0-360 degrees. So this is one of those
times I don't think there's any value in actually
promoting this to a variable. I think that's pretty clear.
When we look at something, we're setting the
actors rotation, and we're going 0-360, so that's obviously we're allowing
for a complete rotation. So I wouldn't actually recommend promoting
this to a variable. Some things can be fine
as a magic number, especially since we
know we're not going to be updating or tweaking this. There's no reason
that I can see that we're going to want to
clamp that to a different. Was promoting this
to a variable means that we don't need to
come back into the code. And if we wanted to
completely change the scale without
finding the code itself, we could set this to
0.4 or 2.5 or whatever we might want this
to be to make really exaggerate the scaling.
I'm not going to do that. I'll keep that as 0.8 and 1.2. So just those three
changes, we're going to have something which is now
looking a lot more kind of random and a little bit more unique than just having
the same two objects popping up exactly
the same scale and rotation when they're
brought into play. Now we can move on to
the movement. So this is why we've kept the eventic. Again, we want a nice,
consistent, smooth movement, and with some really
simple logic, this isn't going to become
too expensive to run. We'll pull from
the execution pin, and we'll find the ad
actor world offset. We want this one
here? Add Actor world offset for the entire
actor of the island. Give ourselves some room
because we need to do some calculation
here for the offset. We'll create a new variable. I'm going to promote the Delta seconds again just
to be lazy here. Promote this to variable so
we get the correct type, and I'll call this
one movement speed. Hit Compile, and we set this to a default of something
like 400 units of speed, but we're going to want to make sure we give this
a negative value, so negative 400 to ensure that this is
scrolling down the screen. We can get rid of
our movement speed. We just want to take
our movement speed multiplied by Delta seconds. And we'll plug this
into our value here. I've just realized
looking at this. We don't want to add
a world offset here, so we'll delete this. We actually want to
add the local offset. So we'll use the add
actor local offset. Split the structure
p for the Delta, and then we'll plug that into our Delta X location
because again, we're only moving this
on the X forwarding backward across the screen. We don't need to calculate with any physics or
anything like that, or try and make any
pseudo physics over time. So we're not using
the world offset. We can use the local offset. I'll take where the
actor currently is, and it'll just add this
value to that X direction. In fact, the reason
that came to mind is if we go back into the
shoot 'em up tutorial, we'll go to our projectiles
and open a projectile class. We can see the event here. This is actually the same logic that we're using to
move our projectiles. The island is essentially
just a very slow, very big projectile in the
way that we have our code. Again, this is actually
something which is making use of the same code. It's just something to
keep in mind that we are doing a little bit of
duplication again, and this could be
in a larger project where you start
looking at things like composition where we could have a custom
movement component, which because it's so
similar and all we really need is a
direction or a speed, we can have those values
exposed in a custom component, and we could drop those onto the different actors that need them. As I've mentioned, slightly out of scope for
the content that we're going through
at the moment, but just something to consider. This isn't the end of the world, so we're going to
keep this as it is. And now we can actually
start spawning these in. So this is where the simple
generic actor class, the spawner class
that we've created is going to become quite useful. So if we go into
our level, first, I just want to very quickly test how the islands
are going to look, so I'll grab one of
the islands and drop this onto that water
plane at the bottom. We want to get a rough idea of the height that we
want to spawn this. Again, this will come down
to a little bit of tweaking. I'll do some of
that now, but I'll probably refine this offscreen. So I think, actually the first time I dropped one of these in, it was, actually, the same as the plane
itself, so -4,000. So we can go with
that as a baseline. We'll probably set the
spawner to the same location. We want to make
sure that this is further back out of camera. So this is something we
can now test easily. We're going to grab
our camera actor. We will pin this here,
and we can start actually tweaking and making sure that everything
spawns off screen. So for the big island here, plus maybe some accounting for we can up to a scale of 1.2, remember, is our
randomized scaling. So if we get the
biggest island spondm, we need it to be at
least at the point of 11,360 on the X to be completely out of
view when there is spondu. So that's our essential
point that we want our island spawner
to be placed in. So, in fact, I'm going
to grab this here, turn that zero on the y, so we're going to
start in the middle. What I'm going to do
is with the selected, I'm going to come back into
the blueprint folder here. I'm going to go to
the spawners folder. Select this. And with this
selected in the content draw, just to handy tip you're
about to see here. We can right click
on this island. We'll go down to replace Actor, and we can replace that with
the thing that we currently have selected inside
of our content drawer. So we're going to rename
this. This object, we can see is now a
BP underscore spaner. I'm going to rename this one to Island spawner. So that's
clear what that's doing. And in fact, when
it comes to naming, this is going to start getting
a little bit sloppy as we. What I should actually
have done is we'll call this spawner underscore island because we can see that
this alphabetizes things. So we can also then
rename this one to spawner underscore enemy. So it's now a little bit clear that this is
our enemy spawner. This is our island spawner, and they're going to be
clumped together. Now, another thing we
can see as a problem is the static mesh is
just the wrong size. I think I may have
been considering a different asset I was
working with before. So we just want to maybe
increase this to something like 120 on all of the axes, maybe even 160. Perfectly fine. The main thing this is
covering the entire camera. So we can see now in play mode, we have the water
covering everything. The spawner will be just
outside of the camera so we can ensure that the
islands spawn off screen. Now, one really important thing
I was about to completely miss is if we go back
to the spawner island, when I was tweaking this,
I set the scale to 1.2. Now we always want to
make sure that we keep these uniform because in
some of the spawning code, you may be taking in the
actor's location rotation in scale as the point
at which to spawn. So we don't want to
add any default, kind of like preset offset
to the scaling of anything, so just make sure that we
turn that back to one. Then to get this working
for the island spawning, we're going to want
to drop this in and select our island class. So we want the BP
underscore island. We're going to allow a random
offset, so we'll tick this. We'll provide an
offset distance of, let's say, 5,000
units either side. So remember, this is just using the built in functionality
we've already implemented when we were
planning ahead a little bit when creating our
generic spawner class. And in the spawn intervals, we don't want too many islands
cluttering up the scene. So we'll set this
to something like 5 seconds and every 10 seconds. So 5-10 seconds, we
should get a new island. I just have a very
quick recap of the the reason we're using
the offset here, remember, we've created in
our spawner base, we have our branch on the spawn begun or
a Boolean check here, sorry, on whether we should
allow some random offset. If we say no, then we would just spawn the island at exactly
the point of the spawner, which means we just have one straight line
of constant island, which again, would be
a little bit weird. If we say yes, then
it's going to pick a random float within a
range, which in our case, because we've set this to 5,000, which would between
5,000 on the Y and negative 5,000 on the Y. So that should give us a lot of variation in their start point, their scale, their rotation,
and even the mesh. So we can test this now if we press we should see
somewhere within 5 seconds. I'm going to get rid of
the camera very quickly. In play mode, we can test this and just wait around
a little bit, and we should see the
islands start spawning in. We're looking out for
things like making sure they're not just
popping into existence, making sure that they're
going the correct direction, which I think we may
have an issue with here, and also that they are
spawning correctly within the offset that we've provided. So I'm not
seeing something. There's a very small island. That may have taken
the 10 seconds, but we have a very small island there just outcropping
from the bottom. It's kind of comical, but I think that still
looks pretty good. And we should see,
hopefully, some more islands coming within the
next several seconds. Okay, so we actually
have a lot of islands this is one problem we're having is that with
their random rotation, they are moving forward based on the direction that
they've been spawned. So let's go ahead. I think maybe I did
mess up some of the logic when we're
creating the movement logic. And I think, really,
it's just going back to what we were
looking at previously. So back in the island class,
I think it was this here, the local offset versus world
offset that I had in mind. So it would be nice if
we could keep the code, similar to the
projectile, and we could probably change the
projectile code instead. But I think what
we're doing just to keep this nice and simple, we're going to
change this from our local offset because remember, when we're doing a
local addition or an offset to the local
position of the actor, that will take into account the current rotation
that it has, because we want to rotate or if we're rotating 180
degrees the other direction, then forward locally
has changed, so we're going to move
in the other direction. Saying that, though, that's made me realize that
we could probably do something a little bit cleaner here, focusing on hierarchy. So again, this is
something where in pre development, I
just overlooked this. And this is me, I thinking
kind of on the spot, which is why I'm keeping this
in just to show ways that we can solve different
problems in various ways. So what I'm tempted to do is because I do want
to keep this logic. I want to do the local
offset movement. What I think we'll do instead is we're going to add
a new component. We're going to create an
empty scene component, and we'll make this
the new route. So the scene component will
be the way that we're facing, which is just always going
to be forward in the world. And I think what
we'll do instead is we're going to set our rotation, but we're not going to set the rotation of
the entire actor. We're going to leave the scene component
always facing forward. And this will just give
you a nice example of different ways that we can
use hierarchy, as well. We can grab our static match, and instead, we're going to
set the rotation of this. This is also something
that we haven't seen yet, so we can set the
relative rotation of our components relative to their parent component
that they're attached to. But exactly the
same logic, though. We're going to plug
this in. We're going to take this random float
that we already have, split the structure pin
like we've done before. We're just going to plug this
into the Z here instead. That means that we can keep our movement logic
exactly the same. And now we're not
changing the rotation of the entire actor and
the way it's facing. That's still facing
the same direction. We're just changing the
rotation of the visual element. Kind of similar to how we separated that out
with the planes, where the visual element
is just the plane body, but the actual
collision and what everything's resting upon
is that collideosphere. So if we go back in and play, we should see this is now working. One other useful console command here is we can hit
the Tilda key, type slow mo, and then set this to how many times
faster we want this to go. So if we set this
to slow mo five, you can see that this is
moving five times faster, much easier for
testing and actually seeing if the islands
are spawning properly. So they're now moving
down the screen. That looks like it's
working correctly. We've got that test in remember, where we're not going to
keep spawning things. If the player is dead. Console command again, we
can type a restart level, go straight back into Slom five, and we can do some testing
just to make sure that the islands just making sure that we get several
tests on the go, that the islands
are working okay. So they're bunching a
lot of bit, but I mean, again, this is where
randomization comes in. We may have that happen,
but it's providing some nice kind of variation, and I think that
looks pretty good. May want to add a
little bit more of a delay between the
initial islands, but I think just for
this type of project, that's looking pretty
cool and having some overlap just gives
it that extra variety. One other thing
you may want to do is in the spawner base class, if we come in here,
we're going to immediately spawn Actor. So we should have
one. If we just hit simulates. We should
just double checking. Yeah, we get one straightaway,
which is perfectly fine. I was thinking that
we might have already had an initial delay, but I think the only
reason that we don't see it for a little
while is just because it takes a tiny bit of time
to come down the screen. So we actually spawn with
an island straightaway. I'm just kind of wondering where that
is right now, though. So, it's just it's taking especially if it spawns a
small one, first of all. It's going to take a little
bit longer to see it before it comes into frame.
Not the end of the world. Again, randomization, you're going to have some
different results there. Even with the bigger
island, I think that's taking quite a while to
get into screen though. So maybe what I would be
tempted to do, and again, this will come down to a lot of traveling now playing around. Maybe we were a little bit too concerned with how far
away this is this morning. So we'll just bring this
back in a little bit, and just again, making sure that every time you press plate, keeping an eye to make
sure nothing just pops in, but we want that island to maybe show on screen just a
little bit earlier. So as I mentioned, trial and
error, see how things go, keep an eye on the results
that you're getting and making sure that the game is looking good as
you're going through. So I think that's pretty cool
there. This is where things should start getting a little bit more
interesting, though. You're now in full control over the level design, where you
wanted things to spawn, the speed you want them
to move, really kind of just refining the play and the look of the game that you have. If things are popping
in, then you can just move the spawner back
a little bit further. If you want them
to appear sooner, then we could bring them
a little bit closer. If the water is not
covering the whole view, then just increase the
plane as you've just seen. And if the islands
are too close, then just lower the spawning
speed and things like that. And this is just pure iteration, play, observe, adjust, repeat. It's rare that you'll get
this perfect first time. You've just seen me have
to tweak and change a few things even for
this demonstration here, but that should be
where some of the fun of creating games
comes in because this is where you're now
in much more control of refining that final result. Now one thing I'd
say is that it's going to be worth at
this point as well, keeping an eye on the outliner, like I've mentioned before. We've got some things, got our blocking volumes
are perfectly fine. Maybe putting
things into folders like with the lighting
that came by default. One thing I've realized we're
not actually making use of is the sky sphere in the background.
This is an old object. It's not doing anything. We can get rid of
the sky sphere. You can see nothing changes, just to make sure we don't have more objects in the
level than we need. Everything else is fine. This is providing the light and atmosphere in
the background. But feel free to start
playing around with these, changing the direction of
the directional light, the strength of it to make
things look brighter, darker. Again, really start getting
creative and playing with the different
properties and details to make the game look as
you kind of intended. So that's it for our
level. Things are now looking a little
bit more live. We have water islands,
parallax de you can play with. It's actually starting to look like a little
bit of a game. Next, we can start getting
into the polished pass, making things feel
really interesting and much more kind of visually
appealing to the player.
24. 23 - NiagaraParticles: Point where we can
start improving the general feel and appeal for somebody
playing our game. We're going to start with the
polished pass of effects, adding things like
thrusters, explosion, impact all by using
Unreal engine built in Niagara system. So a quick overview
again of the systems and emitters that we'll be looking
at inside of our project. If we navigate to our project, go into the Assets
folder and effects, we have our Niagara
systems just here. But just open one of
these. We'll go with the pixel engine for now just to show what
we're working with. So what we're inside
of at the moment is a Niagara system. Systems are generally
built up of one and quite often more
than one emitter. We can see here the
orange tabbed panels are our emitters making up the different points
of our overall system. This makes Niagara much
more flexible to work with than the older
cascade particle system. It's much easier
in the new system to create some base emitters, and then we can start
stitching them together to make custom and
unique systems on. In a really simplistic
way, you can think of this as something like a
flame particle system. You may have many emitters
that would go into this. You'd have the sparks
coming from the top of the flame. You'd
have some smoke. You could have the
distortion effect, and you'd have the
main body of the flame and the fire itself. All of these could be
individual emitters building up that one big system. The important thing
to keep in mind is that emitters are
meant to be reusable. We don't place these directly in our blueprints or in
the the systems that we'll be taking to
use and actually make use of inside of our code
or the level itself. If you wanted to start trying
to play around with some of the systems I've
already provided, you can see I've
got several here. You can very easily
come in and start playing around with the
properties in the emitters. If you wanted to see which
part is responsible for a certain effect
going on here in the view pot, we can
untick these here. We can see that this is
just a light source, so nothing really changes. If we turn this back on, but then turn off the other emitter, we can see that this is clearly
the one responsible for the body of our engine
particle effect there. So if you then wanted
to come and start trying to play around with
things like the color, the size of this, you may
not know exactly what you're looking for straightaway,
which is perfectly fine. But just from having a quick look through the
properties provided, we can see we could
probably change the color from our
scaling values here. This is making the yellow
orange color here. If we give this more blue,
then we're going to get a different color over the
lifetime of the particle. It's another great
way to get used to some of the systems
inside of Unreal, just finding the things that
instinctively makes sense to you and playing around and weaking with the
values you can find. You can add different
things like extra velocity to make this
travel further, as well. And like I've
mentioned before, one really great way
to find out what things are responsible for is add a really silly
large value to this, so we can see that if
we make the velocity here 11,000 rather than 1,000, we can see exactly what
that was responsible for, and then we can refine
and tweak down to a value that we think we
might want to work with. So like with some of
the other systems inside of the project that
we've looked at so far, the more artistic side
of things really is not the focus of what
we're trying to get through in these topics. So I'm not going to go
any deeper into Niagara. I want to show you
how to use it and how we can implement
it into a project. Useful if you've got things like access to Asset PACs or some of the free examples
from things like the content example
project provided by Epic. The first thing, since we're
already in this particle, we could make use of
this for the thruster for both of our plane classes. Both of them will need this, both the enemy and the player. So if we go to our
blueprint class, we'll go to CR, and we'll find the BP underscore plane base. So we can put this in
the base class again, and this will be used for
both of our different planes. We need to be very careful again where we're
going to place this. We could place it
directly on the sphere, but I think at the moment, in case we wanted to do any
specific offset rotation, things like that to
the static mesh, we'll place it on here instead. So we'll grab the static mesh. We're going to add
a new component, and we want to
search for Niagara. We can see here we have
just the one option, the Niagara particle
system component. And in the details panel,
we'll see the drop down that we're probably looking out for and quite familiar with by now. We can drop this down and
select the pixel engine. If we navigate to the viewpot, we can see this has kind of been docked in the middle of Amopla, so we're just going to need to move this back a little bit. And I think I'll just
place this roughly by the metal circle that I've
added on the plane just here. If we hit Compiling safe, there's a few things we might
need to test with this. So the first one is
the different rotation that we're going
to be applying to the different child classes. So if we start off inside of the plain player and just
check the viewpoint here, this one's looking
perfectly fine. This is essentially the
same setup as plane base. If we go into plain enemy,
go into the viewpoard here, so we can see that
the particle effect is looking fine here as well. However, if we go
and press play, so if we go into our
viewpot and play mode here, we can see that it's
seeming fine on the player, but the enemies particles are
looking just a little bit strange as they're this is
going to be because remember, when we come into play mode, we are completely
flipping the rotation of our enemy based on
the spawners location. And even though this
particle effect is nested, if we look at the setup in the
P underscore pixel engine, we can see that the
velocity is set to a specific direction,
negative 1,200. So what we want to do, and
this is just going to be a very simple tweak to an
existing particle system. So you can start getting at
least a little bit hands on, we need to make a duplicate
of the pixel engine. So we're going to come
back into our effects. We're going to go to
our Niagara systems. We have our pixel
engine just here. One other thing, these
were copied from the content examples they
don't have the best naming, so we're going to
take this and we'll rename this one to Ns. So the proper naming convention
would be Nagra system, underscore pixel engine, and we'll call this one
underscore player. So we can leave this
one exactly as it is. We can then press
Control, indeed, to duplicate this,
and we'll call this one underscore enemy. And if we double click to
open the enemy version, what we want to do is we're
going to find our velocity. We've already seen that
this is controlling the direction that the
particle effect is going, and we're just going to remove
the negate option here. We could also do something
whilst we're here, making this one maybe a
little bit more green so we could drag the value up here to give that a slightly
different tint. And if we take that one
past the red value, we can see that's a
little bit more of a green thruster
here just to make it slightly more unique for the enemy version that we're
going to place this on. So we can hit compile
and save on that. We'll go back into
the enemy class. We'll grab our Niagara
particle system here, and all we want to do is
change this from the player, which is the one
we've just renamed and change this to
the enemy version. So this is going to look
broken in the blueprint. That's perfectly fine, because as soon as we go into playode, this is taking into account
the world direction of the particle system
movement rather than the entity that is on. So we can see that's now
working perfectly fine. Slightly different because
we've got different colours, so it also looks a little
bit more interesting. A nice simple tweak just to get a little bit hands on with
the particle system there, and we have both the player
and the enemy system working. So that's one way that we
can apply particles without code directly into components
and existing features. Now if you remember back,
we have a few things lined up ready to start implementing
in our code as well. Remember some of
those to do comments. So one of those that I remember
having was actually in our blueprint class
for our projectiles. So if we go into BP
underscore projectile base, and exactly what I was
mentioning earlier, I'm just going to
type todo down here. We'll find that
comment that I've left myself, and it's just down here. So if you didn't leave that
comment, you just need to navigate to your
component begin overlap, and this is where we want
to put in some effects. And we're going to start with
our simple particle system to show that two
things have impacted. So between our apply damage, and again, really important before we destroy
the projectile, we're going to pull
from the execution pin and we're going
to search for something called spawn
system at location. So we want this
option just here, span system at location. Now, there's a couple of different things if
you start looking more into particles yourself to build upon what
we're doing here. You may also come across
the term spawn emitter. So if we search for Born
emitter at location, it's a very similar terminology, and this is essentially
the legacy version of the particle system. So it's not fully removed yet, but I've mentioned it very
briefly a moment ago. There used to be
something called the cascade particle system. It wasn't quite as flexible, doesn't have as many features. It's not as easy
to program into, whereas we now use
the Niagara system. So when you want to
work with Niagara, which is ideally what
you'll be focusing on, you should be looking
for something called the word system will
come up quite a lot. When you're working with
cascade, if you're in a legacy project or
an older project, you might see the
word emitter come up. So cascade articles were
referred to as emitters, whereas now, as I've mentioned, an emitter is just
a single element that builds an entire
Niagara system. So that's the easiest
way to remember it. So we're looking
for a system. You know you've got
the correct one as well, because if
we drop this down, we don't have anything
valid to put into the emitter slot. So
we'll get rid of this. If we drop this one
down, we want to find our metal impact. So this is what we
want to use when two projectiles or a projectile
hits another surface. So that's the emitter
that we want to spawn. And this allows us to
bring these in at runtime, making the projects look a
little bit more dynamic. The location is going
to be really simple. We'll pull from here,
and we're just going to search for get actor location. So this will be
where we want the particle effect to spawn, the impact point, essentially,
of where two things hit. The rotation in scale
we'll leave as default. We don't need to change
these. We'll come back and we'll do some sound
effects and things later. So I'm actually going to leave this comment for myself later, as I will need to come back
and add a few more things. But again, we can now
come in and test, and what we should
see is every time a projectile hit something, we're going to get some type of particle effect being played. So it's a little
bit of an extreme, but we can definitely
see that it's happening. And again, you can
come in, refine the different scaling
and things like that. But we've now got some feedback that two
things have collided, which just makes the
game feel that much more interesting and
intuitive to play. I've also provided, as
well as the metal impact. You could also potentially
use pixel explosion, so completely up to you. I think they look
slightly different. Just double check
what they look like. That one might be a
little bit more fitting actually for the
smaller impact there. So I'm going to keep that
with the pixel explosion. But that's the main thing we now have that visual feedback. And then we want to
do the same thing for both of our planes. So again, we've flashed
this out in the base class, so we're going to go into the
BP underscore plane base. I think I've left myself
a too here as well, and we'll just double
click on this. So to do implement
plane death effects. So these will be the
effects I want to play when either of the
planes are destroyed. So same thing again. We're
going to pull from here. We'll search for the
Born system option. Take the span system at
location. Same property. So wherever the plane has just been destroyed, that
will be the location. So we're going to find
the get act to location. And we're going to do this a very slightly
different way here. So like we've done in
the past, we want to make this flexible
because we're exposing this for use in
the child classes of the parent where
the core logic is. So we're going to take
our system template here, and we're just going to
promote this to a variable. We're renaming this one to
explosion particle effect. And we'll need to change this on the child classes
because the player and the enemy have just very slightly different
particle effects. So we hit compile and save. We're going to go
into the plane class, the player plane class. Make sure that we've got
the top element selected, and we should be able to
see somewhere down here, we now have an explosion
particle effect. From this one, I'm going to
find Pixel explosion player, and then we'll hit compile,
save go into the enemy plane. Same again. Grab
the top element. We're going to find our
explosion particle, and we'll change this one
to the enemy version. So nice and simple,
compile and save again. And now we want to go and
test and see how this is working when things
actually blow each other. So we see there's a little bit of a lag the first
time that comes in. In a fully flashed light game, you're going to want
to run something to get the shaders
to precompile, to make sure that all
of the efects are essentially round at least
on, so we don't get that lag. But now that we've
got past that, we can see we've got the
different effects. And we can just test, as well. When the player dies,
we get the same thing. So it's slightly different if we double check and look at these. I think one is
slightly more blue, so we've got kind of a
blue explosion here, and the other one
has that green tint. So you may want to come in and change the color properties. And you can see as things get
a little bit more complex, we can see the complexity, the number of emitters is going. But once you start getting familiar with the things
you're looking out for, these are relatively
easy to tweak and change and set to
work exactly as you want. That is the programming
side of things. That's really all
we need to do to get some particle
efects playing. So it's a relatively simple step to add some nice polish
into our project. And this is another one of those things where
experimentation and playing around with
things is really going to benefit you more
than anything else. For example, I've taken the particle effects directly from the epic content examples, and I think just for
the general setup that I the explosions look
a little bit too thin, not quite as chunky as
I might have wanted. So what I might be tempted to do on both of these would
be to come in and find the things specifically related to the
scale of the mesh. So these are three
these static measures. I can see here we've
got scale mesh size, and I could just
increase this to something much
bigger by default. I can see here exactly which element that's
taking effect on, so we can see which ones have
just gotten a lot bigger. I've made that four times
larger than it was. Now, again, this
is somewhat time consuming because I need to do that for each of the
different elements. So I'd come into this one, for example, set this
to four, four and four. But we have now immediately
a much more kind of nice chunky explosion for the kind of star
that we're going for. So really simple changes like this can really
make a difference on emitters and all
I've had to do is come in and find the mesh size. So if I wanted to test that on our enemy, I'd just do
the same thing again, so we could grab the mesh scale, set this to four, four and four, so it matches the player. And we're just doing
this for each of the different emitters so that
everything stays somewhat uniform because they're
meant to kind of represent different elements of
the explosion system. So compile and save
that, we can come in and play and see
how this looks. A I think that's much better. I think it just shows a
nice difference between the general projectile
impact compared to the explosion of the ships. So really
small things like that. You could find the
colours as well, especially if you have different colored enemies
to what I've chosen. You could come into
the enemy explosion, find all of the color
properties between all of the different emitters, see which ones are
responsible for which color and then change
them to your liking. Remember, quick tip
is if you're not sure which element is the one
you're looking to change, we can just untick the different emitters here and we can see what's responsible for which
part. So you can go through. This would just be
that kind of after smoke, so we can
turn that one off. The middle one is
for the main color. So if my my, my enemies
are actually green. So maybe it would
make more sense to have a more of a green color, so we can green value, bring this all the way
up. And then we go. We have more of a green
explosion for the enemy night. So we've got a nice
kind of o yellow starting off with a
heated explosion. But then we're showing
that this is actually going back down to
a green overtime, which is the body color
of the enemy ship. So, again, a really,
really quick change. As long as you know roughly
what you're looking for, we can make these changes
quite quickly on the fly. And see that kind of come
together quite nicely. So play around to
these types of things. These are kind of approaches
that will get you understanding the systems
much more quickly. You won't necessarily
know exactly what you're looking for as
you're getting started, but I think, especially
with something like the Niagara system, the
naming conventions, the stuff that you're
looking for, and the immediate feedback you
get in the viewpoint is a really handy way to
learn this very quickly by just some trial error and
tweaking with different values. So that's the first step of improving our
visual feedback. It makes everything feel alive. The enemies are still kind
of popping in from nowhere. Then the next topic,
we want to add those really nice
spawning animations that were provided in
the example project.
25. 24 - AudioEffects: Might have said that
we're going to jump into the animation Festival, but I realize that we're
actually overlooking the missing piece
of our polish here. And this is often something
which gets left far too late in development in
games, and that is audio. We want to start
looking at the use of San cues to randomize a minimal amount of
audio files to make things sound a little bit
more varied and interesting. It's one of those things
as well that if you're prototyping your own
game, it's really, really easy to overlook and underemphasize the
importance of audio. You might feel that your game
is feeling flat or boring. You can add things
like particle effects, camera shake, and all
these other things, and it could just be
that the project is missing some audio that would really help
bring it to life. So we're going to focus
on this festival. So if we go into
our content draw, we're going to go into
the audio folder. I've provided a few
different sound files. We've got one for the explosion, so we'll be making use of these for our planes
when they explode, and we have the gunshot effect, which will play
whenever we're firing. We're probably not going to put this on for the
enemy, but again, you can tweak and play
around the things to see how you like the
project to be set up. I think if there's too many
things firing at once, it can very easily become
an auditory overload. If you wanted to test the audio files and
see how they're sound, we can hop it over
the play button here, and you get immediate
playback in the editor. If we focus on our gunshots, first of all, since
we have two of these, we want to shift
select both of them, right click on either one, and we've got this option here
to create a single queue. So if we choose to
create a multiple que, this will create
file, one que file for each wave file that
we have down here. Instead, we can take
both of these wave files and compress them
into a single queue. So we'll click that
one. Rename this one to SC Underscore gunshot. You can see it's a slightly
different asset type. And basically, SN cues are a very simple way if we
double click to opens. They allow us to work with multiple audio files without needing to edit the source file. So we can keep the
gunshot one and 71 exactly as they were
when they're imported, and we can make overrides
here individually to things like whether
it should loop, the default volume,
and things like that. One thing to note
is that you can see the nrails clever enough. It knows that because
we brought in more than one
different signed file, we probably wanted to randomize the order in which
these are played, which is, in fact,
what we wanted. After that randomizations
been made, though, we want to pull from
here, and we're going to search for something
called a modulator. Or, in fact, you can
see it just down here. We're going to plug
in the result of our modulator into the output. So this will toggle between
which sound to play. It's going to randomize
one of those. Going to throw in
a modulator so we can tweak the audio slightly. And then whatever
that output result is is what will be played
to the end user. With the modulator
still selected. You can see the main thing
that I wanted to override here is the minimum
and the maximum pitch. This is just going
to help ensure that even though we only
have two sound files, they're not going to sound
exactly the same when we fire them potentially hundreds or
however many times in a row. I give both of these
quite a big pitch shift, so we'll set 12.5 and
the other one to 1.5. And again, these might
be quite extreme values, and if the sound
is too distorted, then this is where we can
come back and start just gradually refining it to
sound exactly as we want. The same with the
volume, we can set one to 0.9 and one to 1.1. So we've got a kind
of similar offset to the minimum and maximum volume for both sound effects
every time they play. So that was great. We
could see that that was definitely taking
effect exactly as we wanted with one small caveat. Common gotcha with
the old Q system that we're using here. The randomization isn't
really that random. You can see it's
just simply flip flopping between
the two options. It will play A then B, then A, then B. Quite predictable. So if we grab our random note, one thing we want to do is
we want to untick this here, the randomize
without replacement. Something that people
often overlook. But now if we press play, we don't necessarily
need to listen to them, so I'll just mute
the volume quickly. But just double
check that you can see which wire is firing off. And what we should see is that I had about four or
five clicks there, whereas getting the
same audio playback, which is getting much closer to true randomization rather than that flip flop between A and B. Obviously, you'd want to
do this as well if you had multiple sound waves on top
of this in your single que. So if you had five or six, you wouldn't want it to go one, two, three, four, five, one,
two, three, 45, and so on. You'd want it to pick
randomly between those five or six sound effects. So always remember
to come in and untick randomize
without replacement. With that done, we want to do one similar thing
with the explosions. So I'll right click on
the explosion wave. We're going to create a single
sound queue for this one. We'll call this one SC
underscore Explosion. Obviously we won't need any randomization
between this one. We only have the single
file, but we can still make use of things like throwing in a modulator again in between. So I'm just going to hook up another modulator here
in the same way and maybe provide some slightly less intense differences here. So maybe 0.7 to 1.3. And again, we can
come back and refine if that's too much
of a pitch shift. I'm going to leave the volume minimum and
maximum as they are. And it's just a really
simple tweak that we're going to make inside
of our sound queue. If you're coming from more
of an audio background, there may be other
terminologies and features that you can play around
with in the right hand side, and you can simply just
drag these into the graph, play around to the property, and tweak them as you see fit. For this simple demonstration, ough, I'll keep this
as we have here. So I'm just going to go
through very quickly and begin closing some of the
windows that I don't need, especially the particle effects, so we'll get rid of those. And if we start
in the play base, I'm already here, so I'm going to handle the
death effect first. I'm going to leave the
comment here, as we'll probably need a few more
things a little bit later. So in between our
spawn particle system and the destroy
actor function call, I'm going to pull from
the execution pin, and I'm going to search for
play sound at location. So this is a really nice
feature again inside of Unreal. We already have built in
default spatial audio. So we can provide
the initial point of where we want this
audio to play from. So we can have things a little bit more believable
if a plane is being destroyed to the kind of left upper side of the camera, then we're going to have a sound effect which
should play a little bit more in the left
side of your headset. So we're going to use the
standard function here. We're going to use
get act location to find out where the exploding
plane was destroyed. And then we're going to use the sound drop down
here and we'll pick our SC our sound cue that we've customized
underscore explosion. Now, we don't really need to
promote this because, again, they're both going to use the same explosion
sound effects, and they both have their own randomization
and things like that. Now, I'm not going to use
it here, but I wanted to show you the features that we
do have access to, as well. So if we use the drop
down on this node, if you didn't want to play
around with sound cues or you just wanted to
use the rule wave file, we can still come in
and do things like volume multiplication,
pitch multiplication. We could throw in something
like a random float, again, so we've seen this
plenty of times or ideally a random float in
range, as we've seen before. And that's very similar
to what we've set up in the sound cue for
the pitch multiplier. But because we've got a nice, customizable sound
queue to work with, we'll just use that instead of the rural default
sound wave files. There's a lot more in
depth stuff we could go into with things like
attenuation settings as well. So that's providing the
three D sound system, a kind of a fall off, a range at which the audio
should get louder and quieter, depending on where it's sport. For this, though, I'm
just going to copy the functionality we
have here because we want to add this onto
our fire function. So we will just move
the comment over. We're going to come
back here and do a few more things when we do our final passes in the effects, so this is still relevant. But we're going to grab
this press control in C. I'm going to go into
the fire function. And after the projectile's
been spawned, we can just drop this on here. Same thing again, we
want the location at which the fire sound
effect should be played. And we're going to
grab our gunshot here, and this will automatically play a gunshot now whenever
something fires. It's because I think if there's four or five enemies
on screen and they're all firing it
in quick succession, we may want to
lower their volume. So I think what I'm going
to do is rather than having separate fire functions
in each class, again, we're going to make use of inheritance where we can. I'm going to promote
the volume modifier and I'll promote
this to a variable, and we'll call this one
fire volume offset. So we can default this
to a value of one, which it already was. So we're going to
default to playing the full volume of the
gunshot sound cue. So, of course, if we've set this to randomize a little bit
here with the modulator, the default volume might
be 0.9 towards 1.1. We're then going to take an override on this and take that either multiply that by a total of one or for the
enemies, for example. The reason I want to use this is we'll hit compile and save. I'm going to go into
the enemy class. We'll find that value
here from the properties. I'll give this a default
volume offset of 0.5. So we're going to
have the default volume for enemy shots. So even if they do start
stacking up a little bit, they shouldn't become completely unbearable and like I've said, completely overstimulating
for the audio. Okay, so a really
quick play test there. I found that the gunshots were probably about as loud or maybe louder than
the explosions. So I think what I'm going to do, we're going to set
a minimum volume, and this is just
potentially based on the types of
signs that I found. They were probably
just exported, not with a good
normalized volume. So maybe by default, actually, we're going to take
this to 0.5 to maybe 0.7 or maybe 0.4 to 0.7. And
we'll see how this sounds. Okay, so after a fair
amount of tweaking there, I actually found a
good value was 0.1 and 0.2 for a minimum and a
maximum for the default, and then we're going to
take that even lower in the enemy by adding this
volume offset here. So this isn't the end of
the world. As I said. Assets are perfectly workable, but these are just
useful things to be aware of that you might
get handed or you might find assets from
marketplaces and things like that, and some may have
just been exported with a completely
different volume. So it's useful to know that we've got multiple places that we can apply different volume offsets and
things like that. A project or a full
production game. This is why it's also
really important to have something like
a settings menu with some really flexible audio sliders so that people
can choose to make their sound effects either
quieter or louder than their music and their voice
over and stuff like that. And for the love of everybody's
ears, including mine, please set your
default master volume to somewhere around about 50%. Nobody likes turning the game on and getting their
eardrums destroyed by 100% master volume
when your game is a completely different
volume level to anything else the system
may have been playing. So audio really can be quite
important and it can make a really good or bad
first impression on your game, depending
on how you use it. But again, this is just
something else that you're going to want to tweak
and play around with. Listen to what you think about the gunshots on your system
versus the explosion. See where you might want to
change the volume modifiers, play around with the
different que options that I've shown
you to try and get your game sounding well mixed and as interesting to
listen to as possible, even if we only have a few different audio
files to play with. You can also go to
different websites, HIO, freesound and things
like that to download some new sound effects
and see what you can add into the project to really
start making it your own. The main thing that
you're looking for is just play the game
for a few minutes. Does anything begin
to get annoying after that 32nd to minute mark? And if so, can you
just fix that by reducing the general
volume in the sound queue? Or if needed, as I've said, we might want to even
come in and remove the gunshot from the enemy because it's going to
be happening so often. None of these are clear specific rules that
we have to follow. As with many other
things, it's just going to be a lot of
trial and error to see what works and what best
impacts your end result. With the audio
feedback complete, the game now has
presence, providing visual and auditory feedback. Next, we will actually move
on to the spawn animations, which is just an
optional extra on top of the visual flare that we've already started
adding to our project.
26. 25 - SpawnAnimations: The final polish layer, let's
give our ships a big intro. We'll have both the player
and the enemy ships glide in and spin into position instead of just popping
into existence. Once again, one animation in the base class
that will work for all of the planes that
we want to work with. So for that reason,
we're going to go straight into our
content drawer, blueprints folder,
core, and we'll find the BP underscore
plane base. We want to navigate back
to our main event graph, and next to our begin play, we're going to right click
and we're going to search for a handy feature here
called Add timeline. So it's this option
at the bottom. We can give this a name.
It's kind of a function that has its own built in graph. We'll look at that
in a little bit, but we can give these names. So we'll call this one
anime, as in animate in. As with anything else,
these will only be activated when an
execution reaches them. We're going to pull from
our execution pinhe and we want to plug this
into play from start. So this is going to
be a graph curve that we can animate across, and we just want to make sure
that always starts playing from the zero frame in the
curve all the way to the end. We have other options like to
play from a certain point, stop the animation, play
in reverse or whatever. We just want to play essentially from frame zero to the end. Way this works is kind of
like a temporary update, so very handy if you don't need something to animate constant. This will fire every single
frame for as long as the timeline is playing
before it reaches its end. And then for as long
as that is firing, it will call the
update pin just here. And then once it's
finished the animation, it will call the finished pin. So we know if and when
it's still playing, and we also know when it's reached at the end
of the animation. All of this is going
to be very handy. So if we double click
this yellow node here, and this is one thing to
start noticing is a lot of the nodes that we've been working with have
different colors. These blue ones are the
functions that we're creating, so you always see a function
is denoted by blue. Any custom event or an event in the graph like this is
essentially a function still, but these are denoted by red. We've got things like
the green variables, and we now have a slightly custom time
based function here, our animation timeline,
which are yellow. When we see these quite often, we can double click into these and we'll have a
new feature here. So double click that
node, and we'll be inside of the timeline. There's a few things
we want to do. First of all, we need
a track to work with an animation curve
to begin creating. So if we press the
plus track here, we have a few different options, we can create a float curve, a vector curve, an event track or color
curves, essentially. We just want a really
nice simple zero to one, essentially a normalized
value that we can track. So we're going to create
a float track here. And something which
is really important, we can right click and drag to move around middle
mouse to Zoom innit, like many of the other
navigations inside of Unreal. But one really important
thing before we start adding any keyframes
or pins here, we want to set the
length of our animation. If we start adding pins now, you can see we're going
from a time frame of zero to 5 seconds. So we're going to take this length all the
way down to one, and this just means
we're immediately working with a curve timeline, which is the length that
we want this to be. Now, the reason I do
this, we'll see this a little bit more when
we flesh things out, but we're not thinking
of this as a time. We're not doing this
across 1 second. We can essentially think
of this as a percentage. This would be 0%
of our animation, and this would be 100%
of our animation. Show you handy
little trick that I don't see very often
a little bit later. And this is the way that
we can actually adapt this then to be a five
second animation, a ten second animation, half a second animation,
whatever we wanted it to be. As long as we've normalized this to a range of zero to one, then we can treat this
more as a percentage. Now if we zoom in just
a little bit here, we've now made the curve
a little bit smaller, we can hold left shift and left click and we'll create
these keyframes here. So if you've worked with any
other animation software, these are going to be
quite familiar to you. This is going to be our
time that this happens and the strength or the value
at that point in time. If you've been clicking
around randomly like me, we only actually need
two points here. So I'm going to grab drag
select a few of these points, these keyframes, and
I'll get rid of these. First keyframe, I'm going
to set this to a time of zero and a value of zero. So at this point, exactly none of the animation has happened. And then for the
second keyframe, I'm just going to
drag select this one, and I'll set this to a time
of one and a value of one. So this is our 100% of the animation is
now 100% complete. So you can start seeing how
we're looking at this as a percentage rather
than a specific value. One other thing that we
want to do is we want to make this look nice and
smooth straightaway. If we were to animate along
this track, at the moment, it would just be a
very flat, consistent and somewhat boring animation. So if we drag select
both of these, right click on either of the keyframes and just
set this to Auto, this is going to give us a nice smooth easing
in and easing out. Now you can play around
with these if you want it. You can have it exaggerated
when it first comes in, or you can have it really
slow when it first comes in. And that's one of the
real cool benefits of working with
these keyframes like this is we can play
around with the animation to make
it more stylized, more cartoony, whatever
you're looking for. BdeFelt I'll keep it
like this, though. The final thing is
this track zero or new track zero is something
that we can also tweak. So I'm going to click this. I'll press F two to rename this, and I'll just give this
the name of Alpha. Because when we start
actually animating our planes to fly in, this will essentially be the
Alpha overtime that we're tracking rather than the
specific rotational value. And that's one thing
is that in a lot of examples, working
with timelines, you might see people directly
animating the location or rotation of
actual vector values over a specific amount of time, and hopefully you'll
see by the end of this how that can be quite
inflexible, because, again, if animate something which takes exactly 7 seconds, and you've done that on all of the XY and Z of the location
or rotation values, and then you decide actually
that's rotating too much, and it needs to take
half the amount of time. You'd need to come in,
rechange the track length, move all of the
key frames around, and it can become a little bit
fiddly and time consuming. Where's this? We can
just set this once, and we're now in full control of the animation in our
actual code instead. That's it. That's
pretty much done. So we have our curve ready to provide an Alpha
value over time. So if we hit compile
and then we'll go back into our event
graph up here, all this has done is
just dropped you into a tab inside of the animation
timeline we've created. So we're going to go back
into the event graph. So to actually set our animation first of all, from
our Update pin, remember the one which
is constantly running now until this
animation has finished, we want to call the set actor Location function from here. This is going to be the
result of a lp, again, so we're just going to use the larping function
that we've seen before. So our linear interpolation
between two vectors, not the interp two
or anything fancy, just point A to point
B with an alpha. So you can actually see the
reason that we've called this Alpha is we can
plug these straight and we already have
part of the answer here just based on
naming conventions. So then we just need to
calculate or figure out what point A will be and
what point B will be. To break this down as a concept, point A is simply going to
be the spawning location. So wherever we start from when we spawn
in will be point A. Then we want to get
to a certain number of units ahead of
that spawning point, which will be our point B, something which trips people up quite a lot when they
work with lurping animations and it's
confusing because it looks as though
it's kind of working. But when you think about it, what I'm about to show you would actually be kind of confusing
the animation a little bit. So what some people would do when they're
first working with linear interpolations is we know that point A is our
starting point. So we can right click and we can search Get actor location. This to a lot of people
could make sense that we're going to go from our
start point to an endpoint. And we could say that the
endpoint, for example, would quite simply be
where we are now plus a vector unit in a
certain direction. So if we take where we are at the starting point
and then project, let's say, 300 units ahead, that's where
we want to end up. That would essentially
be our endpoint. Now, this wouldn't work anyway, but just to very quickly put some code in to show that,
we could say something like, we know that X is
forward, so we could get x location plus 300
units forward on the X. That looks as though it
could be our start and end. And I wanted to cover
this and why we won't be using this
precisely because I see this in so many
student projects and new development projects, because as I mentioned, it
does kind of make sense. We have our start point, and we have our endpoint
based on that. But what people aren't
considering when they're doing this is that this isn't
just firing off once. I think that's where people
get a little bit tripped up. I they imagine this just being fired once and this
value staying the same. So if we start at 000
and we add 300 to that, we'd expect to have
30000 as the endpoint. But what's actually happening is every frame this
is being called, and this is being rechecked. So if we start at
zero, zero, zero, and then we move some units,
say we've moved five units. On the next frame, when this gets cooled, we're now at 500. So that means the endpoint is
going to be 30500 instead. So both of these values are
then continuously moving. We're only going to
be animating for that 1 second or however long
it's going to be anyway. So that means we
will get to an end, so it looks though the
animations happening, but it will always and look
just a little bit off, and maybe even a little
bit unpredictable because these two values
are constantly being rechecked and
updated and applying a different lp result at the end of the calculation,
if that makes sense. Was just to introduce why
we're going to need to give ourselves some room, make
some space over here. And what we want to do is
we want to make sure that these values are
stored and unchanging. So it's not a difficult
thing, but I just wanted to highlight that this
happens relatively often, and I can see, I think,
why people would do that. And it's just
something to look out for whenever you go to try and create your own animation or a customization
of this later. Just remember that a loop
needs a constant start and a constant target to
animate smoothly between them. So what we're going to do
instead is we'll take this, so I'm going to cut Control X and paste the get
actor location. We'll promote this
to a variable, and we'll call this
one start location. So nice and clear, hook this up, and then we're going to do a very similar thing over here. And as I've mentioned,
this wouldn't work anyway because we're
just adding a unit. We're not actually
taking into account the direction that
the plane is looking, so we need to do a little
bit extra than this anyway. But even if we did
do the full process, this as it was still
wouldn't work. So we're just going to
move this over, grab this, so cut and paste this over here. We can still make use
of a bunch of this. Now, the first thing
is to remember that the enemies want to
use this as well, and they're going to be getting flipped when they spawn in. So we don't want
them being animated up the screen like the player. We want them to
be animated based on the direction
that they're facing. So what we're going to do is
we're going to search for G actor forward vector. So this will get
the direction that the actor is being rotated
or currently facing. Remember that arrow that we
applied to the projectile, I will essentially be checking which direction that
arrow is facing. We then want to multiply
this by a value. So we're going to take
the forward vector, which is a direction,
not a value, so this will either
be something like 11 and one or 100 if it's facing forward or minus 100 if it's facing
backwards, for example. We're going to multiply
the direction, not the actual vector position, which is why we
can multiply this. We're going to multiply
this by a float, so we're going to right
click on the pin here, turn this into a
float, and we can still take that 300
unit value here. So we want to move 300 units in the forward direction. We
can then make use of this. So we want to take
this value and we'll add this to
another vector. And in fact, we'll
get rid of this one. So these notes won't
be needed anymore, just to keep things
nice and tidy, and we'll tidy these
up a little bit. And what we want to take
is wherever we started, we want our XY and Z
of our start position. Plus 300 units in the
forward direction. So multiplying our forward to
get either a negative or a positive back on whether we're facing forward or
backwards and the X, and we're going to take this
and split some values here. So the first thing to make
this a little bit easier to read is I'm going to promote
this pin to a variable, and we'll name this
one target location. We can alt and left
click to drop this down. We'll split the
structure pin here. And now we've got all of
the nodes that we need. We can tidy this
up a little bit, we actually want our first
pin to be our start location. So again, just
personal preference, I'm going to control and drag the start location in
and replace this one. So we can take where
we started plus an offset in the direction we're looking by X number of units. That's going to move
us 300 units in the X is what we're looking
at here because X is four. So if we could get the
get actor up vector, and if we plugged
this in instead, this would apply an offset on the z axis purely because
Z is up inside of unreal. X is forward, Y is sideways. So we can get different
vector positions with some built in
functions here. We just want to consider
moving forward. And then we can't
use the full value here because we want to keep our Y and Z consistent regardless of where we're kind of rotating or moving here. So I'm going to grab the
start location again, duplicate this down here,
split the structure pin. And we can plug in the Y and
the z to these values here, which is why I split
this a moment ago. And then whatever this value is, so the calculation of
how far we want to move in our local
forward direction, we're going to split this
structure in as well, and this will be our
X value that we want. So remember, this is only
calculating the X anyway, so we can take that X value. That will be how many
units we want to fly towards in the
X or forward value. And that will be our
target location. So we'll plug these in. We might need to tidy
things up and move things around just a little bit,
but that's perfectly fine. We can just move
this down and come back and neaten this
up a little bit later. The main thing is we can now
jump back over to our loop. And again, this is making
things nice and clear now. We're going to give this
our start location and our target location as the
A and the B arguments. So if we go in, we could
probably test this just to make sure that something is definitely happening here. And we can see the
player plane is definitely sliding
forward a little bit there, and so
is the enemy plane. So it's sliding in and then
it's moving side to side. So we can see that
initial animation is working and the
same for the player, which does mean that we probably want to grab
our player start and start it just a
little bit further back. In fact, we
can just come here. Something else you
may not be aware of. And this is quite
handy is we can grab our location because we know this is where we want to end up at this
point in the screen, and we can type in here -300. So if we type in -300, this actually works
as a maths equation, so that gives us -190 -300, and we get exactly minus 490, which will be our start point, and then we'll fly in and we'll end up exactly
where we were previously. So that's really looking a
little bit more interesting. I think that looks pretty cool. So that is definitely
a good start. And you can see,
hopefully how this math is all coming
together and working. And again, we've made
this work by taking in the forward
vector of the actor. So the way that it's been
rotated and just to recap on why that's relevant is because when we spawn
in the enemies, we're rotating them
180 degrees to match the pointing direction
of our spawner class here. So they're moving forward
relative to whether pointing, which means everything is
animating in the correct way. So that's the first
part of the puzzle. The second part will be
making this even more interesting by providing a
rotation offset as well. So after our set actor location, we want to effect just
the visual element of the rotation because
we don't want to mess with the
sphere collider and change potentially the way
that the actors facing. So we're just going
to make this a visual change rather than an
actual position or change. So to do this, we
often get things like, we'll use the static
mesh instead. We'll drag this into the ground, we'll pull from this
here, and we're going to search for set world rotation. So similar thing, but we're now just affecting the mesh instead. And this is much better
because we're not doing any collisions or anything important with the mesh anyway. This is here purely
to do visual things, and this is a visual change. So we'll plug this in so
this gets calledimmediately, and we're going to do
something very similar. We're going to take
our Alpha and we'll do a loop for the rotator. So we'll pull from here
and search for loop again, p rotator, and again, get the same thing, A
and B with an Alpha. This is completely optional.
You could promote the Alpha to a variable just for
use within this function. Something else I
want to introduce, though is that we
could just do this. I've double clicked on the wire. And again, just to keep
things a little bit tidier, I'm just going to plug
the Alphas in this way. Because this is
the only two times that we're going to use it here, I think maybe making
a tract variable just for this one
time fire event, maybe a little bit overkill. So I'm just going to introduce
the use of reroot nodes. This still keeps
this somewhat tidy and easy to kind of
visualize and work with. And then for the A&B, we're going to do a very
similar thing over here again, but we're going to get
the rotation values. So you may want to start tidying up some of
the things here. This is where like
I've mentioned sequences can be really good. So very quick cutaway here. We want to potential start tracking what we're doing
to initialize values, essentially, when we're setting and fracking values
on begin play. That's normally referred
to as initialization. And I think I might
separate that out from our actual
timeline animation. So nice and simple, again, these ref factors
really don't take long. If we pull from
the execution pin here and search for a sequence, that will automatically
hook that up for. So we're not really
changing anything. Again, this is just organizing the order in
which things are handled. I'll just unhook these
nodes just here, and then I'll move
the animation dine. And what we'll do is
we'll make sure that we have all of the variables
ready first of all, and then once we've tracked
all of the variables that we're going to use
on the then one, then we'll actually call
our animation to be played. So again, really all
this is doing is it's just keeping things
a little bit tidier, easier to read and
easier to work with. So that means we can now start moving some of these
out because we want all of these location
based checks first. So that's getting our
start position still. And then after this,
we're going to want to get our start rotation, as well. So from here, we can search for G world rotation on the static mesh, the same
way we've done previously. I'll drag our static mesh we'll
use the G world rotation. And like we did
with the locations, we're just going to take this value and promote
this to a variable. And this is for the
exact same reason. When we're using this
in an animation, we don't want this value
changing over time. However, we begin our rotation when
we're spawned into the world. That is our start point,
and we're going to have a permanent offset against that as our permanent
target point, as well. Now, this we can do a
little bit more easily. We don't need an actual
target for the B. You can promote a variable
to target if you wanted, but this is a little
bit more simple. So for our start rotation, we're just going to drop
start rotation onto A, so that's where we're rotating
from. And then our target. So B, we can just split the structure pin
here because the only thing that we
want to account for is we're doing a 720
degree rolling rotation. At least that's what they
did in the example project. And again, we're just recreating that in a slightly
more tidy way. So what we could do
is we can drag in our start rotation here,
split the structure pin. We know that we want the Y
and the z to stay the same. We don't want to add any
upwards or top down rotation. We're not rotating
around ourself. We know that we're stating
the static mesh is always starting at a rotation of zero because we've
not changed that. And even with the
enemies, we are rotating their entire actor rather than their static
mesh to begin with. So that means that we
can very safely just add a 720 degree rotation to the X, which is rolling around itself. Remember what we're doing
to get it kind of rolling around itself that way. We're doing a very
similar thing here. So if we hit compile and safe, we can test this, and
again, this should work. So Nice, easy. You can see that's
working perfectly fine. So that's what we wanted
there, a 720 degree or two times rotation
around itself. If you wanted it to
just be a single 360, so they rotate a
little bit slower, we can definitely
turn that back down, so it'll just be one roll in, or you could go a
little bit wild, maybe get it to do five
different 360 rotations, and it's going to spin in
quite a few times here. So you can have fun. You can play around with
that exactly as you want. As I said, I think from
the reference project, the one that we're
trying to recreate, I think that was just a 720 degree roll
that they did there. So that's how we can apply that. Nice and simple. We
don't really need a specific target because it's just the start rotation with a 720 degree offset to
get that spinning twice. This also bridges a big thing that we've been missing
for a long time, even though we've had the
code ready in our project. And that is that the player and the enemies shouldn't
actually be moving side to side until they've done their
sporn animation. Again, this is just for parity
with the example project. If you play it from
the content examples, you'll see you're
not actually allowed to control and move the ships until they've
finished this intro animation. So we can actually
make use of that. We already have our
movement enabled. That's always been
getting checked, so we just haven't been
doing anything with it. So we can now grab this
from our base class. We can set this to
default to false, so we won't allow
movement to begin with. So if we come in and press play, we're now completely
stuck, and so is the enemy, which is
kind of what we wanted. And this is where
kind of introducing the animation system
to you was important. Because remember, I said
this is telling us all of the information
whilst the animations still happening is
being called here. And then as soon
as the animations finished, this is being called. So we can now pull
from here or we could just drag in the B
movement enabled. That would give us
a setter just here, and we could set this to be true only when the intra
animation is done. So we can now go in and
we can test, we can play. We're not going to
be able to move until that animation
is finished. So again, completely optional, but just trying to bring this as close parity with the
Example project as possible. It's a feature they implemented, so I thought I'd
cover that here. That's pretty much it, though.
It is technically working. As with anything, I wanted to dive a little bit
deeper with you. This is definitely 100% option, but I find this is a really, really powerful
thing to know about the timelines and a way
that we can manipulate playback speed of our animation without jumping into
the event graph. Now one thing I think
I do want to do, just one final thing to tweak is I'm going
to grab the node here, and I'm going to give
that a little bit more of an exaggerated movement into. It's going to kind
of speed in and then taper off through
that animation. So this is purely visual, but this gives you a nice amount of freedom on how smooth or sharp some of
the animations are. So I don't want it to overshoot. If we had a curve like this, it will actually go
past the target point, which would be
around about here. So the target points here, it would go past
that target point, and then it would animate
back to its target point. So that would look a bit weird. Let's have a quick
look. Gonna better actually if I hit Simulate. You can see that the enemy was way past its target point
and needed to animate back. So just be careful you don't
do anything like that, but I could make it a
little bit sharper, so we come in just a
little bit faster. A little bit hard to see because there's
that loading time, but that's probably
perfectly fine. Now the thing I
wanted to introduce, though, is, as I've mentioned, one thing I see a
lot of developers do is that they might
try this and think, Oh, actually, 1
second is too fast. I want this animation
to take 3 seconds. So you need to come back
up, adjust your length, grab your key frames, move your keyframes across, play
around with the curves again. You can see how this is
going to get quite fiddly, if you have to keep
going back and forth. So we're not going to do that. If you were just
following along then, press control that a few times, and we'll get the curve length back to, so the full
length back to one. What we can do
instead, and this is that trick that I think
is really powerful with timelines is we can
come back up here and maybe just after we're setting
the location and rotation. We can grab our
timeline information is actually made
into a component. So if we drop down this component in the
variable section, we can grab our Anim in timeline details and
drop that in here. So we'll get the
information for Anim in. From here, we can
pull and we can find something called set play rate. So we can see this
is the playrate how quickly this
timeline is playing. And then if we plug that
in here, we now have full control because we know
that we've normalized this, so we've made this 0-1 seconds. If we make the play
rate, let's just say we'll work with simple round
numbers. We'll say two. This is now playing
twice as fast, so it's going to
take half a second, because if it's
playing a default of 1 second, that's its length. If it's playingtwice
as fast, obviously it's going to play
for half as long. So this whole
animation will now be done within half a second. So we basically due to loading, we don't
even see that happen. I'd need to simulate. And yeah, it's almost
impossible to see. But if we'd set this
to something like 0.5, this is now going to
take twice as long, so it's going to take 2 seconds. So we have a nice, long, smooth animation now, 2
seconds for them to come in. So we now have with some
really basic maths, we now have full control
over how long this takes. We can turn this down to
something really, really slow, 0.1 is still doing
the same curve. It's still doing
the same animation, but it's now taking the
extra amount of time based on what you do
to the play rate here. Hopefully, you can see the value in that,
as I've mentioned. It just means that
we never need to go back into our timeline. We never need to tweak the curve again because we've
normalized this to arrange. And if we wanted to increase or decrease the playback time, then we can just grab
this value over here. And I think what I
might do, I think 1 second was probably
a bit too fast, so I might set this to
something like 0.75. We can hit compile and
save, go and double check. I think that looks
quite nice and smooth, so I'm happy with that. But again, you can definitely play around, tweak the values. And that's the
whole goal of these topics as I'm not giving you some predefined
specific result to aim for. I'm trying to show you
all of the different ways that you can expose
and extrapolate some of the values and
variables that we're working with and really
make the project your own. So if you want them to be really snappy and rotate three times, you've got full
control over that with the variables down here. You've got the
playwright. And again, it's probably a
good time to start considering the cleanliness
of your project, as well. We now have a lot of variables. So we may want to start
putting these into categories. We've got some magic numbers, so we could also start promoting things like
this to a variable. We could promote this
one to another variable, call it something like
intro role degrees or something like that. Trying
to make it somewhat clear. It's the intro animation and not specifically the
rotation we want to end up, but the number of
degrees it's rolling. And then over here, we could have the
animation play rate, so we'll promote this
to variable as well. So this is nice because
the main thing, as I keep mentioning
with variables, even if we're not doing anything
specifically with them, this is great because we don't need to dive
into our code. We always want to avoid
needing to come in and dive into hard coded
pieces of our logic. Instead, if we ever wanted
to try something quickly, we could just grab
the plane base now. We can hit compile, make
sure that's been updated. We can grab all of the variables and just find here
that we could tweak the play rate either way
and any of the values that we wanted to quickly
see some changes with. As I mentioned, the same with C plus plus, and it
happens in Blue. You end up with a
lot of variables, so you may want to
start putting things into nice logical categories
or something like that, but that would be
something for you to do in between topics. Another thing for you
in between topics, a small piece of experimentation
here because we've only really just taken a
brief look at animations. I definitely recommend
playing around with things. See if you can get this
to play in reverse after it's already played once. Do something like making
use of custom events. Remember how we can create
our own custom events, how we can call
these from anywhere. We could definitely hook
something up to call reverse. So again, trying to avoid things like when
this is finished, you'll see a lot of code
examples come background and have this horrible kind
of looping logic here. It'd be much nicer to
see something like the custom event you've created, have this one called in code. So if you wanted to play in reverse, something
really simple. I said experiment
with that yourself, but I've just shown you
the actual answers there. But do things like
that. Play around with the different properties and
features provided by these. Maybe get it to stop midway, reverse from a different point, coming in play with
different curves, maybe purposely overshoot or add different keyframes into
the curve animation and see what happens
at different points. Maybe even add some scaling. So you can make it
look really cartoony with them starting off
at a really small scale, bouncing into existence by doing some kind of squash
stretch animation scaling, and then ending up at
their normal scale. Kind of start
having fun and play around with the different
options available to you. But that's the spawn
animations complete. The ships now entering
with style, and next, we're going to get
onto camera shake and our screen effects.
27. 26 - CameraShakeKnockback: Not done yet. There's still more polish that we can
add to this project. We're going to be
looking at knock backck for the collisions
and camera shakes, ensuring that every hit
feels like it matters. We'll look at the knock
back a little bit later. That's a little bit more hands
on than the camera shake. We'll start here. So to start,
we're just going to go to our content drawer and just make sure we've got our
folder structure set up. And inside of the main
Blueprints folder, we're going to create
a new folder here, and we'll just call
this one camera shake. We can add a few different types of camera shake to our project. So we can have some intents
for the explosions, and we can have maybe
some softer ones for the projectiles
firing or just hitting. Again, completely up
to you, depending on how much impact you wanted
to get across to the player. To create a camera shake, we actually need a custom class, which is really nice and
easy. We right click. We can go to our
blueprint class up here. I'm not too sure where
it is in the categories, but quite simply, we can
search for camera shake. You can see, because it's
something that Epic New a very popular thing
to add to many games, they've actually created
some custom classes for us to work with. We want this one here,
the camera shake base. We've got some legacy options and a custom version of this, but we're just
going to work with the rudimentary base version, hit Select, and
we'll call this one BP Underscore Camshak Small. We'll double click to open this, and we actually don't need the event graph in the
background here. So I'm just going to
very quickly close this because a really
simple way to get back into data only view in some Blueprints is to close them, double click
to reopen them. And all we really
need to see are our camera shake
properties just here. We won't be
programming anything, so we can just work with the predefined
properties provided. The main thing we want
to change, we have the root shake
pattern set to none. We're going to drop this
down and search for Perlin. This is providing some
nice randomization using some pre generated
Perlin noise patterns. If you remember these shortcuts I mentioned many topics ago, this is where things
like holding shift and dropping the arrows down
here can be really helpful. So if we click this
again with Shift held, this folds out every
single category. And if we want to fold
all of these back in, we can hold Shift,
click these back up. And again, that just
tucks them away nicely. So because there are so many different properties
we do want to access, I'm going to fold
down everything, so we can go through
these in order and find what so to begin with, we have the location amplitude
and frequency multiplier. I'm going to leave these
at one. This takes the entire animation that's generated and adds
a multiplier to the amplitude and
frequency on the X, the Y, and the z
for the rotation, location, and the field of view. This is handy if we do want to do some really big changes, but generally, we want to
work on a per point basis. So for the small impact that
we're making at the moment, maybe every time a
projectile hits something, we want the camera to kind
of jolt forward a little bit just to show that some
impact has been received. So we know that
forward is the X, so we can take that
and start adding some amplitude to
the X location. If we add a value of 200 here, this is an amplitude, kind of a strength
which is applied. And then the frequency
is how many jolts forward and backwards
this will play. So if we just keep this at
one, it will just be like a nice single jolt every
time a projectile hits. You want to avoid adding too
much frequency in a lot of cases because that's
what will get people feeling a little bit motionsick. It will make it a
little bit harder to read and judge what's
happening on screen. But again, all of this can come down to
personal preference, type of project, and the
general style that you had in. I'm going to leave that
as one for the frequency, and I'm taking that in mind, we can go through a little bit faster for the other axes here. So I want this to
wobble side to side a little bit more, but
with less intensity. So I'm going to set this to
40 and ten for the frequency. Again, ten may be too
much side to side wobble, but by selling this
to something larger, we can come back and refine that later if that is a problem. For the rotation,
I'm going to leave all of these as their default. Quite often, we
don't want to rotate the camera that is almost
more than anything else, that is almost
guaranteed to cause some kind of visual
feedback upset, you'll definitely get some
motion sickness coming from. Unless you have a
really good reason to use it and you know
what you're doing. So I'm going to leave the
rotation completely untouched. Similar for the field of view, you can play around
with these if you want, but I don't want to
tweak those too much. The final things here, the
blending in nighttime, how long these take
to get to their peak and how long it takes to go from the peak back into not animating and
then the duration. So I'm going to make
this last for 3 seconds. It's going to be a
relatively small impact, but it's going to last for
a decent amount of time. So we had compile and save. That's pretty much our
camera shake class setup. Now, with the core
property setup here, whilst we're doing this, we may as well make a
few different options for us to drop and
test things in. So I'm going to take
our camera shake small, press control, indeed,
to duplicate this, and I'll create
one called medium. I'll duplicate that one
and call this one large. So we could have maybe
different types of impacts, especially for things
like the explosions. I'd probably want a slightly different but much bigger impact because it's not happening
too often anyway, and then we can alter or iterate between the small and medium impacts for
different reasons. For medium, I'll come
in here. We'll take the amplitude up to
something like 500, maybe increase the
amplitude on the Y to 90, 15 on the frequency. Again, we can always
come back and we can change these if any of
these are too much. I've just realized
actually thinking back on how I wanted
to set this up. In camera shake small,
I think we want the duration to be
0.3, not 3 seconds. Lasting for 3 seconds. If you imagine me talking now
is about 3 seconds. The screen would
have been wobbling for the length of that sentence, which is a little bit too much. So we're going to
compile that, turn that down to 0.3 because we don't want to overstimulate people visually. So we can
do the same thing here. We'll set this to
0.3. We can maybe make this 0.4 because
there's more shake going on. There might be reason to have it lingering around
for a bit longer. So we'll set this to 0.4.
Same thing for large. We want this one to really
have quite a big kick because there's going
to be feeling as it's quite close to the camera
when something actually explodes those big particle
effects are playing. So this one makes sense it might be a little
bit stronger. So I'm actually
going to change the overall multiplier here to
really give this some kick. So we'll set this up to five, and then the amplitde, I'm going to set this up to 900. The Y amplitude,
put this up to 160. I'm going to leave
the frequency at ten. We don't want this going
side to side too much. I think the time frame again, maybe around about
set this one to 0.5 seconds or just give
them small increments, so they're all
noticeably different when we're doing our
playback and testing. So again, there's
no right or wrong. I may have gone up to
really high values, maybe even too high, but we can always refine
it and bring it back, saying that this is
definitely too high. I said 900, but at up to 9,000. That would be almost impossible to see
what was happening, so we're going to make sure
we tone that back down. Don't say that too high. So we don't have three
intensities to test, and we just need to implement
this into our code. So nice and simple, is a pre
built system ready for us. Going to go back into BP
underscore plane base. I'll keep these open
just in case I do want to come back and tweak
the values at any point. Inside of BP plane Base, one of the obvious
ones to handle first would be the
death function. So we have our handle
Death function. We still have our to do here if you needed
to find it that way. So I've got my todo
comment, and this is the final effect that I knew I wanted to come
back and implement. So we can pull from the
execution pin here. We'll search for
a function called Play World camera shake.
You can see that just here. This is very similar
to our three D signs, so our directional signs and
even our particle system. So everything again,
once you kind of start learning how to do one of the different features
inside of Unreal, you somewhat
understand how to do the other things as well,
if that makes sense. So we've got here, for
example, the epicenter, which is the location the
shake starts playing from. So, nice and simple, we're
going to duplicate this, the get actor location
and plug that in. So the camera shake
should be starting from which Aber ship
has just exploded, and then it will kind
of rip light towards the camera is the general
kind of idea here. We do this based on a radius, so the inner radius is where this is going
to be strongest. So if the camera is right next to the plane that explodes, it's going to get the full force of the camera shake plane. So we'll set this to something
like 2000 units of radius. And then the outer radius
is where anything outside of that zone probably won't be affected by the camera shake, or if it's really
close, it might just get the final sort
of end wobble. And I think we'll set
this to something like 5,000, and again, you can play around
with these if it's happening too much
or too intense, you can change the radius
and kind of play with that. As I've said, for
the explosions, when the actual ships are blowing up, we're going
to drop this down, and we're going to
search for the BP underscore Cam Shake Lidge, so that's going to be our
explosion camera shake. We can hit compile and save, and we can just test this one
because we definitely have the functionality ready to Okay, so that's kind of what I meant where some of these might be
a little bit too intense. So that is going
to be easy to fix. We can I think the
radio should be fine because the camera
just double checking. If the players start is 130 on the z and the camera is
2,400 units on the Z, that means we're just
slightly outside of the inner radius of any
of the explosions anyway. So we should be
getting a somewhat tapered off camera
shake happening. What we can do. I think
maybe I just went straight into the multipler
little bit too high. But like I've mentioned,
that's actually a very common approach to
this type of thing is double, driple or quadruple what you think a reasonable
value might be and then scope it back once
you've kind of tested it. It saves you making loads of really tiny incremental changes which can often
take even longer. So that feels a
little bit better, maybe 1.5, so we can
do a larger override, but keep the general
settings here the same. And I think that's
fine. So I'm not going to spend too long refining this. That is now watchable. I can see and carry
on playing that with too disturbed, so I'm
gonna leave that as that is. So that's our large
camera shake and again, you can refine this
and completely change it to fit your
project as you want. So then we get to the
question of our projectiles. Should we make this
shake every single time a projectile fires or every single
time a projectile hits something or impacts? That would be essentially
constant shaking, which could be
somewhat nauseating. So I think in this
type of project, a better approach
might be just to make it something which
notifies the player that they've been hit because
that way we're taking this visual polish but we're actually implementing
it into gameplay. So one of the most annoying
things for any player of games with combat
involved is being hit but not realizing that
you've taken damage or being able to differentiate it
from the enemy's tan damage. So this kind of solves two different problems
because at the moment, we get a tiny bit of
feedback if we get hit because we get the
spikes on our ship. But it's not super clear
that we're losing damage or losing health until
we've been destroyed. So this might be a nice
kind of fix up to that. This communicates that the
player is taking damage. It doesn't show that the
enemy is taking damage, but they're about
to explode anyway. So it's not quite as
important because they don't have as much
health to go through. And we're still giving that visual feedback
that the particles are impacting the enemy plane. So that's going to
be kind of clear. A nice simple way that
we can do this, then, with that in mind is
we're going to go to our projectile class. We want to do
everything projectile related in the projectile class. If you're not already
here, I just happen to be right next to
my to do comments, so we're going to go to
that to do comments again. And in fact I'm
going to be a little bit lazy like I've
done in the past. I'm going to go to
the plane class. I'm going to grab
all of this code, so the get actor location
and the playworld camera. Press control in S to copy this. Whilst I'm here, I can get
rid of this comment as well. We've now actually done
all of our to do list. We've got all of the
different effects that we're going to implement,
so I'll get rid of that. And then we'll head back on
over to the projectile base, placed in the play
world camera shake, move things around
just a little bit. And the logic is very similar. So we want to do the
same thing again. We want to get the location that the impact just happened, and we're going to
play from there, same radius because we still want the same kind
of camera fall off. But all we're going to
do is we'll maybe check, see if the small impact is
big enough to begin with, and we may want to
change this to medium. And that's another thing.
If you think this isn't impact enough, you
could try changing it. So every single hit
provides a shake, but perhaps on the enemy ships, it's only the small
shake, and on the player ship,
it's a medium shake. Again, completely up to you. We've got different classes now. You can play around with
that and see what works. I think for my project, I'm
going to leave it just to indicate that the players
taking damage, though. Now, there's one thing that we just need to make a
little bit of space for, and I want to again,
show you the kind of way that I've seen people
accidentally break their code. So we know that we've
got a condition here. We only want this to happen if we're hitting
the player ship. So what you might do is you're
throwing a branch here, so we're going to
do a branch check. And we want to check. We already know we're tracking the thing
that we're hitting, so we can take our
overlapped actor. I don't think any of this is kind of new concepts for you. So we'll duplicate this in,
so I'm going to go and get my overlap actor, making
sure I'm holding that one. Press control,
indeed, to duplicate. And we want to check if the
thing that we're overlapping, we know it's not the owner, we're already past that problem. But if it's equal to, and we've
seen this before already, we can use the get PlayerPawn. So if it's equal
to the thing which is currently tracked
as the PlayerPawn, only if that's true, do we want play the
World camera shake? So we just tide did
this up a little bit. And you've probably
seen this isn't exactly the biggest
problem or issue to solve. Some of you might
have already seen it, but if that's not true, if that's false, if
it's something else, then we're blocking off
this function call here. So, remember this is
something just to get familiar with is
the order of operation, the flow control of your code. In written C plus plus, is much easier because
we'll be using FL statements that we can just drop out of inside of
the function body. So it's much easier to kind
of keep track of that. In Blueprints, it's very easy to accident stop the rest of the functionality happening when a single branch
check has been made. So I just wanted to
mention, with this, if you're following this flow where you're just additively updating and improving your
code or adding features, it's very easy to accidentally
whenever the enemy gets hit now, we wouldn't
destroy ourselves. So something that we could
do instead is, again, we could solve all of this
with the use of a sequence. So if I just move this on over, and this is really just
to try hammer home the importance of thinking
about code cleanliness, not just to waste time and make the code look
pretty in Blueprints, but also because it's literally making it easier to read and maintain and make sure
that you're not making these really easy
to make mistakes. So if we throw our sequence
in here, and again, we want to kind of
think of this as do a specific line
of functionality, which in our case,
is applying damage, and I might be tempted to do is everything which isn't
then related to damage, everything which is related
to playing an effect. We're going to throw
into the then one. So I'm just pressing Control X. And we're going
to hook these up. So we're going to
apply damage first. And then if damage is applied, we're going to play
our particle effect, and we're going to
play our camera shake, but only for the player. And then we're going
to do the final thing. So again, it gives us a
nice order of operation. We can see the exact order in which our functions
are going to be called. And like I've
mentioned previously, when we looked at sequences, what would make
this a little bit clearer is if you
gave these comments. So this could be the damage row. This could be the effects row, and this could be the clear up row or
something like that. And you could give yourself a reminder of that in comments. So again, just a really
quick, simple change, and we now know that regardless of whether this gets
fired off or not, we're still definitely
going to always destroy our actor because
once this has been checked, if this gets called,
it's going to then go back up to the next part of
the sequence and come back. If this doesn't get called,
it's still going to come back to the next part of the sequence and then
come back down. So the important thing is that this gets
called regardless. So now we can go in and
test, and just double check. It's not happening
with the enemies. So that was the explosion.
So, the other thing is that if the projectiles
were hitting each other, it could have potentially caused the camera shake when the projectiles hit
each other as well. So now I'm just
going to let myself get hit with nothing
else happening. There we go. So you can kind of see if I'm not dying
at the same time. You can kind of see maybe a little bit too
difficult to get hit, but you can see a camera shake, which is the important thing. Like I've mentioned,
that may be too subtle. If that is, that's
perfectly fine because we have our medium shake
that we can test. If we really wanted
to make it clear, that the players getting
hit. There we go. So that's giving a much kind of bigger indication of being hit. Again, personally, I think that the small one
is perfectly fine. We might want to add maybe a little bit more
strength to it, but keep the frequency
the same or something, but just as a heads
up that they've lost health, I think
that's clear enough. The next step will be going into things like materials and making a flashing health
damage material or something if you really
wanted to visualize that. But I think this is
already a big step up to what we had before because it's just making things a little bit more visual and a
little bit clearer. Thing is we're going
to quickly add a simple knockbck when the planes collide
with each other. For this one, we want to
handle this purely in the player class because that is the only one which is kind
of doing a pseudophysics. So I'm going to close
all of the other tabs. I think we're done with
the camera shakes, and we'll go into the BP
underscore plane player. And inside of here,
what we want to do is we're going to
grab that sphere. We're going to go
down to the collision section in the details panel, and we're going to
bind the on component hit function here, like
we've done in the past. And I'm going to keep this
really nice and simple. And again, I think
this is the same as the example project anyway, where we can bounce off of
the enemies and the walls. So whenever we hit something, we don't need to
check what it is. We're just going to take
our current movement speed, and we're going to
multiply that by a negative value with
a little bit extra. Makes the game feel
kind of fun and bouncy. And more importantly, again, just giving the player that
feedback that you're actually touching another object in the world that has
some permanence to it. So we're going to get the
current speed, as I mentioned. We're going to multiply this by a negative plus a bit of a value because we don't want
to just invert it. We want to add a little bit
of a forceful knock back. So let's say -1.5. And whatever this
is, we're then going to set the current
speed back to that. This is obviously
being accounted for in the larping anyway. So the larping interpolation, which is happening with our general movement function
is going to try to take our new movement speed
and then smooth that back into the kind of
the normal speed it should be going and in the
direction that we're pressing. So this isn't going to feel
too unpleasant, I think. One other thing that
we could do here, whilst we've been doing a lot of these camera shakes
anyway, thinking about it. And again, completely optional, but just something
I'm going to try. I'm going to go into
the projectile base. I'm going to copy the code
here, the world camera shake, paste this in here, and
maybe we will make use of the medium camera shake,
so we'll drop this down. Same radius, same Apicenter, and we can just come
in and press play. No, that's definitely
that's probably too heavy, I was just thinking we
could have it shake a little bit when
the bounce happens. We'll try and do this with the enemies. There's too
much going on there. So, again, trial and error, that may have worked
I may have looked quite cool, but it didn't. Maybe we can just make
it a small shake. The main thing we want
to make sure that we bounce off of the enemies, as well. So we'll
just double check. So nicely bouncing off the wall. Bouncing off the enemy.
I think the enemies are taking too much
damage from our shake. They should I know it was one hit, wasn't
it, so that's fine. It's the player that
dies after two hits. So yeah, perfectly
fine with the hell. And then if you're wondering why this doesn't affect
the projectiles are currently we can still be hit by projectiles, but
we're not bouncing. We're only bouncing
off of the walls and the enemies, which I
think looks pretty cool. The reason for that is going
back to our collision setup. So hopefully this will help the whole collision structure
make more sense, as well. So remember that our sphere
on the player is set to hit. So it's only checking
for physical collisions with other physical objects. So this is doing a
hit on hit check here inside of our
player and our enemies. The projectile, although it
looks relatively similar, kind of the same this is doing a different type
of check remember. This is set as an
overlap collider. So this is why it's
useful to think about how you're going to set up
your different colliders. It means that you can
completely obuscate different types of collision setup to react and respond to their different
counterparts in unique ways. So that means that
we don't need to worry about the projectiles, providing any knockback or kind of physical pushing
to other objects. They're literally
just flying through space. They're not
doing any physics. They're not physically
colliding with things. They're just checking if anything else is
in the same space. And if something else does
end up in the same space, all it's doing is it's
trying to apply damage, trying to play some effects
and then destroying itself. So that's why we can
get away with that. And again, of course,
the level bounds are set up with the
same collision, so these are set to block
the enemies and the player. So that's it. The
project is polished. I think that now feels a lot more interesting
to interact with. It provides a little bit more of a lively and interesting
experience to interact with. Again, a lot of this will
come down to refinement. I'm not expecting you
to leave the project, as I've given you with
these steps so far. If you're not liking the
camera shake at all, or you think you
can do it a little bit more, that's perfectly fine. Add, remove, change all of those properties
as you see fit. And that's just
something again for you to test and experiment with in between topics and really get your hands on
with the project. And create new camera shake
classes if you can see a use case for
something which shakes with more frequency
or less frequency, but with more
amplitude, for example. The main thing is that
every impact should feel appropriate to the feedback that the game is trying to emphasize. And that's what's going to take your projects from feeling like a very amateur beginner
type of game to something which has a
little bit more thought, care and attention
applied to all of the different elements that the player will be
interacting with. Other things to try
is that you could try the wave instead
of the pearl in noise as the camera shakes and any of the other topics which
I've kind of mentioned, but we won't have time
to go fully in depth with every single caveat of all of the
different processes. With that done, though, we've
got our shake, knock back. The weight and impact of the field is getting
quite real now, which leaves the wrap up of creating a UI to handle
the full game loop. At the moment, the players
just kind of stuck in an endless void if
they're destroyed, and we obviously don't
want that to be the case.
28. 27 - UI: Okay, so we are on
the final topic. This is going to be
the game over screen, the user interface, providing a restart system
and a proper exit. Ideally, we'd have
some buttons to press. But we're going to keep
this UI nice and simple. You finish. You can press the I button to restart and then you get
straight back into play. In a larger project,
you may want to add a menu screen or
something like that. But with this, you'll have a
complete and playable game. The first thing that
we want to look at is the concept of widgets. So the UnreLEngine handles
menu systems and UI overlays button presses
and things like that through something
called a widget class. So inside of our
folder structure, I'm going to create
a new folder, and I'll call this
one widgets or UI. You'll often see this
called one of those two when you're looking through
other people's projects. Inside of my Widgets folder, we want to right click in here. We're going to go
to User Interface. We can see here we
have the option for a Widget blueprint. So there's still
blueprint classes, but this just allows
us to put a kind of visual interface with
some code linked to it. We'll select this
option here and create a standard user widget. We'll just and then hit Select, and we'll give this
one the name of WBP for Widget Blueprint
underscore Game Over. We'll double click to open this, and you can see that the
layout is slightly different. The first thing is to note
on the right hand side, top right hand side, we
have two different tabs. We have the designer tab,
which is where we are now. This provides a
visual layout and a really nice canvas
system to drag and drop different elements to set up the way that this
will show on screen. We then have the graph, which is our standard blueprint editor, very similar to what you've seen before with just
some slight nuances. While still in the
designer element here, we're going to go to
the left hand side, and we want something
called an overlay from these different categories you can see there are a lot
of different categories, so I'm just going to
search for overlay. And this is just essentially
a panel for us to place all of the other
important things into. So we'll drop this
in, and you can see here this is our
overlay in the screen. We just want to make sure that
this is a 19 by 20 panel. So we can drag this,
and we'll put this. I just need to give myself
a little bit more space. We can drop this and
we can see it kind of snaps with clicking
onto the bounds here, and this will snap
into place here. So we have a default high
rise 19 by 20 panel. Now, these will be
set to scale and change based on the screen
that they're being placed on. But we'll just default, assuming we're on a 19 by 20 display, and we can work on that premise. Next, we can add some nice kind of visual
to this, as well. So we can search for
something called a blur panel or a background blur, and
we'll just drop this on. We can see that the
hierarchy here, the overlay is where everything will kind of be dropped onto, and then the background blur
is a child of the overlay. We can see if we zoom in the background blur is
a tiny bit small. We'll find this ever so
slightly difficult to see if we were to press
Play and have this shown. So what we want to do is
on the right hand side, we can align this
to our overlay. So if we tell it to
take up, fill all of the space horizontally
and fill all of the space vertically you can
see that is now matching the size of the parent component,
which is the overlay. For the blur strength,
I'm just going to grab this value here
in the details panel, and we'll give this a
blur strength of 15, and we can see that's added
a slight blur to our panel, which I think is going
to look quite nice, kind of modern and just give it a little bit of visual flare. Next, we want to
look at how we could format the text that we're going to want
to show on screen. So we're going to show
something like this, just a simple bit of text
saying game over, and then a tool tip
for how to restart. A nice easy way to manage
multiple elements in a single area on a menu
would be using something like either a horizontal
buy if you wanted things to span horizontally across your
widget or a vertical box. In this case, we're
going to go vertical. So if we search for
a vertical box, we can drop this on and
just make sure that this is a child of the overlay and
not the background blur. For the alignment
of this one, you can see this is taking
up the entire box. Instead, we're going to
set this to center line for the horizontal and the
vertical settings just here. That's perfectly
fine. We can make this scale to the elements inside a bit a little bit later. The elements, as I mentioned, are going to be very,
very simple for this. We're just going to give
this a text element, so we're going to
search for a text, and we'll drop our first text element into the vertical box. And you can see that's
reallyal popped out the vertical box to
fit the element here. For the initial
text, I'm going to set this to say GameOver. So with the text selected, we want to find our
text option here. You can see it's currently
saying text block, and I'm just going to
change this to game over. Change our font size. So at the moment, if we drop down our font
properties just here, the size is 24,
and I think a size of around about 50 would
be ideal for this one. We can change the typeface. Roboto bold often looks
a little bit unpleasant. We can just change it to regular or even light can
look pretty nice. Just looks a little bit cleaner. That is now looking
not quite a impactful. Maybe we want to change
the font size to something like 60 to make it look and sound out a
little bit better. Now with this text
selected, we can press. So we're going to grab
this over here in the left hand side or
duplicate our text. So control, indeed,
to duplicate. And you can see
that because it's inside of this vertical box, it's now making sure that
this is aligned vertically. And if we kept adding
text elements, we'll have this nice alignment
in a vertical order. So this just makes
it a little bit easier to manage and work with multiple elements when
you want them to be roughly in line with each
other in the same place. Now for this one, I'm just going to grab the new text element, or change the text to
say press R to restart. Probably not the most
interesting game over screen, but it
gets the point across. We're going to change the
size of this a little bit. It doesn't need to be quite
impactful as the game over. So we'll give this
a font size of 30. We could also give these
a little bit of padding. So if I grab the GameOver, first of all, in the
details panel on the right, we can give this a default
heading of let's say ten, so it gets a little bit
of a boider around it. And we can do the same thing
with the press R to restart. We'll give this a little
bit of a boider as well. And the main thing is we want
this to be center aligned. So we can grab the
horizontal alignment and make sure that both of
these are center aligned. So they're kind of placed in the middle of their
vertical box, nice and tidy, and we didn't need to do a whole lot of work. One other thing you might
want to do is change the visual colors a little bit. So we could grab the game over. Game oververs are
normally represented with a red or an orange or
something like that, so we could change
the color here to be a little bit more
of a negative color, make that just stand out
against the input here. It looks a little
bit more interesting because we have some
more stuff going on. Some more polished passes that you might want
to make to this, you can bring your
own fonts in really easily to unreil
and then you can just change the font
that you want to use to make it look a
little bit more custom. Play around with things like
the boldness, the font size. You can even add
things like outlines if you wanted to
emphasize certain parts. So if I just zoom
in a little bit. I could add an
outline of two pixels around and we get an outline to our menu elements just here. So whatever you wanted
to do to try make this look a little bit more
interesting, as I said, we're going to
keep this nice and simple just to show
that the game is over and give the player a prompt on what
they can do next. So we're going to
dive very briefly back into the input system because we now need to
have something fire when this user interface
is shown on screen. So if we go back into
our input system, just recap what we have. We already have our restart key. Now, as I mentioned, in a
full game where you've got more of a complex menu
system and things like that. You may have different input
mapping context classes. In this case, we've just bound everything to a single gameplay, and I think that's kind of
fine because we don't have any overlapping uses so if you wanted to
use, for example, the back button on a gamepad, so B on the gamepad, to be an input like jump, but you also wanted it to
be the restart button, that would be where you might
want to share that out. So if you were in a menu, then B would be mapped to restarting. So you'd have your
menu mapping context. And then when you're in
gameplay and it was jump, then you'd have your
gameplay mapping context. In this case, I think just using this one would
be perfectly fine. So we're going to go
back into our GameOver, and we just want to make that binding that we've
done previously. So this is where the code
side of our menu comes in, and this is where some of the
really cool flexibility of the enhanced input system makes things like this much
easier than it used to be. It was definitely possible
with the legacy system, but this is
definitely a step up. We'll navigate to
the graph over here, and these are fairly similar
to what we've seen before. So we have an event
tick, and in this case, we won't be doing
anything on tick in our menu, so we can
get rid of this. So the event construct is essentially the same
as the begin play. This will happen as soon
as the game starts, and the menu is spawned
into existence. So when we show the
menu on screen, this will be five once. Pre construct, you need to be
a little bit careful with. This is useful for
editor changes. So we could code
things so that we could get live editor updates, but you need to be careful
here because if you're referencing other classes or doing things outside
of the menu, this can cause issue if
you're not yet in play mode. So to avoid confusion, we're going to get
rid of this one, and we'll just think of this
as our so on Begin play, what we want to do or
on event construct is we're going to get to the
player controller again, and we want to do exactly
what we've done previously. We want to find the enhanced input local player subsystem. This one here with the pinkish purple function icon just there. And again, same as
we've done before, we're going to add
the mapping context. So multiple classes can have
the same mapping context, so we're going to have
the mapping context now in our player class, which is handling the input
for moving around and firing. We can add that same mapping
context to our menu. So when this is spawned in, it will also be tracking the
relevant button presses. Now, in this case, this is fine because the relevant
button press is only going to be IA
underscore restart. So we can right click
in here. We'll search for IA underscore restart. We want the event again,
so when something happens, we get the execution pin file
of when this is pressed. We quite simply want to reload the level that we're currently and we're just
restarting the game. We only have one level, so
this is nice and simple. There's a function that
we can use to do this, a built in feature in unreal. We can use the get
current level name. So this function just here,
hook this up to started. So again, when this
is pressed once, this will return the name of the level that
we're currently in, which remember is
just called main. So we could do this hard
coded and just type in the level main for the level
that we're about to reload. But just in case you
ever start adding more levels or change
the name, this is just, again, a nice, dynamic and reusable way
to implement this. So then once we have this,
we're going to pull from here, and we're going to search
for the function load level, and we're going to search
for the function open. Can see the option here,
open level by name. Now we get two slightly
different variables, so we have current level
name returned as a string, and we have level
to load returned expected to be fed in
as a name variable. Perfectly fine. Again, we
can just plug these in. They're both a type
of text, essentially, and unreal will make
that conversion for us. So this will take the current name of the
level we're in main, and it will open the
level by the same name. So we're just
restarting our right, the other problem I've
just realized here is that we don't have any
way to leave the game. Something that I'm
really trying to help with here is not to have this feel like a complete beginner game by the end of this process. One thing I find
when testing and demoing games by
new developers is that you're often stuck
in their game and you have to alt F to
close the panel. So whilst we're
looking at the input, we may as well get
rid of this problem, as well What I'm
going to do is I'm going to duplicate IA
underscore restart. I'm going to rename
this one to quit. Inside of the IMC
underscore main, we just want to
add a new mapping. So we know that restart is
a boolean, a binary input. So we already have quit set as a digital Boolean, as
well, which is perfect. So in IMC Underscore main, we're going to add
a new mapping. We'll drop this down and
select the quit button. So for testing in the editor, this is a little bit
complicated because by default, escape is mapped to
end playback anyway. So if we're playing and testing, we wouldn't be able to test. Without disabling our menu
system or our quick button. So I'm going to give this.
If we were to package this, though, and give it
to somebody else. I do want the escape key
to just close the project. There's going to be no menus, there's going to be no pop ups, just a quick way to
close the project, so at least I'm not stuck once I've built
the project out. For editor testing, what I quite often do is I'll give
myself another binding, and I'll set this to
something really random that I otherwise wouldn't be
pressing inside of my gameplay. So I might set this
to be the delete key. And this means that
what I can do is I can go back into my player class. So I'm going to go into
the core the plane player. And again, we're
getting away a little bit from ideal ways
to set this up now. In a real project, you
would probably do this inside of a player controller
or different menu systems, but we're not going quite that deep to make full menu systems. So we're just going to drop this into our input system here. So we have our input
graph. We're still keeping things nice
and tidy, at least. This is still inside of
our IMC underscore main. So that means that all of our new bindings are
still being tracked. And what we can do is we can
find the IA underscore quit. And when this button is pressed, so we want the event again. And when this button is pressed, so started, we just want to call the built in
function, which is quit. So quick game. And
this will just automatically close the
instance of the game for you. So if we hit compile and save, we can test that straightaway. Remember that in play mode, if I press Escape, it would
have closed anyway. So I'm going to press
the delete key, and it's just ended playback. So that will work for
our packaged version, as well as our
editor version here. So we don't need to do
anything else here. And I just wanted to introduce you to the quick functionality, because like I've mentioned, it's really easy to overlook, but it's super, super
simple to implement. And it's just one of
those things that takes your initial kind of feelings
and grading of a game that you're playing
just takes it up a step and makes
you feel like you're playing a game made by someone with a little bit
more experience. Final thing is we want
this user interface to show when the player dies. We know exactly when that
happens because we have our handle death function
inside of the player class. So with this, we're
going to find our handle death function. We want to go to override, and we want to
find Dyna handled. We want to make sure
that we're still calling all of that important
parent stuff, so our effect and everything
like that is being handled. But just before that
happens, or maybe yes, just before that
happens because we're calling Destroy in
the parent version, we want to spawn in
our user interface. And we can do this
really simply, so we're going to pull
from here and we'll find a function called Create Widget. So this is a function to create one of our blueprint widgets. We can plug this in
here, and we want to find our WBP underscore game. Now again I'm going
to make a little bit of a point of this
just because you'd be surprised how
many people forget the next important step. That kind of feels as
though this should work. But if I quickly go in and allow my plane to be destroyed, we're calling that function, we've given it the
certain widget, but we're not seeing
that widget on screen. So again, very common thing. Even though it feels like that's everything
we need to do. There's actually one extra step. We want to pull from
this return pin. So this is created
in the background. There is a game over widget just kind of lingering
around in memory. We want to take that
reference and memory and we want to say
add to viewpoint. A special function called
Need to Make here, and then we're just going
to hook all of these up. And now, this will
actually work. So if we come in and
press play again, just get my planes to be
destroyed, and there we go. So this created that menu, but it's also added
it to the viewpoint. If I press R, we've got
that binding in the menu, so we now have a
restart, as well. So we can come in and
restart the game really, really easily now. We're going to end up
with the same thing. And if I press Delete,
something I overlooked there. I was going to say, if I
press Delete, I can quit. I can't because the
players been destroyed. And remember, our binding
for quit is in the player. So something we might want to do a little bit of duplication. We're going to take
our IA underscore quit and the quit
game function here. We're not going to
remove it here. We want it in both places.
We're going to go back into the game over just going
to paste this down here. So when the player
has been destroyed, they no longer exist. We obviously cannot
quit the game. So we're going to move
that functionality over to the GameOver
widget instead. So perfectly fine to do. Just make sure that
the player still dies. And then now if I press
delete, we can close the game. So same functionality,
we just always need to account for if something's completely
removed from game, completely removed from memory, we need to make sure that all
of the functions that were relevant are still
available somewhere else. And again, if you're finding, like when I was looking at that, I feel as though we had quite a lot of space on
the screen there. So I think what I might do is just a few very small tweaks is maybe make this really big
double the size of our game. And then double the size
of our tool tip here. So it's nothing major, but
it's just going to again, just getting a general
kind of fill and making everything look
as good as possible, trying to present the
game in the best way. So yeah, probably a much better kind of
general style there. Press to restart, everything's working. So pretty perfect. I think for a beginner project, a very simple kind of remake
of something that exists, but trying to
expand and improve, especially the code
base where possible, but also the general feedback and fill and visuals
will hopefully be a nice kind of learning
point and step in the right direction for
your future projects. But that's pretty
much a wrap on this. So a few more things for you to kind of take away in
those learning points. You can play, shoot
enemies, get hit, die, game over screen
appears, and you can do the whole thing again.
Everything looks quite nice. We've got a kind
of semi blurred, modern looking, simple
user interface happening. You can press escape or delete and close the
game from anywhere. So again, we're not
making this feel like a complete newbie project where you're stuck and
you have to alter F four out of someone's
weekend project. So hopefully that's
a nice improvement on what you might
have seen as well. So we have a full
complete game loop. We can play, die,
restart, and quit. So over the space of
all of this time, we've gone from an empty
project to a complete game, movement with frame wrate
independence, projectiles, dynamic materials,
enemies with inheritance, and a brief look at components. We've got spawners
with randomization, ensuring that they've been
made in a generic way, but generic in a good
way that we can create the class once and use them
for multiple properties. Looked at particles, audio, camera shake, knock
backack, and now UI. So this is, hopefully,
a good foundation. You can build upon this. As I've mentioned,
it would be great if you add some unique enemies, try and give them different
movement patterns, different weapon patterns,
maybe speed them up and just make them kamikaze enemies
with known projectiles. Whatever you're feeling
for your extra steps to make this project unique. Bring in some new assets and share your progress and
updates with other people. Could add in things
like scoring as well, which would be really easy to do through the shared
handle death function. We know exactly when
the enemies are dying, so you could build
upon that and create some kind of scoring system. So really make this
project your own. Take references
from other games, look at the content examples, and I definitely recommend
that at this point is actually look at
the content examples. Take a look at the blueprint input map and see exactly where this project was inspired
from and compare the code that you have against
the code in that project. And again, this isn't
to pass judgment, but it's just to see
that there are always different ways that you can
approach a single problem. And see what your
take is on what we have versus the
original project. But the important thing
is that you've now built a game, and
that isn't nothing. That is an achievement, and even more so if you've
been taking those extra steps, and you really feel as
though you're beginning to understand at least a little bit of what you can do
inside of Unreal. Some of this will take a
little bit of repetition. You may need to go back
and try things again, but being able to tweak and implement these
features by yourself is really the big
first learning hurdle that I think a lot of
people get stuck on. So thank you for following
along seriously. If you've got this far,
I truly appreciate you dedicating your time to
something I've helped create. But I go and continue making things. That's
the whole point here.