Transcripts
1. Course Introduction: Do you want to
learn how to create amazing multi platform
video games for free? I'm Brandon and welcome to the
Game Dev Kickstart course, where together we'll create a
fully functional video game from start to finish
using LibGDX and Java. We'll begin with getting
our development environment up and running by
installing Java and IntelliJ idea and setting
up LibGDX a cross platform, free and open source game
development framework. After learning the
basics of LibGDX, we'll get straight to
creating our main project, a two D fighting game called
Stick Figure Showdown. First, we'll learn how to
draw images on the screen, as well as how to
use viewports and cameras to get our game
looking the way we want it. After that, we'll start drawing and animating the fighters, allowing the player
to press keys on the keyboard to make their
fighter move and attack. We'll then learn how
to draw a hud or heads up display on
the screen to show things like the health
bars of the fighters and we'll make it so a fight
can have up to three rounds. Next, we'll learn how
to pause the game, as well as add buttons to the screen that the
player can click. We'll then liven up our game by adding some music and sounds, programming the
opponent fighters AI, allowing the player to change
the difficulty of the game, and showing some blood when
the fighters hit each other. Finally, we'll add a few
more screens to the game, including a main menu screen where the player can
change their fighter and a setting screen where the player can change various
settings of the game, which will be saved and loaded the next time the
player plays the game. In a bonus section
of the course, we learn how to add Android
functionality to our game. This will involve
learning how to set up the Android emulator on our computer and
adding buttons to the screen that the player can use to control their fighter. This is going to be a very
comprehensive course, and by the time you finish it, you will know everything
you need to know to create amazing two D video
games using LibGDX. So without further ado,
let's get started.
2. Install the JDK (Java Development Kit): The JDK or Java
Development Kit includes a set of toys and libraries
for creating Java programs, and we'll need it for
all of our projects. If you've used Java on
your computer before, you might already have
the JDK installed. To find out you can open a command prompt, which
if you're on Windows, you can do by clicking
inside the search box at the left of the task bar
here and typing command. Then opening the
Command Prompt app that should pop up at the top. And here we can
type Java Version. If it says that the Java
command isn't recognized, you likely don't have
the JDK installed. On the other hand, if it
gives you a version number, you do have the JDK installed. However, it's important to know that as of the time of
recording this video, LibGDx only supports JDK
versions 1.1 through 1.8. So if you have a version
of the JDK that is earlier than 1.1
or later than 1.8, you will have to download
a different version. But don't worry. You can have multiple
versions of the JDK installed at once. Okay, to download a compatible
version of the JDK, if we do a search
for Download JDK, one of the top results
should be one from orcle.com entitled something
Like Java Downloads. If we click this,
that brings us to a page where we can download
a version of the JDK. Currently, it says versions
19 and 17 are available. And by the way,
sometimes they put a dot between the numbers
and sometimes they don't. So these are the same as
versions 1.9 and 1.7. All right, and as I
mentioned earlier, LibGDx is only compatible with
versions 1.1 through 1.8. So I'll go with Version
1.7 by choosing it here. And actually, if we go to
the Java archive tab here, we can also choose other
versions like 1.8 or 1.6. But anyway, I'll stick
with 1.7 for now. Okay, with Java 17 chosen, we can now choose our
operating system. I'm using Windows, so I'll
click the Windows tab. We can then choose which type of file we want to download
for the installer. I'll go to the X
64 installer here, which is an EXC file. When I click this link,
the download will begin. After the installer has finished downloading, let's go
ahead and open it up. All right, we can
click Next here. Then we can choose where we
want to install the JDK. You'll probably want to
leave yours on the default, but I like to install
things in the D drive, so I'll change the location
for mine. All right? Now if we click next, the
installation will begin. Once it's finished installing,
we can close this out. Now if we open up
the command prompt again and try the
Java version command, if it didn't work previously, it should now show
us the version of the JDK we just installed. And now we should
be good to go with the JDK. See you
in the next video.
3. Install IntelliJ IDEA: In this course, we're
going to be using the Intellij Idea IDE for writing the code and
running our projects. IntelliJ IDEA is an integrated development
environment or IDE, which is a software application
that is designed to help computer programmers
write code more efficiently. An IDE typically consists
of a source code editor, a compiler or interpreter,
and a debugger. The source code editor
usually provides features that help with writing code more accurately and efficiently, such as by displaying
different parts of the code in different
colors for easier reading, auto completing code so that the developer
doesn't have to type out common code over and over and highlighting errors to make it much easier to fix
mistakes as they occur. The compiler or interpreter
converts the code into executable instructions that can be run on a computer or device, and the debugger allows
the developer to go through the code step by step and find and fix any errors. In tele J idea
developed by JetBrains, is the most popular IDE for the Java and
CotlanPgramming languages and is widely known
for its ease of use. There are other popular IDEs, such as Eclipse and
Android Studio, which can be used to develop
the projects in this course. However, unless you are already very familiar with one of these, I highly recommend using
a tela J when following along with the course in order to avoid running
into any issues. To install IntelliJ IDEA
in our Internet browser, we need to head over
tojrains.com slashA. On this page, we can learn
much more about IntelliJ. If we click the download
button here at the top, it will take us to a page where
we can download IntellaJ. At the top here, we can
choose a download for either Windows, MacOS or Linux. I'm using a Windows PC, so I'll download the
Windows version. At the time of
recording this video, the current version
of IntelliJ is 2022 0.3 0.2 released in
January of 2023. I recommend using
this version or a later version
throughout the course in order to avoid running into any bugs that might have existed in the
previous versions. Okay? And over here, we
can see that there are actually two different
IntelliJ editions, Ultimate and community edition. Ultimate is paid software, and the community edition is completely free
and open source. We can see the differences
between the editions here. For this course, we'll only
be needing the features of the community edition, so
that's what I'll choose. Okay? And for each edition, we can also choose what type of file we want to download
for the installer. I'll just go with the normal EXC version and click Download here, which brings us to this page, and the download
starts automatically. All right, after
the setup file has finished downloading, let's
go ahead and open it up. And here we can click Next. Then we can choose an install
location for Antilla J. Feel free to use
whatever you want, but I'll change mind
to use the Drive. Now we can click Next. And here we have various
installation options. First, we can choose
whether we want to create a desktop shortcut
for the application. I'll check mine. Next, we have this ad open folder
as project option. If we check this, we'll be able to right click a folder on our computer and
get the option to import the folder into
IntelliJ as a project. We won't be needing
this for this course, but it's pretty convenient,
so I'll check mine. Next, we can choose to associate certain files with IntelliJ. This will allow us to double
click a file that has one of these extensions and have
it open up in IntellaJ. This also wasn't necessary, but I'll check dot Java here. Finally, we can choose add
IntelliJ as Bn folder to the path variable and our computers
environment variables. This is useful if we
want to be able to access IntellaJthrough
the command line. We won't be needing to
do so in this course, but I'll check mine as it might come in handy
in the future. A so in order for
this to take effect, we'll have to restart
our computer. Okay? Now we can click next. Then we can choose to
create a folder for IntellllaJ in the Start
menu, which is fine. So finally, we can
click Install and give it a few minutes
to finish installing. All right, after IntellaJ
is finished installing, if we had checked the Add to
path option in the setup, it would now ask us
if we want to restart our computer. This
is only necessary. We want to be able to use IntellaJthrough
the command line, so I'll choose to
manually restart later, but feel free to restart
yours now if you like. Okay, now I'll click Finish. Then we can run IntellaJ
either by using the desktop shortcut or by going into the
installation folder, then the bin folder, and opening up the
application file here. It might first ask us, if you want to import settings from a previous
version of IntellaJ. I don't want to do
this, so I'll leave it on Do Not Import
and click Okay. Then it brings us to
the welcome screen. From here, we can create a new project by clicking New Project. In the new project dialogue, we can give the project a name. It doesn't matter what we use here as this is just
for demonstration, so I'll just call it MPject. Next, we can choose a location for where we want to
save the project. I'll just leave mine
on the default. Then we can choose the
programming language for the project. Let's
keep it on Java. We next get some options
for the build system. This isn't important right now, so let's leave it on ItellaJ. Finally, we can choose
the JDK version that we want to use for
developing the project. If we install the
JDK correctly in the previous video or if we already had a JDK
version installed, we should see it
in this box here. If we click this,
it should show us any other JDK versions
we might have installed, as well as give us the option
to download other versions. All right, so with
the JDK chosen here, we can now create the project by clicking the create
button down here. KanaoPject is up and running. Now we just need
to wait for it to finish indexing the JDK. When that's finished, we can
go into the source folder here and open up our
main class file, which contains the public
static void main method that all executable
Java classes require. The default code inside this method simply prints
out the text hello world. And just to make sure it works, we can right click
the main class in here and choose runmindt Min. And after it builds the project, the run dialogue should pop up, and it should say
hello world in here. Awesome. That means we have it correctly installed
IntlaJ and the JDK. Alright, I'll go
ahead and close up this project by going to File, Close project. I'll see
you in the next video.
4. Set a Java Home Environment Variable: After installing
IntlaJ and the JDK, one more thing we need to do is set a Java home environment variable on our
system that points to the download
location of the JDK. This step will make
things much easier when we start creating
LibGDX projects. To do this, we first need to get the download
location of the JDK. In the ItellaJ welcome screen, if we have projects
selected here on the left, we should now see
the project here that we created in
the previous video. Let's click the project
to open it up again. Now we want to go up to
file the project structure. And here on our platform
settings on the left, we want to choose SDKs, and now we can see the download
location of the JDK here. Let's select the location and
press Control C to copy it. Now we can close this out
and close out of Manta J. Okay, so setting
environment variables is going to be different depending on your
operating system. I currently only have
access to a Windows system, so I unfortunately
can't show how to do it on a Mac or Linux system. However, if you do a
search on how to set the Java Home
environment variable for your operating system, you should be able to easily
find instructions for it, and it should be a
pretty simple process. Okay, so if you're
using Windows like I am to set a Java Home
environment variable, we first want to click
in the search box at the left of the task bar, and if we start typing
environment at the top, we should see a
result that says, Edit the system
environment variables. If we click this, it brings up the system properties dialog. And now we want to
click the Environment Variables button
here at the bottom. In the system
variables list here, we first want to check
if we have a variable called Java underscore
home in all caps. If we do, as long as the value
is set to the location of a JDK between versions 11
and 18, we should be fine. If we don't already have
a Java Home variable, we need to create one by first clicking the
new button here. For the variable
name, we want to put Java underscore
home in all caps, and for the variable value, let's press Control V to paste the copy JDK
download path. We can also browse to
location if we want. All right, let's click
Okay. Now we should see our new Java Home
variable in the list here with the value set
to the JDK location. Also, if you did already
have a Java Home variable, but it wasn't set to
a correct version, you could choose the variable on the list and click Edit here, then paste the copied location into the value box
and press Okay. All right after adding
the Java Home variable, we now need to find the path
variable in the list here. When we find it, let's select it and click the edit button. If we already had a
Java Home variable, we might also already
have an item in here labeled percent sine
Java underscore home, percent sine backslash Bn. If not, we'll need to create
one by clicking New up here. Then typing percent sine Java underscore
home in all caps, then percent sine backslash
BIN and press Center. All right, now we can click
Okay here and here and here, now we should be ready to
go. See the next video.
5. What Is LibGDX?: LibGDX is a game
development framework, which is a collection of
libraries and tools that provide building blocks that developers can use to
create their own games. This is different from
a game engine like Unity three D or Unreal Engine, which provide a UI for dragging and dropping
elements into a game and often come with a ton of extra functionalities that
you may never need to use. Game development
frameworks tend to be much more lightweight and
flexible than game engines. LibGDX is also free
and open source, meaning that you can make as
many games as you want with it and never have to worry
about purchasing a license. It also means that
many developers have created and
are still creating various extensions that
can be added to LibGDx to make it just as functional as the most powerful
game engines. Another feature of LibGDX is that it offers cross
platform deployment, meaning that a game created with LibGDx can be run on
multiple platforms, including Windows, Mac, Linux, Android, IOS, and web browsers. Although we'll be focusing on creating two D games
in this course, LibGDX is also fully capable of handling three
D game development, and you can find many
examples online of amazing three D games that
have been created with LibGDX. Finally, LibGDx is written in the Java
programming language, which is what we'll be
using in this course, but it's also possible
to use it with other programming languages
like Kotlin and Scala. Okay, now that we know what
LibGDXs in the next video, we'll learn how to start
using it. See you.
6. Setting Up LibGDX: To start using LibGDX, we first need to
create a LBGDxPject then import the project
into Antilla J. And to create a LBGDXPject, we use the LibGDxPject
Generator. To find the project generator, you need to go to
the LibGDX website, which is located at libgdx.com. On this page, we can see some
examples of games that were created using LibGDX and we can read more
about how it works. Here we can see that at the
time of recording this video, the current version of
LibGDX is 1.11 0.0. I recommend using at
least this version throughout the course to avoid
running into any issues. Lack up at the top of the page, if you click the Get
Started button here, then click the Set Up a Project
button on the next page. It takes us to a Wiki with information about
how to get started, creating our first LBGDXPject. We already set up our
Dev environment earlier, so we want to move
over to the Generate aPject section by
clicking this button. Then we can download
the project generator by clicking the
Download button here. The project generator file
is called gdxsetup dot jar. It doesn't matter
where we put the file, but just be sure to put in a location that you
will easily remember, as we will need to
use the file every time we want to create
a new LBGDXPject. I simply created a
folder in my Drive called LibGDx and put
the file inside it. I also credit a folder
in here called Projects, which is where I'll
be placing all the projects that we
create in this course. Anyway, as I mentioned before, this file has the dot
Jar file extension, which stands for Java Archive. And to be more specific, this file is an
executable jar file, which can be run
directly through Java. This means that if
we did everything correctly when installing
the JDK earlier, we should be able to simply double click the file to run it. If double clicking it
doesn't work for you, we can also run the file through the command line by using the command Java jar
GDX setup dot jar. Okay, now we have the project
generator up and running. And here we can give
our game a name. We're just going to
use this project as a test to make sure
everything works. So let's just call it test game. Next, we can give the
project a package name. It doesn't matter too
much what we put here, but if you're creating
a project for a particular company
such as your own, the convention is to
put the Internet, the main name of the
company in reverse, followed by the project name. I'm just going to put
com dot B Grant for my name the dot TestGame. And by the way, we
can't use spaces or uppercase letters
for the package name. Okay, next, we have game class. This will be the name used for the main Java class
file of our game. I'll simply use the project
name but with no spaces. Next, we can choose where to
put all the project files. I'm going to browse
to the projects folder that I created
inside the LBGDXFolder. Now I'll right click in
here and choose New Folder. I'll name the folder test game
and click the open button. Okay, under the output folder, we have Android SDK. If you have the Android Studio development kit installed
on your system, it will likely show the
location of it here. However, we won't be using
Android for this project, so we can ignore this for now. Next, we have options for which platforms you want our
project to run on, including desktop,
Android, IOS, and HTMO. For this project, we only want
to run it on the desktop, so let's uncheck all
but the desktop option. Finally, we have
various extensions that we can use in our project. We'll talk about some of
these later in the course, but as this is just
a test project, we won't be needing
any of them right now, so let's go ahead and click
the Generate button here. This will generate all
the project files for us. And if everything
works correctly, it should only take
about a minute. Okay, it says, Build
successful and shows us how to import our project
into various IDEs. We can close out the
project generator now, and in the next video, we'll import the project
into Antilla J and learn about the basics
of LGDx. See there.
7. LibGDX Basics: Now that we have our test
game project created, we can input it
into IntellaJ from the welcome screen by clicking the open
button at the top. Then browsing to the location of the project we just created, which in my case is D,
Lib gDxPject TestGame. Now we want to have
the project folder selected and click Okay. It warns us that the project
may contain malicious code, but we're not worried
about that, so we can click Trust Project. It will then start
building the project, which may take a
couple of minutes. As long as there are no problems
with the project build, we have successfully created and imported our first
LibGDX project. Let's check out
what we have here. First, in the project
panel at the top left, we want to have
projects selected here, and now let's expand
the test game folder. The folders that we're mainly
concerned with in here are assets, core and desktop. And the assets folder is where we put all of the
assets for our game, which includes things
like images and sounds. By default, we get this
bad logic dot JPG image included in the Assets folder. We can double click
the image to view it. Bad logic is the name of the
company that created LibGDX, and this is just an
image they include in every default LibGDX project
for testing purposes. We can close out the image. Let's skip the core folder for now and check out
the desktop folder. The desktop folder is
where we place all of the code that is only relevant to running the game
on the desktop. If while generating the project, we had chosen any of
the other platforms like Android or IOS, we would also have separate
folders for those platforms. Anyway, if we expand
the source folder here, we see the package name that we chosen the project generator, and inside it, we have a
desktop launcher class file. Let's double click it and
check out the source code. At the top of the
desktop launcher file, we have the package
name for our project and some imported
classes and packages. And here we have the start of
our desktop launcher class. It's quite a simple
class consisting of only a main method
and a few lines of code. The first line here instantiates an LWJGL three application configuration object
called Config. LWJGL is a Java library that allows us to create a
BGDX window on the desktop, this class here, which I
won't try to say again, is used for configuring various
settings of the window. For example, the next line, config dot set
foreground FPS 60 sets the target frame rate
of the application to 60 FPS or frames per second. The frame rate is how often the contents in the
window are refreshed, and 60 fps is pretty much the standard
for most video games. Next config dot set title is used to set the
title of the window. The title is only displayed if the window isn't full screen, and by default, it's
set to whatever we use as a name for our project
and the project generator. We can change the title to
something else if we want. Finally, the last
line instance is an LWJGL three
application object using an instance of our
main game class and the Gafik object we created. The main game class,
which in my case, is called Test Game is
located in the core folder. But before we check
it out, let's go ahead and run the
desktop launcher. This is actually the
only runable file in our project, and to run it, we can right click the
file on the list here and choose run Desktop
launcher dot Min. If it runs correctly, we get
a small square window with a red background and the bad logic of JPG
image at the bottom left. We can also see that the
title of the window is whatever we put in the set title method for the C fake object. Okay, we can close this out now. Let's check out the core folder. The core folder is where we put all of the platform
independent code, and it's where we'll be spending 99% of our time working
on the project. In the source folder, we
have a package name again, and in the package, we
have the test Game class, which is our main game class, and what the final line of
the desktop launcher creates an instance of and places
inside the window. Let's double click the file
and check out the code. At the top of the file, we again have our package name
and some imports, then the start of
our main game class. This class extends
application adapter, which is required in order for the desktop
launcher to run it. Inside the class, we
first have two variables, a sprite batch object called batch and a texture
object called IMG. It's important to have
a sprite batch object in each of our LibGDx projects, as is basically what we use to draw all of our
textures to the screen, and the texture class
is used to decode an image file and load
it into GPU memory. Next, we have three
methods that any class which extends application
adapter must override. These include create,
render and dispose. Create is the first method that gets called when the
class is loaded, so it's where we should
instantiate all of our variables. So in here, we instantiate our sprite batch object
and our texture object. As you can see with
the texture object, we pass in the location of
the image we want to decode, which in this case is
bad logic dot JPG. As we saw earlier, this image is located in the assets folder, and it's the smiley
face image we saw when we ran the
desktop blancher. Also note that we
don't have to use assetlash Badogic dot JPG for the location because
LibidX already knows to look inside the
assets folder for all assets. Okay, the next method is render. This method is called at the beginning of every
frame in our game. Because of this,
we don't want to create any objects
inside this method, because the Java garbage
collection will have to deal with possibly thousands of created objects at a time, which can cause the game to
skip frames and become jumpy. The first line we have is
screen utils dot Clear, followed by four float values. The screen utils class contains static helper methods for dealing with different
aspects of the game screen, and the clear method
is used to clear the screen of all textures
and set it to a single color. The four float values are the RGBA values of the color that we want
to set the screen to. RGBA stands for
red, green, blue, and Alpha, with the
minimum value for each one being zero
and the max being one. So the values of one for
red, zero for green, zero for blue, and
one for Alpha, we get a fully
opaque red screen. Alpha, by the way, deals
with transparency. One is fully opaque and
zero is fully transparent. Next, we have three
lines that make calls to methods inside
our sprite batch. The first one batch up again is required before we use the sprite batch
to do any drawing, and it lets the GPU know that we're ready
to start drawing. We then have a call
to Batch Dot draw in which we pass in the
texture that we want to draw, followed by two floats, which are the X
and Y coordinates of the point on the game screen, where we want the texture
to begin drawing. In Lib GDX, the 0.00 is
located at the bottom left, which is why when we ran the
desktop launcher earlier, we saw the image drawn at the
bottom left of the window. And finally, we
have Batch Dot end. Let's let the GPU know
that we're finished drawing and it's required after all calls to
Batch Dot draw. If we had any other textures
that we wanted to draw, we would make a call to Batch
Dot draw for each of them. The final method in
the class is disposed. This method gets called
when we close out our game, and it's where we release
certain resources that our game uses so that they will be
removed from the GPU memory. As you can see, we need to
call the disposed method for the sprite batch and for
any textures that we create. When we create our
main project later, we'll use something
called an asset manager to manage all of our assets. With an asset manager, we'll be able to dispose all
of our textures at once, so we won't have to
worry about calling the disposed method for every
single one of them here. Okay, that's it for
the test game class. But before we move on, let's run the desktop launcher again. And actually, when we ran the desktop launcher
the first time, InteleJ automatically
created a run configuration for us using the
desktop launcher, which we can see up here. So now we can simply
click the Run button next to this to run
the desktop launcher. So as we saw before, we have a red background with the image at the bottom left. If we close this out, we can try changing the background
color to something else. I'll change the
red value to zero. Now with values of zero for red, green, and blue, it will
give me a black background. If we use all ones, it
will give us white. And we can use
different combinations of values for various colors. We can also use different
X and Y values in the code and Batch Dot draw to change
the location of the image. We can use negative
numbers as well, which would draw the image at
least partially off screen. If you wanted to put the image at the top right of the screen, we'll need to get the width
and height of the screen. To do this, we can delete
the X and Y values. Let's type GDX with a G. We'll need to import this
class by pressing Enter. Then we can type
dot graphics dot G width to get the
width of the screen. Then type a comma.
And for the Y value, we can type GDX dot
Graphics dog height. Now if we run this, we
of course won't be able to see the image
because it starts drawing from the bottom
left pixel of the image, so it's actually up
here just off screen. We can close this out
and subtract some pixels from the X and Y values
in order to see the image. If you wanted to
start drawing the image at the center
of the screen, we can divide these
values by two. And we need to put an F after the twos so that the
result remains afloat. If you wanted to center
the image on the screen, we also need to subtract half
of the image's width from the X coordinate and half of its height from
the Y coordinate. To do this, for
the X coordinate, we can do Gdx dot
graphics dot Get Wi, divided by f minus
mg dot get Wi, divided by f. And for
the Y coordinate, GDX dot Graphics dot G height, divided by F minus ImgtGt
height divided by two f. There we go. All right, and that's
pretty much it for a very basic LBGDX project. In the next section, we're
going to learn a whole lot more about LibGDX by
creating our main projects. Stick figure
showdown. See there.
8. Create & Import the Project: To create our main project, we first need to go back to
the location where we saved our LBGDXPject generator
and open it up again. I'm going to name the project
stick figure Showdown. For the package name, I'll
go with comb grant SFS. And for the game class, I'll go with SFS and all caps. For the output folder, I'll browse to my LBGDXPjects folder. Create a new folder
in here called Stick Figure Showdown.
And click Open. Now, to keep things simple, we're going to focus on creating games for the desktop
in this course because I know not everyone has access to Android
or IOS devices. However, at the
end of the course, there will be an optional
bonus section in which we will learn how to add Android
functionality to our project. So feel free to check that out after finishing this
section of the course. For now, though, let's
uncheck it here. Unfortunately, in order
to test our game on IOS, an IOS device is required, but I don't currently
have access to one. Therefore, at least
for the moment, I won't be including a section in this course regarding IOS. I may, however, be able
to do so in the future. For now let's uncheck it. An HTML doesn't support everything that we'll be
including in our project, so we can uncheck it, as well. Next, we have some
official extensions, as well as some third
party extensions, which we can see by
clicking this button. These provide a whole lot of extra functionality that we
can include in our games. For example, free type gives us a lot of control
over the typography in our games compared
to what we can do with the standard
LGDX library. Box two D here gives us advanced collision
detection functionality, and AI gives us advanced artificial
intelligence functionality. The only extension
we'll be needing for our project is free type. So
let's go ahead and check it. I'm planning to make another LibGDX course in
the near future, in which I will cover many if not all of these extensions. Okay, now that we have everything set up
the way we want it, let's click Generate and
let it do its thing. When the project has
finished generating, we can open up and tell a J and import the project by
going to file open, browsing to the stick
figure showdown folder, and clicking Okay. Let's click Trust Project, and we don't need our test
game project open anymore, so we can choose this window. When the project has
finished building, let's go over to the desktop
folder, then source. Then right, click
Desktop Launcher and choose Rundsktop
launcher dot M, just to make sure everything
is working correctly so far. Alright, the demo
game seems to be working fine, so we
can close this out. And in the next
video, we'll start replacing the demo code
with our own code. See you there.
9. Import & Manage the Assets: In this video,
we'll import all of the assets we'll need
into our project. Then we'll set up
an asset manager, which we can use to manage all of our assets in one place. First, if we unzip the
resources dot zip file that I included with this
course and take a look inside, one of the folders we have in
here is this assets folder. This folder contains all the assets we'll need
for our project, including audio files,
fonts, and textures. To import them all
into our project, we simply want to select
all the folders in here, right click them
and choose Copy. Then go to our project
and Intel a J, right click the assets
folder, choose paste. Then click Okay here. And now we have all of the assets
imported into our project. We won't be needing the
bad logic image anymore. So we can right, click
it and choose Delete. Then click Okay here. Next, in order to start using the
assets in our project, we need to load them
all into memory. We'll do this with
an asset manager. Let's first go into the
core folder, then source. Then let's right click
our project package here and go to new then package. At the end of this, let's type resources. Then Press Center. And this package
is where we'll put all the resource classes that we'll create
throughout the course. The resource classes will mostly consist of managers
for things like audio, game settings, and assets. For the moment, we just want
to create an asset manager. So let's right, click
the Resources package and go to New Java class. Let's name the class assets
with a A and Press Center. Okay, so the first thing
we need to create in the assets class is the
actual asset manager. I'm going to put a comment
here that says asset manager. Now on the next line, let's type public final asset manager. Press Enter to import the class. Then manager equals
new asset manager. Open and close parentheses. We want it to be
public so that we can access it from classes
outside of the package. Next, we want to create some static final
string variables that point to the locations of all the assets
in our project, so that we can easily
access them throughout the project without having to
memorize all the locations. First, let's create
variables for all the textures that we'll
use and the actual gameplay. If we go to the
textures Assets folder, the gameplay assets include
background dot png, blood dot Atlas,
front ropes dot PNG, and gameplay buttons dot Atlas. We'll discuss what Atlas
files are a bit later. The remaining gameplay assets are located in the
Sprites folder. These are sprite sheets
for all the animations that our fighters will perform, like the block animation, the hurt animation, and
the idle animation. So back in the assets class, I'll go down a couple
of lines here. Put a comment for
gameplay assets. And first I'll do public
static final string. Background underscored
texture equals textures forglash
background dot png. And because I capitalize the B in the name
of the image file, I want to make sure I
use at B here as well. Okay, I'm going to
continue creating variables for the
remaining gameplay assets. I'll skip through this so the video won't be
ridiculously long. Okay, here are all of the variables for
the gameplay assets. If you're following
along, please pause the video now and copy
these into your class file. Next I'll do the same
for the font assets. If you open the fonts folder, we only have one file
here, Roboto regular TTF. However, we're going to
create several fonts in our project using this file. Some will be large, some small, and some will have borders
around the letters. First, I'll put a
comment here for fonts. Then I'll type public
static final string, Roboto regular equals
fonts Roboto regular TTF. On the next line, I'll do
public static final string. Small font, equals
small font dot TTF. I'm going to press Control D to duplicate this line and
change small to medium. I'll duplicate again and
change this one to Large. As we'll see later, it
doesn't matter that these three files don't
actually exist yet. For the next section, I'll create variables for
the audio assets. These are all of the assets
inside the audio folder. First I'll put a comment
here for audio assets. Then I'll start with the
block sound by typing public static final
string block sound. Equals audios Block
dot NP three. Then I'll continue
creating variables for the remaining audio assets. Here are all of the variables
for the audio assets. Be sure to pause the video now and copy these lines
into your class file. And notice that for
the music variable, I didn't add underscore
sound to the name. That's because we'll be handling the music file a bit
differently than the others. Also, be sure to use dot Ogg as the extension instead of
dotNP three like the others. Finally, I'll create variables
for the menu assets. We only need to create one
variable for the menu assets. This menu items dot
Atlas file here. So I type public
static final string. Menu items Atlas. Equals textures slash
Menu items dot AtlaS. You might have also
noticed that we have a mobile i dot Alas file, which we didn't create
a variable for. That's because we'll
only need this asset for the bonus Android section
at the end of the course. We'll create a variable
for it in that section. Next, we're going to
create some methods that will load all of
the assets into memory. First, let's create a
public void load method. We'll call this method
from the main game class. Eventually, we're going
to create methods for loading each of the
categories of assets. But since we'll
only be needing the gameplay assets for
the next few videos, we'll just create a
method for loading the gameplay assets for now. First I'll put a comment here
that says Load all assets. Then let's call the method
load gameplay assets. Now below the load method, let's spe private void
load gameplay assets. In this method, we'll load
each of our gameplay assets. To do this, we type manager, which is the asset manager
we created earlier. Then dot load open parentheses, and now we type the location of the asset we want to load. So we can first use our
background texture variable here, then and now we need to tell which type of
asset we're loading. This is going to be
a texture asset. So we can type texture with a T, press Enter to import the class, and it might add dot
class here for us. If it doesn't, we'll need
to type it ourselves. Now we can close off this line. I'm now going to do this
for front ropes texture and all of the sprite sheets. And I'll stop at
Gameplay Buttons Atlas. Okay, here's all the code to load the gameplay
texture assets. For gameplaybtons
dot Atlas here, let's type manager dot load,
Gameplay Buttons Atlas. Because this is a texture
atlas, not a normal texture, we need to type texture atlas, pres Enter, then dot class. Now let's do the same
for blood Atlas. Manager dot Load, Blood Atlas, texture atlas dot class. Okay, that's all we need
to load at the moment. The final thing
we want to create in this class for now is the dispose method
to dispose all of our assets, removing
them from memory. Let's put the method at
the bottom of the class. Public void dispose
inside this method, we simply need to type
manager dot dispose. With this, the asset manager will take care of all of
the disposing for us. Now, we need to actually
create an assets object in our main game class and
called the load method. Let's head over to
the core folder and double click the SFS
class file to open it. We won't be needing the
bad logic texture anymore, so we can delete the texture
IMG line at the top, the initialization of
the texture in here, the call to Batch dot draw here, and the mg dot
dispose line here. I'm also going to
press Control A to O to remove the unused
texture class import. I'll press Control A to L to reformat all of the code so that everything
looks cleaner. I'll be using these two
shortcuts throughout the course, and it's a good idea to get
into the habit of using them yourself after adding a lot of code or making
a lot of changes. Okay, now back at the top
under Sprite Batch batch. Let's type assets with a A, press Enter to import it, then assets with the lower KsA. And we actually want to make
both the Sprite batch object and the assets object public, as we'll be using them
from various classes. Next in the create method, after the batch initialization, let's put assets
equals new assets. Now we have our asset manager initialized and ready to go. Now we want to call
the assets manager load method to load
all the assets. I'll go down a
couple lines and put a comment that says
load all assets. Then let's type assets dot Load. If you go back to the
assets class really quick, when we make a call
to manager dot Load, it actually puts
the given asset in a queue and the assets are
loaded asynchronously, meaning that other parts
of our code are able to continue running as the
assets are loading. We're also able to check
the loading progress to see how many of the assets
have actually been loaded. This is another advantage
of using an asset manager, and we'll put this
feature to use later in the course
when we create a loading screen
with a progress bar to show us how many of the
assets have been loaded. Until then, however, we want
to make sure that all of the assets have been loaded before we continue
running the game. This will prevent us from
trying to use an asset like a texture or audio file that
hasn't yet been loaded, which will cause
the game to crash. To make sure all of the assets are loaded before continuing, we can go back to
our main game class. And after the assets
Dow load line, we can type assets, Dot
manager, dow finished loading. This method will block all
other code in our game from running until all the
assets have finished loading. This is just a
temporary solution until we create the loading
screen a bit later. The final thing we want to do in this video is go to
the dispose method, and after batch
start to dispose, that's called
assets Dot dispose. Now, when we close out our game, all the assets will be
disposed of and removed from memory so that no
memory leaks will occur. Alright with all of that
boring stuff out of the way, in the next video,
we actually start putting some textures on
the screen. See there.
10. Create the Game Screen: I in our game, we're going to have
multiple different screens, including a loading
screen, a setting screen, a main menu screen, and a game screen where we'll
actually play the game. We're going to be able to switch back and forth between
these screens. In this video, we're
going to start creating the game
screen so that we can actually start
playing the game and we'll worry about
the other screens later. To start, let's go over to our project package
in the core folder, right click it and
go to New package. Let's call it screens
and press Enter. This package is where we'll place all of our screen classes. Let's right click
the screens package and go to New Java class. Let's call it game screen. I press Center. Now, each screen class we create needs to
implement a class called screen. So after public class
game screen here, let's type Implements screen. And press Enter to
import the screen class. Implementing the screen class requires that we
implement a few methods. To do this easily, we can hover over this line until
this dialogue pops up. Click Implement methods here. Then click Okay. Now we have overridden all of the methods that we
need to implement. We don't have to actually put anything inside all of them. They just have to exist
inside our class. We actually also need to create a constructor for our class, which will be called
when we create a game screen object in our
main game class later on. So at the top of the class, let's type public game
screen, open parenthesis. And we want to pass an a pointer to our main game class here, let's type SFS, press
Enter to import it, then game, and finish
creating the method. Above this method, let's create a private final
SFS game variable. And inside the constructor, let's type this dot
Game equals game. We'll be using this variable throughout our
game screen class. Next, we want to get our
game area textures from the asset manager so that we can render
them to the screen. If you look inside the
textures folder here, the game area textures are background dot PNG and
front ropes dot PNG. If you open front ropes dot PNG, it's basically just a copy of the front ropes of
the fighting ring and background dot PNG. Just to clear up any
possible confusion, the reason we have a separate
image for the front rings is that the first texture we're going to draw is the background. Then we'll draw the
fighters so that the fighters will appear
on top of the background. However, if the fighters
move down here, they're also going
to appear on top of the front ropes, which
won't look right. To fix this, we'll draw the front ropes image
on top of the fighters. Anyway, that's not too
important at the moment, so let's get back
to getting the game area textures from
the asset manager. First, after creating the game variable at the
top of the class, let's go down a couple of lines, and I'll put a comment here
that says background Ring. And this section
is where we'll put all variables related to the background and
fighting ring. Now let's type private texture. Press Ender to import it
the background texture. Let's also create one
for the front ropes. Private texture,
front ropes texture. Next, under the constructor. Let's create a private void
create game area method. And here we'll get the game area textures from the asset manager. First dot comment, get the ring textures from
the asset manager. Then we can type
background texture equals. And now we'll use
our game variable, then dot assets dot
manager dot Git. We now want to pass in the location of the
background texture, which we can do easily
by typing assets, pressing Enter to import it, then dot background texture. This is the public static
final string variable we created earlier
in the assets class, which points to the location
of the background texture. Now let us do this for
the front ropes texture. Front ropes texture equals game dot assets dot
manager dot Git, assets dot Front ropes texture. Now back inside the constructor, I'll comment, create
the game area. Then make a call to the
create game area method. Next, we'll actually draw or render the background
texture to the screen. We'll do this in the
render method here. First, as we learned when we went through the demo project, we want to clear the screen
of any previous drawings. This is important
because when we start animating our
fighters later, if we don't clear the
screen every frame, all the previously rendered animation frames
remain on the screen, causing everything to
look like a jumbled mess. So to clear the screen,
let's type screen Utils. Press Ener to import it, then dot Clear, open Perthess. Let's clear the screen to
black by typing 000 com one. Now we'll render the
background texture. If you recall from
the demo project, we did this with a
call to the draw method of the Sprite Batch, and all calls to draw
must come between calls to the Sprite batches
begin and end methods. It's a good practice to only use a single sprite batch
for the entire game, so we're going to
use the sprite batch we created in our
main game class. First I'll comment,
begin drawing. Then let's call game
dot batch dot begin. Now I'll go down a couple
lines and draw the background. Then let's call game
dot batch dot draw. Now we want to pass in our
background texture variable followed by the
location where we want to start
drawing the texture. Let's go to 00. Then let's
go down a couple of lines, and I'll comment n drawing. Then type game dot
batch dot end. Okay? Now, in order to start using
our game screen class, we need to create a
game screen object in our main game class then switch the screen
to the game screen. So let's head back over
to our main game class. First, before we can switch between screens
using this class, instead of extending
application adapter, this class needs to extend
a class called game. So let's replace
application adapter with game and press Center. We can press Control A O to remove the application
adapter inpuort. The game class actually implements the application
listener class, which is the same class the application
adapter implements. This means that we still
need to override the create, render, and dispose methods. However, we won't
actually be doing any drawing in the render
method of this class. Instead, we need to
replace all of the code in the render method
with superdtRnder. Super refers to the class we're extending
from, which is game. So this calls the render
method of the game class. I'm not sure exactly what the game classes render method does, but it's important that
we make this call here. Now at the top, under
the creation of the assets variable,
I'll comment screens. Then let's type
public game screen. Press Enter, then game screen. Now in the create method, after we load all of the assets, I'll type initialize the game
screen and switch to it. Then this initialized
game screen by typing game screen equals new game screen pass in
this class by typing this. Then to switch to
the game screen, we can type set screen and pass in our game
screen variable. Set screen is a method that is provided by the game class. Okay, now we can go up here
and run the desktop launcher. And if everything
works correctly, we should see our background
texture on the screen. But was see is the bottom
left corner of it. So in the next video, we'll see how we can change
the default size of our game window
as well as how to show the entire background
texture. See there.
11. Window Size, Camera & World Units: We saw in the previous video that when we run the
desktop launcher, we see is a small portion
of our background image. Also, if we change the
aspect ratio of the window, the image becomes warped. Over these next
couple of videos, we'll fix both of these issues. But first, the default
window size is a bit too small, so
let's make it bigger. To do this, let's go into the desktop folder and
open up desktop launcher. And here, somewhere between
the initialization of config and the last line,
let's add another line. And let's call Config,
that set windowed mode. Then pass in 804 80. Windowed mode is actually what
we've been using already, as it puts our
application inside a movable and resizable window that doesn't take up the
entire computer screen. Later on, we'll learn how to
switch to four screen mode. However, if we manually call this set Window and mode method, we're able to pass
in pixel values for the width and
height that we want the window to be
instead of using LibGDx as default values. So now our window
will start out as 800 pixels by 480 pixels. Let's go ahead and run
desktop launcher again to see if it works. There we go. We still can't see all
of the background image, though and to fix this issue, we're going to set up a camera. Cameras and LibGDX determine what the player can
see in the game. They give us the
ability to zoom in and out and follow the player as they move around in the game. In Lib GDX, we have two types of cameras perspective camera
and orthographic camera. Perspective cameras
more closely mimic how the human eye sees as they make objects appear
bigger or smaller, depending on how far away
they are from the viewer. Perspective cameras are
used by most three D games. Orthographic cameras,
on the other hand, don't take depth into account, so objects will
appear the same size regardless of how
far they are away. This is most common
in two D games like side scrollers
and top down games. Because the game we're
making is two D, an orthographic camera
is what we'll use. To set up an
orthographic camera, let's go back over to
our game screen class. At the top, after creating
the SFS game variable, let's type private final
orthographic camera. Press Enger to import
it and camera. Now in the constructor, before creating the game area, I'll comment, set up the camera. Then let's initialize
the camera by typing camera equals new
orthographic camera. And now we need to pass
in the width and height of the area that we want
the camera to show. For now, let's use the
width and height of the window by typing GDX, pressing Enter,
then do graphics, do get width, GDX, DGraphics do get height. Then close off the
line. We next have to tell the sprite batch to use
our camera when rendering. To do this, let's go
to the render method. And before the call to
game Dow batch do begin, I'll comment set the sprite
batch to use the camera. Then type game dot Batch
D set projection matrix, and pass in camera dot combined. It's not important to know
exactly what this method does, but it basically uses
information from the camera to transform everything as it gets
rendered to the screen. Okay, let's go ahead and run the desktop launcher
and see what we get? So a problem we have
now is that the texture starts at the center
of the screen instead of at the bottom left. And the reason for this is that the camera is located
at position 00, but the camera's lens is actually at the
center of the camera. Therefore, when using a camera, 00 is now at the center of the window instead of
at the bottom left. To go back to having
00 at the bottom left, we simply need to
move the camera to the right by
half the width of his viewport and up by half
the height of his viewport. A camera's viewport, by the way, is all the area that
the camera is showing. To move the camera
after we initialize, we can make a call to
camera dot translate the pass in camera D viewport
width divided by two F, coma camera that viewport height divided by two F. And in order for this
to go into effect, we also need to make a
call to camera dot update. If we run Desktop launcher now, the texture starts at
the bottom left again. However, we still
have the problem of not displaying
the entire texture. In fact, it's only showing the bottom left,
quarter of the image. And the reason for this
is that I actually created all the textures at
twice the size that we need. I did this because it helps to minimize loss of quality
on larger screens. If we go into the
textures assets folder and open
background dot PNG, we can see at the top
right here that the image is 1,600 by 960 pixels. This is twice the size of our
game windows default size, which we set in the
desktop launcher to be 800 by 480 pixels. So if we want to show the entire background texture
in the game window, you need to draw the texture at half its width and height. To do this, in the render method of the game screen class, where we draw the
background texture, we can use another version
of the draw method, which lets us pass in the width and height of the texture. So after the Y value, we can put background
texture Get width. And we want to draw
it at half the width, so we can put either
divided by two F or times 0.5 F. Then we can do
the same for the height. Now when we run the
desk top blancher, we see the entire
texture in the window, but we still have one problem. That's if we change
the aspect ratio of the window, the
image gets warped. We'll fix this in
the next video, where we will create a
viewport object to handle our camera's viewport and have more control over how things
appear on the screen. But before we do that, let's talk about something
called world units. So far, with our camera, we've been working in screen units or pixels by passing in the width and height of the
window into the camera. This is okay right now, but
using large numbers of pixels can get pretty confusing and tedious when we start moving
things around in our game. Doing it this way also
forces us to think about things for a particular
screen resolution. However, we're planning
to use our game across many different
resolutions. Therefore, it's much
better to think in terms of world
units of some kind. We can think of world
units as feet or meters or inches or any
other unit we want to use. What I like to do is take the desired within height
I want from my game, divide them by ten, and use those numbers
as the world units. In this game, the desired
within height is 800 by 480, which is exactly half the
size of the background image. So I'll take those numbers
and divide them by ten, which gives me 80 by 48. I could even divide by
100 if I wanted to, giving me eight by 4.8. However, going too
low will make it difficult to accurately place
things inside the game. So I like to stick
with dividing by ten. To start using world units, we need to replace the screen
units that we pass into the camera with the world
units we want to use. However, instead of typing
the numbers directly in here, we're going to make
them public static final variables in
a different class, so that we'll be
able to access them easily across our
entire project. To create this new
class, let's go to the resources package that we created earlier inside
the core folder. Let's right click it and
go to New Java class. Let's name it global
variables. And press center. Throughout the course, we'll
be placing variables in here that we'll need to access from multiple classes
in our project. First, inside this class,
I'll comment world. Then let's type public
static final float. Rolled width equals ADF. Next let's do public static
final float, rolled height. Equals 48 F. Let's actually also put the default within height of the
window at the top here. I'll comment Window, then
type public static final int. Window width equals 800 and
public static final int, window height, equals 480. Now on desktop launcher, let's replace the
values we put in the set windowed mode method
with the variables we just created by typing global
variables and pressing Enter, then dot window width, como global variables,
that window height. Can I go back to Game screen and start using our role units. First, let's delete
these parameters. We pass into the
initialization of the camera. Then put in the world units
by typing global variables, pressing Enter, then
dot world width, como global variables,
dot world height. Now let's run the desktop
launcher and see what happens. All right, so now the
camera's view port is way too small to display the
entire background image. We already scaled
down the texture by half in the draw method, but now we need to scale it
down further by a tenth. Therefore, in total, we need to multiply the
width and height of the texture by 0.5 times
0.1, which is 0.05. But we don't want to keep typing this number in every
time we draw a texture, so let's create a variable
for it in global variables. After the world height variable, let's type public
static final float. World scale equals 0.05 F.
Now back in game screen, we can replace these 0.5 values with global
variable world scale. Now if we run the game, everything should be
back like it was. However, as I mentioned before, we still have the
problem of the image getting warped if we change
the size of the screen. So we'll fix that
using viewports in the next video.
See you there.
12. Viewports: As we learned in
the previous video, a camera's viewport is all of the area the camera
shows on the screen. In this video, we'll
see how we can control a camera's viewport
using a viewport object. Among other things, this
will allow us to fix the warping of
textures that changing the Windows aspect ratio causes. LibGDX actually provides several different
viewport classes we can use extend Viewport, fill viewport, fit view port, stretch viewport, and
screen view port. We can even create our own
custom viewports if we want, but that's a much
more advanced topic. The type of viewport we use
will depend on how we want the objects in our
game to appear on the screen for different
screen resolutions. Let's first take a look
at stretch viewport. At the top of the game screen, after creating the camera, let's type private final
stretch viewport. Press Enter to import
it, then Viewport. Now in the constructor, we actually won't be needing
these camera dot translate and camera dot update
calls anymore. We'll see why in a bit. For now, let's remove the lines. Then type viewport equals
new stretch viewport. And now we need to pass
in the world units, so we can type global
variables do World Width, global variables
do World height. Next we can put a comma then
pass in our camera objects. This will tell the
viewport object to apply its information to the viewport
of the camera we created. We can also remove
the world units from the initialization of the camera as we're now doing this and the initialization
of the viewport. Now, in order for our
viewport to work correctly, we first need to
scroll down here to the resize method that
we had to override. This method is called every time the game window is resized, including when the
window is first created. The width and height
parameters here will contain the new width and
height values of the window. Inside this method, we need to update our viewport with the windows new
width and height. First I'll comment, update the viewport with
the new screen size. To do this, we call viewport
dot update with height. And next we can pass in an
optional Boolean value. If we pass in true, it will center the camera
on the viewport. This is what we want,
so let's type true. This is also why we
no longer need to move the camera
manually in order to get it centered
like we did with the call to the translate
method previously. Now we can go ahead and run
it and see what happens. Re slidee the window, you're probably thinking
this seems exactly the same as it was before we added the viewport.
You would be correct. It seems orthographic
cameras by default, do use something similar
to stretch viewport. What stretch viewport does is it assumes that the windows size
always remains the same as what we pass in when we
create the viewport and the viewport stretches to always take up the
entire window. This will result in the
textures getting stretched and warped if we change the
windows aspect ratio. This could be useful for
certain types of games, but it's definitely not
what we want in our game, so let's check out
the other viewports. Next, let's try Fit viewport. We can simply change stretch viewport here at the top to Fit viewport and we'll have to
press Enter to import it. We also need to change
it and initialization at the viewpoint. Now let's run it. Fit Viewport looks the same as Stretch Viewport
at the moment, but if we change the aspect
ratio of the window, we start getting
black bars either on the sides or in the
top and bottom. This is because like
Stretch Viewport, Fit Viewport assumes that the window size
remains the same, so we'll try to scale
everything as much as possible in order to
fit the entire window. But unlike stretch Viewport, it will maintain the aspect
ratio of everything. This prevents the
textures from warping, but it results in black bars if we change the windows
aspect ratio a lot. And the black bars we see are actually the color we chose
when clearing the screen. So if we want, we can go to the render method and
change the color to, for example, red, now
we will see red bars. Okay, let's change it
back to black for now. For our purposes, Fit
Viewport is better than stretch Viewport because we don't want our textures
to become warped. However, we also
want our game to look good on many
different screens, and we don't want black bars to be
surrounding everything. So let's move on to the next
viewport, Fil Viewport. First, let's replace
Fit Viewport with FieldView port here. Press Enter, and
replace it here. Let's run it. Like Fit Viewport, fill viewport will maintain the aspect ratio of everything. However, fill view port will always fill
the entire window. This prevents the black
bars from appearing, but it also results in parts
of textures being cut off. This definitely
isn't what we want, so let's try the next viewport
type, screen view port. Let's first replace Field view port with screen view port. With screen viewport, however, the only perimeter we can
pass in is the camera object. Now, let's run it. So unlike
the other viewport types, screen viewport only works
with pixels, not world units. That's why our texture
appears so small, because we scaled it down to
world units when drawing it. If you want to work in pixels and you don't mind
that a player with a bigger screen will see more of the game than a player
with a smaller screen, this might be the
viewport type you want. However, it's not what
we want for this game, so let's talk about
what we do want. First, let's open up the
background dot png image. Basically, what we want is
for the entire width of the background to be shown regardless of the
player's screen size. This is because we
don't want any parts of the ring to get cut off. We also want the player to be
able to see from the bottom of the ring up to no lower
than about this point. This is because particularly
for mobile versions, we're going to have buttons
at the bottom of the screen, and we don't want the buttons to be covering up the fighters. And at the top of the screen, we're going to
have a HUD display showing the fighters
life bars and a timer, and we don't want these to
cover up the fighters, either. So we don't want to cut off too much of the top of
the background image. This type of control requires
the final viewport type, extend view port, which
we'll check out now. First, let's close up
the background image and replace screen view port in here with Extend viewport. Press center and
replace it here. With Extend Viewport, before passing in
the camera object, we can pass in a
minimum world width and a minimum world height. That will make sure at
least that much area of the game will be visible
regardless of the screen size. We want to show the entire width of the
world on the screen. So for a minimum world width, let's just use global
variables dot World Width. For the minimum world height, after testing all of this out, I found that about 85% of the world height
works pretty well. So let's go over to the
Global Variables class, and after the world
height variable. It's like public
static final float. Min world height equals
world height times 0.85 F. Now back in game screen, that's at the Min world
height variable here. Now let's run it. I if we increase the
width of the window, we can see that it will start to cut off some of the top of the background until it gets below 85% of the
background height. At that point, we'll start to get a black bar
here on the right. This is actually okay because very few screens
are going to have a width that is this much
larger than the height. However, the image is no longer centered, which
we'll need to fix. But first, if we decrease
the width a lot, we start to see some black
at the top of the screen. This is okay, as well, because our background image
is black here. This is why it's
important that we clear the screen too
black for this game. Okay, so let's now fix the
problem with the background not being centered anymore
if the window is too long. To do this, we also
need to pass in maximum world width and height
values to the viewport. Again, we want the entire world width to always be visible, so let's use the world width variable for the maximum
world width as well. And we actually don't need
a maximum world height, but we have to put something. So if we put a zero, it's
the equivalent of telling the viewport that we don't care about our
maximum world height. Now, let's give it
a try. Okay, now if we increase the
width of the window, the texture is vertically
centered again. We might occasionally get a
user who has a screen with a weird resolution that causes these black bars on
the sides to appear, but it's going to be
such a rare occurrence that we don't have to
worry about it. All right? And if we increase the
height of the window, we can see that the texture
remains at the bottom. This is because we didn't provide a maximum rolled height, and it's exactly what
we want for our game. One more thing
before we move on. We could continue passing
in the camera that we created into the initialization
of the viewport. However, if we don't
pass in a camera, the viewport will actually
create one for us. So if we want, we can remove these lines for
creating the camera. And we'll also
need to remove the camera parameter
we passed in here. Now change the comment
to set up the Viewport. Also in the render method and the call to game dot batch
dot set projection matrix, we need to change
camera dot combined to viewport dot
camera dot combined. Finally, let's
press Control A to o to remove all the
unused imports. Alright, in the next
video, we'll start having some fun by creating the
fighters. See you there.
13. Create the Fighter Class: Before we start
creating the fighters, let's go to our main package
and the core folder, right click it and
go to New Package. Let's call this package
Objects and Press Center. And here as we we'll
create any classes that we'll use multiple
instances of in our game. This includes the fighters. So let's right click
the Objects package, go to New Java class, call it fighter and Pre Center. All right, now let's
set up some things we'll be needing
for the fighters. First, if we go
over to the assets and open the Sprites folder, we have these eight images with sprite sheet
in their names. A sprite sheet is
basically an image consisting of multiple
smaller images arranged in a tiled grid formation
and are often used to hold the frames for
a game objects animation. Each of these
sprite sheets holds the frames for a fighter's
animation state, such as block, kick,
punch, and walk. If we open, for example, punch sprite shehet dot PNG, you can see that it
contains six frames for fighter's punch animation, arranged in a grid of two
rows and three columns. And actually, all of
these sprite sheets have the same two by
three grid arrangement. Another thing to note that, even though it's hard to tell here, due to the backgrounds of the
frames being transparent, all of the sprite sheet images
are the exact same size. And each frame within them is the same size as all
the other frames. This isn't entirely necessary, but it definitely helps
when switching between the different textures
and animation, as we'll see in a bit. Also, you might be wondering why all the fighter textures
are colored white. The reason is that we can
actually add color to our textures and GDX
before we draw them. So we can reuse
these textures for each of the fighters by
simply changing the color. This saves us from having to use a whole bunch of different sprite sheets
for each fighter. But of course, this
is only possible because the fighters are simple, solid colour stick figures. Okay, let's go back to
the fighter class now. Let's create some variables that will be needing
for the fighters. First, we'll need a couple of
variables for the number of rows and columns in each
animation sprite sheet. I'll comment number
of frame rows and columns in each
animation sprite sheet. Then let's type private
static final int, frame rows equals two, frame cause, equals three. Next, we're going to add some
variables for things like a fighter's movement speed and the maximum amount of
life they can have. First I'll comment how
fast a fighter can move. Then type public
static final float, movement speed equals ten F. We'll be able to change this later if we want to
make the fighters walk faster or slower. Next I'll comment, maximum
life a fighter can have. Then type public
static final float. Max life equals 100 F. Next, amount of damage a
fighter's hit will inflict. Then public static final float. Hit strength, equals five F. Each time a fighter gets hit, the fighter's life amount will be decreased by the
hit strength amount. Next I'll comment, factor to decrease damage if a fighter
gets hit while blocking. Then public static final
float block damage factor equals 0.2 F. When a
fighter blocks an attack, we want them to still
get injured so that they won't just stand there
and block the entire game. However, we don't want
the fighter to get as injured as they would if they
hadn't blocked the attack. So we'll multiply
hit strength by block damage factor and decrease the fighter's life
by the resulting number. So in this case, with
a hitch strength of five and a block
damage factor of 0.2, the blocking fighter's life
will only be reduced by one. Okay, in the next
section, we'll create variables for a fighter's
distinguishing details. We only need two of these the fighter's name and
the fighter's color, which we'll be able to set
when we create the fighter. So first, let's type
private string name. Next, let's type private color. We want to make sure
to import the GDX do graphics version here by double clicking it.
Then type color. Next, we need to
add some variables relating to the fighters state, such as their animation state, position, current life
amount, et cetera. I'll come it state. And now
we're first going to create an Enum with values for each of the possible
animation states. So let's type public Enum
state open curly brace, then block, hurt, idle, co kick, lose, come a punch, come a walk, win. After this, we want to create
a private state state, which will hold the
fighter's current state. We also want to create a
state time variable to correspond to how
long the fighter has been in a particular state. This is important
for the animations. So let's type private
float state time. Another thing we
want to do is create a separate render state and render state time
for the fighter. This is because when we make it so we compose the game later, we want to freeze
everything on the screen, including any fighter
movements and animations. However, we still
want the player to be able to change
their fighters state such as to stop moving if they release
a movement button. We just don't want this
change to appear on the screen until they
unpose the game. Therefore, we need
to give the fighter an actual state and a
separate render state. This makes sense a bit later. For now let's type private
state render state. Then private float
render state time. Next we'll add a variable for the fighter's position inside the ring by typing
private final vector two, pressing Inger to import it, and position equals
new vector two. A vector two object
holds an X and Y value, which will be able to
change within the game. We also need one of these for the fighters movement direction. So let's type private final vector two movement direction. I was new vector two. For the X and Y values
of movement direction, we'll be using ones, zeros, and negative ones to indicate whether the
fighter is moving up, down, left, right, or
not moving at all. Let's now create a variable for the fighter's
current life amount by typing private float life. Next, let's type
private in facing. This variable will be
either negative one or one and will indicate which direction the
fighter is facing. We want the fighters to
always be facing each other, so we'll be changing
this value whenever the fighters walk past
each other in the ring. Finally, let's type private
Boolean made contact. This will indicate
whether the fighter has made contact
with their attack. As we'll see later, this is
important in order to prevent a fighter single attack from continuously
injuring the opponent. We only have one more section
of variables to write, which are for the animations. To create animations and LibGDX, we use the animation class. With the animation class, we can create a list of objects that represent an
animation sequence. So, for example, to create
one for the block animation, we can first type
private animation and make sure we import the
graphics g2d version here. We didn't need to type
a less than sign, let the animation know what type of object we want to
create a list of. We'll typically use texture
region objects for this. A texture region points to a particular region
in the texture. For example, in one
of our sprite sheets, each frame here will correspond to a separate texture region. So we can type Texture region here and press
Inger to import it, then type the greater than sign. Now we can call the
animation block animation. Now we simply need to do
this for each animation. Okay, here are the objects
for all eight animations. Next, we need to create a
constructor for the fighter. So let's type public fighter. We want to pass in
three parameters here. First, let's type SFS, press Inger to import it, then game, string
name, color color. Inside the constructor,
if you want to set this dot
name equals name, and this dot color equals color. We're now going to
create some methods for initializing all eight
of the animations. First I'll comment,
initialize the animations. Then type initialized
block animation. We want to pass in game
dot assets dot Manager. Of course, we're
getting an error because we haven't yet
created the method, let's go ahead and
create calls like this for the
remaining animations. Okay, with those
calls typed out, let's actually create these methods below
the constructor. First, let's do private, void, initialized
block animation. Then type asset manager. Press Enter to import
it, then asset manager. Inside the method,
we first want to get the Block Sprite sheet texture
from the asset manager. To do this, we can type
texture, press Enter, Sprite Sheet, equals
asset manager dot Git. Then we can pass
in the pointer to the Blocksprthet dot png
location that we created in the assets class a while
back by typing assets, pressing Enter, then
dot Block Sprite Sheet. Next, we need to get
the individual frames or texture regions
from the texture. To do this, we can
type texture region, followed by open and
closing brackets because this is going to be
an array of texture regions. Then type frames, equals G animation frames and
pass in sprite sheet. We're getting an error because
we haven't yet created the Git animation frames method,
which we'll do in a bit. But first, let's go down
the line and initialize block animation by typing block animation
equals new animation. Less than greater
than open Perthess. And here we need to pass in a desired frame duration
for the animation. This is the amount of time and seconds that we want
the animation to last. If we open up block
spritesheet dot P and G, each frame in here is
actually exactly the same. This is because I
didn't really want the fighter to move
while blocking, but if I change my mind later, I can easily change the
frames and the animation. So because the frames
are exactly the same, it doesn't really matter what we put as the frame duration, but I like to default to 0.05 F. Now we also need to pass in the frames
object that we created. Okay, now let's create this
Get animation frames method. First, let's type
private texture region, open and closing brackets, Get animation frames with the parameter texture
sprite sheet. To get all of the texture
regions from sprite sheet, we'll need to create
a two D array of texture regions by typing texture region with
two pairs of brackets. And this is just going to
be a temporary variable, so we can call it TIP or TMP we'll set this equal to
texture region dot split. Then passed in sprite
sheet, sprite sheet, dot get width divided
by frame calls. Co sprite sheet, Dogg heights. Divided by frame rows. What the split method
of texture region does is it takes the given texture
and splits it up into texture regions with
each region having the size of the tile width and tile height values
that we pass in. Dividing the sprite sheets
width by frame calls, lets the method
know that we want each texture region to be a third of the
sprite sheets width. Similarly, dividing the
height by frame rows, which is two makes each texture region half the
height of the sprite sheet. We now need to take
the two d array that the split method returns and
turn it into a one D array. To do this, let's create a one D texture region array
called frames. Let's set it equal to new
texture region, open bracket. And for the size of the array, we want frame rows
times frame calls. We now need to run through
the two D temp array and put its texture regions into
the one D frames array. To do this, we'll use
nested four loops. First, let's create
an int index variable and set it equal to zero. This will refer to the current
index of the frames array. Now let's type four,
int I equals zero, semicolon I less
than frame rows, semicolon I plus plus. For the columns,
let's type four, int J equals zero, semicolon J less
than frame cause, semicolon J plus plus. We can now type frames
brackets index equals TIP, brackets I, brackets J. We then need to
increment index by one. We can either do it
on the next line by typing index plus plus. Or we could simply put a plus plus after index and
frames index here. We have to be careful to put
the plus plus after index, not before it, so that it will increment
the index variable. After doing frames
index equals temp by J. Okay? And after
these four loops, you need to return
the frames variable. Now we just need to create
these initialized methods for the remaining animations. I'm going to copy this one and paste it above the Get
animation frames method. This will be for the
next animation, rt. So I'll change the
method to initialize Heurt animation and
make it so it uses the Hertzprt sheet and
the Hurt animation. For the frame duration, I find that 0.03 F works well here. Now, let's create a method
for the idle animation. I'll use 0.1 F for the
duration. Next up is kick. I'll leave the duration
at 0.05 F. Next is lose. Also with a 0.05 F duration.
Next we'll do punch. Again, I'll use 0.05
F. Next step is walk. For a walk, I'll use 0.08 F. Finally, we have win. Now I'll leave this one
on 0.05 F. Alright? I think this is a good point
to stop for this video. And the next one
will actually draw the fighters and start
animating them. See there.
14. Draw & Animate the Fighters: Now that we've set
up the variables and animations for the fighters, let's actually get
them onto the screen. First, after the
fighter constructor, let's at a public void
get ready method. And for the parameters,
let's type float position X, come a float position Y. We'll be calling
this method from the game screen class at the start of each
round of a fight, and we'll use it to reset
the fighter's state. First, let's set
both the state and the render state to the
idle animation state. We can do this easily
by typing state, equals render state,
equals state dot idle. We also want to do state,
equals render state time, equals zero F. Next, we'll set the
fighter's position to the position values that
we pass into the method. Because the position
variable is a vector two, we can do this by typing
position dot set position next come a position Y. And we want to set the
movement direction vector two variable to zero, zero, which indicates
no movement. So let's site movement
direction dot set 00. Next, we'll set life
to max life so that the fighter starts each round with the maximum life amount. Next, we'll just
set made contact to false since the fighter
hasn't made contact yet. After this method, let's create a public
void render method. For a parameter, we want
to type sprite batch, price Enter, then batch. We'll leave this method
empty for the moment. Let's also create a
public void update method with a parameter
of float Delta T. We'll leave this one empty
for the moment as well. Now let's head over to
our main game class, SFS. We're going to create
the fighter objects in this class instead of
the game screen class. And the reason is that when we add a main menu screen later, we're going to give the player the ability to choose
their fighter. Therefore, we will
need access to the fighter objects in both the main menu screen and
the game screen. So at the top, after
the creation of the game screen variables,
I'll comment fighters. Then cite public fighter, presenter, then player opponent. Next in the create method, after the lines for
loading the assets, I'll comment, initialize
the fighters. Tennis type player
equals new fighter. Now we need to pass in this, and we're going to
eventually provide a list of fighters for the
player to choose from, which will contain
different names and colors for each fighter. But for now for the name, I'll
go the string Slim Salon. And for the color,
I'll type new color. Make sure to import the
GDX dot Graphics version. Then I'll put F, 0.2 F, 0.2 f1f. This will make the
player a reddish color. That will create the opponent. So opponent equals new fighter, this, and for the name, I'll go thin diesel. And for the color,
I'll do new color, 0.25 F, 0.7 F 1f1f. This will be a bluish color. We've created the fighters, so let's work on rendering
them in the game screen. First, at the top,
we're going to add some variables for the starting positions
of the fighters. I'll comment fighters here. Then let's type private
static final float. Players start position
X equals 16 F. Next, let's type private
static final float. Opponents start position
X. Equals 51 F. And we want both fighters
to have the same Y starting position so that
they will be lined up evenly across from
each other in the ring. So for the Y position,
let's type fighter, start position Y equals 15 F. I came up with these
values mostly by using trial and error until I found
something that looked good. I could have used some math here to get them
positioned perfectly, but I found it wasn't necessary. And by the way, these
values are in world units. Next, down here after
the render method, Let's create a
private void update method and give it the parameter float Delta T. And
inside the method, type game dot player dot update, Pass in Delta T. Then let's do game dot opponent that
update and pass in Delta T. We haven't yet
filled in the update method of the fighter class, but we're going to use
this method to update things like the fighters
animation and movement. Now, this Delta T
variable here will actually come from
the float Delta parameter of the render method. Delta refers to how
many seconds have passed between the previous call to render and the current one. B render is called many, many times per second
in the game loop, Delta will be a
very small number. This number lets
us keep track of how much time has
passed in the game, and it's very important
for consistency and things like animations
and movements of objects. This is because render
might get called more often on a faster computer
than a slower one. So if, for example,
we tell an object to move a certain distance
every time render is called, and we don't take into
account how much time has passed since the
previous call to render, then the object will move faster on the screen
of a user with a fast computer than it would for someone with
a slower computer. So with that in mind, after
we clear the screen here, I'll comment update the game. Then let's make a
call to the update method, passing in Delta. Later on, we're only
going to pass in Delta here when the game
is actually running. If it's not running, such as when the user has
paused the game, we'll pass in zero instead. This will stop all
animations and movements. Okay, next, after we draw
the background Ring here, I'll draw the fighters. Let's make a call
to render fighters, a method that we
still need to create. Let's create the render fighters method, after the render method. By typing private
void render fighters. We're going to be adding
more to this method later, but for now, I'll
comment draw player. Then type game dot
player dot render, passing in game dot batch. Next, I'll comment
draw opponent. Then type game dot opponent, dot render, and again, passing game dot batch. We now need to go back
into the fighter class and fill in the render
and update methods. First, in the update method, you need to increment the
fighters state time by Delta T. I'll comment increment
the state time by Delta T. Then's type state time
plus equals Delta T. Next, we want to
set the fighters render state to
their actual state, but only if Delta time
is greater than zero. If Delta time isn't
greater than zero, the game may be paused, so we want the render
state to remain the same, which will freeze
all animations. So first, I'll
comment, only update the render state if Delta
time is greater than zero. Then's type I Delta
time greater than zero. Then's type renders
state, equals state. We also need to do render
state time equals state time. Don't worry if this
is confusing right now as it all makes sense later. Now in the render method, we're going to use the fighters
render state and render state time to determine which frame of which
animation to draw. First I'll comment, get the
current animation frame. Then we'll need to
create a texture region object for holding the current frame by typing texture region, current frame. Next, we need to create
a switch statement using render state by typing
switch, render state. Now we need to create a case for each type of
animation state. So first, let's do
case, block, colon. To get the current frame
of the block animation, we can first type
current frame equals block animation, DG key frame. We now need to pass
in render state time so that the
method can calculate which frame to get by
using the frame duration that we set when initializing
the animation earlier. Next, we can pass in a
Boolean value to let the method know whether
the animation should loop. If we put it true, then when the state time exceeds
the frame duration, the animation will start
over from the first frame. If we put it false, the
animation will stop. Although, as we saw earlier, our block animations frames
are all exactly the same. We want the animation
to continue as long as the
fighter is blocking, so let's start looping to true. Let's now add a break here so that I won't continue
to the next case. Then let's do case hurt colon. Followed by current frame equals hurt animation, DG key frame. Render stay time, and we only want the hurt
animation to play once. So let's put false for looping. That's do a break.
Then case idle. For this one, we'll
do current frame equals Idle animation,
DG ki frame. Render state time, and true
for looping then a break. Next is case kick. With current frame
equals kick animation do GkiFrame Render state
time false and a break. Next, we have case lose. I'm going to copy these two
lines for the kick case, paste them down here and change kick animation
to lose animation. Now I'll do case punch, paste the lines, and change
it to punch animation. Lex is case walk. I'll paste the lines, change
it to walk animation. And for a walk, I want
looping to be true. Finally, for the wind state, because it's the final state, we can put default colon. Now we can paste the lines, change it to win animation. Let's start looping to true. Because this is the final case, we don't need a break here. Okay, after the
switch statement, we'll draw the current
frame to the screen. First, we'll have to make
it so that the color of the frame corresponds
to the fighter's color. To do this, we can simply
type batch outside color, pass in the fighter's
color variable. Then we can draw the
current frame by typing batch, draw, current frame. Give the position by typing position dot X, position dot y, then and for the width, let's type current frame
dot get Region width, which gets the width of the
texture region in the frame. And we need to scale this by the world scale variable,
so we can type times, global variables, press Enter, then dot world scale. And for the height, let's type current frame, dot
get Region height. Times global variables
world scale. After this, we need to reset
the batch's color to white, which is the same as
telling it to not add any color to the textures that might get drawn
after this one. To do this, we can cite
batch dot set color, then pass in all ones. And before we run this, we
also need to call the Get ready method for each fighter
in the game screen class. So let's go back to
Gamescreen and for now, we can do this at the
bottom of the constructor. I'll comment, get
the fighters ready. Then type game dot
player dot Get ready. I pass in players
start position X. Come a fighter start position Y. Next I'll type game dot
opponent dot get ready. Opponent start position X. Come a fighter start position Y. Now let's run it and
see what happens. Okay, so our fighters are in their starting positions and their idle animations
are running. Awesome. We do want the fighters
to be facing each other, however, which
we'll fix in a bit. But first, we can try some of the other animations to
make sure they work. First, let's go to the
get ready method and change state to state
D block, then run it. Okay, that works.
Now let's try Hurt. Hurt doesn't loop,
so we just see it quickly run through the
animation once at the beginning. Now, let's try.
Kick. Looks good. Now, lose. Okay. Ponchs next. Now, walk. And finally, win. Perfect. Okay, let's
set it back to idle. Al right in the next video, we'll fix the problem
of the fighters not facing each
other. See there.
15. Update the Fighters' Facing: As we saw when we ran the
game in the previous video, the fighters are in
their starting positions and their idle
animations are running, but they're not
facing each other. So let's go ahead and fix this. First, after the update
method in the fighter class. Let's credit a couple methods for changing the
fighter's facing. We can first create a method
for making the fighter face left by typing public
void face left. And in here, we can simply put facing equals negative one. Now we'll do the
same for the face right method by typing
public void, face right. And put facing equals one. Next, we want to compare
the X coordinates of the fighters
positions each frame. The fighter with the
higher X should face left, and the fighter with the
lower X should face right. We'll do this in the update
method of game screen. However, we first
need to be able to access the fighter's position
from the game screen. To do this, we can
create a Gitter method for the position variable. So up here, after
the constructor, Let's type public vector
two Git position. Then we just need to
type return position. Now let's go over to GameScreen
and in the update method. After calling the
fighters update methods, I'll comment, Make sure the fighters are
facing each other. Then let's type Igamedtplayer, dot git position dot X is less than or equal to game dot opponent
dogiposition dot X. And if this is the case, you need to call game
dot player dot face right and game dot
opponent Da face left. If the fighters coordinates
happen to be equal, it doesn't really matter
which direction they face. So we'll just have
the player face right and the
opponent face left. Now we need to put an outs. Then type game dot player, dot face left, and game dot
opponent, Da face right. Now, in order for the facing
changes to actually work, we need to go into the render method of the fighter class. And where we draw
the current frame, we're going to need to use a different version
of the draw method. If I remove the open
parenthesis and type it again, we can see all the versions
of the draw method. We want the one where we
can pass in texture region, region, float X, float
Y, float origin, X, float origin Y, float width, float height, float scale X, float scale Y, and
float rotation. Scale X, let's just shrink or grow the texture
horizontally, and scale Y, let's
just do so vertically. You can also rotate the texture using the rotation parameter. The default values for scale
X and scale Y are one, which means no scaling. And for rotation,
which is in degrees, the default is zero degrees,
which means no rotation. We won't be rotating
our texture, so we'll leave rotation on zero. For scale X and scale Y, if we use a negative
number for either of them, the texture will essentially be flipped as well as scaled. So if we use negative
one for scale x, the texture will
remain the same size, but will be flipped
horizontally. This is what we
want whenever the fighter should be facing left. Also, origin X and origin Y here represent the point at which the texture will
scale or rotate. This will make sense in a bit. For now, let's go ahead and add these new parameters
into our draw line. First, before we
pass in the width, we need to pass in
origin X and origin Y. Let's just use the defaults
of 00 for the moment. Next, after we pass
in the height, we need to pass in scale X, scale Y, and rotation. Because we want to
flip the texture horizontally when the fighter should be facing left and not flip it when the fighter
should be facing right, we can simply pass
in the facing value. For scale Y, we never want to flip the texture vertically,
so let's just put a one. We don't want to
rotate the texture, so we'll put a
zero for rotation. Now let's go ahead and run it. So the fighters are now both facing in the correct direction, but the opponent fighter
has moved to the left. This because we set
the origin to 00, which is the bottom
left of the texture. So the texture, which was
originally in this area, got flipped horizontally
over its bottom left corner. To fix this, we need to put origin X at the center
of the texture. So back in the code, let's
replace zero for origin X with current frame dot G
region width times 0.5 F. And we also need to
multiply it by world scale, so times global variables
dot WorldScale. And because we're not flipping
the texture vertically, we can just leave
origin Y at zero. And I'll go ahead and
clean this line up a bit. Now let's run it.
Okay, the fighters are in their correct
positions again, and they're facing each other. They will also
continue facing each other if they pass each
other in the ring. This leads us to the next video, where we will learn
how to process keyboard input from the player so they can do things
like make their fighter move around and
attack. See there.
16. Handle Keyboard Input: In order to start handling
input from the player, we need to go to our game
screen class at the top, we need to implement
another class called Input Processor. After implement
screen, let's type Input processor and press
Enter to import the class. This requires that we
implement several methods. Let's hover over this line. Click Implement methods.
Then click Okay. Now at the bottom of the class, we have eight new methods. All of these methods are used for dealing with
certain types of input, such as keyboard input, touch input, and mouse input. In this video, we're going
to focus on keyboard input, giving the player
the ability to use different keys for things like making their fighter
move and attack. The methods for dealing with
keyboard input are keydown, key up, and key typed. Keydown is called whenever
the player presses the key, key up is called when
they release a key and key typed is called when the
player types in a character. Key typed is mainly
for processing some texts that a player might type into a text
field, for example. We won't be needing
this in our game, so we'll ignore the
key typed method, but we'll be using both
keydown and keyup. And we'll begin by
using these methods to let the player
move their fighter. But before we do this,
we need to go to the fighter class and
add a few methods. First, we need a method
that we can call to change a fighter's
animation state, such as from idle to walk. So after the face right method, let's type private
void change state with a parameter of
state new state. Inside the method, we first want to type state equals new state. We also want to reset
the state time. So next we'll type state
time equals zero F. That's it. Next, we need to create some methods for
setting a fighter's movement. First, let's create a private
void set movement method. With parameters
float X and float Y. And here we need to set
the movement direction variable to the X and
Y values by typing movement direction dot set X Y. We now need to determine
whether to put the fighter in the walk
state or the idle state. If both X and Y are zero, meaning the fighter
shouldn't move, and the fighter is currently
in the walk state, we need to change
the state to idle. If on the other hand, the
fighter is currently in the idle state and either X
or Y isn't equal to zero, the fighter needs
to start moving. So we'll change
the state to walk? So first let's type if state
is equal to stake dot walk, and X is equal to zero, and Y is equal to zero, then change state and
pass in state dot idle. Now let's do Asif. State is equal to state dot
idle and open parenthesis, X is not equal to zero, or Y is not equal to
zero, closed parenthesis. Change states dot walk. If neither of these is the case, we won't change the
fighter's state. So, for example, if
the player's fighter is currently in the punch state, we don't want the
punch animation to get cut off if the player
presses a movement key. However, we will have
the fighter start walking after the punch
animation is finished, which we'll take care of
later in the update method. Next, we need to create some public methods
that we can call from game screen to move the fighter in
different directions. First, let's type
public void, move left. And here, let's
type set movement. Then pass in negative one for X, and for Y, let's pass in
movement direction Y. Passing in negative one for X will cause the
fighter to move left, and we don't want this method to affect the fighter's
vertical movement, so we pass in their current
vertical movement for Y. Next, let's do public
void, move right. Set movement one
movement direction Y. Now we need two methods
for vertical movement. So let's type public void, move up. Set movement. This time we want to keep the horizontal
movement the same, so we'll put movement
direction dot X for X. Then to move up, we'll
put a one for Y. Finally, let's do public
void and move down. Set movement, movement direction dot X comma negative one. We now need to create
some methods to stop the fighter from
moving in each direction. First, let's type public
void, stop moving left. And for these methods, we only want to stop the
fighter from moving in a certain direction if they are currently moving
in that direction. This is because, for example, the player might press
the left movement key, then without releasing
the left key, they might press the right
key to start moving right. If the player then releases the left key while still
pressing the right key, we don't want to stop
the horizontal movement. This will make sense
when we add some code to the key up and key
down methods in a bit. For now, to check whether the fighter is currently
moving left, let's type if movement
direction dot X is equal to negative one. And if this is the
case, you want to stop the horizontal movement
by typing set movement, zero, and keep the
vertical movement the same by typing
movement direction dot Y. Next, we'll do public
void, stop moving right. Check whether the
fighter is currently moving right by typing I movement direction
X is equal to one, then set movement, zero
moving direction Y. For vertical movements, let's type public void,
stop moving up. If movement direction
Y is equal to one, set movement movement
direction.x0. And finally, public
void, stop moving down. If movement direction Y
is equal to negative one, set movement moving
direction.x0. A to actually put that movement
direction into effect, let's go into the update method. At the bottom of the method, we want to check whether the
fighter is in the walk state by typing if state is
equal to state dot walk. And here I'll comment, if
the fighter is walking, move in the direction of the
movement direction vector. We now need to move
the fighter's position in the direction of the
movement direction variable, and we want to do
so at the speed of movement speed, which
we said earlier. However, movement speed is how many world units
to move per second. So we'll need to
multiply this value by the Delta time variable
and the update method. We learned previously that this is the amount of time that has passed between the current game render and
the previous one. So to change the
fighter's position, let's go back in here and first type position
dot X plus equals, moving direction dot X times movement speed
times Delta time. So if moving direction
dot X is negative one, position dot X will be
decreased by this amount, causing the fighter
to move left. If it's zero, position
dot X won't change, and if it's one,
position dot X will be increased by the amount causing the fighter
to move right. And we also need to do
this for position dot Y. So let's cite position
dot Y plus equals, moving direction dot Y. Times movement speed,
time stilt the time. Alright, with all of
that out of the way, we can go back into the
game screen class and handle the keyboard input to get the player's
fighter moving. First, at the top of key down, I'll comment, check if player has pressed
that movement key. Okay, for the movement, we're
going to let the player use either the arrow keys
or the WASD keys. This is pretty common
in computer games. So first, we need
to check whether the player has pressed
one of these keys. When the key down
method gets called, the key that gets pressed is passed into the
keycode parameter. So we can use keycode
to check whether the pressed key is one of
the keys we're looking for. Let's start with
the left movement, which the player
can do by pressing either the left arrow
key or the A key. So let's type if keycde
is equal to input, press Enter to
import it, dot keys, dot Left, or key code. Is equal to input dot keys A. Input dot keys here holds variables for all the
keys on the keyboard, and we need to check keycode
against those variables. Now in here, we can call game
dot player dot move left. We now have to check for write
movement by typing El SIV, Keycode is equal to input
dot keys dot right. Or keycode is equal to,
and for write movement, we'll use input dot keys
dot D. Then in here, let's type game dot
player Move right. Now for vertical movement, we want to use a
separate I statement. This is because we want
to let the player move both horizontally and
vertically at the same time. After this I statement,
let's first check for up movement by typing
if key code is equal to input dot keys dot
up or key code is equal to input dot ks dot W. Then put
game dot player dot MOV U. Finally, for down
movement, let's type SIV. Keycode is equal to
input dot keys dot down. Or keycode is equal to
input dot keys dots. Then game dot player
dot move down. One more thing we want
to do in this method is change return
false to return true. Let's let the input
processor know that we handled the key
down events ourselves. Otherwise, it might
do some extra stuff in the background
which we don't want. We next need to use
the key up method to determine if a movement
key has been released, and if so, we should stop
moving in that direction. First, let's change return
false to return true. Then above it, I'll comment. I player has released
a movement key, stop moving in that direction. Now, I'm actually going to
copy the two F statements and key down and paste
them and key up. Then I'll change
all the move method calls to the respective
stop moving calls. So stop moving
left for this one. Stop moving right. Stop moving up and stop moving down. Okay, I think we
should be ready to go now, so let's give it a try. It doesn't seem to
be working yet, and that's because we
forgot one important step, which is to tell LibGDX to use the game screen class
as the input processor. And we need to do this in
the game screen show method. This method gets
called every time the game screen becomes the
visible screen in our game. The reason we need
to set Gamescreen as the input processor and the show method is
that we're going to be switching between screens at
various times in our game. So it's just by going
to the main menu screen or the setting screen
to change something. And each time we do
so, we'll need to set the current screen
as the input processor. Okay? So in the show method, I'll comment,
process user input. Then we simply need to type GDX, press Enter to import it, then input dot set Input
processor and pass in this. Now should work when we run it. Okay, cool. We can move our
fighter around the ring. We do have a couple
of problems, though. The fighter appears
to be in front of the front ropes and we can
walk outside of the ring. We'll fix both of these in
the next video. See there.
17. Define the Ring Bounds: Let's first fix the problem of our fighter appearing in front
of the rings front ropes. To do this, we simply need
to draw our front rope stop PNG image after
we draw the fighters. We actually already
got this image from the asset
manager earlier in the create game area method and put it in front
ropes texture. So in the game screens
render method, after the call to
render fighters, I'll com it, draw
the front ropes. Then we can draw the front
ropes by first typing game dot batch dot draw
front ropes texture. Now for the position, if you open the front ropes
dot PNG image, it's hard to tell here, but I actually made
the image the same width as the background image with transparent
padding on the sides. Also, the bottom of the poles here reach the bottom
of the screen. So like with the
background texture, we can use 00 as the position and it will line
up correctly on the screen. For the width, let's type front ropes texture
dot Get W times global variables rolled scale. And for the height,
front ropes texture, D get height times global
variables, that roll scale. And if we run the game now, and move the fighter down here. The front ropes now appear
on top of the fighter. We can still walk
outside of the ring, though, so let's fix that next. First, we want to go to
the top of game screen and set up a few variables to
define the ring bounds. We'll do this in the
background slash Ring section after the texture variables. First, we need to
define the minimum and maximum X and Y values
for the ring bounds. These need to be in world units, and I basically use trial and error to find values
that work well. First is type private
static final float. Ring Mn X equals seven F. Next, private static final float. Ring max X equals 60 F. Then
private static final float. Ring men Y equals four F and
private static final float. Ring max Y equals 22 F. Now, if you open the
background image, the sides of the
ring actually slope inward at the top to
account for a perspective, and we need to take
the slope into account when defining
the ring bounds. By subtracting this
point from this one, I found the slope
to be about 3.16. So back in game screen, aside
private static final float, ring slope equals 3.16 F. Next, in every frame of the game, we need to check whether
either fighter's position has exceeded the ring bounds. We'll do this in
the update method. First, at the
bottom, I'll com it, keep the fighters within
the bounds of the ring. Then we're going to call
a method that we'll create called keep
within ring bounds. And we'll pass in a
fighter's position. So for this one, let's do game dot player
dot get position. We also need to call this method for the
opponent fighter, so keep within ring bounds. Game opponent, Dk get position. Now let's create the keep within ring bounds method after Update. Let's type private void, keep within ring bounds. Then vector two, press Enter
to import it, then position. And here we'll have a
couple if statements. Checking the position against the Y bounds is the easiest,
so let's start with that. We can type if position dot
Y less than ring men Y. Then position dot Y
equals ring men Y. So if the fighter goes below
the bottom of the ring, we'll move them back up to
touch the bottom of the ring. Next, we'll do out
Sif position dot Y, greater than ring max Y. Position dot Y
equals ring max Y. Below this, we'll do an F
statement for the X bounds, and we'll need to take into account the slope of the ring. So first, let's type I
position dot X less than position dot Y divided by
ring slope plus ringmX. The position dot X equals position dot Y divided by
ring slope plus ring Min x. This expression here takes the Y coordinate of the
fighter's position, divides it by the slope
of the ring side, and adds the minimum X
coordinate of the ring to determine the
lowest X coordinate that the fighter is
allowed to have. Now for the right
side of the ring, let's do Sif position dot X greater than position
dot Y divided by, and because the ring slopes in the opposite direction
on this side, we need to put
negative ring slope, then plus ring max X. And here, let's type
position dot X equals position dot Y divided by negative ring slope
plus Ring Maxx. If you run the game
now and walk around, our fighter is now forced to stay within the
bounds of the ring. Also, if we pass the opponent, we can see that they're
facing updates correctly. However, if we go
below the opponent, the opponent is still being
drawn on top of the player. This, of course,
isn't what we want, so let's fix that really quick. To fix this, we need to go into the render
fighters method. And we need to use the Y
coordinates of the fighters positions to determine which
fighter to draw first. First, let's select
all of these lines and cut them with
Control X. I'll comment, use the Y coordinates
of the fighters positions to determine which
fighter to draw first. Then we need to
compare the fighters Y coordinates using
a NIF statement. So let's type if
game dot player dot g position dot Y Greater
than game dot opponent, dog position dot Y. Now let's paste
the lines we cut. This would draw the player
first, then the opponent, which is what we want because
the player is higher up in the ring. Now we can do ts. Then paste again and switch
the locations of these lines. If we run the game now, the
fighters get drawn correctly. Okay, cool. Next,
we'll continue with the keyboard input
processing by allowing the player to press keys to
attack and block. See there.
18. Attacking & Blocking: Before we give the
player the ability to press keys to
attack or block, we need to create
a few methods in the fighter class for setting the fighters state to each of the attack and block states. So in the fighter class, after all of the movement methods, let's create a public
void block method. We actually only want to change the fighters state to block if they are currently
in either the idle state or the walk state. This is because if the fighter
is in an attack state, we don't want to interrupt
their attack animation. Also, if the fighter already happens to be in
the block state, there's no point in
changing the state. So this type of
state is equal to state dot idle or state is
equal to state dot Walk. Change state state dot block. We also want to create a method to make the fighter
stop blocking. We'll call this method
when the player releases the block button. For this method, let's type
public void Stop blocking. And we only want to stop blocking if the player
is actually blocking. So in here, let's first type if state is equal
to state dot block, next, we want to determine
whether the fighter should go into the idle
state or the walk state. That's because the
player might be pressing a movement
key while blocking, and we want their
fighter to immediately start walking when
they stop blocking. To determine whether the
fighter should start walking, we can simply check if either
moving direction dot X or moving direction dot
Y is not equal to zero. So first, I'll comment, if the
movement direction is set, start walking,
otherwise, go to idle. Then this type, I moving direction dot X is
not equal to zero, or moving direction dot
Y is not equal to zero. Change state state dot walk, else, change state
state dot idle. Okay, one more block
method we want to create is one that returns whether the fighter is
currently blocking. This will be helpful
when we start coding the opponent AI later on. For this method, we can type
public bullying is blocking. Then return state is
equal to state dot block. So if state is equal to block, it will return true, otherwise
it will return false. Okay, now let's move on to
the methods for attacking. Let's first create
one for punching by typing public void punch. Like with blocking, we
only want to change to an attack state
if the fighter is currently in the
idle or walk state. So in here, let's type
if state is equal to state dot idle or state is
equal to state dot walk, then change state
state dot punch. Now let's create a
method for kicking. That's called a
public void kick. Now we can simply copy
and paste the code from the punch method and change state dot punch
to state dot kick. Like with blocking, we want a boolean method that returns whether the fighter
is attacking. For this, we can type public
boolean is attacking. Return state is equal
to state dot punch. Or state is equal
to state dot kick. Next, because the punch
and kick animations are only supposed
to happen once, when the animation is finished, we want to put the fighter
in either the idle state or the walk state if their
moving direction has been set. We can do this in
the update method by adding an CIF to the
F statement here, where we check if the fighter
is in the walk state. What we want to do is check
whether the fighters in the punch state and the
punch animation is finished, or if they're in the kick state and the kick animation
is finished. To check if an
animation is finished, we call the is animation
finish method of the animation class
passing in the state time. So here we want to type open Parthess St is equal
to state dot punch. And punch animation. Do
I animation finished. State time. Closed parenthesis, closed parenthesis,
or open parentheses. State is equal to
stake Dot kick. And kick animation. I animation
finished. State time. And here I'll comment.
If the animation is finished and the
movement direction is set, start walking,
otherwise go to idle. And we want to check if moving direction has been
set by typing, I moving direction dot
X is not equal to zero, or moving direction dot
Y is not equal to zero. Then change Stsatedt walk. Then else change staatesidl. Okay, so if the fighter is in an attack state and the
animation has finished, we either put the fighter in
the walk state if they're moving direction has been
set or in the idle state. Now we can move on to
the game screen class and add the keyboard processing for attacking and blocking. First in the key down method, we're going to create
an I statement for the block and
attack keys that is separate from the I statement
for the movement keys. This is because later on, we're going to make
it so that each fight can have up to three rounds. At the beginning of each round, we'll have a short delay
where it first tells us the round number on the
screen then says fight. During this delay, we don't want the fighters to be moving
around in the ring. So we'll make it so that
the movement keys will only be processed after
the round has begun. However, just for fun, we'll let the player attack and block while waiting for
the round to begin. Okay, so before the
return statement, I'll comment, check if player has pressed a
block or attack key. Now for blocking, we're
going to use the B key. For punching, we'll
use the F key and for kicking the V key. We'll use these keys
because they're pretty close to the WASD keys, so they'll be easy to reach if the player is using
the WASD keys to move. So first, let's type if keycde is equal to
input dot keys dot B, then game dot player dot block. Lexus type Osif keycode
is equal to input, do keys F. Game dot
player dot punch. And finally, Osif keycode
is equal to input, Da keys V. Game dot
player dot kick. Now, because the attack
animations only go through one time when the
player presses an attack key, we can ignore when the
player releases the key. For block, on the other hand, we want the fighter to continue blocking until the player
releases the block key. Therefore, like with
the movement keys, we need to deal with the block
key and the key up method. Before the return
statement, I'll comment, I player has released the
block key, stop blocking. Then this type, I key code is equal to input
dot keys dot B, game dot player
dot Stop blocking. Okay, now we can run the game
and give these keys a try. We can punch, kick, and if we hold down the
B key, we can block. And when we release the B
key, we go back to idle. Or if we hold B, then hold a movement key
before releasing B, we immediately start walking. Awesome. Okay, in
the next video, we'll make it so our
punches and kicks actually have an effect on
the opponent. See you there.
19. Getting Hit: In this video, we'll make it
so when a fighter gets hit, they have a visible reaction and also lose some of
their life amount. And to do so, we first need to add a few more things
to the fighter class. First, when one of the attack
methods has been called, we want to set the made
contact variable here to false because if the fighter
has just started an attack, of course, the attack hasn't yet made contact with
the other fighter. We'll soon see why it's important to use this
made contact variable. For now, let's go down
to the punch method, and after the call to
change state, I'll comment, just started attacking, so
contact hasn't been made yet. Then type made
contact equals false. And we can copy and paste
this into the kick method. We also want to create
a couple extra methods for the made contact variable, one for setting it to true
and one for returning it. So first, let's type
public void, make contact. And in here, let's set
made contact to true. For the return
method, we can type public BollyanH made contact. Return made contact. Another method we want to
create is one for returning, whether the fighter is
actively attacking. There are two
conditions that must be met for the fighter to
be actively attacking. First, made contact
needs to be false because we only want the attack to hit the other fighter once. Otherwise, the other
fighter will constantly get hit during the entire duration
of the attack animation. And second, if we open one of the attack animation sprite
sheets like the one for kick, we're going to make it so the attack is only active during the middle third
of the animation instead of the entire animation. This isn't entirely necessary, but I find it looks better
than having the attack hit the opponent the
instant the animation starts or in this case, after the fighter has already
put their leg back down. Also, having the attack only be active during
the middle frames gives the other fighter
the chance to move out of the way or block
before getting hit. Okay, so with that in mind, let's go back to
the fighter class. And for the method, let's cite public bullying
is attack active. And here I'm going to comment, the attack is only active if
the fighter has not yet made contact and the attack animation has not just started
or is almost finished. Can was first check if the fighter has already
made contact? We can do this using the
has made contact method. So let's type I
has made contact. And if so, we want
to return false. Next, we want to check
which attack state the fighter is in and determine if the animation
for that state is within the middle third of
the animation duration. Let's first check if the fighter is in the punch state by typing Osif state is equal
to state Dot punch. Now to check how far into the punch animation the
fighter currently is, we can compare the state time variable to the punch
animation's duration. To get the duration
of an animation, we call the G animation duration method of
the animation class. So what we want to do here is return whether state
time is greater than one third of the
animation's duration and less than two thirds of it. So let's type return state time, greater than punch animation, dot G animation duration, times 0.33 F and state time, less than punch animation, DG animation duration time 0.66 F. So if state time is between the first third and
the last third of the animation, it
will return true. Otherwise, it'll return false. Now let's do the same
for kick by typing Osif. State is equal to
state dot kick. Return state time greater
than kick animation, get animation duration time
0.33 F and state time, less than kick animation, do get animation duration. Times 0.66 F. Finally, we want to put outs
at the bottom. Then return false because this means the fighter
isn't in an attack state. Next, we want to create a method to call when the
fighter gets hit. This method will reduce the life of the fighter by
a certain amount. And if the fighter's life
has fallen to zero or below, the method will make
the fighter lose. So for this method, si
public void, get hit. And we want to give it the
parameter float damage, which we'll use to determine by how much to reduce
the fighter's life. Now, there are a few animation
states that the fighter could be in where we don't
want the fighter to get hit. First, if the fighter
is in the hurt state, they've already just been hit, so we don't want them to
immediately get hit again. We also don't want
them to get hit if they're in the
win or lose state, because in either
of these cases, the round or the game
is already over, so there's no point in
the fighter getting hit. We also don't want to interrupt their win or lose
animation. Okay? So first, let's type if state
is equal to state dot HRT, or state is equal to state dot win or state is
equal to state dot lose. Return. This will
cause the method to finish straightaway and not perform the next lines of code. Next, we want to reduce the
fighter's life either by the full damaged amount or
if the fighter is blocking, we'll only reduce their life by a fraction of
the damage amount. To calculate this fraction, we'll multiply the
damage value by the block damage factor variable that we
created a while back. First I'll comment, reduce
the fighter's life by the full damage amount or a fraction of it if the
fighter is blocking. And we can do this with
a ternary operator by typing life minus equals. State is equal to
state do block. Question mark, damage
times block damage factor, colon, damage. This is the equivalent of doing I state is equal
to state do block, the reduce life by damage
times bock damage factor, else reduce it by damage. Next, we want to check
if the fighter's life has dropped to zero or below, and if so, we make
the fighter lose. So let's type if life less
than or equal to zero F, and I'll comment, I no
life remains, lose. We're actually going
to create a public method for making the fighter lose because we
might want to make a fighter lose at some
other point in the game. For example, when we
add rounds to the game, we're going to make the
rounds have a time limit. And if neither
fighter's life has reached zero before
the time runs out, the fighter with
more life will win, and the fighter with
less life will lose. Okay, so let's make a call to a lose method, which
we'll create in a bit. And if the fighter does
still have some life left, we want to start the
fighter's hurt animation, but only if the fighter
isn't currently blocking. So here let's put OutsiF State is not equal
to state dot block. Now comment, if not
blocking, go to Hurt state. Then let's type change
state state dot HRT. Next, we need to create
the Lose method. So down here, let's
type public void Los. In this method, we first want to change the fighter
state to the Los state, so let's type change
state state dot Loos. We also want to
set life equal to zero F in case it has
dropped below zero. This will prevent any problems
from occurring when we display the life amount for each fighter on
the screen later. Before we move over
to game screen and put these methods to work, we want to do one
more thing in here. In the update method,
where we check if the fighters punch or kick
animation as finished, we also want to check if their hurt animation
is finished. So before the final parenthesis
of the I statement, we can put another or
then open parenthesis, State is equal to stat
HRT and RT animation. D is animation
finished. State time. And that's it. Now let's
move over to game screen. In the game screen class, we first want to create
a method for checking whether the fighters are
within contact distance. This basically means that
the X and Y coordinates of the fighters
positions are within certain limits in which if
one of the fighters attacks, the other fighters
should get hit. So first, let's go
up to the top of the class and define
these limits. We can put these in
the fighters section. Like with the fighter
starting positions, I came up with these values
using trial and error, and they will be in world units. For the first variable, let's type private static final float, fighter contact distance
X equals 7.5 F, and for the second one,
private static final float, fighter contact distance Y, equals 1.5 F. So if the fighters
X coordinates are within 7.5 rod units of each other and the Y coordinates are
within 1.5 rod units, the fighters are in
contact distance. Now we'll create a method to
check if this is the case. To create the method,
let's go down here below the keep within
ring bounds method. Let's type private bullying
or within contact distance. And for the parameters,
let's type vector two, position one, vector two, position two, which will pass
in the fighter's positions. And here I'll
comment, Determine if the positions are then the distance and which
contact is possible. Now, we want to
credit a couple of variables to hold the
distances between the X coordinates
of both positions and the Y coordinates
of both positions. So first, let's type
float x distance equals position one dot X minus
position two dot X. One problem we
might have, though, is that if position one dot X is less than position two dot X, the result of this
will be negative. All we care about
is the distance between the points,
not the direction. So we want the result
to always be positive. To do this, we can put this
calculation inside a call to the ABS method of the math class that Java provides for us. So just before the calculation, let's state math dot
abs open parenthesis and put the closing parenthesis
before the semicolon. Because the math class is in
the Java dot Ling package, it's automatically
included in our project, so we don't have to import it. Now we want to create
the variable for the y distance by typing float y distance equals math dot abs, position one point Y minus
position two point Y. Next, we simply want to
type return, X distance, less than or equal to
fighter contact distance X and Y distance, less than or equal to
fighter contact distance Y. Okay, now let's go up
to the update method. And I'll comment, check if the fighters are within
contact distance. Now we want to type I within contact distance and pass in game dot player dog position. Ca game opponent docu position. Next, we want to check whether the player is
actively attacking, which we can do by calling the I attack active method
that we created. So let's type I game dot
player dot I attack active. And here I'll comment, if
the fighters are within contact distance and player is actively attacking,
opponent gets hit. Now we can make the
opponent get hit by calling game dot
opponent dot get hit. And for the damage, we'll
use the public static hit strength variable we
created in the fighter class. So let's type fighter, press Inger to import it,
then dot hit strength. Okay. Now, once we get
the Hut on the screen, we'll be able to see how
much life each fighter has. But for now, for
testing purposes, we can just print
the opponent's life. However, first, we need
to add a method in the fighter class for getting
the fighter's life amount. So let's go to the fighter
class and near the top, after the Gibosition method. Let's tie public
float, get life. Then return life. Now
back in game screen, we can type system dot dot
print LLNopponents Life. Colon space plusgamedt
opponent dot GLIC. Now we can run the game
and see what happens. Okay, so when we
hit the opponent, they go into the Hertz state. However, as you can
see in the output, sometimes it prints
the opponent's life multiple times when
we hit them once, and the only reason
the opponent isn't losing life multiple
times with each hit is because the duration of
the hurt animation happens to be longer than
the active part of the attack animations. If we were to go into
the fighter class, then go down to the initialized
Hurt animation method and change the duration to
something lower like 0.0 03f, and maybe, for
example, the punch animation to something
higher like 0.2 F. Now if we run the game
and punch the opponent, They lose life multiple
times with each hit. The reason this happens
is that we forgot to set the player's made
contact variable to false by calling the
make contact method. So, back in the
game screen class, after these lines, I'll comment, deactivate
players attack. Then it's called game dot
player dot make contact. Now if we run the game
and hit the opponent, They only get hit
once per attack. And if we keep
hitting the opponent until all their life is gone, they go into the lose animation. When one of the fighters goes
into the lose animation, though, we want
the other fighter to go into the win animation. For this, we'll need a method for determining
whether a fighter has lost and a method for putting a fighter
in the win state. So let's go back to
the fighter class and after the lose method, let's type Public
Bolling has lost. Then return state is
equal to stake lose. Next, for the win method, let's type public void win. Then simply called
change States dot win. Now back in game screen. After we deactivate
the player's attack, I'll comment, check
if opponent has lost. Then it's type I game dot
opponent that has lost. Now I'll comment, I opponent
has lost, player wins. Then let's type game
dot player dot win. Okay, now if we run the game and hit the opponent
until they lose, We go into the wind state. We can also temporarily
put the opponent in the block state at the
start of the game in order to test whether
hitting them will only reduce their life by 20%
of the hit strength. To do this, let's go
into the show method. And somewhere in here, let's type game that
opponent, that block. Now when we run the game
and hit the opponent, Instead of reducing
their life by five, it only reduces it by one. It also doesn't put the opponent in the hurt state,
which is good. All right, we can close
this out and remove the line for putting the
opponent in the block state. We can also go ahead
and remove the line in the update method that
prints the opponent's life. Let's not forget to go
to the fighter class and set the HR animation
duration back to 0.03 F and the punch animation
back to 0.05 F. Okay, in the next video, we'll
work toward displaying the Hud so that we can see some useful
information on the screen, including the amount of life each fighter has. See you there.
20. Generate the Fonts: The Hud or heads up
display is the part of the screen where some
important information about the game is
displayed to the player. In our game, we're going to put the health bars of each
fighter at the top of the screen with the
player's health bar at the left and the
opponents at the right. We'll also put the names of the fighters inside the health bars. Some other information we'll
display are a ratio of how many rounds the player has won versus how
many they've lost. We'll display the current
difficulty of the game, which the player will
be able to change, and in the center of the Hud, we'll display the amount of time remaining in
the current round. So we're going to be
using a lot of text. Therefore, the first
thing we need to do is set up the
fonts we'll use. The standalone LibGDX library comes with a class for creating
fonts called Bitmap font. However, bitmap fonts
rely on images, and the default bitmap
font images and LibGDX look very pixelated
when scaled a lot. We could create our own
Bitmap font images, but that would be a very
time consuming process. Instead, we're going to use
the free type extension, which we included as an add
on when creating our project. With free type, we can provide a font file and a
desired font size, and it will generate bitmap font images for us straightaway, using the font file and size. This means we can use pretty
much any font we want. And our game will be using
the Roboto regular font. We can replace this with a
different font if we want. It just has to be a TTF
or true type font file. If you go to the assets class, when we created variables
for the font assets up here, we learned that
we're going to use our font file to generate
three different fonts, small font Da TTF, medium font Da TTF, and large font D TTF. We'll do this using
a load fonts method. So after the load
gameplay assets method, let's type private
void Load fonts. The first thing we need
to do in here is tell the asset manager how to work with free type and TTF files. It's not important
that we know exactly how this works
because it will be the same for any
project in which we generate fonts
using free type. So first, we need to
create something called a file handle resolver by
typing file handle resolver, pressing Enter to import it. Then resolver equals new
internal file handle resolver. Press Enter to import it. Next, we'll set an asset
loader for free type in our asset manager
by typing manager, dot set loader, free
type font generator. Presenter, then Do class, new free type font
generator loader Presenter, and pass in the
resolver variable here. We also need to set
an asset loader for loading bitmaps from TTF files. So let's type manager dot set loader, Bitmap
font, presenter, dot class, TTF New
free type font loader, presenter and pass in resolver. Okay, with all of that
complicated stuff out of the way, let's generate and
load some fonts. First, let's do the small font. I'll comment, Load
the small font. We first need to create a free type font loader
parameter variable. This variable will let us set things like the font file we want to generate the font
from the size of the font, and add a border around
the letters if we want. So to do this, let's type free type font loader parameter. Press Enter to import it. Let's call it small font. Then equals new free type. And instead of typing all of this out, we can
just press Enter. Next, we want to give small font the name of the font
to generate from, what would be the
Roboto regular file. To do this, we can type
small font font file name equals Roboto regular. Next, we'll give
the font a size by typing small font
dot font parameters, dot size equals,
let's make it 32. We can always change
this later if we want. We'll mainly use the small font for displaying the
names of the fighters. We don't want a
border for this font, so we don't need to change any other settings
at the moment. All we need to do now is
have the asset manager use the small font settings to
generate bitmap font images into the small font dot TTF file and add the file to the
asset loading queue. This sounds complicated,
but to do it, you just have to call
manager dot Load, then pass in the small
font static variable. Then Bitmap font dot class. Then the small font
perimeter variable. And the asset loaders we set up earlier will take care
of everything for us. Lets us load the medium font. I'll comment Load
the medium font. Then we can first copy
these four lines for the small font and paste
them for the medium font, then change the small font
variables to medium font ones. For the size of the medium
font, let's go with 106. Among other things, we'll use the medium font to display the time remaining for
the current round. We want to put a border around the characters in this font. To do this, after
setting the size, let's site medium font
dot font parameters. D border width equals. And for the width, I found
that four looks pretty good. We can also set the
border color by using medium font, dot font
parameters, that border color. But the default color is black, which is what we want, so
we don't need to change it. Finally, let's load
the large font. I'll comment, Load
the large font. Then let's copy and paste
the medium font lines. And change the variables
for the large font. Let's make the size
of the large font 150 and the border width six. Okay, now that we've created
the load fonts method, we need to go up to the load
method and add a call to the load fonts method so that the fonts will actually get
loaded when we run the game. All right, in the next video, we'll put our new fonts to use by displaying the Hood. See.
21. HUD (Heads-Up Display) 1: Wins/Loss Ratio and Difficulty Text: Before we display the Hud, we'll need to create
a few variables related to the information
that we want to display. So first, let's go into the
global variables class, and at the bottom, let's add a section for variables
related to colors. I'm going to create
a gold color here that we'll use for different
things throughout the game, including the
background color for the fighters health
bars in the Hud. Let's create the
variable by typing public static final color, making sure to import the
GDX graphics version. Then gold equals new color, 0.94 F, 0.85 F, 0.32 f1f. Next, let's create a
section for game variables. And here we're going
to create an Enum for the possible difficulty
levels of the game. And the reason we're doing it in global variables instead of
game screen is that we want the player to be able to
change the default difficulty setting through the
setting screen, which we'll create later. So we'll need to access
the difficulty Enum in both the game screen class
and the setting screen class. Okay, so to create the Enum, let's type public
Enum difficulty. For the difficulty levels, we want easy, medium, and hard. Next, let's go to the top
of the game screen class. And after we create
the Viewport, let's set another variable
section here labeled game. In this section,
we want to create a variable for the
difficulty by typing private global variables
difficulty difficulty. And by default, let's
set it equal to global variables dot
difficulty dot easy. Now let's create another
section for rounds. And here we want to create
a couple of variables to store how many rounds the
player has won or lost, and we want to initialize
them both to zero. So let's type private and
rounds one equal zero. Rounds lost equals zero. We also need a round timer, which we'll display to show how much time remains
in the current round. But first, let's create a
static final variable to hold the maximum round time by typing private
static final float. Max round time equals. Let's set it to 99.99 F. We'll use this to reset
the round timer each round. And the reason we're using 99.99 instead of 100 is that
when we display the timer, we're going to cut off the
decimal part and always show two numbers
from 99 down to 00. This isn't completely necessary, but I find it looks
better and fits more nicely between the fighter
health bars and the Hud. Okay, for the round timer, let's type private
float Round timer equals and initialize
it to MaxoundT. Next, we'll need to create
some variables for getting each of the fonts we created
from the asset manager. So first, let's create a
section here for fonts. The small, medium and large
fonts that we'll get from the asset manager are of
the bitmap font type. So let's type
private bitmap font. Press Enter to import it, then small font, medium
font, large font. We're also going to create
a default font color, which we want to be white. For this variable, let's type
private static final color. Make sure to import the GDX
style graphics version, then default font color. Equals. Now for this, we could do new color, then type in the RGBA values like
we've done before. However, the color class actually provides
some colors for us. To access them, instead
of doing new color, we can type color dot. Then either choose the color
we want or type it in. I'll type white. Okay, let's now create a section for a HUD. In this section,
we're simply going to add a couple variables for the colors of the health bar and the health bar backgrounds. For the health bar color, let's type private static final color. Health bar color equals let's make it red by
typing colour dot Red. For the Health Bar
background color, let's type private
static final Color. Health Bar Background color. Equals. Let's make it the
gold color we created in global variables by typing
global variables dot Gold. Now we need to get
our fonts from the asset manager and set
up some things for them. We'll do this in the
game screen constructor. After we create the
game area here, I'll comment, set up the fonts. Let's make a call
to a setup fonts method that we'll create next. Let's create the setup
fonts method after the create game area method by typing private
void setup fonts. And here let's first
get the small font from the asset manager by typing small font equals game
dot assets dot Manager, dot get assets dot Small font. Now, like with our textures, we need to scale the font by
the world scale variable. We created in global variables. To do this, we can type
small font dot Git data dot set scale, global variables WorldScale. Next, we can set the
color of the font to the default font color
variable we created by typing small font dot side
color, default font color. One more thing we
want to do is call small font dot set U integer
positions and Pass in False. I'm not sure exactly
what this does, and it might not be
necessary for all fonts, but for our fonts, if used integer positions
is set to true, which it is by default, the letters get all jumbled up. Okay, now we just
need to do all of this for the medium
and large fonts. So we can simply copy and paste these lines and change the
variables to the medium font. Then we could do the
same for the large bot. Okay. Now we're ready
to display the Hud. We'll do this in a render Hud method that we'll call
from the render method. So first, let's go into
the render method. After drawing the front ropes, I'll comment, draw the Hud. Then let's call Render HUD. Now we'll create the render Hud method after the render fighters method by typing private
void render Hud. Let's first define a Hud margin, which will refer to
the space between the hood and the sides
and top of the screen. For this, let's type
float, Hood margin equals. Let's set it to one F, which will mean one world unit. Next, we'll draw the ratio of
rounds one to rounds lost, and we'll do so using
the small font. To draw text with Bitmap font, we simply call the Bitmap
font objects draw method. So first I'll comment, draw
the rounds one to loss ratio. Then let's type
small font dot draw. And in this method,
we first need to pass in the sprite batch by
typing game dot batch. Next, we need to pass in the string of texts we want to draw. So let's type quote wins,
colon space, quote, plus rounds one, plus space, space, quote, plus rounds lost. After that, we need to pass in the starting
position of the text. When drawing with a bitmap font, the starting position refers to the top left point of the text. Therefore, the position of the text should
be height margin. For the Y position, we
need to take the height of the viewport and subtract
HUD margin from it. Because we're working
in world units, we need to use the
viewports world height. To do this, we can type viewport Get world height
minus HUD margin. Now we can go ahead and run the game to make sure this works. Okay, at the top, it says
wins zero to zero. Good. Next, we're going to
display the difficulty setting at the top
right of the screen, and we'll also use the
small font for this. First, I'm going to comment,
draw the difficulty setting. Now let's create a string
variable called text. Let's set it equal to
difficulty, colon space. We're now either going to add the word easy, the word medium, or the word hard to
the text variable depending on what the
difficulty setting is. Let's do this with
a switch statement by typing switch difficulty. A us write case, easy, text plus equals, easy. Then break. Next case medium, text plus equals,
medium. Then break. Finally, since hard
is the final case, we can just do default. Text plus equals quote hard. Now we can draw the
text by first typing small font dot draw
game dot batch text Now for the draw position, we want to align the text to
the right of the screen with HUD margin spacing between the right side of the text and the right side
of the screen. To align text, we'll use a longer version of
the draw method. Also, when we align
text to the right, the starting position becomes the top right of the text
instead of the top left. So for the position, let's type viewport dot Get
world width minus HUD Margin. We use the same Y position as the win loss ratio text so that the tops will be
aligned horizontally. So let's type
viewport dot Get roll height minus Ht margin. Now for the longer
version of draw, we first need to pass in
a target width value. Target width refers to how wide we want the
line of text to be. We don't want to
restrict the width, so we can pass in a zero, which will cause target
width to be ignored. Next, we need to pass in how
we want to align the text. To do this, we type a line, press Ender to import it, then dot and choose
the alignment. We, of course, want to align the text to the right
so let's choose right. Finally, we need to
pass a boolean value for whether the
text should wrap. If we set target width to
something other than zero, if the width of the text
exceeds target width, the text will wrap or move down one or more lines in order to keep the width from
exceeding target width. We don't need this in our case, so let's put false. That's it. Now we can run the game
and see if it works. Okay, we get difficulty easy
aligned to the top right. Okay, in the next video,
we'll continue working on the Hud by displaying the
fighters health bars. See.
22. HUD (Heads-Up Display) 2: Fighter Health Bars: For the health bars, we first
need to set up a bunch of variables for the sizes and positions of the
parts of the health bars. These parts include
each fighter's name, red rectangles for
the health bars, and gold rectangles for the
health bar backgrounds. The way we're going to do the
layout here is only one way of displaying things like
HUD's and UI and Lib GDX. When we create the
main menu screen and setting screen later, we'll look at
another and usually more efficient way of doing it. Okay, I'm going to comment here, set up the layout
sizes and positioning. Well first create a variable
for the health bar padding. This will refer to
the spacing between the fighter name and the red rectangle in each health bar. So for this, let's type float, health bar padding and set
it equal to 0.5 F. Next, we want to set the
health bar height or the height of
the red rectangle. We want this to be the height
of the fighter name text, plus the health bar
padding doubled. To get the height of a font, we use the Get cap
height method. So let's type float
health bar height equals small font
dot Get cap height. Plus health bar padding, times two F. Doing it
this way will allow us to change the small font size later and have it also change the height
of the health bar. Next, we want to set the maximum
width of the health bar. This will refer to the width of the red rectangle when the fighter's life
is at full capacity. We'll make it so the red rectangle gets
shorter as the fighter loses life for a
nice visual display of the fighter's current life. For the max width, I found
that 32 works pretty well. Type float, health bar, max width. Equals 32 F. Next, let's define the padding of the health bar background, which refer to the amount
of spacing between the red rectangle and all
sides of the gold rectangle. For this, let's type float, health bar background padding. Equals 0.2 F. Next, for the height of the
health bar background, you want to use the
health bar height plus the health bar
background padding doubled. So let's type float, health
bar background height equals health bar height plus health bar
background padding. Times two F. Unlike
the health bar, we want the health bar
background's width to remain the same, regardless of how much
life the fighter has. And we want it to be the
health bar max width, plus the health bar
background padding doubled. So let's type float, health
bar background width equals health bar max width plus health bar
background padding, times two F. We also want to set a margin between the health bar background and
anything above it, like the wins loss
and difficulty text. For this, let's do float, health bar background
margin top. Equals 0.8 F. Now we just want to set up the Y
positioning of everything. First, for the Y position of
the health bar background, we want to take the world
height of the viewport, then subtract it by HUD margin, then the height of
the small font, then the health bar
background top margin. And like textures, shapes are
drawn from the bottom left, so we also need to subtract the height of the
health bar background. Okay, so with all
of that in mind, let's type float, health
bar background, position Y. Equals a view port
D G road height, minus Hud margin,
minus small font, DG cap height, minus health
bar background margin top. Minus health bar
background height. For the Y position
of the health bar, we can simply take
the Y position of the health bar background and add the health bar
background padding. So let's type float health
bar position Y equals health bar background position Y plus health bar
background padding. Finally, we need to set the Y position for the fighters names. Because text is drawn
from the top left, we can take the Y position
of the health bar, add the health bar height to it, and subtract the
health bar padding. Okay, so let's type float, fighter name position Y equals health bar position
Y plus health bar height minus
health bar padding. Now in order to draw
rectangles and Lib GDX, we have to use something
called a shape renderer. Shape renderer is
similar to sprite batch, except we use it to draw shapes. And like sprite
batch, we only want to use a single shape
render in our project. So let's create one in
our main game class. At the top, after creating
the sprite batch, let's type public shape render. Press Enter to import
it, then shape render. Now in the create method, after we initialize batch, let's initialize shape
render by typing shape render equals
new shape render. I also like with sprite batch, we need to dispose
the shape render. So let's go down to
the disposed method, after we dispose the batch, that's call shape
render dot disposed. Okay, now we can head back
over to the game screen class. All right, so another
similarity with Sprite Batch is that
we have to do all of the shape render
or drawing between calls to the begin
and end methods. And because we call
the render Hud method, in between calls to Batch
Dop again and batch Dot end, before we begin drawing
with shape render, we have to call Batch Dot
end and side render Hood. And after drawing
with shape render, we need to call Batch DoP
again again because we'll later be adding more rendering here after rendering the hood. Okay? So at the bottom of
the render Hood method. Let's type game
dot batch dot end. Then game dot shape
render dot begin. Now in here, we can
pass in a shape type. Shape type can either
be filled or line. Filled will fill in the entire
shapes we draw with color, and line will just
draw a colored border around the shapes, leaving the inner
part transparent. Line is the default, but we
want our shapes to be filled. So to pass in the
filled shape type, we can type shape type
precenter dot field. Let's also go ahead and call game dot shape render dot end. Then game dot batch dot begin. Let's go in between the shaped render begin and end calls. C every ready to start drawing
with the shaped render? Because the health
bar backgrounds will appear at the bottom, let's start by
drawing them first. How common here, draw the fighter health
Brground rectangles. We first want to set the
color of the shaped render to the health bar background color static variable that
we created before. To do this, we call game dot
shape render dot set color, health bar background color. Now if we type game
dot shape render, dot, we can see that we
have methods for drawing things like
arcs, circles, ellipses, lines,
polygons, and rectangles. We, of course, want
to draw a rectangle. So let's use the rect method. And here we first
want to pass in the X and Y positions
of the rectangle. This one will be for
the player's fighter. So for X, we simply
want to put HUD margin. For Y, we can use
the health bar, background position, Y
variable that we created. Next, we need to
pass in the width and height of the rectangle. We also created
variables for these. Let's type health bar
background width, C Health bar, background height. That's it. Now we need to draw the one for
the opponent spider. Let's type game, do
shape render, direct. To get the exposition
of this one, we need to take
the world width of the viewport and subtract both the HUD margin and the
health bar background width. So let's type viewport
dot Get rolled width minus Hud margin, minus health bar
background width. The remaining values are the
same as the other rectangle. So let's type health bar,
background position Y. Come Health bar
background width, Come a health bar
background height. Now let's run the game
and make sure it works. Okay, so it appears the
rectangles have been drawn really small way
down at the bottom left. The reason for this is that
like with sprite batch, we need to set the shape
renders projection matrix to the viewport cameras
projection matrix so that I would know
what units to use. We do this up in the
main render method. Okay, so here, where we set the sprite batch
as projection matrix, I'll change the comment to set the sprite batch and the shape render to use the
viewports camera. Then, after setting
the BatchsPjection matrix, let's type game, dot shape render, dot
set projection matrix. Viewport dot get
camera dot combined. Now let's run it. Awesome. Next, we'll draw the red
rectangles for the health bars. First I'll comment, draw the fighter health
bar rectangles. Then a such shape
renders color to the health bar color
variable by typing game, dot shape render, dots
color, health bar color. Now, when we draw
the health bars, the width of the rectangles will depend on how much life
the fighters have. To find the width, they can
take the maximum width of the health bar and multiply it by the fighter's
current life, then divide by the maximum
life a fighter can have. Instead of typing all of this
out inside the rect method, let's create a variable
for it by typing float, health bar width equals
health bar max width. Times we'll first create
the players health bar. Let's get the players
life by calling game dot player dot GET Life then divide by
fighter dot MaxLif. Now we can draw the player's
health bar rectangle by first typing game dot
Shape Render direct. For the position,
we want it to be the HUD margin plus the padding of the
health bar background. Let's type HUD Margin plus
health bar background padding. Then, health bar position Y, C health bar width,
co health bar height. Now, we want to set health
bar width for the opponent. So let's type Health bar
width equals health bar max width times game dot
opponent dot get Life. Divided by fighter dot Max Life. Now with the
player's health bar, as the player loses life, the health bar rectangle will shorten from the center
to the left side. With the opponent, we want it to shorten from the center
to the right side. So to get the position, we'll take the world width of the viewport and
subtract the HUD margin, then subtract the health
bar background padding, then the health bar width. So as the health
bar width shrinks, the rectangle will
move toward the right. Okay, so let's draw
the rectangle by typing game, dot shape render, Direct, viewport
dot G world Width. Minus HUD margin, minus health
bar background padding, minus health bar width. Then, health bar position Y. Come at health bar width, come at health bar height.
Okay, let's give it a try. Nice. And as we
hit the opponent, their health bar gets shorter
and moves to the right. Perfect. Next, we'll
draw the fighter names. This means that we're going
to need a Getter method in the fighter class
for the fighter's name. So let's head over to the
fighter class, and at the top, before the gitter
for the position, let's type public string, G name, return name. Let's also go ahead
and create a setter for the name by
typing public void, set name, string name. This name equals name. Let's also create some Gitter and setter methods
for the color. We'll be needing all
of these methods when we create the main
menu screen later. So let's type public color, get color, return color. In public void set
color, color color. This color equals color. Okay, back in game screen, because we use the sprite
batch to draw text, we need to draw
the names after we call game dot batch
dot begin here. First I'll comment,
draw the fighter names. Now let's use the small font to draw the player's
name by first typing small font dot draw game dot batch game dot
player dot G Name. For the exposition, we'll use the HUD margin plus the health
bar background padding, plus the health bar padding. So let's type HUD margin plus health bar
background padding. Plus health bar
padding. And for the Y, we'll use the fighter
name position Y variable that we created. Now let's draw the opponent's
name by first typing small font dot draw game dot batch game.opponent.gn
For the opponent name, we're going to align it to
the right of the health bar. This means that the
starting position will be at the top right. So for X, we'll take the
world width of the viewport, subtract the HUD margin, then the health bar
background padding, then the health bar padding. Okay, so let's type
viewport dot GetR Width. Minus Hood margin, minus
health bar background padding. Minus health bar padding. For Y, let's type
fighter name position Y. Next, we'll put a zero
for target width, line dot right for the
alignment, and false for rap. Let's give it a try.
Cowsom. Finally, we just seem to draw the round timer at the
center of the hood. First do comment,
draw the round timer. We're going to use the
medium font for this. Let's site medium font
dot draw game dot batch. Now, when we draw the
value of round timer, we want to turn it into a string and we don't want to show
the decimal part of it. To do this, we can call
integer two string, open parenthesis,
open parenthesis, int, closed parenthesis
round timer. We're doing here is
we're first casting the float value of round
timer into an integer, which removes the decimal part. Then we're calling the integer two string method on
the resulting integer, which will turn
it into a string. Next, we want to center the text horizontally
in the viewport, and to help with this,
we're going to use a line dot center for
the text alignment. When we use a line dot center, the starting
position of the text becomes the top center point. This means that to
center the text horizontally in the viewport, we simply need to set the
position to viewport dot Get Rod width divided
by two F. F Y, let's use viewport dot G Rod
height minus Hut margin. This will align the top
of it with the tops of the wind loss ratio
and difficulty text. Now let's type zero
for target width, align dot center for the
alignment, and false for rap. Let's give it a run. Okay, we have our round timer
in the center of the Hud with a nice black
border around the numbers. One thing to note, though,
is that if we go up here and set round timer to
something less than ten, like five, then run it. It just displays a five. To make it look nicer,
we can make it so it puts a zero before
numbers less than ten. To do this, when
we draw the text, we need to use the format
method of the string class. So let's go back
down to render Hud. Let's replace the integer
dot two stream call here with string dot format. The first thing we
need to pass in here is a local object, which represents a
specific geographical, political, or cultural region. There are many things we can
do with the format method, and depending on the
locale we set for it, the result may be different. For our purposes here, though, the local doesn't really matter, so we can just use the
default one by typing locale, pressing nter to import it, then dot get default. Next we need to pass
in something called a format string that represents the format we
want to use for the text. What we want to do is check
if the text is two numbers, and if it's only one number, we want to pad it with
a zero on the left. To do this, for
the format string, we can type percent
sign 02d quote. Percent sine D represents
numbers or digits. The two before the D represents how many digits
we're looking for, and the zero after the percent sine tells us that
we want to pad the text with zeros
at the left if we don't have the quantity
of digits we're looking for. Passing in a five to this
will give us a string of 05. If we were to change the
two here to a three, it would give us zero, 05. Like I said, there are a lot
of things we can do with the format method and many different format
strings we can use here. I highly recommend looking more into the method when
you get a chance. Okay. And the final
thing we need to pass in here is what we
want to format. We want to format the
value of round timer, but without the decimal part. So we can pass in round timer, cast it to an integer by
typing open parenthesis, int closed parenthesis,
round timer. Now, let's run it. All right, we now have a 05 for the timer. And if we set round
timer back to Max round time, We get 99. Alright, that's it for the Hood. In the next video, we'll set
up our game to start using rounds so that we can see
our round timer in action. S
23. Set Up the Rounds: In order to start using
rounds in our game, we need to set up a
few more variables in the game screen class. Let's start at the top
of the game section. And here, we're going
to need a variable to indicate what state our
game is currently in. The game can be in
three possible states, running, paused and
finished or game over. So first, let's create an E NUM for these states by typing private Enum game state Running, paused, and game over. Now we need to create
a variable for the current game state
by typing private, game state, game state. Next, let's go to the top
of the round section. A round can also be in
three possible states, including starting in
progress and ending. So let's create an Enum
for the round states by typing private,
Enum, round state. Starting in progress and ending. As credit variable to hold the current round state
by typing private, round state, round state. We actually also need
to keep track of the amount of time around
has been in a certain state. This will be used for things
like the round timer and for the delays that we put at the start and end of a round. So for this, let's type private
float Round state time. Let's also go ahead
and create a couple of variables for the starting
and ending round delays. First, let's type private
static final float. Start round delay equals. Let's set it to F, which will mean a
two second delay. Now let's type private
static final float, end round delay, and also
set it to two F. Next, we need a variable to indicate what round the game
is currently in. So let's type private
end current round. And finally, let's create a static final variable for the maximum number of
rounds a fight can have by typing private
static final nt, Max rounds. Let's set it to
three. Okay, next, we need to create
some methods for starting the game
and for starting, ending, winning and
losing a round. We'll call the start game
method from the show method, so that it will get
called each time the game screen becomes
the active screen. So let's go down to
the show method. And at the bottom, I'll
comment, Start the game. Then let's call Start Game. Now let's create the
Start game method after the show method by typing
private, void, start game. And here we want to set the
game state to running and reset both rounds one
and rounds lost to zero. So let's site game State
equals gamese dot running. Rounds one equals rounds
lost equals zero. Next, we want to
start Round one. First outle comment,
start Round one. Then we want to set the
current round to one. By typing current
round, equals one. Then we'll start the round by calling the start Round method. Let's create the start
Round method next by typing private,
void, start Round. The first thing we'll
do in here is get the fighters ready for the round by calling their
get ready methods. We're actually currently doing
this in the constructor. So let's select these lines here and cut them
with Control X. Then let's paste them into
the start Round method. Next, we want to set the
round state to starting, the round state time to zero and the round timer to the
maximum round time. First out comment,
start the round. Then let's type round state equals round state dot starting. Round state time equals zero F, and round timer equals
max round time. Next, we need to create
an end round method by typing private,
void, end round. Here we simply want to
set the round state to ending and reset
the round state time. I'll come it end the round. Then type round state equals
round state dot ending. Round state time equals zero f. We next need to create a method to call whenever the player
wins a round. So let's type private
void, win round. In this method, we want to put the player in the win state, the opponent in the lose state, and add one to the
rounds one variable. We also want to end
the current round. So first I'll comment, player wins the round, and
opponent loses. Then let's type game
dot player dot win. Game dot opponent dot lose
and rounds one plus plus. Next I'll comment in the round. Then let's call round. We now need to
create a method for the opposite situation when
the player loses the round. So let's type private,
void, lose round. And here I'll
comment. Player loses the round and opponent wins. Then's type game dot
player dot lose. Game opponent dot win. And this time, we want to
increment the rounds lost variable by typing
rounds lost plus plus. Next, I'll comment in the round. Then let's call in Round. Next, let's go down
to the update method. At the top of this method, we want to check a
couple of things related to the round state. First, if the round
state is on starting, we want to check if
the round state time has reached the
start round delay. If so, we want to move
the round state into the in progress state so the
fighters can start fighting. So let's do this by typing
if round state is equal to round state dot starting and round state time greater than or equal to
start round delay. And here I'll comment.
If the start round delay has been reached,
start the fight. Then type round state equals
round state in progress. And round state time
equals zero F. We also want to check if
the round state is on ending and the round state time has reached the end round delay. We can do this with an
SIF here by typing LCIF. Round state is equal
to round state ending and round state time greater than or equal
to round delay. If this is the case, we need
to decide whether we should move to the next round or put the game in the
game over state. With Max rounds set to three, the fight could possibly
have three total rounds. However, if one of the fighters has already won two rounds, then there's no
point in going to the third round because even if the other fighter
wins the third round, they will still lose
the game overall. If we were to set
Max rounds to five, we would only want to
go to the next round if fewer than three rounds have been won by
a single fighter. In other words, we only want
to go to the next round if fewer than half of Max rounds have been won by
a single fighter. Otherwise, the game is finished, so we put it in the
game over state. So first I'm going to comment
all of this by typing, if the end round delay has been reached and player has won or lost more than half of the max number of
rounds in the game, otherwise, start the next round. Now let's check whether
the player has won or lost more than half
the rounds by typing. I rounds one greater
than Max rounds, divided by two, or rounds lost, greater than Max
rounds, divided by two. And if this is the case,
we'll end the game by typing. Game state equals gamete Game O. Otherwise, we need to
start the next round. So let's put outs. Current
round plus plus, start round. Alright, now, if neither of
these situations is the case, we want to increment
the round state time by Delta T. So here let's put outs. And I'll comment, increment the round state
time by Delta time. Then let's type round state
time plus equals Delta time. Can let's go down to
where we check if the fighters or the
contact distance. Above this, we want to check if the round state is in progress, and if so, we want to decrease the round
timer by Delta time. So let's type if round state is equal to round
state in progress. And here I'll comment, if
the round is in progress, decrease the round
timer by Delta time. Then let's type round
timer minus equals Delta T. Another thing we want to do in here is check if the round timer has finished. If so, neither of the fighters
has won the fight yet. We'll make it so
the fighter with the most life
automatically wins, and the other fighter
automatically loses. If both fighters have
the same amount of life, we'll just let the player win. So first, let's
type I round timer less than or equal to zero
F, then I'll comment. If the round timer has
finished and player has the same or more life than opponent, player wins the round. Otherwise, player
loses the round. Ellis type, if game
dot player dot G Life, greater than or equal to game
that opponent dot get Life. When round, else, lose Round. Also, because we only
want the fighters to be able to hit each other when
the round is in progress, we can cut all of these
lines of code here. I paste them into
the I statement where we check if the
round is in progress. I and what we call game
dot player dot win here, we want to change
it to win Round. So we'll also increment rounds one and start the next round. I'll add to the comment
here. Player wins the round. All right now we can finally run the game and see what happens. After a two second delay, the round timer
starts counting down. It actually appears to
be a three second delay, and that's because we start
the round timer at 99.99. A three second start delay will actually be good
because in a minute, we're going to display some text to tell the player what
the current round is, then let them know when
they can start fighting. Okay? And if we attack the
opponent until they lose, we get a two second delay,
then it starts round two. We can also see that round
one has increased by one. If we win again, because we won more than half
of the Max rounds, it doesn't go to round three. We'll later add an overlay
here with buttons to let the player either restart the game or go to the main menu. But first, if we
run the game again, we're able to walk around
during the start delay. We want to make
it so we can only walk around when the
round is in progress. To do this, let's head down
to the key down method. Let's cut all of the lines for checking if the player
has pressed a Momi key. Now let's type I round state
is equal to round state in progress, Then pace the lines. We'll still let the player press the attack and block buttons
during the delays, though. Okay, now if we run the game? We can't walk around
during the start delay. Okay, to finish up this video, let's add some text at
the start of the round. We'll do this in a
new render method called Render Start Round Text. So let's go up here to below
the render Hud method. Let's type private void
renders Start Round text. During the first half of
the start round delay, we're going to draw some text stating what the
current round is. And during the second half, we'll just draw the word fight. So first, let's
create a variable for the text by typing string text. Next, we want to check
if the round state time is less than half of
the start round delay, and if so, we'll draw
the round number text. Otherwise, we'll
draw the fight text. So let's type I
round state time, less than start round delay. Times 0.5 F, text equals round space quote
plus current round. Then, text equals fight. We're going to use the medium
font to draw the text. We want to center
both horizontally and vertically on the screen. So let's call it medium font dot draw game dot batch, text, Comma viewport dot
get rolled Width, divided by F, C viewport, DGRd height, divided by F. We want to use center alignment. So let's put a zero
for target width, align dot center for the
alignment, and false for rap. Okay, now we want to go up
to the main render method. And after rendering the HUD, we'll check if the round
state is on starting, and if so, we'll draw
the start round text. So first I'll comment if
the round is starting, draw the start round text. Then this type, I round
state is equal to round state dot Starting
render Start Round text. Now if we run the game, first
says Round one, then fight. And if we win the game,
it says Round two, fight. Awesome. Alright, now, one thing you might
have noticed is that the text for the round timer sometimes shifts slightly
when it changes. In the next video, we'll
learn how to fix this. We'll also make it so the
text changes color when the round timer reaches
a certain critical time. See there.
24. Improve the Round Timer: As we saw in the previous video, as the round timer
counts down in the Hud, at certain times, the
numbers seem to shift a bit. The reason for this is
that the numbers in the Roboto font family aren't
exactly the same width. So the width of the text
is fluctuating slightly. And because we have its
alignment state centered, it appears that the
text is shifting. One solution to this is to
use a monospace font family, which use characters that all
have the exact same width. However, depending on the font, this might leave a
bunch of extra space between the numbers, which
won't look very good. A better solution is to
use left alignment on the text with this left edge at the center of the viewport, then shift the text to the left by the width of one number. This means that it won't be perfectly centered at all times, but it will only
be off centered by a very small amount, so
it won't be noticeable. Okay, so to do this, let's go into the game screen's
render Hut method, where we draw the round
timer at the bottom. Because we want to
use left alignment, which is the default, we can just remove these last
three parameters. Now for the position, we want to take viewport
Get Rod height divided by two F here and subtract it
by the width of a number. However, because every character and a font can have
a different width, we can't just get the width
of any character we want. And actually, in Lib GDX, we only have a
single method that lets us get the width of
a character in a font, and that's the G SpaceX
advanced method. So here let's type minus
medium font GespaceX advance. This method returns the width of the space character
in the font, and in Roboto regular, the space character is less than half the width
of a number character. So we need to bootuply
this number by something. I found that doing times 2.3
F here works pretty well. If you decide to use a different font
family in your game, you will likely have to use
a different number here. Okay, let's give it
a go. Alright, so the round timer isn't
shifting anymore. And even though it isn't
always perfectly centered, it's very difficult to tell. Okay? So another thing
we can do to improve the round timer is when it gets down to a
certain critical time, say 10 seconds, we can change the color of the text,
like maybe to red. This will help to get the
attention of the player, letting them know the
round is about to end. Okay, so to do this,
let's first go up to the top of the class
and create a couple of static final variables to define the critical round time and the critical round time color. Let's put them at the bottom
of the round section. First type private static final float, critical round time. Let's set it to ten
F for 10 seconds. Let's us type private
static final color. Critical round time color equals and we can set it to
red by typing color dot red. Now, let's go back down
to the render Hud method. Alright, so before we
draw the round timer, we want to check
whether the round timer has dropped below the
critical round time, and if so, we want to
change the color of medium font to the
critical round time color. So let's type if round timer, less than critical round time then medium font dot set color, critical round time color. Now, when we change
the color of a font, it will remain that color
until we change it again. So medium font will
continue to be red for all subsequent
renders in the game. Therefore, after
drawing the text, we need to set
medium fonts color, back to the default
color that we defined earlier by
typing medium font, dot side color,
default font color. Can I just go back up
to the top and set Max round time to something
like 13 F for now so that we don't have to wait too long to
see if it works. Then let's run the game.
Okay. When the timer drops below ten, it turns red. And when it reaches zero, because the fighters had
the same amount of life, the player automatically wins. Okay, let's set Max
round time back to 99.99 F. And in the next video, we'll learn how to
handle touch input from the player so that they can
press buttons on the screen, as well as skip
the start and end round delays by touching
the screen. See there.
25. Handle Touch Input: It's nice to have a short delay at the
beginning of a round, letting the player get
ready for the fight. But sometimes the player
might just want to skip the delay and get
straight to the action. We're going to let
the player do this by touching or clicking the
screen during the delay. Therefore, we need
to learn how to handle touch and mouse
input from the player. Below all of the keyboard
handling methods at the bottom of game screen, we have five methods for
handling touch and mouse input. Touchdown, touch up, touch, dragged, mouse
move, and scrolled. Touchdown is called
when the player either touches the screen
with their finger or clicks it with the mouse. Touch up is called when
the player either removes their finger from the screen or releases the mouse button. Touch dragged is called when the player drags the
finger across the screen. Mouse moved is called when the player moves their
mouse across the screen, and scrolled is called when the player scrolls their
mouse well up or down. In the desktop
version of our game, we only need to use
the touchdown method. The touchdown method contains
four integer parameters, screen X, screen Y,
pointer and button. Screen X and screen Y refer to the X and Y coordinates of where the touch
or click occurred. This is in pixels, and the origin of the screen
is at the top left. So in order to get the location where the touch occurred
in the viewport, we need to convert
these coordinates into world units with the
origin at the bottom left. Fortunately, LibGDX makes
this pretty simple to do. Next, the pointer
parameter refers to which finger or pointer the player used when
touching the screen. This allows the player to use multiple fingers on
the screen at once and is really only useful for touchscreen devices
like mobile phones. On a desktop, the player will likely be using a mouse
to touch the screen, so we don't need to worry about handling
multiple pointers. Finally, if the player
is using a mouse, the button parameter will refer to which mouse
button they clicked. This lets us perform
separate functions for left clicks
and right clicks. This won't be
necessary in our game, so we'll just handle all
mouse buttons the same way. Okay, so let's see
how we can use the touchdown method to let the player skip
the round delays. The first thing we're
going to do is convert the screen coordinates of the touch into
world coordinates. We'll do this with the unproject method of the camera class. The unproject method actually works with both
two D and three D, so it requires that we pass in a vector three
variable containing the coordinates we
want to convert. Therefore, we first
need to put screen X and screen Y into a
vector three variable. I'm going to comment
here, convert the screen coordinates
of the touch click into world coordinates. Then it's type vector three, press Enter to import it. Then position equals
new vector three, screen X, screen Y. And because our game is two D, we won't be needing
a Z coordinate, so we can just put a zero. Next, we can call the
unproject method on the viewport camera by typing viewport get camera
to Unproject. And here we need to pass
in the position variable. Then, viewport dot get screen X. Ca viewport dot get screen Y. Come a viewport that
gets screened with. Come a viewport that
gets screened height. This method will
actually replace the screen coordinates in the position variable with
the world coordinates. So now we can use the
position variable to check exactly where in the viewport the player touched or clicked. We won't actually be
needing to do this until we add buttons to
the screen a bit later, but I figured we should go ahead and get it
out of the way now. All right, so when we check if a screen press should
skip around delay, we only want to do so when the game is in the
running state. If it's in the pause
or game over state, we'll have some buttons on the screen for the
player to click. So we'll handle touch input
differently in those cases. So let's first check
if the game is in the running state by typing if Game State is equal to
gamete dot running. Now, if the current round
is in the starting state, we want to set the round state time to the start round delay. Similarly, if it's
in the ending state, we want to set the state
time to the end round delay. So let's first check if it's
in the starting state by typing if round state is equal to round
state dot starting. I'm going to comment here if the round is starting and
the screen has been touched, skip the start round delay. Tins type, Round state time
equals start round delay. As two out Sif. Round state is equal to
round state do ending. I'll comment if the round is ending and the screen
has been touched, skip the end round delay. Tennis type Round state time
equals end round delay. Finally, like with
the keyboard methods, we want to change return false
down here to return true, to let LibGDX know that we handled the touch
event ourselves. Okay? Before we run the
game and give this a try, let's go to the top
of the fighter class. And temporarily set
Max's life to five F, so we only have to hit the
opponent once to beat them. Now if we run the game, we can click the screen to skip straight to the
start of the fight. If we beat the opponent, we can click to skip
the end round delay and click again to skip
the start round delay. If we beat the opponent again, though, the game is finished and we can't
do anything else. Now that we know how to
handle touch events, we'll soon be adding
some buttons on the screen to let the player
choose what to do next. But for the moment,
let's make it so we can press the space bar
to restart the game. We'll also make it
so we can press the spacebar to skip
the round delays, since having to
click the screen on the desktop can
sometimes be a pain. Let's first go into the key down method of the
game screen class. At the top of the method, a check of the space
bar has been pressed. We can do this by typing if key code is equal to
input dot keys dot space. Now we need to
decide what to do, depending on the
state of the game. If it's in the running state, we'll check if the
round state is in either the starting
or ending state, and if so, we'll skip the delay. If the game is in the game over state, we'll restart the game, which we can do
by simply calling the start game method
that we created earlier. When we make it so the player
can pause the game later, we'll also make it so
pressing the space bar, we'll resume the game, but we'll leave that
for another video. Let's first check if the game is in the running state by typing, I Game state is equal to
gamestate dot running. And here I'll comment,
if the game is running and the space
key has been pressed, skip any round delays. Now let's check if
the round is in the starting state by typing, I round state is equal to
round state dot starting. And if so, let's set round state time equals
start round delay. Now let's do out Sif. Round state is equal to
round state dot ending. Round state time
equals round delay. Okay, now, after checking
if the game is running, let's check if the game is
over by putting Out SIF. Game statate is equal to
Gametate Game O. I'll comment. If the game is over and the space key has been
pressed, we start the game. Then let's call Start game. And actually, if the space
bar has been pressed, there's no point
in checking if all of these other keys
have been pressed. So let's put an outs here. Then let's cut all of
these other lines before the return true line and paste
them into the outs part. Et's give it a run. If we press the space bar, we
can skip the delays. At the end of the game, we
can press the space bar once to skip the delay and
again to go back to round one. Cool. One thing you
might have noticed, especially if we
maximize the window, is that the edges
of the textures and the text look pretty jagged. In the next video, we'll
see how we can fix this by changing something called
texture filters. See there.
26. Texture Filters: Texture filters and Lib
GDX basically define the algorithm used for scaling a texture up or down when
rendering it to the screen. Each texture we create has two changeable texture
filter settings, Mag filter and Min filter. Min filter or
minification filter defines the algorithm used
when scaling down the texture, and Mag filter or
magnification filter defines the algorithm used when the texture is scaled
up or magnified. For both Min filter
and Mg filter, there are several
texture filters we can choose from nearest, linear, and some
options for MIT Maps. MID maps are mainly
used in three D, because we're focusing
on two D in this course, we'll just talk about
the nearest and linear texture filters
in this video. The nearest texture filter is the default filter
that LibGDX uses. When a texture is scaled
using the nearest filter, for each pixel in the scaled
image that gets rendered, it will simply
choose the color of the pixel nearest to that
point in the original image. This is the least CPU
intensive filter. However, using it can lead to a loss in quality because some of the colors from
the original image might not be used in
the rendered image. The textures in our game
are relatively large, so for most screens,
they'll be scaled down. This is why when
we run the game, the edges of the
textures appear jagged. This could be what we
want if we were using pixel art or didn't care
much about image quality, but if we want smoother edges, we need to use the
linear texture filter. When choosing the
color of a pixel for a scaled image using
the linear filter, instead of just using the single nearest pixel in
the original image, like the nearest filter does, linear takes the nearest four pixels and blends them together, then uses the resulting
color in the scaled image. This tends to produce a
better looking result, especially around the
edges of the textures, because the blending makes
the edges look smoother. Now if we want to set the
texture filters in our game, there are two different
ways we can do it. First, we can change
the filters of a particular texture by using the set filter method
of the texture class. Second, we can change the
filters for all of the textures at once when we load them
with the asset manager. Let's first see how we
can change the filters of a single texture by changing them for the background texture. To do this, let's go into the create game area method
of the game screen class. After we get the textures
from the asset manager, let's call background texture. Got set filter. First, we
can set the Min filter, and to do so, we can
type texture filter, press Enter, then dot. Now we can see all of the
available texture filters. Let's go with linear. Next,
we can set the Mg filter. Unless we test this on
a very large screen, we won't be able to tell the
difference when changing Mg filter because the textures are all going to be scaled down. But in any case, we can go ahead and set it
to linear as well. I'll just type linear
and press Enter. Now if we run the game, we can see that the edges of the background image
appear smoother, especially along the ropes. If we comment this
line out and run it, the edges go back
to being jagged. Okay. Now let's
see how we can set the texture filters for
all of our textures. First, I'll delete
this line of code. Now let's head over
to the assets class. Okay? So when we load textures
with the asset manager, like we're doing here in the
load gameplay assets method, we can actually pass in
another variable at the end of the load method for
various parameters we want to use when
loading the asset. The type of object we pass in here depends on the type
of asset we're loading. For texture assets, we use
a texture parameter object, and we can share it with all of the texture assets that we load. To create a texture
parameter object, let's go to the top
of this method. Let's type texture parameter. Press Enter to import it, then parameter equals new. Now just press Enter here
to fill in the code. Now if we type parameter dot, we can see a few parameters
that we can set, including Mg filter
and Min filter. Let's set Min filter to
linear by typing Min filter, equals texture filter, pressing
Enter, then dot linear. We can also go ahead and set Mag filter by typing parameter, dot Mag filter, equals linear and pressing
Enter to fill it in. Okay, now we can add
the parameter object to the end of each of the lines where we load a texture asset. I'll copy this part here and paste it into
the other lines. Now, we don't want
to put this in the lines for loading
texture atlases, because texture atlases
are a bit different. With texture atlases,
we can actually set the texture filters
when we create the atlases. We'll learn all about texture
atlases in the next video. But for now, let's run the
game and see how it looks. Okay, so all of our textures are looking pretty smooth now. Nice. The text, however, still does not look very
smooth, so let's fix that. If we go down to the
load fonts method, when we create the fonts here, it actually generates
textures for them, and we can set their
texture filters, and we can do it using the free type font loader
parameter object that we've already
created for each font. So first, before we
load the small font, let's type small font, D font parameters,
dot Min filter, equals linear and Presenter. Then let's type Small
font, D font parameters. Dot Mag filter equals
linear and pre center. Now we can copy these two lines and paste them for
the medium font. And change small
font to medium font. Then do the same
for the large font. Alice run the game.
Alright, even our text is looking pretty smooth, now. Okay, in the next
video, we'll learn what texture atlases are and how
to create them. See there.
27. Texture Atlases & Packing Textures: If we go into the
textures assets folder, we have a few assets with
the dot Atlas extension. And for each one, we have an image file that shares
the same file name. Each of these pairs of files form what's called
a texture Atlas. If you open up one of the
image files, for example, gameplay buttons dot PNG, we can see that it's basically a single large image file consisting of multiple
smaller images. In this case, the textures for the buttons we'll
use in our gameplay. If you open up the gameplay
buttons dot Atlas file, we can see that this file holds some information about the gameplay buttons dot PNG image, including its size and format, as well as the texture filters to use when loading
the textures. We then have the names of all the textures inside
the image file, along with whether or not
they should be rotated, the XY coordinates
of the texture inside the image
file and their size. Now the reason it's
a good idea to combine multiple
texture images into a single image file like
this is mainly that it reduces the number of textures that need to be
loaded into memory. This reduces memory usage
and improves performance. Putting textures
together into a texture atlas is called
packing textures, and LibGDX provides us with a texture packer class
containing methods we can use to pack textures. Packing textures
from within the IDE, however, can get pretty tedious. Fortunately, the guys over at crashmbders.com
created a utility called GDX Texture Packer, which is a visual wrapper over the LibGDx Texture Packer class. To find the GDX texture Packer, we can go to github.com
slashASHEbdRS, slash GDXTextPacker GUI. You can also just Google
GDX Texture Packer, and this page should pop up
in the top couple of results. Okay, here, we can
go through all of the code for the GDX
texture Packer if we want. However, if we just want to get straight into packing textures, we can go down here to the using the app section and click
the releases page ink. We next get a list of the
current and previous versions of GDX Texture Packer. At the time of
recording this video, the current version is 4.11 0.0, and to download it, we
have some links down here. I'll choose the EXE version. After the file
finishes downloading, we can run it to
begin the setup. We might get a
warning that running the app might put
our PC at risk, but GDX Texture Packer
is officially supported by the creators of LBDX so we don't have to
worry about this. I'll click More Info,
then click Run Away. We can agree to the
license agreement, choose whether we want to create shortcuts in the start
menu or desktop. Then choose an install
location and install it. I already have it installed on my computer, so I'll
just click Cancel. But once you get it installed, let's head over to the
installation folder. In the installation folder, we have a GDX
texture Packer file. This is an executable jar file. So like with the Lib
GDX project generator, we can simply double
click it to open it. We can also use the
command Java jar, GDX,texturepacker, dot jar. All right, so at the top
left of the application, we have an atlas section. The first thing we
want to do is create a new atlas by clicking this button that
says create Atlas. And here we can give
the Atlas a name. We're going to be recreating the gameplay buttons
Atlas in this video, so let's call it gameplay
buttons. Click Okay. Okay, we now see our gameplay buttons Atlas in the list here, and we get a whole bunch of
settings that we can change. Before we talk
about the settings, let's go ahead and add all of
the gameplay button images to the source file section here. Within the
downloadable resources that I provided with the course, we have this packing
Textures folder. In this folder are all of
the separated images that we saw combined together to form the gameplay buttons textualis. We'll use these images to
recreate the gameplay buttons textualis and we need to import them into the
GDX texture Packer. To do so, we can either click the ad input files button
in here or we just simply select all the images and drag and drop them into
the blank space down here. Also in the export section, we want to go ahead and
set the output directory, which is where the texture
atlas files will be saved. As this is just for
testing purposes, it doesn't really matter what we choose for the output directory. I'll just use the
installation folder of the GDX sextupator. Okay. Now let's talk
about the settings. If you would like to see
what any of them do, you can hover over it and some information
about it will pop up. Fortunately, though, there are only a few settings that
we need to focus on. First, we have a Min
filter and MAG filter. As we learned in
the previous video, these are for setting
the texture filters. The default for both is nearest, but for better results,
we should use linear. Next, we have padding
X and padding Y. If we take a look
at the gameplay buttons image in
our project again, we can see that there's
actually a small amount of spacing between
each of the textures. This is called padding, and
it's a good idea to use it, especially when using
the linear filter. This is because if the textures are right up
against each other, the colors from the edges
of the textures will start to bleed into each other when the
textures are scaled. I find that the default
of two pixels for both padding X and padding
Y works pretty well. Next, we have a
bunch of checkboxes. The edge padding option here
puts half of the padding X and padding Y settings around the edges of the
entire pack texture. This is good because
it allows for anti aliasing around the
outside edges of the textures, which keeps the edges
looking smooth. We should also make sure to have the bleeding option here
checked because otherwise, no anti aliasing
will be used between the edges and the transparent
pixels of the padding. Another option we have
here is force POT. POT stands for power of two. Power of two textures
are textures in which both the width and the
height are a power of two, such as four by four,
eight by eight, 128 by 256, et cetera. In the past, due to
GPU limitations, many devices were only capable of rendering
power of two textures. The vast majority of devices nowadays can handle non
power of two textures, but it's still a common practice to make game textures
power of two. The POT option here simply forces the outputed packed
image to be power of two. As we can see in the project, the gameplay button image is 1024 by 512 pixels,
which is power of two. If the total width and height of the textures don't add
up to powers of two, it will simply add in some transparent spacing
in the image. Now that we understand the
most important settings in the GDX texture packer, and we have everything set
up the way we want it, let's go ahead and pack
the textures by pressing the pack button here at the
right of the atlases section. Once it's finished processing, we can close out this dialog, and now we see a preview
of the packed image here. If we hover over one
of the textures, we can see that it
uses the name of the source image file without the extension as the
name of the texture. And it also tells us the size
of the texture and pixels. If we go into the
output directory, we can see that
it created both a Gameplaybtons dot PNG file and a gameplaybtons
dot Atlas file, which contains all
the information we need for rendering the various
textures to the screen. Okay, we can go
ahead and close out the GDX texture packer now. And by the way, we can also save the current texture
packer project. In case we want to change
some settings later or add more textures,
I'll just choose no. Alright, in the next video, we'll learn how to put the
gameplay buttons texture atlas to use by creating a game over overlay at the
end of the game with buttons that the player
can click. See there.
28. Game Over: All right, so at
the end of a game, instead of just having
the winning fighters win animation run on
for all eternity, we want to put some
kind of text on the screen to let the player
know that the game is over, as well as give
them some buttons that they can press to either restart the game
from the beginning or return to the main menu. So in this video,
we'll do just that. First, we need to
go to the top of the game screen class and
create a few variables. Let's add a section after the fighters section
called buttons. For the game over overlay,
we'll need two buttons, a play again button to
restart the game and a main menu button to go
to the main menu screen. We, of course, don't have
a main menu screen yet, so the main menu button
won't do anything, but we can still draw
it on the screen. For the buttons, we're going
to create Sprite objects. A sprite object's main
purpose is to hold a texture, but it also holds information about the texture
that we can set, including its draw position, draw size, and draw rotation. More importantly, for
our purposes, however, is that it has a method
that we can call to check if the player has
clicked inside the Sprite. Okay, let's first
create the continue button Sprite by typing private Sprite pressing
Enter to import it, then play again button Sprite. Let's do the same for the
main menu button sprite. Private Sprite, main
Menu button Sprite. Next, we need to
set up the sprites by getting their textures
from the asset manager. We'll do this in a
create buttons method that we'll call from
inside the constructor. So after the line, we we
call the setup fonts method, I'll comment,
create the buttons. Then let's call Create buttons. And below setup fonts. Let's create the method by typing private void,
create buttons. Now the textures that
we need for the buttons are inside the gameplay
buttons texture atlas. So we first need
to get the texture atlas from the asset manager. I'll comment here, get the gameplay button texture
atlas from the asset manager. Then let's type texture atlas, press Enter to import it. Then button texture Atlas equals game dot assets dot
manager dot Get assets dot Gameplay Buttons Atlas. Now, when we
initialize a sprite, we need to pass in
either a texture or a particular texture region
from a texture atlas. Because we're using
a texture atlas, we need to get texture
regions from it, which we can do by calling it Fine region method and passing in the name of
the region we want. If you open up the gameplay
buttons dot Atlas file, we have four texture
region names in here. Continue button,
main menu button, pause button, and
play again button. For the moment, we
want to get the play again button and main
menu button regions. So back in game screen, let's first initialize the
play again button Sprite. I'll comment, create
the play again button then let's
start by typing. Play Again button Sprite
equals new Sprite. And now we need to pass in the Play Again Buttons
texture region by typing button texture Atlas
dot Fine Region. Play Again button. We can also go ahead and
set the size of the sprite, scaling it down by world scale so that we don't have to do
it in the render method. To do this, we call Play
Again button Spriteset size. And for the width, let's type
play again button Sprite, dot get width, times global
variables, that world scale. And for the height, play
again button Sprite, dog height times global
variables that world scale. We now just need to do all of this for the main menu button. So I'll comment, create
the main menu button. Then type Main Menu button
Sprite equals new Sprite. Button texture atlas dot Fine
Region. Main Menu button. And main menu button
sprite that sets size, main menu button
Sprite dot get width, times global variables,
dot world scale. Come main menu button
Sprite dot get height. Times global variables,
world scale. Okay, now we need
to actually draw these buttons along with some
text when the game is over. We're going to do this in a
render game overlay method that we'll call from
the main render method. So let's first go to
the main render method. Near at the bottom,
where we check if the round is in
the starting state, we first want to check if the game is in the
game overstate. If so, we'll draw the
game over overlay. If not, we'll then check if the round is in
the starting state. So we can select
all of these lines here and cut them with
Control X. Now comment. If the game is over, draw
the game over overlay. Now let's check if the game
is in the game over state by typing I GameSte is equal to Gameste GameOver if so, let's call Render
GameOver overlay. Now we can put a notes here, then paste the
lines that we cut. Okay? Now below all of
the other render methods. Let's create the render
game over overlay method by typing private void
render game over overlay. The first thing we'll do in here is make the game area and the hud darker so
that the buttons and texts that we overlay on top of them will
be easier to see. To do this, we can
draw a rectangle that's the same
size as the screen. And when we set the
color of the rectangle, we'll make it black with an Alpha value that
is less than one, which will make it
partially transparent. So first I'll comment,
cover the game area with a partially transparent black rectangle to darken the screen. Now to draw a rectangle, we of course need to
use the shape render. This means that we
first need to call the end method of
the sprite batch, then called the begin
method of the shape render. So let's type game dot batch, dot end game dot shape
render, do begin. Remember that by default, shape renderer draws only an
outline around the shapes. So let's make it draw
filled in shapes by typing filled and pressing
Enter to auto complete. And so we don't forget
to do so later. Let's also go ahead and
call game dot shape render, do end and game dot
batch, do begin. Now let's go in between
these lines of code. Then let's set the color
of shaped render to black by typing game dot shape
render, dot set color. 00 comma zero. And for
the Alpha channel, let's go with 0.7 F. This
will make it 70% opaque. Now let's draw the
rectangle by first typing game dot
shape render direct. We want to put it at 00, and
for the width and height, let's do viewport do
get rolled width, viewport, do Gerold height. So that covers the
entire game screen. Run the game right now, however, and beat the game, The
screen just turns black. This is because the
blending of textures in LibGDX is disabled by default. To enable it, we
have to do so before the call to the shape
renders begin method by typing GDX dog dog Enable GO, pressing Enter to import it, then typing GL Blend. And there are actually
many different ways to blend textures in LibGDX, we also need to tell it
how to blend the textures. We do this by calling the GDX dotg dot G Blend Funk method. Here we have to pass
in two integer values, one for the S factor or
source blending factor, and one for the D factor or
destination blending factor. These tell LibGDX how
to blend the source or incoming RGBA values with
the destination RGBA values, which are basically
the colors that the source colors will
be drawn on top of. To see all of the
blending options, they can type Go 20 dot. Now, these aren't all
blending options, but a lot of them are. Such as the ones that start with GODST that start with Go one and the ones
that start with GOSRC. When you get a chance,
I recommend trying different combinations of these
to see where you can get. But for now, what we
want to choose for the S factor is GoSRC Alpha. For the D factor, you want
go 20 G one minus SRC Alpha. Now, after the call to
Shape Render dot n, you want to Disable
blending again by typing GDX dog dot G disable
got GO blend. Okay, if you run
the game now and be All of the textures
become darker. Right now, let's work on getting the text and the
buttons on the screen. First, let's set up
the dimensions for the entire layout in which will place the text and buttons. I'll comment here, calculate
the layout dimensions. Then this type float,
text margin bottom equals two F. This will refer to the spacing between the text and the
button below it. Lexus type float button spacing, equals 0.5 F. Which will refer to the spacing
between the two buttons. We can now calculate the
height of the layout. We're going to use the large
font to draw the text, so the layout height will be the cap height of
the large font, plus the text margin bottom, plus the height of the
play again button sprite, plus the button spacing, plus the height of the
main menu button sprite. So let's type float,
layout height. Equals large font,
do Git cap height, plus text margin bottom, plus play again button sprite. Do get height, plus
button spacing, plus main menu button
sprite. Dog height. Finally, we want to calculate the Y position of the layout. We're going to center it
vertically in the viewport. So for the Y position, we'll take half the
world height of the viewport and subtract half
the layout height from it. So let's type float,
layout position Y equals viewport, DGRd height. Divided by F minus layout height divided by two F. Alright, so when we set the positions
of the parts of the layout, we're going to start from
the bottom and move up. So first the main menu button, then the play again
button, then the text. This way, we can set
the main menu buttons y position to be the
layouts wy position. Then use the main menu buttons
position to help position, the play again button and so on. So first I'll comment,
draw the buttons. Then I'll set the main menu
buttons position by typing main menu buttons
sprite, do set position. For X, we want to center it
vertically in the viewport, which we can do by typing
viewport dot G roll with, divided by F minus main
menu button Sprite, dot G W, divided
by F. And for Y, we can just use
layout position Y. Now, to draw a Sprite, we simply have to
call the Sprites draw method and pass in
the sprite batch. So main menu button Sprite
dot draw, game dot batch. Now let's set the
play again buttons position by typing play
again button Sprite. Do set position. Again we
want to center vertically, so let's put viewport dot
Get world with divided by F minus play again button
Sprite Dt get Width, divided by two F. For Y, we can use layout position Y plus the height of
the main menu button, plus the button spacing. So main menu button,
sprite, do get Y, plus main menu button sprite Dog height plus button spacing. Then we can draw the play again button by typing play again, button Sprite dot
draw game dot Batch. This is why it's
convenient to use Sprites when dealing
with things like UI. Now we just need
to draw the text. First I'll comment,
draw the text. The text that we
draw will depend on whether the player has
won or lost the game, which we can determine by comparing rounds
one to rounds lost. Let's do this with a
ternary statement by typing string text equals rounds one
greater than rounds lost. Question mark, quote
one. You lost. So if the player has
won more rounds than they lost, the text will be one. Otherwise it will be lost. Now we can draw the text using the large font by first typing large font dot draw
game dot batch, text. We're going to center align the text and also center it
vertically in the viewport. So for X, we can
simply put viewport Get world width divided
by two F. For Y, we need to take the Y position
of the play again button, add the height of
the button, then add text margin bottom. And because text is drawn
starting from the top, we also need to add the
large fonts cap height. So it's like play
again button, sprite, Doll get Y, plus play
again button sprite. Dull get height, plus
text margin bottom. Plus large font,
thou get cap height. Now we'll put a zero
for Target width, Align dot Center for the
alignment, and phosphor RAP. Okay, I think we're ready
to give this a try. Alright, we got our UO
text and our two buttons. Of course, the buttons don't
do anything yet, though. So let's see how we can
make the play again button. We start the game
when we click it. Let's set down to the
touchdown method. After we check if the
game is running here, we can add an outs
to this statement. And here we want
to check whether the game is in the
game over state and whether the player
has clicked or touched inside the play
again button sprite. So first, let's type,
I GameSte is equal to Gameste dot GameOver and to check off a point is inside the bounds of the play
again button Sprite, we can call play
again button Sprite, D Git bounding rectangle. That contains. We can then pass in the X and Y coordinates of the point we want to check. We want to use the point
where the player pressed the screen after the point has been converted to
world coordinates. We converted the point to world
coordinates at the top of this method by calling the unproject method on
a viewpoards camera. Unproject puts the
converted coordinates inside the position variable
that we passed into it. So in here we can
type position dot X, come on position dot Y. And if all of this is true, we can restart the
game by calling the start game method.
First I'll comment. If the game is over and the play again button
has been touched, start the game from
the beginning. Then let's call it Start Game. Now, let's give it a try.
Okay, if we beat the game, we can now click the play again button to
restart the game. In the next video, we'll
see how we can pause the game and add a pause
overlay to the screen. See.
29. Pausing the Game: I the player pauses the game, we want to stop all
action within the game. Like with the game overstate, when the game is in
the pause state, we'll display an overlay on the screen with some
texts and buttons. The buttons will consist
of a continue button, which will let the player resume the action immediately
where they left off and the main menu button for returning to
the main menu screen, once we create one
later in the course. We'll also, of course,
need a pause button, which we'll display
at the bottom right of the screen at all times. All right, so let's begin
by creating the variables we'll need at the top of
the game screen class. First, we'll need
two more sprites for the continue button
and the pause button. So in the button section, let's type Private Sprite,
Continue button Sprite. Then private sprites,
Pause button Sprite. Let's also set a variable for the margin of the pause button, which will represent how
much space to leave between the pause button and the bottom and right
side of the screen. For this, let's type
private static final float. Pause button margin
equals 1.5 F. Okay? Now, like we did with
the other buttons, we need to get the
continue button and pause button texture
regions from the gameplay buttons textualis. So let's go down to the
create buttons method. We can copy the lines here for creating the
main menu button. Then let's paste them down here. This will be for the
continue button, so I'll change the comment. Change the region name
to Continue button. And make it so all
of these methods are called in the Continue
button Sprite. Now let's do the same
for the Pause button. Next, let's go to
the render method. Near the bottom, after
we draw the Hud, but before we check if we should draw the game over overlay, we're going to draw
the pause button. We'll create a method for this called Render Pause button. So first I'll comment,
draw the pause button. Then let's call
Render Pause button. Now, inside this s part
of the If statement, where we check if the game
is in the game over state, we'll check if the game
is in the pause state, and if so, we'll draw
the pause overlay. If we want to do this
after the If statement for checking if we should
draw the start round text, because if we do draw
the start round text, we want the pause overlay
to be drawn on top of it. So after this I
statement, I'll comment, if the game is paused,
draw the pause overlay. In this type, if GameState
is equal to gamete dot pas, let's call Render pause overlay, which we still need to create. Okay, now let's go
down here before the Render GameOver
overlay method. Let's create the Render
Pause button method by typing private void
render pause button. First, we want to
set the position of the pause button sprite. So let's type Pause button
Sprite, D set position. For X, we want to take the world width of the viewport and subtract both the
pause button margin and the width of the
pause button sprite. So let's type viewport,
Dot get rolled width minus pause button margin, minus pause button
sprite Doll get width. For Y, we can simply put
Pause button margin. Now let's draw the
Pause button sprite by typing Pause button, Sprite, Da drawGame Da Batch. Okay, let's now go below the render GameOver overlay method. And create the render
pause overlay method by typing private void
render Pause overlay. This method is going
to be almost the same as render
game over overlay, so we can first copy and paste all of the lines
from that method. We can leave all of the stuff for darkening the
screen the same. For the layout dimensions part, we just need to
change play again button Sprite here to
continue button Sprite. When we draw the
buttons, we, of course, want to draw the
continue button sprite instead of the play
again button Sprite. And for the part where
we draw the text, we can get rid of the
string text line here. And inside the large
font dot draw call, we can change text here
to quote game pause. We also want to
change the play again button spray instances to
continue button spray. Okay, if we run the game now, We get the pause button
at the bottom right, but we haven't yet set it to do anything when the
player clicks it. So let's do that now inside
the touchdown method. We only need to check
if the pause button has been touched when
the game is running. So we'll check inside
the If statement here. However, we also want
touching the pause button to take precedent over skipping
the start and round delays. So above the inner if
statement, let's type, I pause button sprite
Git bounding rectangle. Do contains position
dot X come position dot Y. I'll first comment, if the pause button has been
touched, pause the game. For pausing the game, we'll create a method
called pause game. Let's type pause game here. And we actually want to let
the player pause the game even during round delays
without skipping the delays. Therefore, we need
to combine these if statements together by moving this line up to the
closing curly brace above it and adding s here. Before we create the
Pause game method, let's also go ahead and set up the Continue button to resume
the game when it's clicked. We only want to check if the continued button
has been clicked, if the game is currently
in the pause state because that's the only time the continued button
will be visible. So we can add on to this IF statement where we check
if the game is over, and the play again
button has been clicked. Let's do so by adding CIF here. Then typing GameState
is equal to gamestate dot Pause and Continue button Sprite Git
bounding rectangle that contains position dot X, come on position dot
Y. I'll comment. If the game is paused and the continued button has been
touched, resume the game. For resuming the game, we'll
create a resume game method. So let's type Resume Game. Now let's go up here below
the Start game method. Let's create the
pause game method by typing private void pause game. We'll be adding a few things
to this method later, but for now, all we
need to do is set gamete equal to
gamesate dot pas. Similarly, let's create
the resume game method by typing private
void resume game. And for now, we just need to put Gametate equals
gamest dot running. Okay, if we run the game now, we can click the Pause button, which will bring up
the pause overlay and we can click the Continue
button to resume the game. However, if we click
the Pause button again, we can see that it doesn't
actually pause the game. The round timer is still going and we can
still move around. To fix this, let's take a
look at the update method. The update method gets called every time the render
method gets called, which is every
frame of the game. Inside this method, we do things like called the update
method for the fighters, which updates things like
their movement and animations. And when we make these calls, we pass in Delta T, which as we learned before, is the amount of time
that has passed between the previous render and the
current render of the game. We also use Delta T
to change things like the round state time and
the round time each frame. Therefore, in order to
essentially freeze the game, all we need to do is
pass in a zero for Delta time when we call update at the top of
the render method. We mainly want to freeze the game when the
game is paused, and it's not
completely necessary, but we can also do so
when the game is over. Therefore, we can make it so
that if the game is running, we'll pass in Delta
to the update call. Otherwise, we'll pass in zero. Let's do this for the
ternary statement by typing Gamesate is
equal to gamestate dot Run Question mark Delta Colon zero F. I'll also
add to the comment here, Delta time should be zero if the game isn't running
to freeze the game. Now if we run the game and
click the pause button, the round timer and
animations freeze. Also, if we beat the game, once the game over
overlay pops up, the fighters when
animation freezes. Let's also make it
so that the player can press the P key to either pause the game if it's running or resume
it if it's paused. To do this, let's go to
the key down method. Before the s here, we can
add SIF to check if the game is in either the running
or the pause state and the P key has been pressed. So let's type ECIF, open parenthesis,
open parenthesis, Gamese is equal to
Gamestate dot running. Or game State is equal to
games state dot paused. Closed parenthesis, and key code is equal to input dot keys dot P. I'll comment if the game is running
or paused and the P key has been pressed
pause or resume the game. Now we'll check if
the game is running, and if so, we'll call
the pase game method. Otherwise, we'll call
the resume game method. So let's type I Gameste is equal to Gamestate
dot running, Pause game Outs, resume game. And for fun, let's
also make it so we can press the space bar to resume
the game when it's paused. We're already
checking up here if the space bar has been
pressed, and inside here, we're already checking if the game is either
running or over, so we can simply add an outs here for when the
game is paused. I'll comment, if the
game is paused and the space key has been
pressed, resume the game. Now, let's call Resume Game. Okay, now if we run the game, we can press P to pause and
P again to resume it. And if we pause again, we can also press the space
bar to resume. One more thing we can
do is make it so when we minimize the game
window, it pauses the game. Right now, if we minimize the window and bring it back up, we can see that the game just
continued on like normal. To handle what happens when
the window gets minimized, we use the pause method
of the screen class. And here we want to check if the game is currently running, and if so will post. First I'll comment, if the
game is running, pose. Then this type, I
GameState is equal to Gamestate dot Run, Pause game. And when we bring a
minimized window back up, the resume method is called. So we could call the resume game method in here
if we wanted to, but I think it would be
better to let the player resume the game
when they're ready, so I'll leave mine empty. Okay, now if we run the game, minimize it and
bring it back up. We can see it has been paused. Alright, in the next video,
we'll liven our game up a bit by adding some
music and sounds. See.
30. Add Sounds & Music: Before we can add sounds
and music to our game, we of course need to first load the sound and music assets. So let's head over
to the assets class. After the load fonts method, we're going to create
a method for loading all of the audio assets
that we'll need. Let's call it load
audio and create it by typing private
void load audio. If we take a look at the
audio assets folder, we have six files. Five are MP three files and consist of sounds like
cheer, click, and hit. The last one is an OGG
file called music dot gg. This file is actually just
a very short audio clip. If we loop this
clip, it will play seamlessly and actually
sound like music. And the reason we're
using an OGG file instead of an MP three
file, like for the sounds, is that when we encode
audio data to MP three, it actually adds a very short pause at the beginning
of the file. This isn't good if we want the sound to be a seamless loop. Therefore, we need to use
something other than MP three, like OGG or WAV. Okay And when we load
audio assets with the asset manager,
for sound assets, we want to use the sound class, and for music assets, we want to use the music class. So in here, let's start
with block.p3 by typing manager dot LOG and since we
already created variables, pointing to the audio
file locations, we can pass in Block sound. Next, we need to pass in the
type of asset we're loading. So let's type sound, press Enter to import it, then Dow class. Next, we'll load the
Bo file the same way. Then click. Then here. And finally hit. To load the music file, we can type manager,
dot load, Music. And for the type,
let's type music, press center, then dot class. Okay, now we just
need to go up to the load method and add a call
to the load audio method. We're next going to create
an audio manager class for handling all of our audio. Let's do this in the
resources package by right clicking the package, going to new Java class, calling it audio Manager
and pressing Iter. The first thing we want to do in here is create a bunch of variables for all of the audio
assets and audio settings. Let's start with the settings, our first comment settings. We only need two settings, one for indicating whether
the music is enabled, and one for whether the
sounds are enabled. So first, let's type private
Boolean music enabled. When we add a setting
screen later in the course, we'll let the player enable
or disable the music and sounds and save those settings for future runs of the game. But for now, let's
just set this to true. Now let's do the
same for the sounds by typing private bullying, sounds enabled, equals true. Next, we'll create variables
for holding all of the audio assets that we'll
get from the asset manager. We're going to separate these into three categories, music, UI sounds and game sounds, first doll comment music. And the only music asset
we have is music dot GG, which will be a music object. So let's type
private final music, press Enter, then music. Nextll comment UI sounds. We also only have one UI
sound, click Do MP three. We'll play this sound whenever the player presses a button. This will be a sound object. So let's type
private, final sound, pre center, then click Sound. All of the remaining sound
assets will be game sounds. So let's type
private, final sound, block sound, private
final sound, boo sound, private final sound, cheer sound, and private
final sound Hit sound. We're getting errors here right now because these
are final variables, and we haven't
initialized them yet, but we'll take
care of that soon. Another variable
we want to put in game sounds is an array list to hold all of the game sounds. That's will allow
us to easily pause, resume and stop all
game sounds at once, which will be important later. So let's type private
final array list. Presenter, less than sign, sound, greater than
sign, all game sounds. All right, next,
we're going to create a constructor for
the audio manager, which will be called
when we initialize an audio manager object inside the main game
class in a bit. For this, let's type
Public Audio Manager. And we'll need access
to the asset manager. So for parameter, let's
type asset manager, presenter, then asset manager. Now we need to get all
the audio assets from the asset manager and store them in the
variables we created. Our first comment, get all audio assets from
the asset manager. Let's start with the
music by typing music. Equals asset manager, dot Git
Assets precenter dot Music. Now let's get click sound
by typing click sound, equals asset manager, dot
Git Assets dot click sound. Then let's do the same for
all of the game sounds. Next, we want to initialize
the A Game sounds array list and add all of
the game sounds to it. First I'll comment,
create an array list of all game sounds
for easily pausing, resuming and stopping
them all at once. Then let's initialize
the array list by typing all game sounds. Equals new array list. Less than greater than open
and closed parentheses. Now let's add all of the game
sounds to the array list, starting with block sound
by typing all game sounds. Add block sound. Now let's do the
same for the others. Finally, because music objects
don't loop by default, we need to make it so that
our music variable loops. First I'll comment
set music to loop. To set whether a music
object should loop, be called the set looping method and pass in either
true or false. So set Music dot set looping. True. We next need to create some methods for
handling things like enabling, disabling, playing, and
pausing the music and sounds. Let's start with the
method for enabling music by typing public
void, enable music. And here we'll
enable the music by setting the music enabled
variable to true. I'll first comment enable music. In this type music
enabled equals true. We're also going to
start playing the music in this method if it
isn't already playing. I'll comment here, I music isn't already playing, play it. To check whether a music
object is playing, we call the I playing method. You want to check if the
music isn't already playing, so let's type I not
music dot is playing. And to play the music, we
simply type music dot play. Now, we need to
create the opposite method for disabling the music. So let's type public
void disabled Music. Holl Comment disabled music. Tennis Type music
enabled, equals false. We also want to stop the music
if it's currently playing. I'll comment I music
is playing, stop it. Tennis Type I music
dot is playing, and to stop the music,
we call music dot stop. Next, we want to
create a method for toggling the music on or off. This will allow the player
to press a key inside the game to enable or disable the music
whenever they want. For this method, let's type
public void Toggle Music. I'll come it enable
or disable music. If the music is enabled,
we want to disable it. Otherwise, we want to enable it. So let's type I music enabled, and to disable it, we can simply call the disabled music
method we just created. Then let's put Os enable music. We now want to create a
method for playing the music, but only if the
music is enabled. For this, let's type
public void play music. And here we want to
check if the music is enabled and isn't
already playing. If these conditions are
true, we'll play the music. First I'll comment, I music is enabled and isn't
playing, play it. Now, let's type I music
enabled and not music that is playing music dot play. The final music
method we need to create is one for
pausing the music. For this, let's type
public void pause music. And here we want to
check if the music is enabled and is
currently playing, and if so we'll pause it. Okay, so I'll comment, I music is enabled and is playing posit. Then let's type if music enabled and music
dot is playing. To pause the music, we
call music dot Paz. When we call the play
method after calling paz, it will start the music exactly where it was when
pause was called. This isn't too important with our music because it's
a very short clip. But if we had
something longer like an entire song playing,
this would be important. Next, we want to also create a couple methods for enabling
and disabling the sounds. For the enable sounds method, let's type public
void Enable sounds. Because the sounds don't loop, we don't have to bother
with stopping them. So in here, we can just put
sounds enabled, equals true. Similarly, for the
disabled sounds method, we can type public
void disabled sounds. Sounds enabled equals false. We're also going
to need a method for playing a particular sound. For this, let's type
public void, play sound. For a parameter, we'll
use a string that refers to the location of
the sound asset to play. So let's type
string sound asset. We only want to play a sound
if the sounds are enabled, so we need to check that first. I'll comment here, I sounds are enabled, play requested sound. Then let's type if
sounds enabled. Next, we'll use a
switch statement to check which sound asset the past in sound asset variable refers to, then we'll
play that sound. So let's type
switch sound asset. First, let's do case
assets, dot, click sound. And like with music objects, we can play sound objects
by calling the play method. So let's type
clicksund dot play. Then break. Let's continue
for the remaining sounds. Okay, now we just need to create three more methods for pausing
all of the game sounds, resuming all of them, and
stopping all of them. Let's start with
pausing by typing public void pause game sounds. Our first comment, pause any
instances of game sounds. Now we need to loop through the all game sounds array
list and pause each one. We can do this with
a four loop by typing four sound sound, calling all game sounds. There's no method for checking if a sound object is playing, but it's not really necessary. And to pause a sound, we
call the Pase method, let's type sound dot pase. Next for resuming
the game sounds, let's type public void
Resume Game sounds. I'll come, resume any instances
of pause game sounds. The foreloop for this will be similar to the one in
pause game sounds, so let's copy and
paste it from there. To resume a sound objects, we use the resume method. So let's change pause
here to resume. This we'll continue playing
this sound from where it left off if and
when we called pause. Finally, let's create the
stop game sounds method by typing public void
stop game sounds. I'll stop any instances
of game sounds. We can paste the four
loop in here and to stop a sound object,
we call the stop method. And by the way,
with sound objects, we can actually play
the same sound asset multiple times using
a single object. Calling the pause resume or stop methods with no parameters
like we're doing in these methods
will pause resume or stop all instances of the sound asset
stored in the object. If you wanted to call
one of these methods on a particular
instance of the sound, the play method actually returns a long value that represents
the current instances ID, and we can pass in this ID
when we call pause resume or stop to perform the action on just the sound
instance with that ID. This isn't really
necessary in our game, though, as we just want to
handle the sounds all at once. All right, now we
can head over to our main game class and create
an audio manager object. First, let's go to
the top of the class. And after we create
the assets object, let's create an
audio manager object by typing public Audio Manager, pressing Enter to import
it, then audio Manager. Now in the create method, we want to initialize the
audio manager after the assets have been loaded so that all of the audio assets
will be available. So after calling assets dow
manager dot finished loading, I'll comment, initialize
the audio manager. Then let's type audio Manager, equals New audio Manager. And here we need to pass
in Assets dot manager. Like I mentioned before, we're eventually going to
have settings that we'll check to determine whether to enable the music and sounds. We already enabled
them by default, but the music won't start
playing until we tell it to. So let's go ahead and call
audio manager dot play Music. And now if we run
the game, the music immediately starts
playing on loop. It's definitely not great music, and will probably get
annoying after a while, but it's not too bad for
demonstration purposes. Alright, now we can go
to the game screen class and start putting
our sounds to use. But first, because we're
already at the pause method, let's make it so that
minimizing the window, we also pause the music. If we run the game again
and minimize it right now, the music keeps playing. So in the pause method, after we check if we should
pause the game, I'll comment pause music, then it's called game dot
audio Manager dot pas Music. And to make the music
start playing again, when we bring the
window back up, we can resume it in the
rezoom method here. I'll comment resume music. And remember that we
made it so calling the play music method will only play the music
if it's enabled. So I'll also put
if it's enabled. Ellis type game audio Manager play music. Okay,
let's give it a try. Minimizing the window,
pauses the music, and bringing the window
back up, resumes the music. Perfect. All right, now let's make it so when
we press a button, it will play the click sound. To do this, let's head down
to the touchdown method. First, inside this IF statement, where we check if the pause
button has been touched. After we call pause game, I'll comment, play click sound. Tennis type game dot
audio Manager Play Sound. We need to pass in
assets do click Sound. Okay, I'm going to
copy these two lines. And down here where we check if the Play Again button
has been touched. Let's past the lines
after calling Start Game. Let's do the same
for the continue button here after calling resume game. Okay,
let's give it a try. Clicking the pause
button plays the sound, clicking Continue plays it. And if we hoops, it looks like we
have a bug because pressing an arrow
key pauses the game. If we go up to the key
down method really quick, I accidentally put the
inner closing parenthesis here at the end of
the If statement. So let's delete it
here. And instead, we need to put it here
after Gamestate dot paused. Okay, now if we run
the game again, the arrow keys work like normal. And if we beat the
game, clicking the play again button
plays a click sound. Cool. Okay? Now, let's make it so when we
hit the opponent, it will either play the hit
sound or if they're blocking, it will play the block sound. For this, let's go into
the update method. Near the bottom of the method, after we call the get hit
method on the opponent, we can determine which sound to play based on whether the
opponent is blocking. To do this as type, if game
dot opponent dot is blocking. I'll comment I opponent is
blocking, play block sound. Then let's call game dot
audio manager dot play sound. Assets dot BlocksunD.
Now let's do ts, and I'll comment, I opponent isn't blocking, play it sound. Then's type game dot audio
manager, dot play sound. Assets dot Hit sound. Okay, if we run the game and hit the opponent, I place
the hits sound. And if we go up to the
start Round method, after getting the
fighters ready, we can put the opponent
in the block state by typing game dot
opponent, do block. Now if we run the game and hit the opponent, Place
the block sound. Okay, I'll delete this line. The final two sounds
that we need to play are the cheer
sound and the Bo sound. We'll play the cheer sound
when the player wins around, and we'll play the boo
sound when they lose. So let's head down to the
Winound and Lose Round methods. Before calling end Round and
the Win Round method here, I'll comment play Cheer sound. Tennis type game audio
Manager dot Play Sound. Assets dot Cheer sound. And before calling end Round
and the ose Round method, I'll comment play Boo sound. Dennis type game
dot audio Manager, dot play sound
Assets Dabo sound. Now if we run the game and win the round plays the cheer sound. The player, of course,
can't lose a round yet since we haven't set
up the opponent's AI, but just to test it, we can change cheer sound
here to boo sound. Now if we run the
game and win around, pace. Plays the Boot sound. But of course, we don't want the player to feel
like the bad guy, so let's set this
back to cheer sound. Another thing we want to do is make it so pausing
the game we also pause all the game sounds and resuming the game, we'll
resume the sounds. So let's go to the
pase game method. At the bottom of the method, I'll comment pause game sounds. It might also be a good
idea to pause the music, so I'll add music. Now we can call game the audio
Manager, DPAs game sounds. Then game the audio
Manager, D pas music. Okay. Now on the
resume game method, I'll comment, resume game sounds and music if it's enabled. Tennis type game
the audio manager, the resume game sounds. And game the audio
manager that play music. Okay, if we run the game and win the round, Then pause it. We'll also pause the game
sounds and the music. If we press Continue, it
will resume the audio. Okay. One final thing we'll
do in this video is make it so the player can press a key to talk about the
music on or off. So let's head down to
the key down method. Let's do this by adding
another Out sift to our statement here
before the outs part. For the music toggle
key, we'll use. So let's type key code is
equal to input dot keys dot M. And here I'll comment, Toggle music on or off. Then we can simply call game dot audio manager doTogO Music. Now if we run the game, we can press to toggle
the music on or off. Excellent. Alright,
in the next video, we'll finally make it so
the opponent can fight back by setting up
their AI. It's there.
31. Add Opponent AI: In this video, we'll get
our game mostly fully working by programming the
AI for the opponent fighter. And we're going to
make it so that the harder the difficulty the player chooses for the game, the harder the opponent will be. The things we'll
cover in this video will probably seem a
bit confusing at first, mainly because I came up with a lot of it through
trial and error, but I'll do my best to
explain everything as we go. It's also not going
to be perfect AI, so I encourage you
to improve it or change it completely
to suit your needs. Okay, with that
said, let's begin. We'll first need to
go to the top of the game screen class and
create some variables. After the buttons section here, let's create a section
for opponent AI. First, we're going to need a
timer for the opponent AI, which we'll use to determine if they should perform
the next action. Let's create this by typing private float opponent AI timer. Another variable we'll
need is a boolean for indicating whether
the opponent is currently making a
contact decision. The opponent will make
a contact decision when the fighters are within contact distance
from each other, and there will basically be
three possible decisions. Attack the player, block
the player's attack, or move away from the player. Okay? For this variable,
let's type private Boolean, opponent AI making
contact decision. Next, we need to set up
a delay time between the opponents previous contact
decision and the next one. We actually have three
separate variables for this, one for each difficulty setting. The harder the difficulty, the shorter the opponent's contact decision delay will be. So for easy, let's spe
private static final float. Opponent AI, contact
decision delay easy equals 0.1 F. So
for the easy difficulty, there will be a tenth
of a second delay between the opponents
contact decisions. For medium, I'll duplicate
this line with Control D, change es to medium. Instead it to 0.07 F. And for
hard, I'll duplicate again, change it to hard, instead it to 0.01 F. It's only 100th
of a second for hard. Next, we need to set up
a couple chance factors. One for determining whether the opponent should block
the player's attack, and one for determining if
the opponent should attack. We can also create
separate variables for these depending
on the difficulty, but to keep things simple, we'll just use the
same chance factors for all difficulty levels. For the block chance, let's type private static final float. Opponent AI block chance equals 0.4 F. This basically means that the opponent will block
a player's attack four times out of ten. And for the attack chance, let's type private static
final float, opponent AI, attack chance equals 0.8 F. So if the fighters are
within contact distance, the opponent will try to attack the player eight
times out of ten. Next, we also need
a delay between opponent decisions
when the fighters are not within contact distance. We'll also just create a
single variable for this. Let's type private
static final float. Opponent AI, non
contact decision delay equals 0.5 F. So when the fighters aren't
within contact distance, the opponent will make a
decision every half second. The non contact decisions
will be either to walk in a random direction
or to pursue the player. Finally, we'll create
some variables for pursuing the player. First, we need a
boolean to indicate whether the opponent is
currently pursuing the player. For this, let's type
private boolean, opponent AI pursuing player. Now we just need to
pursue chance factor to determine whether
the opponent should pursue the player. We'll use three different values depending on the difficulty. The harder the difficulty, the higher the chance the opponent will
pursue the player. So for easy, let's type
private static final float, opponent AI, pursue
player chance EZ. Equals 0.2 F. This
means that on EZ, the opponent will pursue
the player 20% of the time. For medium, I'll duplicate this line, change it to medium. Let's set it to 0.5 F
or 50% of the time. Finally, for hard,
I'll duplicate again, change it to hard
and set it to one F. This means that on hard, the opponent will pursue the
player 100% of the time, so the player will have no
time to rest on hard mode. All right, now we're
going to credit a few methods to start
using these variables. For these, let's go
down near the bottom of the class after the R within
contact distance method. The first method will be for performing the
opponent's AI, and we'll call it from
inside the update method. Okay, let's create the
method by typing private, void, perform opponent AI. We actually want to pass in the Delta time to this method, so let's say the
parameter float Delta T. The first thing we want to do is check if the opponent is currently
making a contact decision. I'll comment, check if opponent is making a contact decision. Attack block, et cetera. Then let's type I opponent
AI, making contact decision. We then want to check if the opponent is
currently blocking, and if so, we need to decide whether to make
them stop blocking. So first, this type, I
game opponent is blocking. There are several conditions for when we want to make the
opponent stop blocking. When the fighters are no longer
within contact distance, when the player isn't
currently attacking or when the player's attack has already made contact with the opponent. I'll first put all of
this in a comment. If opponent is blocking, stop blocking if the fighters are not within contact distance, or player isn't attacking, or player has attacked
and made contact. Okay, to check all of this as type if not are within
contact distance, game dot player dot G position, game opponent dot get position. On game dot player that is attacking or game dot player
that has made contact. Now, we want to make the
opponent stop blocking by calling game dot opponent
dot stop blocking. Okay, after checking whether
the opponent is blocking, we next want to check if the opponent isn't
currently attacking. Let's do this by putting
an OCIP down here, the not game dot opponent
that is attacking. Next, we'll check
if the fighters are within contact distance. First I'll comment, I opponent
isn't currently attacking, check if the fighters are
within contact distance. Then's type, I within
contact distance, game dot player
dot get position. Cat opponent, do get position. If the fighters are
within contact distance, we'll check if the opponent's
AI timer has finished. If so, we'll make another
contact decision. If not, we'll just decrease the AI timer by
Delta T. So first, let's check if the AI timer
has finished by typing, I opponent AI timer less than or equal to zero
F. And I'll comment, I the fighters are
within contact distance and the opponent AI
timer has finished, make a contact decision. To break this method up, we'll create a separate
method for making a contact decision called opponent AI make
contact decision. Now we need to put an s here. And I'll comment, decrease the opponent AI
timer by Delta time. Then this type, opponent AI timer minus equals Delta time. Okay? And if the
fighters are not within contact distance, the opponent shouldn't
make a contact decision, so we need to set opponent AI making contact
decision to false. Let's do this by putting an s
down here and I'll comment. If the fighters aren't
within contact distance, opponents shouldn't make
a contact decision. Then let's type opponent AI, making contact
decision. Equals false. All right, so all of
this takes place when the opponent is making
a contact decision. We next need to decide what to do when this isn't the case. So let's add an te to
the main statement, where we check if opponent AI making contact decision is true. First, we want to
check if the fighters are within contact distance, and if so, we'll make
a contact decision. So let's type if R
within contact distance. Game dot player
dot get position. Coma game opponent,
do get position. I'll comment, I opponent
isn't currently making a contact decision and the fighters are within
contact distance, make a contact decision. And again, let's
call the opponent AI make contact decision method
that we haven't yet created. If the fighters are not
within contact distance, we want to check if the
AI timer has finished, and if so, we'll determine
if the opponent should pursue the player or simply
move in a random direction. So let's put notes here. Then type, I opponent AI timer, less than or equal to
zero f. I'll comment. If the fighters are not within contact distance and the
opponent AI timer has finished, either pursue the player or
move in a random direction. To determine if the opponent
should pursue the player, we'll get a random float 0-1 and check it against the pursue player chance variable
that we created. But if you recall, we
separated this into three values depending on
the difficulty setting. So we'll first
create a temporary float variable and set it to the correct pursue
player chance value using a ternary statement. To do this, let's type
float pursue chance equals difficulty is equal
to global variables, D difficulty, D easy. Question mark, opponent AI, pursue player chance easy. Then Colon, for this part, we could put another
ternary statement by typing difficulty is equal to global variables do
difficulty dot medium. Question mark, opponent AI, pursue player chance medium. Colon, opponent AI, pursue
player chance hard. To get a random float 0-1, we can use the public
static random method of the Math Utils class, which is a class
that LibGDX provides for us for doing many
different math functions. And we want to check that
the float is less than or equal to the pursue
chance variable, so let's type I Math Utils press Ender to import
it dot random, less than or equal
to pursue Chance. If this is the case, we want to set the
opponent AI pursuing player variable to true and make the opponent
move toward the player. I'll comment opponent
is pursuing player. Then type opponent AI
pursuing player, equals true. Next, I'll comment, move in
the direction of player. And for this, we'll
create another method called opponent AI
move toowardPlayer. Okay. Now if the random float is greater than pursue chance, you want to set
opponent AI pursuing player to false and move
in a random direction. So let's put an s here, and I'll comment, opponent
is not pursuing player. Then type opponent AI
pursuing player equals false. Next, I'll comment, move
in a random direction. And we'll also
create a method for this called opponent
AI move randomly. Okay, so after we make one of these non contact decisions, we want to set the AI timer to the non contact
decision delay so that there will be a pause between
the non contact decisions. So after this if statement
in here, I'll comment, set the opponent AI timer to the non contact
decision delay. Then type, opponent AI
timer equals opponent AI, non contact decision delay. Finally, we want to do
a couple of things when the AI timer has
not yet finished. First, if the opponent is
currently pursuing the player, you want to keep
calling the opponent AI move toward player
method to update the opponent's
movement direction in case the player has moved
in a different direction. Lastly, we want to decrease
the AI timer by Delta time. So let's add an e
to the I statement where we check if the
timer is finished. I'll comment, I opponent
is pursuing player, moving the direction of player. Then this type, I opponent
AI pursuing player, opponent AI move toward player. Next below this SIF
statement, I'll come it, decrease the opponent
AI timer by Delta T. Then let's type opponent AI timer
minus equals Delta T. Now we need to create the opponent AI make
contact decision method. The opponent AI move
toward player method, and the opponent AI
move randomly method. Let's start with the make
contact decision method down here by typing
private void, opponent AI make
contact decision. The first thing we want
to do in here is set opponent AI making
contact decision to true. Next, we're going to make
a contact decision that will depend on whether the
player is currently attacking. If so, we'll decide
whether to block the attack or move
away from the player. If not, we'll either attack the player or move
away from the player. First I'll comment here,
make a contact decision. Then this type, I game dot
player that is attacking. Now, we only want to
bother blocking or moving away if the player
hasn't yet made contact. If they have made
contact, it's too late. So I'll comment, I player is attacking and hasn't
yet made contact, determine whether
to block players attack or move away from player. Then let's type, I not game
player dot has made contact. To determine whether
the opponent should block or move away, watch like a random
float against the block chance variable
that we created, like we did with the
pursue chance variable. So let's type I
MatuTils dot random, less than or equal to
opponent AI block chance. If so, we want to block
the player's attack. So I'll comment block
players attack. Then let's type game
that opponent do block. Otherwise, we want to move
away from the player. So let's type LTs and I'll
comment move away from player. And for this, we'll
create a method called opponent AI move
away from player. Okay, we next want
to decide what to do when the player
isn't attacking. So let's not an s
to the I statement here where we check if
the player is attacking. Here we're going to
determine whether to attack the player or move
away from the player. And similarly to blocking, we'll do so by checking
a random float against the attack
chance variable. So first I'll comment, I
player isn't attacking, determine whether to attack player or move away from player. Then let's type if
Matthew tills dot random. Less than are equal to
opponent AI attack chance. And with attacking, we of course have two options
punching or kicking. Let's make it so 50% of the
time the opponent will punch, and the other 50% of the
time they will kick. To do this, we can generate a random integer of either zero or one with zero meaning
punch and one meaning kick. We can actually also
use the Matthew tills dot random method for this. If we pass in a
number to the method, it will generate a
random number from zero up to and including
the pass in number. Also, the value it
returns will be of the same type as the type
of number we passed in. Therefore, if we pass in a one, which is an integer, the method will randomly return
either a zero or a one. So I'll comment
here, attack player. Equal chance of
punching or kicking. Then's type I Matthew
tills dot random, one is equal to zero. Game that opponent do punch. Else, game that
opponent do kick. Okay, if the random
float we generated up here is greater than
the attack chance, we want to move away
from the player. So let's at a notes
this SIF statement. And I'll comment, move
away from player. Then's call opponent AI,
move away from player. Finally, below the
Minif statement, we need to set the
opponent AI timer to a contact decision delay. And because we
created a separate delay value for each difficulty, we'll use a switch statement to determine which delay to use. So first I'll comment, set the opponent AI timer to a difficulty based
contact decision delay. Then it's type
switch difficulty. Let's first do case, easy. Opponent AI timer
equals opponent AI, contact decision delay easy. Break. In case medium,
opponent AI timer, equals opponent AI,
contact decision delay, medium, break. And for hard, we can
just do default, opponent AI timer,
equals opponent AI, contact decision delay hard. Next below this method. Let's create the
opponent AI move toward player method
by typing private, void, opponent AI,
move toward player. And I'll comment move in the direction of players position. To do this, we need to get the position of both
fighters and compare them. Basically, if the
opponent's exposition is greater than the
player's exposition, the opponent needs to move left, and if the opponent's exposition is less than the players,
they need to move right. Similarly, if the opponent's y position is greater
than the players, they need to move down, and if it's less than the players,
they need to move up. If their X positions match, the opponent doesn't need
to move horizontally, and if their Y positions match, the opponent doesn't
need to move vertically. However, the X and Y positions don't need to match exactly. The opponent just needs to get within contact distance
of the player. Therefore, when checking the distance
between the fighters, we can factor in the fighter
contact distance X and fighter contact
distance Y values that we created back near
the beginning of the course. Okay, so let's begin by setting a couple vector two
objects to point to the fighters positions
so that we don't have to keep calling the Git position
method on the fighters. For the player's position,
let's type vector two, player position equals game
player dot get position. And for the opponent,
let's type vector two, opponent position equals game dot opponent
dot get position. Next, we want to check
if the opponent's exposition is greater than the player's exposition
with the value of fighter contact
distance X added to it, and if so, the opponent
needs to move left. So let's type I
opponent position dot X greater than player
position dot X plus fighter contact distance X. Game opponent move left. If this isn't the case, we want to check
if the opponent's exposition is less than the player's exposition with fighter contact distance
X subtracted from it, and if so, we'll move the
opponent to the right. Let's put Osif opponent
position dot X. Less than player position dot X minus fighter
contact distance X. The game that opponent
that move right. If neither of these
cases is true, we don't want the opponent
to move horizontally, so we can put s, then stop the opponent's left
and right movements by calling game that opponent
thou stop moving left. And game that opponent
thou stop moving right. We now want to create
a separative statement for comparing the Y positions. Let's start by putting if
opponent position dot Y. Less than player position dot Y minus fighter
contact distance Y. Game that opponent that move up. Then let's do Osif. Opponent position dot Y. Greater than player
position dot Y plus fighter contact distance Y. Game that opponent
that move down. And finally, else, game that opponent does
stop moving up. Game that opponent
does stop moving down. Okay, below this method, let's do private void, opponent AI, move randomly. In this method, we're
going to randomly set both the horizontal
and vertical movement of the opponent. For horizontal movement, we'll decide whether
to move left, right, or not move
horizontally at all. Similarly, for
vertical movement, we'll decide whether to move up, down or not move vertically. This means that for both horizontal and
vertical movement, there will be three
possibilities that we'll give equal
chance to all three. Let's start with the
horizontal movement. I'll first comment, randomly set opponent's
horizontal movement. And we want to generate
a random integer 0-2, which we can do by
calling Matthew tills dot random and passing
in a two as a parameter. Let's do this in a switch
statement by typing switch Matthew tills
dot random two. That's where we turn either
a zero or one or two. So let's first check if it's
a zero by typing case zero. If so, let's call game that
opponent, that'll move left. Then break. Let us do case one game that opponent,
that move right. Break. For two, we
can put default, and we want to stop all
horizontal movement by calling game that opponent
stop moving left. And game that opponent
that stop moving right. For the vertical
movement, we can copy and paste all
these lines here. And I'll change horizontal
here to vertical. For case zero,
let's call move up. For case one, let's
call move down. And for default, let's call stop moving up and
stop moving down. That's it for this method.
Finally, let's create the move away from player method below
this one by typing private, void, opponent AI,
move away from player. This would be kind of
like the opposite of the method for moving
toward the player. But fortunately, it
will be a bit simpler. Like with moving
toward the player, we could first set some vector two objects to point to
the fighters positions. First I'll comment, move
away from player's position. And I'm going to go
up to the move toward player method and copy the
first two lines of code here. Then paste them in
the move away method. In this method, we just want
to move the opponent in a direction that will
get them away from the player as
efficiently as possible. Basically, this means
that if the opponent is currently to the
right of the player, the opponent should move
even more to the right. Otherwise, they should
move to the left. Likewise, if the opponent is
currently above the player, the opponent should
move up even more. Otherwise, they
should move down. Let's start with a
horizontal movement by typing if opponent position dot x greater than player
position, dot x. Game dot opponent
dot move right. ELTsGame that opponent
that move left. For the vertical movement, let's type if opponent position dot Y, greater than player
position dot Y. Game that opponent that move up, EltsGame that opponent
that move down. That's it. We're finally done with the
opponent AI methods. Now let's put them
to work. Let's first go up to the update method. To get the opponent AI working, we simply need to add a call to the perform
opponent AI method somewhere in here
before we check if the fighters are within
contact distance. I'll comment perform
the AI for opponent. Then let's call
perform opponent AI. We need to pass in Delta T here. Now let's run the game. Okay,
after the round begins, the opponent starts moving
around in the ring. And if we get close to them,
they start attacking us. However, their attacks
aren't hurting us. That's because inside
the update method, where we check if the
player is attacking, we forgot to also check if
the opponent is attacking. Basically, we can
copy this whole if game player that is attack
active statement here, add an outs to the bottom of the statement and
pace the lines. Then switch all the player
and opponent stuff around. So game dot opponent that
is attack active here. Now change the comment
to opponent is actively attacking player gets hit. Change this to game
dot player do get hit. Change this one to game
dot player is blocking. Now change opponent in
these comments to player. Deactivate opponent's
attack here. Game dot opponent
that make contact. Here I'll change the comment
to check if player has lost and change this to game
dot player that has lost. For this comment,
I'll change it to if player has lost, player
loses the round. We need to change win
round here to lose round. We also never set the
Max Life variable in the fighter
class back to 100, so let's go over to
the fighter class and change Mxlife equals five F here to MXLpe equals 100 F. Now let's run the game. I Right now we can actually
fight the opponent. It's pretty easy
right now, though. That's because the difficulty
is set to easy by default. The player will be able to set the difficulty in the
setting screen later, but for now, we can make it so they can change it
by pressing a key. So let's go down to the key down method of the
game screen class. After we check if
the key has been pressed to tackle the
music on or off here, we can add another Sif. For the difficulty,
let's go with the key by typing keycde is equal
to input dot keys dot L. Our first comment
change the difficulty. Each time the player
presses the key, we want the difficulty to
move sequentially from easy to medium to hard
then back to easy. Let's do this with a
switch statement by typing switch difficulty. Case easy. Difficulty equals
global variables. Do difficulty do medium. Break case medium. Difficulty equals global
variables, the difficulty, the hard. Break. Default. Difficulty equals global
variables do difficulty do easy. Now, let's give it
a run. Alright, we can press the key to change
the difficulty to medium. Press it again to
change it to hard. Now the opponent is much harder. If we press L again,
it goes back to easy. Cool. Okay? In the next video, we'll add some blood
to the game. See.
32. Blood 1: Blood Splatters: Each time a fighter
gets hit in the game, we'll display two
types of blood, a blood splatter that appears briefly on a fighter's body and a blood pool that appears on the ring under the fighter
and slowly fades away. We're going to create
two different classes for the two types
of blood and use these classes to create
multiple blood objects that will draw to the
screen throughout the game. We'll add both of these classes
to the objects package. So first, let's right
click the Objects package, go to New Java class. Let's start with the blood
splatter class by typing blood splatter and
pressing Enter. If we go into the
textures Assets folder, we have blood dot atlas
and blood dot PNG. If we open blood dot PNG, we can see all the
textures we'll use for both the blood splatters
and the blood pools. The blood pool textures
are these three here that look like a
bunch of red dots in an elliptical shape. When we create the blood
pools in the game, we'll use a random blood
pool texture for each one. The blood splatter textures, however, form an animation. Therefore, in the
blood splatter class, we need to set some
variables to hold things like the blood splatter state
time and its animation. Let's start with the
state variables. I'll come it state. And let's
first create a variable for the state time by typing
private float state time. Okay, we're actually going
to create an array of blood splatter objects in
the game screen class later, and we'll reuse the
objects as needed. In order to do this, we
need to keep track of which blood splatter objects are active and only draw
those to the screen. So let's create a variable
for indicating whether the blood splatter is active by typing private boolean active. One more state variable we'll need is the position
of the blood splatter. So let's type private
final vector two, press Enter to import it. The position equals
new vector two. Can now we just need a variable
to hold the animation. I'll comment here animation. Let us first type
private animation and make sure to import the
graphics GTD version here. The animation will be using texture regions from the
Blood texture atlas, so let's put less
than texture region. Presenter, then greater than, let's call it
splatter animation. Next, we want to create a
constructor for the class. So down here, let's type
public Blood splatter. And we need to pass in the main game class to the constructor. So let's type SFS,
precenter, then game. The first thing we're
doing here is initialize the state time to zero
and set active to false. I'll comet initialize state. And let's put state time equals zero F, active equals false. Now we need to initialize
the animation. I'll comet initialize
the splatter animation. And for this, let's call
it a method that we'll create called initialized
Spider animation. As a parameter, we'll need to
pass in the asset manager. So let's p game dot
assets dot manager. Can always create the initialized
Spider animation method below the constructor
by typing private, void, initialized
Spider animation. Asset manager, press enter to import it, then
asset manager. First, we need to get the blood atlas from the asset manager. We created a blood
atlas variable for this in the assets class. So back in the blood
splatter class, I'll comment, get
the blood texture atlas from the asset manager. Then it's type texture
Atlas, presenter. Blood Atlas equals asset
manager dot Git Assets, presenter dot Blood Atlas. Now we just need to
create the animation. I'll comment, create
the animation. This type spider animation
equals new animation. Less than texture region, greater than open parthess. For the frame duration, I found that 0.03 F
works pretty well. For the texture reasons of
the blood splatter animation, if we open the blood Atlas
file and the assets folder, the blood splatter regions are all labeled blood splatter. So in the blood splatter class, we can put blood atlas, dot Fine regions,
blood splatter. And we need to make sure to
put fine regions with S here, as we're looking for multiple regions labeled blood splatter. Next, we need to
create a couple of methods for activating and deactivating the blood splatter from the game screen class. First, for the activate method, let's type public void activate. And when we activate it, we also want to set its position. So for parameters, let's
put float position X, come a float position Y. Okay. And here, I'll comment, activate the blood splatter. Now let's set active to true, state time to zero F, and set the position by
typing position dot set, position X, common position Y. Okay, for the deactivate method, let's type public
void deactivate. And we don't need any
parameters for this. I'll comment deactivate
the blood splatter. And all we need to do in
here is set active to false. Now we need an update
method to update the state time and a render method to draw the
current animation frame. For update, let's type
public void Update. And we need to pass in the
Delta T. So for parameter, let's put float
Delta T. All right, first, if the blood
splatter isn't currently active, we don't
want to do anything. So I'll comment, I not
active, don't update. Then let's type, I
not active, return. Next, we need to increment the state time by
the Delta time. So I'll comment, increment
the state time by Delta T. Then this type, state time plus equals
Delta T. Finally, we want to check if the
animation has finished, and if so, we'll deactivate
the blood splatter. First I'll comment. If the splatter
animation has finished, deactivate the blood splatter. Then type, I splatter animation is animation
finished, state time. I think it's something called
the deactivate method. Under the update method, let's create the
render method by typing public void render. We need to pass in
the sprite batch, so let's type Sprite Batch. Press Enter to import
it, and Batch. Like with update,
we don't want to do anything if the blood
spider is inactive. So I'll comment, I not
active, don't render. Then this type, if
not active, return. Now we need to get
the current frame of the animation, then draw it. First I'll comment, draw the
current animation frame. And the animation frames
are texture regions. So to get the current frame
using the state time, we can type texture region, current frame equals
splatter animation, Get key frame, state time. And to draw it, we
can type batch, dot draw, current frame, common position dot X, common position dot Y, co current frame, dot get region with Times global variables. Press Enter to import it, then dot rolled scale. Come a current frame
DG region height. Times global variables,
dot rolled scale. And that's it for the
blood splatter class. Now let's head over to the
top of the game screen class. After the opponent
AI variable section, let's set a section for blood. We're actually going to make
it so the player can turn the blood on and off through
the setting screen later. So before drawing any blood, we'll need to check if
showing blood is enabled. For this variable, let's type private bullying showing blood. Let's set it to true for now. Next, we'll create a
separate array of blood splatter objects for
each of the fighters. So let's type private
blood splatter. Press Enter to import it, then open enclosed brackets, then player blood splatters. After that, let's do
private blood splatter, open closed brackets,
opponent blood spiders. We also need a couple of
variables that point to the current index of the
two blood spltr arrays. So let's type private nt current player blood
splider index, and private nt, current
opponent blood splider index. Next, we need to create a static final variable to indicate how many blood
splatter objects to create for each blood
splatter array. Actually, due to things like the duration times that we set for the different
animations, it's very unlikely
that there will ever be more than one blood splatter
on the screen at once. But just in case we
might want to make the animations faster
or something later, we'll set the blood
splatter amount to five. Let's type private static final
nt blood splatter amount. Equals five. Finally, we'll need a couple of static final variables for the positioning of
the blood splatters. This will be an offset
that will add to the current position
of the fighter who owns the blood splatter. For these, let's type
private static final float. Blood splatter offset X, equals 2.8 F and private
static final float, blood splatter offset Y equals 11 F. To come
up with these values, I use trial and error, and they'll put
the blood splatter up near the fighter's head. Okay, we next need
to create a method for creating the
blood splatters. We'll actually use
the same method for creating the
blood splatters, as well as the blood
pools in the next video, and we'll name it create blood. We'll be calling
this method from the game screen constructor. So first, at the bottom
of the constructor, I'll com it, create the
blood splatters and pools. Then let's call create Blood. Alright, now let's
create the method below the create buttons method here by typing private
void create Blood. And here we first
need to initialize the blood splatter
arrays and objects. I'll comment, initialize
the blood spltrs. Then let's type platter
blood splatters. Equals new blood splatter, open bracket blood
splatter amounts, close bracket, and
opponent blood splatters equals new blood splatter, bracket, blood splatter amounts. Now we need to loop
through the arrays and initialize the
blood splatter objects. Let's do this with
a four loop by typing four inch I equals zero, semicolon I less than
blood splatter amount. Semicolon I plus plus. Then player blood splatters
I equals new blood splatter. We need to pass in the
game variable here. Then let's do opponent
blood splatters I equals new blood
splatter game. Finally, we need to set both of the current blood
splatter indexes to zero. I'll come it, set the current
blood splatter indexes to the start of the arrays. Then let's type current player blood splatter index equals zero and current opponent blood splatter index equals zero. And that's all we need
to do in this method until we create the blood
pools in the next video. Okay, now we need
to create a method for drawing the blood splatters, which we'll call render
blood splatters. Let's create it below the
render fighters method. By typing private void
render blood splatters. And we want to pass in which blood splatter array to draw. So for a parameter, let's put blood splatter brackets,
blood splatters. And here we want to
check if showing blood is allowed, and if so, we'll loop through
the array and call the render method for each
blood splatter object. And remember that
we made it so that the render method will only draw blood splatter
if it's active. So first I'll comment,
I showing blood, draw all active blood
splatters in the given array. Then this type if showing
blood for blood splatter, blood splatter, colon
blood splatters. Blood splatter, dot render, and we need to pass
in game dot Batch. Okay. Now when we draw
a blood splatter, we want to draw it after
we draw the fighter that owns it and before we
draw the other fighter, so that the blood splatter will appear on the
correct fighter. Therefore, we need to
draw the blood splatters inside the render
fighters method here. First, after the line near the
top or we draw the player, I'll comment, draw player's
blood splatters if enabled. Then let's call Render
Blood splatters and pass in player
blood splatters. Now I'll copy these two lines and paste them below where we draw the player in
the outs part here. Next, I'll paste the lines up here after we
draw the opponent. And in the comment,
I'll change players to opponents and change
player blood splatters to opponent blood splatters. Then I'll copy and paste these lines after we draw
the opponent in the Os part. Okay, we now need
to do a few things down here in the update method. First, after we
update the fighters, I'll comment, update
the blood splatters. Then we want to loop
through the blood splatter arrays and call the update
method for each one. So let's type four
int I equals zero, semicolon I less than
blood splatter amount. Semicolon I plus plus, platter blood splatters I the update and pass
in Delta time. Then opponent blood splatters
I the Update Delta Time. Can let's go down here to where we check if the fighters
have hit each other. Whenever a fighter gets hit, we'll call a method that we'll
create called spill blood. This method will take
a fighter object as a parameter and will activate a blood splatter for
the given fighter, as well as a blood pool
in the next video. Now, if we want, we can call
the spill blood method, regardless of
whether the fighter that gets hit is blocking. However, I think
it will be better to just call it when the fighter isn't blocking and not draw any blood if the
fighter is blocking. So in the ts part of
this If statement, where we check if the
opponent is blocking, after playing the hit sound, I'll comment, spill some blood. Then let's call it spill blood
and pass in game opponent. Now, I'll copy and paste these
lines into the Os part of the O statement down here where we check if the
player is blocking. Now change opponent
here to player. Alright, for the
spill blood method, let's credit below the
update method by typing private void spilled blood with a fighter fighter
as a parameter. First, we need to use
the given fighter to determine which blood
splatter array to use, along with the correct
current blood splatter index. I'll comment here, use
the given fighter to get the correct blood splatter
array and current index. Then let's type blood
splatter brackets, blood splatters and int
current blood splatter index. We set these two variables to the correct variables
for the given fighter. To check which fighter
the given fighter is, we can use the equals
method of the object class, which every class
in Java extends. The equals method takes an object as a parameter
and will return a boolean value
indicating whether the parameter object is the same as the object
calling the method. If you want to check
if the fighter is the player, for example, we can type a fighter dot equals and passing
game dot player. If so, we can set
blood splatters to player blood splatters and current blood splatter index to current player
blood splatter index. And if the fighter
isn't the player, then it must be the opponent. So we can put outs.
Blood splatters equals opponent blood splatters. Current blood splatter index equals current opponent
blood splatter index. Okay, now that we've gotten
the correct variables, we want to go outside of this
I statement and activate the blood splatter
at the current blood splatter
index in the array. First I'll comment, activate the current blood
splatter in the array. Finns type blood
spiders, open bracket, current blood splider index,
closed bracket activate. We need to pass in the position
of the blood splatter. For this, we'll use the
given fighter's position with the blood splatter
offset values added to it. Let's type fighter,
docu position dot X plus blood splatter offset X. Coma fighter, docu position dot Y, plus blood splatter offset Y. All right now we just
need to increment the current blood
splatter index so that if the previous blood splatter
is still animating on the screen and we want
to activate another one, it won't replace
the previous one. And we want to
increment the actual current index for
the given fighter, not the local variable here, because that won't have any effect outside
of this method. So we first need to
check which fighter the given fighter is again, then increment the
correct index. Also, if the current index has reached the
end of the array, we need to set it back to zero. Okay, I'll comment
here, increment the correct current
blood splatter index, or return to the first if the end of the blood splatter
array has been reached. Then let's check if
the given fighter is the player by typing if fighter dot equals
game dot player. Okay, because we set the size of the blood splatter arrays to be the blood splatter amount variable that we
created earlier, the final index in the arrays is blood splatter
amount minus one. So we can check if
the player's current blood splatter index
hasn't reached the end of the array by typing if current player
blood splatter index, Less than blood spider
amount minus one. And if so, we'll increment
the index by typing current player blood
spider index plus plus. If this isn't the
case, the index has reached the
end of the array, so we'll set it back
to zero by putting s, current player
blood spider index. Equals zero. Okay, now we just need to do all of
this for the opponent. So let's add an outs
to the If statement where we check if the
fighter is the player. Then let's copy this
inner If statement here, paste it in the outs part, and change current player
blood splatter index to current opponent blood splatter index in all of these lines. That should do it. Now we can give the game a run
and see if it works. Alright, as the fighters
hit each other, blood splatters appear
near their heads. If we get hit while
blocking, no blood appears. Perfect. Okay, in
the next video, we'll add some blood
pools as well. See there.
33. Blood 2: Blood Pools: For the blood pools, which we'll draw on the ring under the fighters
when they get hit, we'll need to create
another class. Like with blood splatter, we'll create the class
in the objects package. So let's right click the package and go to New Java class. Let's call it Blood
Pool and press censor. Okay, unlike blood splatters, which are animations that only appear on the screen
for a short duration, a blood pool consists of
a single texture that gets drawn to the screen and
slowly fades away over time. And similar to blood splatters, we'll have an array
of blood pools in the game screen class, which will go through and activate the blood
pools as needed. So to begin, we'll
need to create some state variables
for the blood pool that include things
like a state time, whether it's active or not, its position on the screen, the amount of time it
takes to fade away. And in order to make the
blood pool fade away, we'll need to lower
its opacity or Alpha channel over time as
we draw the blood pool. Okay, so our first
comment states then let's start with the state time by typing private float state time. Next, let's do private
Boolean active. And to hold its
current Alpha value, let's type private float Alpha. Next for its position, let's
do private final vector two, press Enter to import it. The position equals
new vector two. Finally, we need a static final variable for
its fade time. So let's type private
static final float. Fade time equals. Let's set it to 60
F for 60 seconds. So by the time the
blood pool has been on the screen
for 60 seconds, it will have disappeared. All right? And we
also need a couple of variables for the
blood pools texture. If we open the blood
dot PNG asset again, these three elliptical shape textures are for
the blood pools. We'll assign our random one to each blood pool
when we create it. So back in the blood pool class, I'll come it texture. Then let's credit a
variable for its texture. And because it's going
to be a texture region inside a texture atlas, let's do private final
texturesion Press Enter to import it and texture. Also, when we create a method to get a random texture later, we need to let the method know how many textures there
are to choose from. As we just saw on blood dot PNG, there are three textures. So let's type private
static final int. Texture amount equals three. Next, we need to create a constructor where
we'll initialize all of the variables and get a random texture from
the blood atlas. So first, let's type
public blood pool. And we need to pass in the
main game object here, let's type SFS, press
Enter, then game. And here I'll comment,
initialize state. Then let's set state
time to zero F, active to false,
and Alpha to one F, which means folly opaque. Next, we'll set the
texture variable to a random texture region
from the bd atlas. How come it, set the texture to a random blood pool texture. We're actually going to
create a separate method for this called Get Random
Blood pool texture. So let's type texture equals. Get Random blood pool texture. And we need to pass in
the asset manager here, so let's type game dot
assets dot Manager. Okay, for the Get Random
Blood Pool texture method, let's go down here and type
private texture Region. Get Random Blood Pool texture. Asset Manager, precenter
then asset manager. First, we need to get the blood atlas from
the asset manager, so I'll comment, get
the blood texture Atlas from the asset manager. Then this type texture
atlas precenterblood Atlas equals asset
manager dot Git assets, presenter dot blood Atlas. Next I'll comment, return a random blood pool texture
region from the blood atlas. If you open the
blood atlas file, the blood pool regions are
labeled blood pool zero, blood pool one, and
blood pool two. Therefore, to choose
a random one, we need to use the
string blood pool plus a random integer 0-2. So back in the blood pool class, let's first type return blood atlas dot fine
region. Blood pool. Plus, and to get
a random integer, we can use the random method
of the Matthew Tills class, passing in the largest
integer that we want, like we did when we
had the opponent AI choose random actions. So first, we can
type Matthew tills, press Enter to import
it, then dot random. And the maximum
integer we want is texture amount minus one. We set texture amount to three, so this will pass in a
two to the random method, which will then return
either a zero or one or two. We next need to
create a method for activating the blood
pool where we'll do things like we set the
blood pool state time and Alpha and set this position. So for this, let's type
public void, activate. We need to pass in the
position, so let's type float, position X, come a
float position Y. And here I'll comment,
activate the Bod pool. Let's first set active to true
then state time to zero F, Alpha to one F. Let's set the position by
typing position dot sets, position X, comment position Y. Another method we need
to create is one for updating things like the Bod pool state time and Alpha value. For this method, let's
type public void update. We need to pass in Delta time, so for a parameter, let's
put float Delta time. First, if the blood
pool isn't active, we don't need to update it. So I'll comment, I not
active, don't update. Then this type, if
not active, return. Next, we'll increment
the state time by Delta T. So I'll comment, increment the state
time by Delta time. Then this type state time
plus equals Delta time. Next, we need to reduce the
blood pool's Alpha value, making it get closer
and closer to zero as its state time
reaches the phat. Because the Alpha needs
to be a float 0-1, the calculation we can use to
get the current Alpha value is one minus the current state
time divided by the fat. So first I'll comment, reduce the Alpha to make the blood pool more transparent over time. Then this type Alpha equals one F minus state time
divided by fade time. Finally, we'll check if the
Alpha value has reached zero, and if so, we'll
deactivate the but pool. For this, I'll comment, if
the Alpha has reached zero, deactivate the but pool. Then this type, if Alpha, less than or equal to zero F. Now we can set Alpha to zero F in case it dropped below zero, let's set active to false. Okay, now we just need a render method to draw the blood pool. So after the update method, let's type public void render. We need to pass in
the sprite batch. So let's type sprite batch,
percenter, then batch. First, like with update, if the blood pool isn't active, we don't want to do
anything. So I'll comment. If not active, don't render. Then let's type, if
not active, return. Next, we need to
set the color of the sprite batch to use the
blood pool as Alpha value. Our first comment, set the sprite batch as color
using the blood pool as Alpha. Then it's type Batch
dot side color. If you want to use ones
for the RGB values, then Alpha for the Alpha value. Using ones for the
RGB values will make it so it won't affect the RGB channels of the texture. We just want to affect
this Alpha channel. Okay, now we can draw
the blood pools texture, so I'll comment,
draw the blood pool. Then it's type Batch dot draw texture coma
position dot X, coma position dot Y, coma texture dot G region with. Times global variables, pre
center that roll scale, come a texture that
get region height, times global variables
that roll scale. Finally, we need to set
the Sprite batches color back to folio opaque. So I'll com it. We set the sprite batches
color to foli opaque. Then let's call it
batch Ds color one, one, one, comma one. Okay, we're finished with
the blood pool class, so let's head over to
the game screen class. First, at the top of the class, we need to add a few blood pool variables to
the blood section. First, unlike with
the blood spliders, we'll be drawing the blood pools underneath both fighters. So we'll only need a
single blood pool array. For the array, let's
type private blood pool. Press Enter to import it, then open closing
brackets blood pools. Next, we need to keep track of the current index in the array. So let's do private
current blood pool index. Finally, we need to set
a blood pool amount. Because the blood poles will stay on the screen for a while, we need to have
enough blood pool objects in the array to prevent active blood pools from being reset before they
have finished fading. With a fade time of 60 seconds, we probably only need about
20 or so blood pool objects. But just in case we want to
increase the fade time later, let's go ahead and
create 100 objects. So for this variable, let's type private static final
int blood pool amount. Equals 100. All right, now we need to head to
the create blood method and initialize the
but pool variables. First, after all of the
blood splatter stuff, let's initialize the but pools. I'll come initialize
the blood pools. And we first need to initialize the bud pool array to have the correct amount of blood
pools by typing bud pools, equals new blood pool, Brackets but pool amount. We now need to loop
through the array and initialize every
blood pool object. That's type four
inch I equals zero, semicolon I less than
blood pool amount. Semicolon I plus plus, blood pools I equals
new blood pool. We need to pass in
the game object. Finally, we need to set the
current blood pool index to zero, so I'll com it. Set the current blood pool index to the start of the array. Then let's type current blood
pool index equals zero. Can let's head to the
main render method. Because we want
the blood poles to appear underneath the fighters, we need to draw them before
we draw the fighters. So before we call
render fighters here, how come it draw
the blood pools. We'll create a method for this
called Render Blood Pools. Now let's go down below the Render blood
splatters method. And create the
render blood pools method by typing private, void render Blood pools. And here we simply
need to check if showing blood is
enabled, and if so, we'll loop through
the blood pool array and call the render
method on each one, which will only draw the
blood pool if it's active. So first I'll comment,
I showing blood, draw all active blood pools. Then this type,
if showing blood, Four blood pool blood
pool, call in blood pools. Blood pool, dot render, and we need to pass
in game dot batch. All right, that's
set for rendering. So now let's go down
to the update method. And here, after we update
the blood splatters, we also need to update
the blood pools. So I'll comment, update
the blood pools. Then let's use a four
loop to look through the blood pool array by
typing four blood pool, blood pool, call in blood pools. Now we can call Blood
Pool dot updates and pass in Delta T. Finally, we just need to add a few things to our spilled blood
method down here. First, after all of the
blood splatter stuff, we need to activate
the blood pool, the current blood pool
index in the array. So I'll comment, activate the current blood
pool in the array. Then it site Blood pools, brackets current bod pool index. Dot activate. And we need to pass in the
position of the blood pool. When I created the
blood pool textures, I made them so that they will align with the shadows
of the fighters. So for the position,
we can simply put in the given fighter's
position by typing fighter, De position, dot X, coma fighter, docket
position dot Y. And now like with
the blood splatters, we need to either increment
the current blood pool index if it hasn't reached the end
of the array or if it has, we'll set it back to zero. So first, I'll
comment, increment the current blood pool index or return to the first if the end of the blood pool
array has been reached. Then this type, if
current blood pool index less than blood pool
amount minus one. Current blood pool
index, plus plus s. Current Bod Pool
index equals zero. Okay, now we're
ready to give this a go by running the game. Now when the fighters
hit each other, we get blood pools under
the injured fighter, and these will slowly fade
away as we play the game. They will also
carry over between rounds and between fights. Okay, before we move on,
as I mentioned earlier, we're going to let
the player turn the blood on and off in
the setting screen later. But for now, we can
make it so they can press a key to tackle the blood. To do this, let's go to
the key down method. After we check if the key has been pressed to toggle
the difficulty, we can add an SIF
to this statement. And for the blood toggle key, we'll use K, so we
can type key code, equals input keys K.
And here I'll comment, toggle blood on or off. Then we can simply do showing blood equals not showing blood. So if it was true,
it becomes false, and if it was false,
it becomes true. Now let's give it a try. Alright, if we press K, it hides the blood. If we press it again,
the blood comes back. Nice. Okay, in the next video, we'll start working
on the other screens by creating the main
menu screen. See there.
34. Main Menu Screen 1: Set Up the Widgets: Before we create the
main menu screen, we need to go into
the assets class and create a method for loading
all the necessary assets. Let's create this method below
the load audio method here by typing private void
load menu assets. Now if we take a look at
the textures Assets folder, we actually only have
two menu asset files, menu items dot Atlas
and menu items dot PNG, which make up the
menu items textualis. If we open up menu items
dot PNG really quick, this contains a bunch of
textures that we'll use for both the main menu screen
and the setting screen. Okay, so in the load
menu assets method, we simply need to load
the menu items textualis. So first I'll comment, load
the menu items texture atlas. Tennis type manager dot load, menu items Atlas,
texture atlas dot class. We also need to go up
to the load method and add a call to the
load menu assets method. Like with the game screen class, the main menu screen class is going to implement the
screen class so that we'll be able to switch
back and forth between it and our other classes
that implement screen. Like we did with the
game screen class, we want to put the
main menu screen class inside the screens package here. So let's right click the package and go to New Java class. Let's call it main menu
screen and part Center. Okay, so first, we need
to implement screen. So let's type implement screen and press Enter to import it, and now we need to implement
all of the required methods, which we can do by
hovering over this line, clicking implement methods
here, then clicking Okay. Next, let's go above
the show method here. Let's add some variables. First, we'll need a
variable pointing to the main game class. Let's put private, final, SFS, press Enter to
import it, then game. Next, we'll need a stage object. The stage class is part of the scene two D
framework in Lib GDX. Scene two D provides a hierarchy of graphical
elements called actors, which consist of
things like textures and UI elements that
we draw to the screen. The advantage of
using scene two D, especially for user interfaces, is that we can
have parent actors which influence the size, positioning, and appearance
of their child actors. As we'll see, this will provide a more efficient way of
laying out UI elements on the screen without
having to calculate all the positions and sizes like we did when
creating the HUD. Anyway, the stage class is the root class of
Scene two D and is responsible for doing things
like rendering the actors to the screen and handling
input from the user. And to create a stage object, we can type private,
final stage. Make sure to import the scene two D version, then type stage. Next, we'll need a
variable for getting the menu items texture atlas
from the asset manager. So let's type private
final texture Atlas, presenter, the menu item atlas. We now need to create some
variables for the widgets. Widget is a class in the
Scene two D framework for creating user interfaces, and there are many
classes that extend the widget class,
including image, which is basically used for
displaying textures, label, which is used for
displaying text and button, which lets us display textures
that the user can click. We'll see later
that using widgets, instead of just plain textures, makes displaying UI
much more efficient. There are also many
more types of widgets, including ones for
creating checkboxes, progress bars, and dialogues, and I recommend checking them all out when you get the chance. Okay, so let's take a look
at menu items dot PNG again. Mainly, we have a whole
bunch of buttons, but we also have some textures that we don't want
to be clickable, like the logo down here, the difficulty textures, and the fighter
display textures, which will show the
player's chosen fighter. For these non
clickable textures, we'll use the image class, and for the rest, we'll
use the button class. So back in the main
menu screen class, let's start with
the image objects. Our first comment
here, image widgets. Then let's create
one for the logo by typing private image and making sure to import
the sent DUI version here, then logo image. Next, we'll need one for the
fighter display background. Let's type private image, fighter display,
background image. We also need one for the
fighter display itself. Let's type private image,
fighter display image. And the difficulty textures in here are actually for
the setting screen, so we don't need to create
variables for them yet. For the buttons
in the main menu, we just need the play button, the settings button,
the quit button, and this triangle button, which we'll put on the fighter
display so that the player can move back and forth between the different fighter choices. Notice, however,
that each button actually has two
different textures, one that's gold and one
that's more orange. We'll use the gold ones as the normal textures
for the buttons, and we'll use the orange ones for when the player
presses the buttons. And that's another advantage
of using the button class because we can actually
set different textures for different button states. Okay, so back in
main menu screen, I'll create a section
for button widgets. Then I'll start with
the play game button by typing Private button. Again, making sure to import
the scene two D UI version, then play a game button. Next us through Settings
button by typing private button Settings button. Then the quick game
button by typing private button
Quick Game button. Okay, for the triangle button, we're actually going to be using the same texture for
two different buttons. One for switching to the
previous spider choice, and one for switching to
the next Spider choice. The previous button will face to the left and the next
button will face right. So for these buttons,
let's type Private button, previous Spider button
and private button. Next fighter button.
Okay. And we also need to create
a label object, which we'll use to show
the chosen fighter's name. For this out first
comment, label widgets. Us type private label. Import the send UI version, then fighter display name label. Okay, that's it for the widgets. One more variable
we'll need is one to indicate the index of the player's current
fighter choice, which we'll put to
use in a later video. For this out comment,
fighter choice. Then let's type private nt
current fighter Choice index. Right now we need to create a
constructor for this class. So after all of the variables, let's type public
main menu screen. And for a parameter,
let's type SFS game. First, let's set our global
game variable to the past in one by typing this
game equals game. Next, we need to
initialize the stage. Also, the stage itself
has a camera and viewport which you'll use
when drawing the actors. So we'll need to
set this viewport to the correct one for our game, which will be the same
as the extend viewport that we used in the
game screen class. So first I'll comment,
set up the stage. Then it's type stage
equals new stage. Next, let's set its viewport, which we can do by calling
stage dot set Viewport. And we want it to be
an extended viewport, so let's type new
extend Viewport and press Enter to import it. And here we need
to first pass in the minimum worldwidth
of the game, which we want to be
the world width value we created in the
global variables class. Let's type global variables, press Center, then
dot road width. Next, we need to pass in
the minimum world height, which is global variables
dot Min world Height. Next, for the
maximum worldwidth, we'll also use global
variables dot world Width. And we don't want a
maximum road height, so let's put a zero here. Finally, we need to
pass in a camera. We want to use the
stages camera, and we can get it by calling
stage dot Get camera. The next thing we need
to do in the constructor is get the menu items Atlas
from the asset manager. How come it, get the
menu items texture atlas from the asset manager. Then let's type
menu items Atlas. Equals game dot
assets dot manager dot Get Assets precenter then dot menu items Atlas. Finally, we need to create
all of the widgets. We'll do this in separate methods for
each type of widget. First I'll comment,
create the widgets. Then we'll create the images
by calling create images. Then the buttons
will create buttons. Then the label would
create labels. Okay, below the constructor, let's create the
create Images method by typing private
void, create images. First, we'll create
the logo image. So I'll comment,
create the logo image. Then let's type logo
image equals new image. And here we need to pass in
a texture or texture region. We'll be using a texture region from the menu items Atlas. So let's type menu items
Atlas Dt Fine Region. And the name of the
texture region is logo with a L. Okay, we also need to scale
the image's size by the world scale variable. To do this, we can call
Logo image set size, Logo image dot Get width times global
variables dot world scale. Kamal Logo image get Heights, Times Global Variables
world scale. Next, we'll create the fighter
display, background image. So I'll comment, create the fighter display
background image. Then it's type fighter
display, background image, equals new image, menu
items Atlas, D fine region. This one is called fighter
Display Background. To set the size, I'll copy the line for setting the
logo image size here. Then I'll just replace
the logo image instances in here with fighter
display background image. Now we just need to create
the fighter display image. I'll copy and paste all
of these lines here. Then change them up for
the fighter display image. And This one is called fighter display. If we open up menu items
dot PNG again really quick, the fighter display is
just a small version of a frame from one of the
fighter sprite sheets. Like with the fighter
sprite sheets, the texture is white and we
can add some color to it. However, because the player will be able to change their fighter, we'll change the color of
the fighter display later in the show method to make sure it's always set
to the correct color. Next, we'll create the buttons. So below this method, let's type private void
create buttons. We'll start with the
play game button, so I'll comment, create
the playGame button. Now because we're using
different textures, depending on whether the
button is being pressed, we need to create a button
style object for each button. With a button style object, we can set which texture to
use for each button state. To create one for the
play game button, we can type button style, press Enter to import it, and play game button style equals new button
dot button style. Okay, and the default button
state is the upstate, which means that
the button isn't being pressed by the user. To set a texture for
the buttons upstate, we can first type play game
button style dot U equals, and this actually requires a texture region
drawable object. But this isn't a
big deal because if we pass in a
texture region object, when creating a texture
region drawable, it would do the
conversion for us. So let's type new
texture region drawable. Press Inger to import it. Now we need to pass
in the texture region for the Play game
buttons Upstate, which we'll get from
the menu items Atlist. For this, let's type
menu items Atlas, D fine Region, and the name of it is simply
play game button. Next, when a button
is being pressed, it goes into the down state. So for setting this, we can type playGame button style dot down equals new Texture
Region drawable. Menu items Atlas, DfineRgion. This one is called
Play Game Button Down. Okay, now we need to
create the button itself. To do this, we can type play game button
equals new button, and we need to pass in
play game button style. Like with the images, we need to scale the button
by the world scale. So let's type play game
button, that set size, play game button, do get width, times global variables,
that world scale. Come a play game button. Do get height times global
variables, that world scale. Okay, now we'll repeat all of this for the
remaining buttons. Okay, here's the code
for the settings button, quick game button, and the previous fighter and
next fighter buttons. Notice that because the fighter buttons share the same texture, I created a single
style for them both called Triangle
button style. This style used the
triangle button and triangle button down
regions from the textualis. Then when I created the previous
and Next Spider buttons, I passed in the triangle button style object for each of them. We're actually not yet finished with the previous
button, however. If we open menu items
that PNG again, the triangle button textures
point to the right. This is good for the
next Spider button, but for the previous
spider button, we want the texture
to point to the left. To do this, after we create the previous spider
button and set its size, we first need to call
previous Spider button that set transform
and pass true. This will allow us to
transform the button, such as by rotating it or flipping it, which is
what we want to do. Now, by default, the origin of the button is at
its bottom left, so it will rotate or
flip along that point, which will affect
the positioning of the button on the screen. Instead, we want to set the button's origin
to its center. To do this, we can
call previous fighter button dot set origin, previous fighter button, dot
get width divided by two F, coma previous fighter button. DG height divided
by two F. Finally, we want to flip the
button horizontally. To do this, we call
previous fighter button, D set scale X, and pass a negative one. Okay, that's it for the buttons. Now we just need to create the fighter display name label. So below this method, let's type private void create labels. For the fighter display name, we'll use the small font. So first, we need to create
a Bitmap font object and get the small font
from the asset manager. I'll comment, get the small
font from the asset manager. Then we can do so by
typing Bitmap font, pressing Enter to import it, and small font equals game dot assets dot manager dot get assets dot Small font. We also need to scale the
font by the world scale, which we can do by
calling Small font, do Git dataset scale, global variables dot WorldSCL. We need to call
small font dot set us integer positions False, which as we learned back
when we first created the fonts will prevent the letters from
getting jumbled up. Okay? And for labels, we need to create a label style object, which will let us set
things like which font to use for the label and
the color of the font. First I'll comment,
create the label style. Then to do so let's type
label style, press Enter, then fighter display
name label style equals new label
dot label style. To set the style font
to the small font, we can type fighter
display name label style. Da font equals small font. And to set the
color, we can type fighter display
name label style. Da font color equals. Let's set it to black
by typing color, making sure to
import the GDX dot Graphics version,
then dot black. Finally, we just
need to create the fighter name display
label itself. First style comment, create the fighter display name label. Then let's type fighter, display name label
equals new label. Now on here we first
need to pass in a string that will be
the text for the label. This is, of course, going to change if the player
changes their fighter, so we'll wait to do this
in the show method. For now we can just use a
blank string by typing quote. After that, we need
to pass in the style. So let's type fighter
display name label style. And that's it. Okay,
in the next video, we'll get all of our widgets
laid out on the screen. See.
35. Main Menu Screen 2: Draw the Widgets: As I mentioned at the end
of the previous video, we want to set the text of the fighter name display label to the player fighter's name every time the show
method gets called. This is because the
player might have changed their fighter
in the main menu, then played the game, then
returned to the main menu, which will cause the show
method to be called again. Okay, so in the show
method, I first comment, set the fighter display name labels text to the name
of player's fighter. And to set the
text of the label, we can call fighter display
name label dot set text. We want to use the
player's name, which we can get by calling
game dot player dot get name. I also think it would
look better if you use all uppercase letters
for the name label. To do this, after game dot
player dot G name here, we can call the two
uppercase method of the stream class by
adding dot to uppercase. Alright, another thing we
want to do in here is set the color of the
fighter display image to the player fighter's color. For this our first comment, set the fighter
display images color to the color of
player's fighter. And to change the color
of an image object, we can simply call it set color method and
pass in a color. So let's type fighter
display image, dot set color and pass in game
dot player dot Get Color. Okay, we're next
going to start laying out the widgets
inside the stage. But before we do
this, let's make it so we can see our
progress as we go. First, we need to set
the main menu screen as the default screen instead
of the game screen. To do this, let's go to
the main game class. At the top, after we create an object for
the game screen, let's also create one
for main menu screen by typing public
main menu screen, pressing Enter, then
main menu screen. Next in the create method, because we don't want to switch to the game screen anymore, I'll remove this part of the comment and delete the
SutscreenGame screen line. Now our comment initialize the main menu screen
and switch to it. Then we can initialize it
by typing main menu screen equals new main menu
screen and pass in this. And to switch to it, we can type set screen main menu screen. This won't work yet, though, because we haven't
told the main menu screen how to render itself. So let's go back to
the main menu class and go down to the
render method. The first thing we will
do in the render method is clear the screen
to a blue color. We're actually going
to use the same color for the setting screen
and the loading screen. So let's first go over to
the global variables class and create a variable
for the color. We can create it here
in the colors section. Let's do so by typing public static final color blue background
equals new color, 0.25 F, 0.42 F, 0.61 f1f. Okay, back in main menu screen, let's set the screen color
by typing screen Utils, pressing Enter to import it, then dot clear global
variables, dot blue background. Okay, when we're using a stage, we don't have to do
a bunch of calls to the Sprite Batchs draw method like we did in
the game screen class. Instead, we simply have to call two methods stage dot act, which will perform
some actions in the background and
stage dot draw, which will draw all the
stages actors to the screen. So first I'll comment, tell the stage to do actions
and draw itself. Then us call stage dot at, and we need to pass in
the Delta variable here. Next, we can call
stage dot draw. And before we run this, we need to do a couple more things. First, in the resize method, we need to update
the stage's viewport with a new screen size. So I'll comment, update the stage's viewport with
the new screen size. And to do so, we can type
stage dot get viewport, dot update with height, and true to center the camera. Next, in the dispose method, you need to call
stage dot dispose, which will release the stage as resources from the memory. Okay, if we run this now, We currently just get an
empty blue screen, and that's because we
haven't yet actually added any actors to the stage. To do so, let's go back
to the constructor. Another huge advantage
of using scene two D for UI is that we can organize and position the UI
elements using tables. Tables in scene two D work
similarly to HTML tables, and that they allow
us to organize things into rows and columns. Also their size and
positioning will automatically adjust as
we add things to them. For our layout, we're going to need a main table
to hold everything. Inside the main table, we'll add two inner tables,
a left side table, which will hold the logo and fighter display widgets
and a right side table, which will hold
the three buttons. And actually, all the
fighter display widgets will be placed inside
their own table, which will be added to
the left side table. This will all make sense later. Okay? And we're
going to do all of the table creation in a
method called create tables, which we need to call after
we create all the widgets. So at the bottom of
the constructor, I'll comment, create the tables. Then let's call create tables. Alright, below all of these methods for
creating the widgets, let's create the create
tables method by typing private void
create tables. We'll start by first creating the main table and
adding it to the stage. I'll comment here, create the main table and
add it to the stage. To create the table,
we type table, import the scene DUI version, then main table
equals new table. And for the main table, we wanted to fill up
the entire stage. To do this, we call
main table dot set fill parent
and pass in True. Another thing we
want to do is called main tablet set Round false. If we don't do this, when we
add widgets to the table, the positioning will be rounded to the nearest integer values. This will result in
the widgets not being lined up perfectly and
won't look very good. We'll do this for
all of our tables. And finally, to add a table
or any actor to the stage, we call stage dot at actor
and pass in the actor. So in this case, main table. If we run this now, it appears that nothing
has happened because we haven't added any visible
elements to the stage yet, but we now have a table that fills in the whole area here. And actually, if we
go up here and call stage dot Seti Bgal
true then run it. It's hard to tell,
but there are now lines going along the
edges of the screen. The blue one here on the
right is the easiest to see. These are called debug lines, and they will help to make sure everything is lined up
the way we want it. Calling the stage a set debug all method and passing
in true will create debug lines along
the bounding boxes of all of the actors,
as we'll see in a bit. If we wanted to, we
could also do this for a particular actor by calling it set debug method and
passing in true. But we'll just debug
them all for now. And when we're finished
getting everything laid out, we just have to remember
to remove this line. Okay, next, we'll create
the left side table. First I'll comment, create
the left side table. Then a type table, left side
table equals new table. Let's call it left side
table dot set round. False. For this table, we just want it to
be big enough to hold all of the widgets
we'll add to it, so we don't want to set
it to fill the parent. Okay? The first widget
we want to add to the left side table
is the logo image. I'll comment, add the logo
image to the left side table. And to do so, we call left side table dot AD and pass
in an actor, so logo image. Okay, before we
do anything else, let's go ahead and add
the left side table to the main table so we can run the game and
see how it looks. Down here, I'll comment, add the left side table
to the main table. Then let's do so by calling Mintable dot A left side
Table. Let's give it a run. Okay, so we have our logo image, but it has been stretched to
fill up the entire screen. This is the default
nature for a table. To fix this, when we add
an actor to a table, we need to also tell the
table what size to use. We can actually do
this on the same line where we add the
actor to the table. So here what we call left side
table dot add Logo Image. Before the semicolon,
we can put dot size, then pass in logo
image dot get width, coma logo image, dog height. Now let's try it again.
Okay, much better. Note that by default, LibGDx centers things
in the table cells. Also, because we set
debug all to true, we can see that it
put green debug lines along the bounding
box of the image. Okay? Now if we were to add another actor to the
left side table, it would put the actor
into a new column, but we want to add it
to a new row instead. To do this, after we add the
local image to the table, we need to call the row
method on the table by typing left side table dot row. This will add a new
row to the table. I'll also add to the common
up here and start a new row. Also, by default, the actors will be placed right up against
each other in the table. It would be better if
we have some spacing or padding between them. To add some padding,
after calling row here, we can go before the
semicolon and type dot pad. We have methods for padding all sides and for padding
a particular side. All we need is some padding
for the top of the new row, so let's go with pad top. In this method, we need to pass in how much
padding we want. This will be orld units. And I found that a good
value for this is 1.5 F, but feel free to try
other values if you want. Okay? Now we're ready to create a fighter display table and add it to the
left side table. The fighter display table will actually consist
of two tables, a main table that will use the fighter display
background image as his background and
an inner table that will have the
previous fighter button, the fighter display image,
and the next fighter button. Okay, so before we add
the left side table to the main table down
here, I'll comment, create the fighter
display table and set as background to the fighter
display background image. Lt us create the main fighter display table by typing table, fighter display table
equals new table. Let's also call
fighter display table that set round false. All right, so to set the
background of a table, we call it set
background method. So let's type fighter display
table that set background. And we actually need to pass
in a drawable object here. Fortunately, when we add a
texture to an image object, it converts the texture
into a drawable object, and to get the drawable, we call the images get
drawable method. So we want to call fighter
display background image that Get drawable. We also need to set the
size of the table to be the same as the size of the fighter
display background image. So let's fighter display table, that set size, fighter display, background image, Dogg width, coma fighter display,
background image, Dogg height. Okay, let's give it a run. So we don't see the fighter
display table yet, and that's because we forgot to add it to the
left side table. So back in the code,
I'll comment down here. Add the fighter display table
to the left side table. Then let's call it
left side table dot A, fighter Display Table. We also need to tell
the left side table what size to use for the actor. So we can call it size, fighter display
table dot get width come a fighter display
table, do get height. Okay, let's try running
it again. There we go. Now we have the logo image and the fighter display
background image on two separate rows and with
some padding between them. Now the reason we set
the image here as the background of the
table instead of adding the image to the
table is that we want the inner table to be displayed
inside this same area. If we added the image
to the table instead, when we go to add the
inner table to it, it would put it in
a separate column. And by the way, the music is actually still
playing in the game. I just muted it for the video so it wouldn't get too annoying. Can now let's create the
fighter display Inter table. Let's do it before we add the fighter display table
to the left side table. For the inner table, we'll have three widgets and three
separate columns. The previous fighter
button on the left, the fighter display
image in the center, and the next fighter
button on the right. So first I'll comment, create the fighter display in table and add the previous and
Next fighter buttons and the fighter
display image to it. Then let's create the
table by typing table, fighter display in our
table. Equals new table. And also called fighter display
in our tablet Set Round. False. Okay, to add the previous fighter
button to the table, we can type fighter display in our table dot add,
previous Fighter button. We need to set the
size by typing size, previous fighter
button, dog Width. Come a previous fighter button. Dog heights. Next, we'll add
the fighter Displayimage by typing fighter Display inner table dot Ad fighter
Displayimage. Dot size, fighter
Displayimage, dot get With. Come a fighter display
image, Dog heights. Finally, let's add the
next fighter button by typing fighter display in our table dot AD, Next Fighter button. Dot size. Next fighter button,
dot get width. Come on Next fighter
button, dot get height. Let's not forget
to add the table to the fighter display table. Down here, I'll comment, add the fighter display inner table
to the fighter display table. Then it's type fighter
display table dot A, fighter display inner table, size, fighter
display inner table. Dog width, C fighter display
inner table. Do get height. One thing to know
about tables, however, is that unless we
actually set the size of a table manually by calling
it set size method, calling get width
and get height on it won't automatically return
the correct values. In order to get the
correct values, we first need to call
the tables PAC method. So up here, after we finish adding things to
the inner table, let's call fighter display
inner table Da Pack. This will tell the table
to correctly set its size according to everything
that's inside it. Can now let's run the game. Alright, we have our widgets in the correct order
and positions, and the fighter display is using the same color
as the players spider. One thing we might
want to do, though, is add some padding between the buttons and the
fighter display. To do this, we can
go to the line where we added the fighter
display image to the table, and before the semicolon, we could put dot pad left. Let's go with 0.5 F. We also need to add the same amount of
padding on the right, so let's type dot
pad right, 0.5 F. Now if we run the widges are
spaced out a bit better. Now one thing we
forgot to do is add the fighter display name label to the bottom of the
fighter display table. When we do this, we want to have it centered in this
empty area here. As we've seen, when we
add an actor to a table, it by default, gets centered
inside this table cell, so we don't have to
worry about that. We also don't need to worry
about the width because that has already been taken care of when we added
the inner table. So when we add the label, all we need to do
is tell the table what height to use for the cell. Because we have the same
amount of spacing on the top and the bottom to calculate the
height of this area, we can subtract
half the height of the display image from half the height of
the background image. We also don't want to count
this black border here, which is about
half a world unit. So we'll subtract that as well. Okay, so after we
add the inner table to the fighter
display table here, we, of course, first
need to add a new row. So let's call fighter
display table dot o. Now, let's add the fighter
display name label by calling fighter display table dot ad, fighter Display Name label. And because we just want to set the height, we can
call it dot height. And for the height, we want fighter display background
image, Dog height, divided by F minus
fighter display image, Dog height, divided by two F, then -0.5 to account
for the border. Okay, let's try it.
Get our label widget with the correct text and the correct height as we can see by the debug
lines around it. However, everything inside the main display table
has been pushed up. To fix this, we need
to add an empty row to the top of the table to account for the space
up here as well. To do this above
the line here where we add the inner table
to the display table, we first need to add an
empty actor to the table and make the height the same as the height that we use
for the name label. To add an empty
actor to the table, we can simply call
fighter display tablet AD with no parameter. Let's set the height by calling height fighter display
background image. Dog height, divided by F minus fighter display
image, Dog height, divided by two F -0.5
F. And after this line, we need to go down
a row by calling fighter display tablet Row. I'll also change the
comment here too, fill in the fighter
display table with an empty row on
top for alignment, the fighter display
inner table in the center and the fighter display name label
at the bottom. All right, let's
see how it looks. Perfect. Next, we'll create
the right side table. The right side table is going to be much simpler than
the left side table, as it would just consists
of three buttons in three separate rows. So down here, after adding the left side table
to the main table, I'll comment, create
the right side table. Then this type table, right
side table equals new table. Let's not forget to
call right side table round false. Next I'll comment. Add the play game settings and quick game buttons to
the right side table. Now let's have the play
game button to it by typing right side table dot add, Play game Button dot SHE
Playgamebton, dot get Width. Come a playGame button
dot get height. Now we need to go down a row. So let's type right
side table dot R. We'll also add one world unit of spacing
between the buttons. So let's do dot pad
top one F. Okay, now we can add the
Settings button by typing right side table dot a, Settings button dot size, Settings button dot G Width Settings
button dot get height. Then go down a row by typing right side table
dot R dot pad Top one F. Now we just need to add the Quick game button by typing
right side table dot ad, Quick game button, dot
SE, quickgamebton, dot G W. Come a quick game
button, dot get heights. Finally, we need to add
the right side table to the main table,
so I'll comment. Add the right side table
to the main table. Then let's call maintable dot a. Right side table. Also, you might be wondering why
we don't have to set the size when we add
something to the main table. And that's simply because we set the main table to fill
the entire screen. Therefore, the sizing
of the table doesn't need to be adjusted every
time we add something to it. Okay, let's run the game
and see what we get. That looks okay, but we
probably want to put some padding between the
left and right side tables. To do this, at the end of the line where we add
the right side table, we can put dot pad left and set it to something like F. All
right, let's have a look. Nice. Everything is
laid out perfectly. If we click one of the buttons, however, nothing happens yet. We'll fix that in the
next video. See there.
36. Clicking the Buttons: Okay. In order to get the
main menu buttons working, we first need to go down
to the show method, like we did in the show method
of the game screen class, we need to tell LibGDX what to use as the
input processor. In GameScreen, we implemented
the input processor class, and in the show method, we set GameScreen itself as
the input processor. When using scene two D, however, we set the stage
as the input processor. And we don't even have to bother with all the methods
like key down, key up and touchdown as the stage will actually
handle them for us. Okay, so I'll comment, set the stage as the
input processor. To do so, we can type GDX, press Enter to Import it, then dot input, dot
set input processor, and pass in stage. If we run the game now,
When we click a button, we see that it switches back and forth between the up
and down textures. So we now know that the clicks are at least being registered. Now we just need to
tell each button what to do when the
player clicks them. For this, let's go back to
the crate buttons method. In order to tell a button what it should do
when we click it, we need to add what's called a change listener to the button. We're going to do this for
all of the buttons inside a method that we'll create
called Add button Listeners, and we'll call the method at the bottom of the
create Buttons method. So first I'll comment here, add the button listeners. Then let's call Add
button Listeners. Okay, let's create
this method down here by typing private
void add button listeners. Let's begin with the listener
for the playGame button. First I'll comment, add the
playGame button listener. And to add a listener
to a button, you need to call it
add listener method. So let's type playGame
button dot add listener. The change listener
class that we'll use for the listener is
an abstract class. So we'll instantiate it with an anonymous inner
class expression by typing new changed listener
and pressing Enter. As we can see, changed listener requires that we override
the changed method, and this is where
we tell the button what to do when the
player clicks it. First, we can make
it so clicking the button plays the clicksund. I'll comment, play clicksund. Then we can call game dot
audio manager dot Play Sound. Pass N Assets, click Sound. Also, we need to
make sure to put a semicolon after the closing curly brace
in parenthesis here. Let's go ahead and
give this a try. Now when we click the
Play Game button, it plays a click Sound. All right, and of course, the other thing we
want to happen when we click the button is to switch the screen to the game screen. For this, after playing
the click Sound, I'll comment, switch
to the game screen. Then we can call
Game dot set screen and pass N game dot Game screen. Okay, let's see if it works. When we click the
button, it plays the click sound and
the game begins. Awesome. All right, next, we add a listener to
the Settings button. So after all of these lines, I'll comment, add the
Settings button listener. Then this type Settings
button dot a listener. New change listener
and press Center. We haven't yet created
the setting screen, so we of course, can't switch to
it, but we can go ahead and make it so the
button plays the clicksund. I'll just copy and paste the lines from the play
game button listener. Okay, let's put a semicolon down here and make
sure it works. Now we'll add a listener
to the Quick game button. I'll comment. Add the Quick
game button listener. Then let's type
Quick game button, dot add listener,
new change listener. Press Center. Let's go ahead and paste the lines for
playing the click sound. Now we want to make it so the
button closes out the game. First I'll comment,
close out the game. And to do this, we call
gdx dot app dot EXE. All right, let's put a
semicolon down here and try it. Perfect. Okay, now
we just need to add listeners to the previous
and Next Spider buttons. We're actually
going to wait until the next video to start
changing the fighters, but we can go ahead and make the buttons play
the click sound. I'm just going to copy
all the lines for the settings button and
paste them down here. Then change it all for the
previous Spider button. And I'll do the same for
the next Spider button. Make sure they work. Nice. Right now, if we start the game and
press the pause button, we, of course,
want to make it so clicking the main
menu button here, take us back to the main menu. We want to do this in the
game over overlay as well. To get these buttons working, you need to go down
to the touchdown method of the game screen class. Inside this method, we first check if the
game is running, and if so, we handle
all of this stuff. Then in the outs part
of the statement, we figure out what to
do when the game is either in the pause state
or the game over state. For GameOver, we check if the play again button
Sprite has been pressed, and for pause, we check if the continue button
Sprite has been pressed. Unlike these two buttons, the main menu button
gets displayed in both the pause state
and the game overstate. Therefore, to handle
when the player presses the main
menu button Sprite, we can just add another
Osif to this statement. Then type main menu
button Sprite, Dt Get bounding rectangle, dot contains position dot X, come up position dot Y. Okay. And here, let's
first play the clickund. I'll just copy and paste
the lines from up here. Another thing we want to do
is stop all the game sounds. This is mainly for when
the player presses the main menu button and
the game over overlay. If they press it while the cheer or boos sound is playing, we don't really need that sound to carry over to the
main menu screen. So first I'll comment,
stop all game sounds. Then it's type game audio
Manager dot Stop Game Sounds. Also, if the player
pause the game, then music will also be paused, so we want to resume it if the player return
to the main menu. So I'll comment resume music
if the game is paused. Then we can type I Gamesate is equal to gamestate
dot paused, game audio Manager
dot play Music. Okay, and one more thing we want to do before switching to the main menu screen is we want to deactivate all of
the blood splatters. This is because when we switch between
screens in the game, the previous screens
objects remain in memory. So if the player pauses a game while a blood splatter
is still animating, it's animation will be paused. But if the player then goes
to the main menu screen, then back to the game screen, the blood splatters
animation will resume. So we'll get a random
blood splatter appearing at the beginning
of the next game. Alright, so to deactivate
all of the blood spliders, we simply need to loop
through both fighters blood splatter arrays and call the deactivate method
on each blood splatter. First I'll comment, deactivate
all the blood spltrs. Then it's type four,
int i equals zero, semicolon I less than
blood splatter amount. Semicolon I plus plus. Plater blood splatters
I dot deactivate, opponent blood spiders
I dot deactivate. Finally, we can switch
to the main menu screen. I'll comment. Switch to
the main menu screen. Then it's called game dot set screen game dot
Main Menu screen. Okay, let's run the game and
make sure everything works. Alright, so if we click
the pause button, it pause the game and the music, and now if we click
the main menu button, it resumes the music and takes
us back to the main menu. Now if we play the game
again and finish it, clicking the main
menu button will stop the sounds and take us
back to the main menu. If we click the
playGame button again, we still have the blood pools
from the previous fight. We could deactivate these as well when we go back
to the main menu, but I actually like
how they carry over. So I'll leave them in, but feel free to deactivate
them if you want. If you do want to, you
will first have to create a public deactivate method
in the blood pool class. Okay, before we move
on to the next video, if we run the game again and minimize the window while
it's on the main menu screen, the music continues to play. It would probably
be better to pause the music the way we did
in the game screen class. Then when we bring the window back up, we can
resume the music. To do this, let's go back
to the main menu screen and go down to the pause method. As we learned before,
the pause method is called whenever the
window gets minimized. So in here, we can
also pause the music. I'll comment pause music. Then it's called game dot
audio manager dot pas music. And to resume the music when
the window comes back up, we can use the
rezoom method here. I'll comment resume
music if it's enabled. Then it's called game do
audio manager dot play music. Now if we run the game
and minimize the window, the music pauses, and when
we bring the window back up, the music resumes playing. Okay, in the next
video, we'll finish up the main menu
screen by making it so the player can choose
their fighter. See
37. Changing the Fighters: Before we can let the player
choose their fighter, we need to have a list of
fighters they can choose from. If we go into the data
assets folder here, I've created this JSON file
called Fighter Choices. If we open this up, the file basically consists of a list of four fighters with a name and RGB color values
for each one. We could add another fighter
if we wanted to by simply copying one of these chunks
surrounded by curly braces. Put a comma after this
closing curly brace here, then go down the line, paste the chunk and change the
name and color values. But for now, I'll just stick
with these four choices. Okay, so what we
want to do is write some code to read this file and create an array of objects for holding the names and colors
of each fighter choice. To begin, we'll create a class for holding
each fighter choice, and we'll call it
fighter choice. Let's create the class here
in the Objects package. By right clicking the package, go to New Java class, typing fighter Choice
and pressing Enter. The first thing we
want to do is add a couple variables
that will hold the name and the color
values of the fighter. We look at the Json file again, the parts that are
shown in purple here, name and color values
are called keys, and the part that follows
the col and after a key are the keys
value or values. The value for the name key is a string as denoted by the
quotation marks around it. For the color values key, we have three numbers
between a pair of brackets. The brackets denote an array, and even though we don't have
an F after these numbers, like we would in Java,
these are actually floats. So the color values key
points to an array of floats. In order to convert
these key value pairs into fighter
choice objects, the fighter choice
class needs to have variables that
match the keys exactly. This means that the names of the variables have to be the same as the
names of the keys, and the types of the
variables have to be the same as the types
of the keys values. So with that in mind, let's go back to the
fighter choice class. And by the way, it's also necessary that we make
the variables public. So to create the variables, let's type public, string, name, and public float
brackets color values, which will be an
array of floats. Okay? And in order to set
the color of a fighter, we'll call the fighter
set color method, which requires a color object. Therefore, we'll need to convert the color values array
into a color object. And to avoid having to
do this multiple times, we'll store the converted color inside a private color object. For this, let's
type private color, import the GDX style graphics
version, then color. Okay, now we just need to
create a couple of methods for returning both the name variable and the converted color object. For getting the name,
let's type public string, get name, return name. For the color object, it's
like public color, get color. Now, the first thing
we want to do in here is check if the color
variable is null. If it is, it means that the
color values have not yet been converted into a color
object, so we need to do so. Okay? So first, let's type
if color is equal to null. I'll comment in here, I color
hasn't been initialized, initialize it using
the color values. Then this type color equals
new color, color values zero, color values one,
color values two, and we want it to
be fully opaque, so we use a one for
the Alpha channel. And after the I statement,
we need to do return color. All right, so the first time
this method gets called, it will first convert the color values into a color object. Anytime it gets
called after that, it would just return the
already converted color object. Also, you might be
wondering why we didn't create a constructor
for this class. And the reason is that we're going to be creating objects of this class a little bit
differently as we'll see next. Right now, let's go to
our main game class. In this class, we're
going to create a method called Load
fighter Choice List, which we'll use to convert
the fighter choices dot JSON file into an array of
fighter choice objects. And because we'll need to access this array from the
main menu screen, we'll create a public
global variable for it at the top of
the main game class, and we'll add it here in
the fighters section. We're actually going to use
an array list for this, so that we'll be able to
add items to it as we go. So first, let's type
public final array list. And press Enter to import
it, then less than sine. If you wanted to hold
fighter choice objects, so let's type fighter Choice
here and press Enter. Then let's put a
greater than sine then fighter Choice list
equals new array list. Less than greater than open
and closed parentheses. Next, for the load fighter
choice list method, let's go down here
below the create method and type private void
load fighter Choice List. Okay. And now, the reason
I use the JSON file for the fighter choices instead
of a simple text file, is that Lib gDx actually
makes it very easy for us to read or parse JSON files,
which we'll do now. I'll comment here, Load the fighter Choice
list from the assets. First, we need to
create a JSON object. We can do this by typing JSON, pressing Enter, then
JSON equals new JSON. Next, we need to call
the parse method of the JSON reader class and pass in the location
of our JSON file, and we need to store the
result in a JSON value object. Let's go ahead and do this. Then I'll explain how it works. So first, we need to
create a JSON value object by typing JSON value, pressing Enter to import it. Let's call it fighter choices because it will be holding
all of the fighter choices. Now we need to set this
equal to new JSON reader, press Enter, then dot parse. And here we need to pass in the location of the JSON file, which we can do by typing
GDX pressing Enter, then dot files dot Internal. And the file is located
in the data folder. So let's put data slash Fighter Underscore Choices dot JSON. Okay, if we take a look
at the JSON file again, what the Parse method basically does is it separates all of these parts between curly braces and returns them as an array, which we store inside
a JSON value object. So now we just
need to go through the array in the
JSON value object, convert each item into a fighter choice object and add it to our
fighter choice list. So to go through the
fighter choices array, let's use a four loop by
typing four and I equals zero, semicol and I less
than fighter choices, dot size, semicol
and I plus plus. Now we need to add the item
to our fighter Choice list by first typing fighter
Choice list dot AD. And to convert the item into
a fighter choice object, we use json dot from JSON. And as the first parameter, we use fighterhoice, dot class. For the second parameter, we need to pass in
the current item from the fighter Choices array. However, it's also required that we convert the item
into a string. So to do this, we can type string dot value,
fighterhoices, dot GTI. And as long as there
are public variables in the fighter choice class that match the keys in the JSON file, this should all work
without a hitch. Okay, now let's go
up to the create method of the main game class. When we create the
fighters here, instead of typing up
the names and colors, we can use the first
and second items from the fighter Choice list, which are actually the same
as what we typed in here. But first, we need to actually load the fighter Choice list. So above these
lines, I'll comment, Load the fighter Choice list. Then let's call Load
Fighter Choice list. Okay, for the player fighter, I'll change the name to fighter Choice list dot
Getzer dot Get NAM. And the color to
fighter Choice List, dot get zero dot get color, which converts the color
values into a color object. And for the opponent fighter, I'll use fighter Choice List, dot get one dot get Name. And Fighter Choice List, dot get one dot get color. All right. And just
to make sure this works correctly,
let's run the game. So the default player
fighter is still named Slim Stalon and
uses the red color. The opponent is still named Den diesel and uses
the blue color. Awesome. Alright now let's head over to the main
menu screen class and make it so the player
can change their fighter. First, if we go up to
the top of the class, back when we first
created this class, we created a variable here called current
fighter Choice Index. This will indicate where in the fighter choice list the player's current
fighter is located. And we need to set
this every time. The main menu screen
becomes the visible screen. So let's go down to
the show method. Okay, so at the bottom
of this method, we're going to first
set the current fighter choice index to zero. Then we'll loop through the
fighter choice list until we find the item that matches the player's current fighter. To determine this, we'll simply check if their
name values are equal. Okay, so I'll comment,
find the index of players currently chosen fighter in the fighter choice list. Then let's first set current fighter choice index to zero. Now let's loop through
the fighter Choice list by typing four int
i equals zero, semicolon, I less than game dot fighter
choice list dot size. Semicolon I plus plus. And here we want to
check if the list items name matches the
player fighter's name. To do this, we can type I game dot fighter Choice
list dot dot g name equals game dot
player dot get Name. And if so, we want to set the current fighter
choice index to I. We also don't need to continue looping through
the array anymore, so we can put a break
here. All right? Now we just need to
go up to the Add button listeners method and tell the previous and
next fighter buttons what to do when the
player clicks them. First, for the previous
fighter button, we want to decrease the
current fighter index by one, but only if the index is
currently greater than zero. If it's zero, we'll set it to the last index in the
fighter choice list. So after playing the
click sound, I'll com it, choose the previous fighter
in the fighter choice list or choose the last fighter if the beginning of the
list has been reached. Then this type, if current
fighter choice index, greater than zero,
current fighter choice index minus minus. Then else current
fighter choice index equals game dot
fighter Choice list, D size minus one. After this, we want to get the current fighter
choice object from the list and use it to set the player fighter's
name and color. We also want to use it
to set the color of the fighter display image
in the main screen, as well as the text of the
fighter display name label. So first I'll comment,
set the name and color of player's fighter and the fighter display to those of
the chosen fighter. Now let's type fighter Choice. Press Enter to import it. Then fighter Choice equals
game do fighter Choice list, do GT, current
fighter Choice Index. After that, let's do game
dot player dot set name. Fighterchoice dot gt NAM. Then game dot player, dot side color, fighter
Choice, dot get Color. Then fighter display image. Dot side color, fighter
Choice, Dog color. And finally, fighter
display name label, dot set text, fighter
Choice, Dogname. Let's make it uppercase by
adding dot two uppercase. Okay, for the next
fighter button, let's copy all of these lines
for the previous button. Then paste them in here after
playing the click sound. For this one, you want to either increment the current
fighter choice index by one or set it to zero if the end of the
list has been reached. So I'll change the comment to
choose the next fighter in the fighter choice
list or choose the first fighter if the end of the list has been reached. For the I part here,
you need to change this to current fighter
choice index less than game dot fighter choice
list dot size minus one, and change the line here to current fighter choice
index plus plus. And in the outs part,
you need to set current fighter choice
index to zero. That's it. If you run the game now, you can click the previous
and next fighter buttons to scroll through
the fighter choices. And if we start
the game, it will set the player's fighter
to the chosen fighter. If you go back to the main menu, it will still display
the correct fighter. Perfect. All right. Now one more thing we can do is choose a random fighter
for the opponent. We also don't want
the opponent to have the same fighter as the player that would just cause confusion. To do this, let's go
to where we set what happens when the player
clicks to play a game button. Before switching to
the game screen here, we'll choose a
random fighter from the list to use as
the opponent Spider, and we'll make sure it doesn't
match the players spider. First I'll comment, choose a random opponent fighter
from the fighter Choice list, making sure it's different
from players Spider. To get a random
index from the list, we'll use the MatheuTills
dot Random method and pass in the size of the
list subtracted by one. Let's type int index
equals MatuTills, Press Eger to import it, then random game
dot fighter Choice list dot size minus one. Let's get the fighter choice at that index by typing fighter
choice, fighter choice. Equals game fighter
Choice list GT index. And we want to keep
doing this until we find a fighter choice that doesn't
match the player's fighter. Let's do this with a
Wil loop by typing Wil fighter choice dot G name equals game dot
player dot Get Name. Then we can copy and paste
these two lines in here. But because we have already
defined these variables, we need to remove the
types in these lines. Finally, after the WOW loop, we just need to set
the name and color of the opponent by calling
game dot opponent, D side name, fighter choice, Dog name, and game dot
opponent, do side color. Fighter choice, Dogg color. Alright, let's give it a run. Then when we start the game, it chooses a random
fighter for the opponent, and it will never be the same
as the player's fighter. Okay, now we can get rid
of these debug lines in the main menu screen by going to the crate tables
method and removing the stage set debug all
true line here at the top. Alright, in the next video, we'll start working on letting the player change the settings
of the game. See there.
38. Manage the Settings: To manage all of the settings
that the player can change, we'll create a settings
manager class. Let's do this in the
resources package here by right clicking it, choosing new Java class, calling it settings manager.
I'm pressing Inter. Now with the settings, we're going to want to save them to the player's computer or device so that when they run
the game later on, we can load their
previous settings. The settings will
be saved inside our preferences file with
the dot preface extension, and we'll need to give a name to each setting that we save in the preferences so
that we can use the same name to load
the setting later. So first, we'll create some
static final string variables for holding the names
of each setting. I'll add a section
here for settings. Then let's begin
with the setting for the music by typing private
static final string. Is music on, equals
quote Is music on. So there is music on
string value here will be the name of the setting inside the preferences file. We also need one for the sounds. So let's type private
static final string. R sounds on, equals
quote R sounds on. Next for the difficulty setting, let's do private
static final string. Difficulty setting
equals quote difficulty. And for the blood setting, private static final
string is blood on, equals quote is blood on. We actually want
one more setting, which is whether or not to make the game
window full screen. For this, let's type
private static final string is full screen on, equals quote is full screen on. Okay. We also want
some variables that will hold the
values of all of the settings so that
we don't have to keep reading from the
preferences file. All of the settings, apart from the difficulty setting
will be boolean values. So for the music setting, let's do private boolean, music setting on equals. Let's set it to true by default. For the sounds, let's
type private boolean, sound setting on equals true. For the difficulty setting, we'll use the difficulty Enum from the global variables class. So let's type private
global variables. And because this class is
actually in the same package as the global variables class, we don't need to import it. So let's type dot difficulty,
difficulty setting, equals. By default, let's
set it to global variables dot
difficulty dot easy. Next for the blood setting, let's do private boolean. Blood setting on equals true. And finally, for the
four screen setting, let's do private boolean, four screen setting on equals, let's set this one
to false by default. One more variable we'll need is a preferences object for holding the data of
the preferences. Our first comment preferences. Then let's type private
final preferences. Making sure to import the
bad logic dot GDX version here, then prefs equals. To get data from a
preferences file, we type GDX precenter then
dot app dot Git preferences. And here we need to
pass in a string that refers to the name of the preferences file
we want to use. Feel free to use
whatever you want. I'll go with quotesfSPEFS. It doesn't matter that this
file doesn't exist yet, as it will get
created automatically when we first save the settings. Next, we need to
create a method for loading all the settings
from the preferences file. For this, let's type
public void load settings. And here I'll comment, get all the current settings
from the preferences. Now, when we get a setting
from the prefs variable, the method we use
depends on the type of value we used when saving the
setting to the preferences. For most of the settings
we'll use Booleans, and we can get a
Boolean value from a preferences objects by
using the G Boolean method. So for the music setting, let's type music setting on
equals prefs dot Get Boolean. And here we first need to tell which setting we want
to get the value from. So let's pass in the is
music on static variable. We also have the option of
passing in a default value for the method to return in case it can't find the setting
in the preferences file. We need to do this because the first time we
load the settings, the preferences file
won't actually exist yet. So for the music, let's
put true for the default. Similarly, for the sounds, let's do sound setting on equals prefs,
that'll get bullying. R sounds on true. For the difficulty setting, we can't actually save E Num
values in the preferences. Instead, we'll convert
the setting into an integer and save the integer. And when we load the setting, we'll convert the integer back into the correct Enum value. For Es we'll use a zero, for medium, we'll use a one, and for hard, we'll use a two. And to get an integer
from the preferences, we use the G integer method. We want to call this method
inside a switch statement, then set the
difficulty setting to the Enum value that corresponds to the returned integer value. So spe switch prefs dot G
integer. Difficulty setting. In case zero, difficulty setting
equals global variables, that difficulty that easy. Break case one,
difficulty setting, equals global variables,
that difficulty that medium. Break. And for hard,
we can do default. Difficulty setting
equals global variables, that difficulty that hard. And by the way, with
the G integer method, we don't have to pass
in a default value because if it doesn't
find the setting, it will just return a zero, which is fine because
that will set the difficulty to
easy by default. Okay, for the blood setting, let's do blood setting on. Equals preps, I get bullying. His blood setting on, true. And finally, for full screen, let's type full screen setting on equals prefs
that get Boolean. Is full screen on, and we'll use false for
the default value. All right, we next
need to create two methods for each setting, one for changing its value and one for returning its value. For changing the
Boolean settings, we'll create methods that
toggle the setting on or off. So for the music setting, let's do public void
toggle music setting. For a parameter,
it's Tyblean on. If this is true, we'll
turn the setting on, and if it's false, we'll
turn the setting off. Okay, we only need to
bother saving this setting if its current value doesn't
match the past in value. Otherwise, we'll just be wasting time and processing power. So I'll comment here, if the new setting is
different, update it. Then let's type, if music
setting on is not equal to on. First, we want to update the
music setting on variable to the new value by typing
music setting on equals on. Next, with getting a value
from the preferences, the method we use to save a value depends on the
type of the value. For Booleans, we use
the Put Boolean method. So let's type prefs
dot Put Boolean. Okay, and we want to
pass in the name of the setting, so is music on? Then we want to
pass in the value for the setting so we can either pass in the music setting on variable or just pass
in the on variable. Finally, we need to add dot flush to the end of this line. A call to the flush method will force the save to
happen straightaway. Okay? And for the method
to get the music setting, we can simply type
public boolean. Is music setting on. Return music setting on. Next for toggling
the sound setting, let's type public void, Tako sound setting, Boolean on. We can actually copy and paste the lines from the Togo
music setting method, then change them for
the sound setting. So sound setting on sound
setting on and sounds on. The n let's create the
Gitter method by typing public boolean is
sound setting on. We turn sound setting on. For the difficulty setting, let's do public void
set difficulty setting. And for a parameter, we want to pass in a difficulty Enum value. Let's type global variables. Difficulty. All right, first we want to check if
the current difficulty setting is different from the
past in difficulty value. I'll comment here, if the new setting is
different, update. This type of difficulty setting is not equal to difficulty. Let's go ahead and set the difficulty setting
to difficulty. Can now we want to use a
switch statement to convert the new difficulty setting enum value into
an integer value. So first, let's create an
integer variable to hold the converted value by typing int difficulty int equals zero. Then let's do switch
difficulty setting. Now, because we already
have difficulty int set to zero by default, we can just skip the easy case. Let's type case medium. Difficulty int equals
one, break, case hard. Difficulty int equals two. Finally, after the
switch statement, we need to save the integer
value to the preferences, which we can do with
a put integer method. T's type prefs dot put integer, difficulty setting,
coma difficulty Int. Let's not forget
to add dot flush. All right for the Gier method, we can simply do public
global variables. Do difficulty, get
difficulty setting. Return difficulty setting. Next for the blood setting. Let's type public void, Togo Blood setting, bullying on. I'll copy and paste the lines from the Taco sound
setting method. Change sound setting on
to blood setting on. Change our sounds
on to I blood on. For the Gitter let's
do public boolean. His blood setting on. Return blood setting on. Finally, for the
four screen setting, let's type public void, Tuggle four screen
setting, boolean on. Then let's paste the lines. Change sound setting on to
four screen setting on. And our sounds on to
Is four screen on. And for the Gitter, let's
type Public bullying. Is four screen setting on. We turn four screen setting on. Okay, we're finished with
the settings manager class. So now let's head over to the
main game class where we'll create a settings manager object and load all the settings. First, at the top of the class, after creating the
audio manager object, let's like public
settings manager. Press Enter to import it
and settings manager. Okay, now on the create method, we'll initialize the
settings manager and load all of the settings. We want to do this
before we initialize the audio manager
because we'll be using the settings manager to get the music and sound settings
for the audio manager. So in here I'll
comment, initialize the settings manager and
load all the settings. Finish type settings
manager equals new settings manager and
settings manager Load Settings. Next, we need to
remove the audio manager dot play
music line here. Then I'll go down a
line and comment, update the audio settings
in the audio manager. First, we want to
check if the music setting is on in the
settings manager, which we can do by typing I settings manager dot
is music setting on. And if so, we want
to enable the music. Otherwise, we'll disable it. So let's type audio
Manager dot Enable music. Else, audio manager
dot disabled music. Remember that we made it
so the enable music method will also start
playing the music. Now let us do the
same for the sound setting by typing I
settings manager, that is sound setting on Audio manager
that enables sounds. Else, audio manager
that disables sounds. Alright, one more thing
we want to do in here is check if the full
screen setting is on, and if so, we'll make the window full screen. So
first I'll comment. If the full screen setting
is on, go to full screen. Then this type, I
settings manager, that is full screen setting
on And to go to full screen, we type GDX Graphics, dot set four screen mode. GDX Graphics dot G display mode. Now, because the default mode in our game is Windowed mode, we don't have to do anything if the four screen
setting is turned off. But if we had made it so the
default was four screen, to go to Windowed mode, we would use GDX Graphics, dot set Windowed mode and pass in the
width and height and pixels that we want
the Window to be. Okay, I'll delete
this. Now, just to check that the full
screen mode works, we can add an
exclamation point before settings manager here in the If statement
and run the game. Now it starts out
on full screen. Okay, I'll remove the
exclamation point so that it defaults
to Window mode again. And in the next video,
we'll get to work on the setting screen
so that the player can actually change
the settings. S there.
39. Settings Screen 1: Set Up the Widgets: To start creating
the setting screen, we first need a
setting screen class. So let's right click the
screens package here. Go to New Java class, call it setting screen
and press Enter. This class needs to
implement the screen class. So after public class
setting screen here, let's type implements, screen, and press Enter to import it. Let's go ahead and
implement all of the necessary methods by
hovering over this line, clicking implement
methods. Take clicking ok. Okay, at the top of the class, we first need an object
for the main game class. So let's type private, final
SFS, precenter then game. Like with the main menu screen, we'll use a stage for
the setting screen. So let's type
private, final stage, Import the scene two D
version, then stage. We'll also need to get
the menu items texture atlas from the asset manager. So let's create a variable
for this by typing private, final textutis, pressing Enter, then menu items Atlas. All right, we next need to add some variables for the widgets. If we open up menu items dot PNG and the Textures Assets folder, the widget we'll need are
the settings image here, the sounds background image, the music background image, the full screen
background image, the blood background image, the difficulty background image, and images for displaying
the difficulty settings. This longer Show
blood image here is actually just for mobile
versions of the game, and we'll use it in the bonus
Android section, right? And we'll also need
a few buttons. The back button here, which will take us back
to the main menu, the on and off checkbox buttons, the on and off toggle buttons, and we'll use the
triangle button again for switching between
the difficulty settings. Okay, so back in the
setting screen class, let's start with the images. Our first comment,
image widgets. Then let's type private image. Import the Centd dot UI
version, then settings image. Lets us to private image, music setting background image. Then private image, sound
setting background image, private image, difficulty
setting background image, private image, full screen
setting background image. Private image. Blood
setting background image. And for the difficulty
setting images, let's type private
image, easy image, then private image,
medium image, and private image Hardimage. Okay, next, for the buttons, I'll comment button widgets. Then let's type private button, Import the scene two D
version, then back button. Okay, we're actually
going to create two buttons that use the
Toggle button textures, one for the music setting and
one for the sound setting. So first, let's do private
button music toggle button. Then private button.
Sounds Taco button. We also need two
buttons that use the triangle button
texture for switching between the previous and
next difficulty settings. For these, let's
type private button, previous difficulty button, and private button Next
difficulty button. Finally, we'll need two buttons that use the checkbox textures, one for the full screen setting, and one for the blood setting. Let's type private button, full screen check button, and private button
Blood check button. All right well next create a
constructor for the class. So below all of the variables, let's type public
setting screen. Befor a parameter,
we'll need SFS game. And here let's first do
this game equals game. Next, we need to initialize the stage and set
up its Viewport. I'll comment set up the stage. Then this type stage
equals new stage. After that, let's do
stage Dot Set viewport, and we'll use the same
extend viewport settings as the other screen classes. Let's type new extend
viewport, presenter, then global variables,
presenter, rod width, comma global variables,
dot Minord height, comma global variables,
dot rod Width, C zero, and comsge dot G camera. Now we need to get
the menu items Atlst from the asset manager. So I'll comment, get
the menu items texture atls from the asset manager. Then let's type menu items Atls. Equals game dot
assets dot manager, dot Git, assets precenter
then dot menu items Atlist. Okay. Now we'll create methods
for creating the images, the buttons, and the tables. Let's go ahead and
call the methods here. First I'll comment,
create the widgets. Then let's call create
images and create buttons. Next all comment,
create the tables. Then let's call create tables. Okay, below the constructor. Let's begin with the
create images method by typing private
void, create images. First, we'll create
the settings image. So I'll com it, create
the settings image. Then I'll start by typing Settings image equals new image, menu items atlas,
Dt Fine region. And the region for this is
called settings with as. Now we need to scale it
size by the world scale. So let's type settings
Image, set size, Settings Image dot get Wi times global variables,
dot world scale. Settings image get heights, times global variables
world scale. Next, we'll create
the background images for the settings. I'll comment here, create the settings, background images. Then I'll start with
the music one by typing music setting
background image. Equals new image,
menu items Atlas, Dt fine region, music
setting background. Then let's set the size
by typing music setting, background image, dot set size, music setting, background image, Dog width, times global
variables that row scale. Come a music setting
background image. Dog height, times global
variables that row scale. Okay, and now I'll
do all of this for the sound setting
background image, the difficulty setting
background image, the full screen setting
background image, and the blood setting
background image. Okay, here's the code for
all the background images. Now we just need to
create the images for the three
difficulty settings. First I'll comment, create
the difficulty images. Then let's start with
easy image by typing easy image, equals new image. Menu items at list,
define region, easy. And easy image that set size, easy image, that get width, times global variables
that roll scale. Came my easy image, get heights, times global variables
that roll scale. Okay. Now I'll do the same for medium image and hard image. There we go. Okay, we're done
creating the image widgets. So now let's create the
create buttons method below this by typing private
void, create buttons. Let's begin with
the back button. First style comment,
create the B button. And like with the buttons we
use in the main menu screen, we'll have two
different textures for each setting screen button. Therefore, for each button, we need to create a button
style object to connect the buttons to textures to two different
states for the button. For the back button,
we have a texture for the up or default state and a texture for the down state when the player
presses the button. So to create the
button style object, let's type button style, press Enser to import it, then back button style equals new button
dot button style. Next, we'll set the up
texture by typing Back button style dot U equals. Remember that this needs to be a texture region
drawable object. Let's type New texture region
drawable. Press center. Then we can pass in menu items
atlas, DfineRgion Babton. Okay, for the down state, let's type Babton style dot down equals new
texture region drawable. Menu items Atlas, Define
region, Babton down. Next, we can create
the back button itself by typing back button, equals new button, and
pass in Bbton style. We also need to set it sizes
site B button, set size, BbtonG width, times global
variables, that world scale. Come a Bbtont get height. Times Goble variables,
that world scale. Right next, we'll create
the music toggle button and the Sounds toggle button. These will share the
same button style, which we'll call
toggle button style. So let's create it
first. I'll comment. Create the Toggle button style. Then let's type button,
D button style, toggle button style equals
New button, D button style. For the Upstate, let's do Toggle button style dot U equals New texture
Region drawable. Menu items Atlas dot Fine
Region, Toggle button off. Okay. And instead of
doing a down state, we're going to do
a checked state. This is because the
down state is only for when the user is currently
pressing the button. Once they release the button, it goes back to the upstate. For the toggle buttons, we want them to switch
back and forth between being on and off each
time the user clicks it. To do this, we can
switch the button between the check state
and the unchecked state. The unchecked state is actually
just a default upstate. And for the checked
state, we can do toggle button style checked equals new texture
region drawable. Menu items atlas DFineRgion
toggle button on. Can let's create the
Music Tokle button. First I'll comment, create
the Music Tockle button. Then let's type
Music Tockle button equals New button and pass
Sintogle button style. Then let's set the
size by typing Music Talkle button set size. Music Toggle button, dot G width times global
variables, do world scale. C Music toggle button
dot get height. Times global variables,
world scale. Okay, and for the
Sounds tackle button, I'll copy and paste
these lines here. Change the comment to sounds Tackle button and change all of these to
sounds tackle button. Next, we'll create the previous the next difficulty buttons. Both of these will use the
triangle button textures. So start by creating
a style for this. I'll comment, create the
triangle button style. Then it's type button,
Di button style. Triangle button style equals new button, Die button style. We'll just use the up and
down states for this. For the upstate, let's do triangle button style dot up equals new texture
region drawable. Menu items atlas. Do fine region, triangle button. And for the downstate, let's do triangle button style dot down, equals new texture
region drawable. Menu items Atlas, DfineRgion
triangle button down. Okay, for the previous
difficulty button, I'll comment, create the
previous difficulty button. Then let's type previous
difficulty button, equals new button, and pass
in triangle button style. Then let's set the size by typing previous
difficulty button, dot set size, previous difficulty
button, dot get width. Times global variables,
that rolled scale. Come a previous difficulty
button that get height. Times global variables,
that rolled scale. For the next difficulty button, I'll copy and paste these lines and change everything to
next difficulty button. Let's not forget, however,
that we need to flip the previous difficulty button horizontally so that the
texture faces to the left. To do this, after setting
the size of the button, we first need to make it
so that we can transform the button by typing
previous difficulty button, Do set transform True. Next, in order to get the button to flip without
changing position, you need to set its origin to its center by typing
previous difficulty button, dot set origin, previous
difficulty button, Dot get width divided by two F, come a previous
difficulty button. Now get height divided
by two F. Finally, we can flip it
horizontally by calling previous difficulty
button dot set scale X, and pass on negative one. After creating the
difficulty buttons, you need to create
the four screen check button and the
blood check button. Both of these will use the
same check button style, which we use on and
off checkbox textures. For the style, I'll comment, create the check button style. Then it's type button,
D button style. Check button style equals
new button do button style. For the upstate, let's do
check Buttonstyle dot up. Equals Du text Region drawable. Menu items alist. Do Fine Region.
Check button off. Like with the toggle buttons, we want to use the
check state for this. Let's type Check button
style dot check. Equals New texture
Region drawable. Menu items alist
Dot Fine Region. Check button on. Okay, for
the four screen check button, I'll comment, create the
four screen check button. Then let's type four
screen check button equals new button.
Check button style. And four screen check
button that says size, four screen check
button do get width, times global variables,
D Raw scale. Come a four screen check
button, do get heights. Times global variables,
that roll scale. Then I'll copy and
paste these lines. And change them for the
blood check button. All right. Before we start
creating the tables, let's get everything set
up correctly so that we can start drawing things
to the screen as we go. First, let's go down
to the render method. Like we did with the
main menu screen, we're going to first
clear the screen to the blue color we define
in global variables. So let's type screen
Utils, press Enter, then clear Global Variables,
dot blue background. Next, we need to call the act and draw methods on the stage. So I'll comment, tell the stage to do actions
and draw itself. Then let's type stage dot Act, and pass in Delta.
Then stage draw. Lex in the resize method, we need to update
the stages viewport. So I'll comment, update the stages viewport
with a new screen size. Then this type stage dot get viewport dot update
with height, true. Okay, and down here in
the Dispose method, that's call stage dot dispose. We can also go ahead and make
it so the music pauses in the pause method and resumes
in the resoom method. So in the pause method,
I'll com it pause music. Thens type game dot audio
Manager dot Pas music. And in the rezoom method, I'll comment Resume
music if it's enabled. Then it's called game dot
audio manager dot play Music. Now we need to make it so we can actually get to the
setting screen, which we'll do by clicking the settings button in
the main menu screen. But first, we need to go
to the main game class and create a setting
screen object. Alright, so in the screens
variable section here, let's type public
setting screen, presenter, then setting screen. Next in the create method. Before we initialize and switch
to the main menu screen, let's initialize
the setting screen. I'll comment, initialize
the setting screen. Then a slide setting
screen equals new setting screen,
and pass in this. Can now let's go to the
main menu screen class. And here we want to
go to where we add the Settings button listener and the Add button
listeners method. After the line where we
play the click sound, I'll come it, switch
to the setting screen. Then it's called game D set
screen game D setting screen. Okay, if we run the game, we get an error saying that the
create tables method and the setting
screen doesn't exist. So let's go down here below the create buttons method and create it by typing
private void create tables. And we'll leave
it empty for now. Right now, if we run the game, and click the settings button. It takes us to our currently
empty setting screen. In the next video,
we'll start creating the tables and get our widgets
on the screen. See there.
40. Settings Screen 2: Draw the Widgets: All right inside
the create tables method of the setting
screen class. Let's first type stage, do set debug all. True, to show the
debug lines for everything so we can make sure
it's all properly aligned. Next, we'll create a main table, which will fill up
the entire screen and hold all of
the other tables. We'll go ahead and
add it to the stage. So first I'll comment, create the main table and
add it to the stage. To create it, let's type table. Import the scene to D version. The main table equals new table. We wanted to fill the screen, so let's type main table, dot set fill parent. True. We also want to
call main table dot set Round false so that the positioning and size values don't get rounded to integers. Now let's add it to
the stage by typing stage dot add actor and
pass in main table. Okay, so inside the main table, we're actually going to
have four separate tables, one for the back button
and settings image, one for the audio settings, one for the difficulty setting, and one for the full
screen and blood settings. Each of these
tables will go into a separate row inside
the main table. Also, each settings table will actually consist
of two tables. The audio table will have a music table and a sound table. The difficulty table will
have a main table with a background image and an inner table for choosing
the difficulty setting, and the full screen
and blood table, which you'll call the
bottom table will consist of a full screen
table and a blood table. Alright, so with all
of that in mind, let's start with the
table that will hold the Back button and
the settings image, which we'll call
the banner table. So first, I'll com it,
create the banner table. Then this type table, banner
table equals new table. Let's go ahead and call
banner tablet Set Round. False. Okay, next, we'll add the back button and the settings image to the table. So I'll comment.
Add the back button and the settings image
to the banner table. Then let's type Banner
table dot AD Babton. We need to tell what size
to use by typing dot size. Back button, dot get width, coma Bbton dot get heights. Next for the settings Image, let's type banner table dot
Ad settings Image size, settings Image Get Width, Coma Settings Image
dot get height. Let's go ahead and add the banner table
to the main table. I'll comment here. Add the banner table
to the main table. And let's type main table
dot ad, banner table. Now if we run the game and
go to the setting screen, We have our widgets in two
separate columns in the table. Currently, however, the widget are centered together
vertically on the screen, so the center point
is about right here. When we start adding
the other tables, this isn't going
to look very good. Instead, we want to make
it so the center point of the table is at the center
of the setting image. To do this, we can add a
blank actor to the table on the right side of
the setting image and make it the same
size as the back button. So after we add the two wiges, but before we add
the banner table to the main table, I'll comment, add an empty cell to
the banner table, the same size as the back button in order to center
the settings image. And to do so, we can type banner table dot AD
with the no parameter. Then dot size, Bbton
dot get width, Back button, dot get height. If we try it now, We have
an empty cell on the right, and the center point is at the center of the
settings image. Okay? And because we want to put the next table in a new row, after we add the banner
table to the main table, let's go down a row by
calling main table dot o. Let's also add one world
unit of padding to the top by doing dot Pad
top one F. Okay, next, we'll create
an audio table, which will contain the music
table and the sounds table. So first I'll comment,
create the audio table. Then this type
table, audio table, equals new table, and audio
table that's set round false. Alright now we need to
create the music table, we'll set its background to the music setting
background image. So I'll comment, create
the music table and set its background to the music
setting background image. Then it first New
table, music table, equals new table, and music
table that's set round false. Okay, to set its background, we can type music table
that set background. This needs to be a
drawable object, which we can get from
the music setting background image by
typing music setting background image, get drawable. We also want the table to be the same size as the
background image. So let's type music
table, that's the size. Music setting background
image, dot get width. Come a music setting,
background image, dog height. Okay, now we want to add the music toggle button
to the music table. However, we want
the button to be aligned to the right
side of the table. To do this, we can first add
a blank actor to the table. That's the width of
this empty area here. To get the width, we can take the width
of the music setting background image and subtract the width of
the button from it. We also want some padding
of about two world units on the right between the button and the
edge of the image. So we'll also subtract
that from the width. Okay, so down here,
I'll comment, add an empty cell for alignment and the music toggle
button to the music table. Then let's add the
empty cell by typing music table dot ad
with no parameter, and we only care
about the width. So let's type dot With Music
setting background image, dot get With minus
Music toggle button, dot get Width, minus two F. Now we can add the music toggle button
to it by typing music table, dot A, Music toggle
button dot size, musictoggle button.gw. Come a music toggle
button, dot get Heights. All right. And in
order to check this, let's go ahead and add
the music table to the audio table and the audio
table to the main table. First I'll comment, add the music table to
the audio table. Then type audio table
dot ad Music table. We need to tell it what size
to use by typing dot size, music tablet get width. Come on music table get height. Next I'll comment, add the
audio table to the main table. Then it's type maintable
dot a audio Table. And as we learned in the video about the main menu screen, because the main table
fills the entire screen, we don't have to set the size
when adding things to it. All right, if we
run the game now and go to the setting screen, we have our music table
with this background set and the button aligned to the right with
some padding here. Perfect. Next, we'll create a table just like this
one before the sounds. Then put it in the audio table to the right of the music table. Okay, after adding the music
table to the audio table, but before adding
the audio table to the main table, I'll comment, create the sounds
table and set as background to the sound
setting background image. Then let's do so
by typing table. Sounds table equals new table. Sounds table, that's
set round, false. Sounds table, that's
set background, Soundsetting backgroundimage,
that get drawable. And sounds table that set size, sound setting background
image, that get width. Come a sound setting background
image, that get heights. Okay? And like with
the music table, we need to first add an
empty cell to the table, then add the Sounds
toggle button. So first, I'll comment,
add an empty cell for Alignment and the Sounds Toggle button to the sounds table. Then it's type
soundstable dot ad dot W, sound setting background image, do get With minus
Sounds Toggle button, dog With minus two F. Then sounds table dot
A, Sounds toggle button. Size soundstogO button, dog With coma Sounds to or
button, dog heights. Alright now we'll add the sounds table to the audio table. So I'll comment, add the sounds
table to the audio table. Then this type
audio table dot a. Sounds table Sounds
table dot get W. Sounds table dog heights. Now if we run the game
and click Settings, We get our music and sounds
tables lined up nicely. Also, notice that
we already have some spacing between
the tables here. This is because when I created the sounds background texture, I added some extra blank space at the front of the image here. I did this to make it easier to get everything
lined up correctly without having to
worry about figuring out exactly how much
padding to add. Okay, we'll next create
the difficulty table, which will contain a
difficulty selection table for switching between
the difficulty settings. But first, we want to put the difficulty table
below the audio table. So after we add the audio
table to the main table, let's go down a row
by typing maintable dot R. Let's add some
padding to the top by typing dot Pad
top one F. Okay, below this, I'll come it, create the difficulty
table and set as background to the difficulty
setting background image. Finish type table, difficulty
table equals new table. Difficulty table,
that set round, false, difficulty table,
that set background, difficulty setting background
image, docket drawable, and difficulty table
that set size, difficulty setting
background image, docket width, Come on
difficulty setting, background image, do get height. Alright, now we need to create the difficulty selection table, which will go inside
the difficulty table. So I'll comment, create the
difficulty selection table. Finish type table,
difficulty selection table equals new table. Difficulty selection table. That's set round, false. Okay, so first, when we add the difficulty setting
images to the table, we actually want to put them
on top of each other in the same table
cell and only show the one that corresponds to the current difficulty setting. To lay out widgets on
top of each other, we use the stack class of
the scene two D framework. We can add the
widgets that we want to stack into a stack object, then add the stack
object to the table. Alright, so to create
a stack object for the difficulty images, our first comment, create the difficulty image stack and add the difficulty
images to it. Then it's type
stack, pre center, then difficulty Image
stack equals new stack. And to add a widget
to the stack, we can use either the add
method or the Add actor method. So to add the easy image, we can type difficulty image
stack, dot add Esimage. Then we can do the same for the medium and hard images by typing difficulty image stack,
dot add medium image, and difficulty image
stack dot add Hart Image. We also need to set
the size of the stack. Because these three
widget are the same size, we can just use, for example, the size of the easy image. So let's type
difficulty Image stack. That set size, easy
image, that get width. Come a easy image,
that get heights. All right, now we can add the
previous difficulty button, the difficulty image stack, and the next difficulty button to the difficulty
selection table. So first, I'll comment this out. Add the difficulty
selection buttons and the difficulty Image stack to the difficulty
selection table. Then this type difficulty
selection table dot add, previous difficulty button size, previous difficulty
button, dot get width. Come a previous difficulty
button dot get heights. Then difficulty
selection table dot add, difficulty Image stack size, difficulty Image
stack, dot get width. Come difficulty image
stack, do get heights. We also want to add
some padding of, say, half a world unit between the image stack and the
buttons on both sides. So let's type dot
pad left, 0.5 F, dot pad right, 0.5 F. Finally, let's type difficulty
selection table dot AD, Next difficulty button size. Next difficulty
button, do get width. Next difficulty
button, dog height. Now we can add the difficulty selection table to
the difficulty table, then add the difficulty
table to the main table. However, because we want the
difficulty selection table to be aligned to the right
side of the difficulty table, we also need to
add a blank actor to fill in the empty
space on the left side. So first I'll comment,
add an empty cell for alignment and the difficulty selection table to
the difficulty table. Then type difficulty
table dot add, dot W, difficulty setting
background image, Dt get Wi, minus difficulty
selection table, doll get Width and minus F to add some
padding to the right side. Now we can add the difficulty
selection table by typing difficulty table dot A. Difficulty selection table size, difficulty selection
table, do get Width Coma difficulty
selection table, dog heights. Nextyle comment, add the difficulty table
to the main table. Then let's type main table
dot add difficulty Table. Let's go ahead and add
a new row by typing main table dot R and
add some padding to the top by typing
dot pad top one F. Now if we run the game and go to the settings, We have
a couple of problems. First, the difficulty table has been expanded to fill up
the rest of the main table. I know I said
before that because we have the main table
filling up the screen, we don't have to set the size
when adding things to it. However, we will
occasionally run into times when we do need
to set the size. I'm not sure exactly what causes the issue, but in any case, to fix it, after adding the difficulty table
to the main table, we simply need to add
size, difficulty table, dog width, coma difficulty
table, dog height. But check out the
setting screen now, The difficulty
setting background image is properly sized, but the difficulty selection table is too far to the right. This is because, as
I mentioned briefly, when we were creating
the main menu screen, adding things to a table doesn't automatically set the
table size values. So when we call
get width and get height here on the
difficulty selection table, it doesn't return
the expected values. To get the correct values, after adding everything to the difficulty
selection table here, we need to call difficulty
selection table Doc PAC, which will force it to
calculate its size values. Now when we check out
the setting screen, Everything is lined
up correctly. Now we just need to
add the bottom table, which will contain
the four screen table and the blood table. For the bottom table, I'll com it, create the bottom table. Then it's type table, bottom
table, equals new table. Bottom table, that's
set round, false. Next for the four screen
table, I'll com it, create the four screen
table and set as background to the four screen
setting background image. Thinness type table,
four screen table, equals new table,
four screen table, that set round, valse, four screen table,
that set background, four screen setting,
background image. Do get drawable four screen
table that set size, four screen setting, background
image, do get width. Come a four screen setting, background image, do get height. Okay, now we'll
add an empty actor to fill in the left
side of the table. Then add the four screen
check button to the table. I'll comment, add
an empty cell for alignment and the four screen check button to the
four screen table. Then it slide four
screen table dot add dot W. Four screen
setting background image. Doll get width minus four screen check
button, do get width, minus two F. Then four
screen table dot A, four screen Check button, size, four screen check
button, dot get Wi. Come a four screen check
button, do get heights. Add the four screen table to the bottom table.
So I'll comment. Add the four screen table
to the bottom table. Then type bottom table dot
Ad four screen table size, four screen tablet get width. Come a four screen
tablet get height. Let's also go ahead and
add the bottom table to the main table so we can
make sure it all works. I'll comment at the bottom
table to the main table. Then type main table
dot A, bottom table. Now let's run the game
and go to the settings. Nice. Okay. Finally,
for the blood table, I'll first copy all of these lines for creating and adding the full screen table and paste them before adding the bottom table to the main table. Then I'll change
everything up to use the correct objects.
Okay, here's the result. We have to make sure to
use the blood table, the blood setting
background image, and the blood check button.
Let's give it a try. Perfect. Like with the sound
setting background image, I added some extra
transparent space on the left side of
the blood setting background image to
give it some padding. Okay, now we can go to the
top of the crate tables method and remove the
set Debug all line. Alright, in the next
video, we'll make it so these settings actually
show the correct settings. Let the player change
them. See there.
41. Changing the Settings: In order to make
it so the player can click the buttons
in the setting screen, we first need to go to
the show method and set the setting screen stage
as the input processor. So first I'll comment, set the stage as the
input processor. Then type GDX, pre center, then dot input dot set
input processor, stage. Another thing we need to
do in the show method is make it so the widgets show
the correct current settings. So first I'll comment, set the settings widgets to
show the current settings. Let's start with
the music setting. So what we want to do is check
if the music setting is on by calling the Is music setting on method of
the settings manager, and if so, you need to set the music toggle button
to the check state. If the music setting is off, you can just leave the button
on the default upstate. So let's first type I
game settings manager, do I music setting on. And to set the button
to the check state, we call Music toggle button
checked and pass in true. Similarly, for the
sound setting, we can do if game that
settings manager, that is sound setting on Sounds toggle
button set checked. True. Next for the difficulty, we need to check
what the current difficulty setting is by calling the G difficulty setting method of the
settings manager, then we need to hide the
two difficulty images that don't match the
difficulty setting. Let's do this with a
switch statement by first typing switch game
Dot settings manager, Dot get difficulty setting. Case easy. If the
difficulty is set to easy, we need to hide the medium
image and the hard image. To hide a widget, we call it set visible method
and pass in false. So for the medium image, we can cite medium image, dot set visible, false. And for the hard
image, hard image, dot set visible, false.
Now let's put a break. Then case, medium. Easy image that's
a visible, false, hard image, that's
a visible, false. Then break. Finally, for hard, we can just do default. Easy image that's
a visible, false medium image that's
a visible false. Okay, now we just have the four screen setting and
the blood setting, which we do the same way as
the music and sound setting. So for the four screen setting, let's do I game does
settings manager, there is four screen setting on. Four screen check button. That's checked, true. And for the blood setting, if game that settings manager, there is blood setting on. Blood check button.
Dt checked, true. If we run the game now
and go to settings, it shows us the
correct settings. The music and sounds are on. The difficulty is easy, full screen is off
and blood is on. Now let's make it
so we can click the buttons to
change the settings. To tell the buttons what
to do when we click them, we need to add some
change listeners to them. Like we did in the main
menu screen class, we'll create a method for this called Add button Listeners, which we'll call at the end
of the create Buttons method. So first, let's go to the end of the Create Buttons method. And I'll comment, add
the Button listeners. Then let's call Add
button Listeners. Then below the create
buttons method, let's type Private void
add Button Listeners. We'll start with
the Back button, which we want to take us back to the main menu when we click it. Okay. First I'll comment. Add
the Back button listener. Let's do so by typing
Babton dot ad listener, New change listener
and press Center. First, we want to
play the click Sound. So I'll comment, play Clicksund. Then this type game
dot audio Manager, dot play sound assets
dot click SMD. I want to set the screen
to the main menu screen. So I'll comment, switch
to the main menu screen. Then this type game dot set screen game dot
main menu screen. Can I put a semicolon down here. And if we run the game
and go to the settings, we can click the Back button
to return to the main menu. All right next is the
Music toggle button. I'll comment. Add the music
toggle button listener. Then it's type Music
toggle button, dot add listener, New change
listener, and press Center. First, we'll play
the click sound, so I'll just copy and paste
the lines from up here. Now, each time we
click a button widget, its state actually
switches back and forth between being
checked or unchecked. So what we want to do is
call the toggle music setting method of the
settings manager and toggle the music
setting on or off based on whether the music toggle
button is currently checked. So I'll comment,
toggle the music setting based on the
buttons checked state. Tennis type game settings
manager, DTggle music setting. And to get a buttons
checked state, we use the I checked method. So music toggle button,
that is checked. One more thing we want to do
is use the new music setting to determine whether
to enable or disable the music in
the audio manager. So I'll comment, if
the music setting is on enable music,
otherwise, disable it. Then this type game settings
manager is music setting on game audio manager
that enable music. Else, game audio
manager disabled music. Now we can put a semicolon here. Then if we run the game
and go to the settings, we can toggle the
music on or off. This also saves the setting
to the preferences. So if we close up the
game and run it again, we can see that the
music isn't playing. And if we go to the settings, we can see that the
music setting is off. Next, the listener
for the Sounds toggle button is very similar. So I'll first copy and
paste all these lines. I'll change this to
Sounds toggle button. Change this to sounds
toggle button. Change this to toggle
the sound setting. Change Toggle music setting
here to to sound setting. And music toggle button
to soundstago button. Then I'll change this comment to if the sound setting is on, enable sounds,
otherwise, disable them. Now change this to
Is sound setting on. Change this to enable sounds and this one to disable sounds. If we run the game
now, we can turn off the sounds which includes
the click sound. And if we start the game, we no longer hear any hit sounds. Okay, next, we have the previous and next difficulty buttons. With these, we want to cycle through the difficulty settings, changing the setting in the
settings manager and showing only the difficulty setting image that corresponds
to the current setting. Starting with the previous
difficulty button, I'll comment, add the previous difficulty
button listener. Finis type previous
difficulty button, dot add listener, new change
listener, pre center. We want to first play
the click sound, of course, so I'll copy and
paste the lines from up here. Next, I'll comment, go to
the previous difficulty setting or go to hard
if currently uneasy. Also make the corresponding
difficulty image visible and the
other is invisible. Then let's type switch,
game dot settings manager, Dog difficulty setting. Case Easy. If it's
currently easy, you want to change
the setting to hard in the settings manager. Let's type game D
Settings manager, D set difficulty setting. Global variables
difficulty dot hard. Next, we want to hide
both the easy image and the medium image by typing
easy image, that's visible, false and medium image, that's a visible false. Then we want to show the
hard image by typing hard image that's visible, true. Okay, let's set a break
then case medium. We want to set the difficulty
to easy this time. So that's called game
that settings manager, that'll set difficulty setting. Global variables dot
difficulty dot easy. Now let's show the easy
image by typing easy image, that's a visible, true. Then hide the others by
typing medium image, that's a visible false. And hard image, that's
a visible false. Then let's at a
break. And finally, for hard, we can do default. Game Dow settings manager, dot set difficulty setting, global variables dot
difficulty dot medium. Easy image, that's
a visible, false, medium image, that's a visible, true, and hard image
that's a visible false. Okay, let's add a
semicolon down here. Let's go ahead and
copy and paste all of these lines for the
next difficulty button. I'll change this to
Next difficulty button and this to Next
difficulty button. For the comment in
here, I'll put go to the next difficulty setting or go to easy if
currently on hard. Okay, if it's on easy,
we want to go to medium. And we want to make the
easy imagion visible, the medium image visible, and the hard imagion visible. Next for medium,
we'll go to hard. Make the easy and medium images invisible and the
hard image visible. Finally, for hard,
we'll go back to easy. Make the easy image visible, and the other is invisible. All right, if we
run the game and go to the setting screen now, we can cycle through the
difficulty settings. If you put it on medium or
hard and start the game, however, the game
is still uneasy. This is because we
never made it so that the game screen gets the difficulty setting
from the settings manager. So let's do that now by going to the game screen class and
up to the show method. Before calling Start game here, I'll comment, get the difficulty setting from the
settings manager. Let's do so by typing
difficulty equals game, that settings manager,
get difficulty setting. We actually also need to get the blood setting from
the settings manager. So I'll comment, get the blood setting from the
settings manager. Then a type showing blood equals game that
settings manager. That is blood setting on. Okay, if we run the game now, change the difficulty
setting and start the game. It uses the correct
difficulty setting. Okay, back in the
setting screen class, we just need to
add listeners for the full screen check button
and the Blood check button. For the full screen
check button, I'll comment, add the full
screen check button listener. Then it's type four
screen check button, dot a listener, New change
listener and press Center. First, we'll play
the click sound, so I'll copy and paste
the lines from here. Next, we want to toggle
the full screen setting, based on the buttons
checked state. So I'll come it toggle
the full screen setting, based on the buttons
check state. Finnis type game
settings manager. Do Toggle four screen setting. Full screen check
button that is checked. Next, based on the new
four screen setting, you want to either
put the game in full screen mode
or Windowed mode. So first I'll comment, if the
four screen setting is on, go to full screen mode, otherwise, go to Windowed mode. In this type, if game
that settings manager, that is full screen setting on. And as we saw earlier
in the main game class, to go to full screen mode, we type GDX digraphics
that set four screen mode. GDX digraphics Dot get display
mode. Now let's do ls. And to go to Window mode, we type GDX dot graphics, dot set Window mode and pass in the desired
within height of the window. We can use the
defaults we created in the global variables class
by typing global variables, dot Window width, coma
global variables. Dot Window height. Now let's put a semicolon down here
and give this a try. Okay, we can turn four
screen on or off now. If we turn it on and
close out the game, when we run it again, it automatically goes
to four screen mode. Finally, let's add the blood
check button listener. I'll comment. Add the blood
check button listener. Then let's type
blood check button, dot add listener, new change
listener price Center. First I'll paste the lines
for playing the click sound. Now we simply just need to
toggle the blood setting, based on the buttons
check state. So I'll comment, Toggle
the blood setting, based on the buttons
check state. Then let's do so by typing
game that settings manager. Do toggle Blood setting. Blood check button is checked. All right, if we put a semicolon here and then run the game, We can now turn off
the blood setting, and if we start the game,
there will be no blood. And if we turn the
setting back on and play the game, the blood returns. Awesome. Alright? The
only thing we have left to do now in our game is
add the loading screen, which we'll do in the
next video. S there.
42. Create the Loading Screen: The loading screen
is going to be the first screen we
display in the game, and it will basically just
load the assets into memory and show the loading progress by displaying a progress bar. When the assets are
all finished loading, it will then switch to
the main menu screen. Okay, so to begin, let's create the loading screen class by right clicking the
screens package here, go to New Java class, typing loading screen,
pressing Enter. This needs to implement
the screen class, so let's type Implement screen
Press Enter to import it. Then let's implement the methods by hovering over this line, choosing implement methods,
and clicking Okay. Okay, now at the
top of the class, we first want to
add some variables. We'll start with objects for the main game class
and for the viewports. Let's set private final SFS, precenter then game, and private final viewports,
precenter then viewports. Next, we need to create
some static final variables to define the progress bar. The progress bar is going to
consist of two rectangles, one for the background and one for the progress bar itself, which will grow in width
as the assets get loaded. So for the progress
bar variables, I'll com it progress bar. Then I'll start with private
static final floats, progress bar max width equals 58 F. Then let's do
private static final float. Progress bar height equals five F. And for the background, let's do private
static final float, progress bar background width. Equals progress bar max width plus F and private
static final float, progress bar background
height equals progress bar height
plus F. All right. And because the assets will
get loaded pretty fast, we're also going to add a
short 1 second delay between when all of the
assets get loaded and switching to the
main menu screen. So for this, I'll
comment, delay. Let's type private
float delay timer. Private bullying, delay started. And private static final float. Delay time equals one F. Okay, we next need to create
a constructor for the class, SustPublic
loading screen. I will need SFS game
as a parameter. Let's go ahead to set
this dot game to game. Next, let's set up
the viewport using the same extend
viewport settings we use in the other
screen classes. First dot comment,
set up the viewport. Then a side viewport equals
new Extend viewport, precenter then global variables, precenter that world width, como global variables,
dot Mint world height. Comma global variables
world width, comma zero. Next, we want to initialize
the delay variables. So I'll comment, initialize
the delay variables. The next type delay
timer equals delay time. And delay started equals false, since we won't start it until after the assets have
finished loading. Finally, we want
to start loading the assets from
the asset manager. We're currently doing this
in the main game class, but we'll be removing
it from there in a bit. Okay, I'll comment.
Start loading assets from the asset manager. Let's do so by calling
game dot assets dot Load. And remember that when we load assets with the asset manager, it actually adds them
to a loading queue, or the assets will get loaded into memory as soon as possible. Okay, next, let's
go ahead and update the viewport here in
the resize method. I'll comment,
update the viewport with the new screen size. Then type viewport dot
update with height, true. All right, now we
just need to do a few things in the render method. First, we'll clear the screen to the same blue color we use in the main menu screen
and the setting screen. Subtype screen utils,
precenter then clear, global variables,
dot blue background. After that, we need to check
if the delay has started. So let's type if delay started. If the delay has started, we want to check if the
delay time has finished, and if so, we'll
call a new method in the main game class
called assets loaded. This we'll let the
main game class know that the assets
have finished loading, so it can then switch to
the main menu screen. So I'll comment if the
delay has started, check if the delay
timer has finished. Then it's do I delay timer less than or equal to
zero F. And I'll comment, If the delay timer has finished, tell the game that assets
have finished loading. Then it is called
game dot assets loaded, which we'll
create later. If the delay timer
hasn't finished yet, we simply want to decrease
the delay timer by Delta. So let's do ts,
and I'll comment, decrease the delay
timer by Delta time. Theins type delay timer,
minus equals Delta. Okay? And if the
delay hasn't started, you want to check if the
assets have finished loading. If we take a look at
the main game class, after we load the assets
in the create method, we call the asset managers
finished loading method. This method basically freezes the application
until all the assets have been loaded into memory. With the loading screen,
we don't want to freeze the application because we want to be able to show
the loading progress. Therefore, we don't want to call finished loading in the
loading screen class. Instead, to check whether the assets have
finished loading, we can call the asset
managers update method. This will return true if all of the assets
have been loaded. Otherwise, it will return false. So back in loading screen, let's put an CIF here. Then game dot assets
dot manager dot update. If this returns true, all the assets are loaded, so we want to start the delay. I'll comment, I assets have finished loading,
start the delay. Then this type delay
started equals true. Now we'll draw the progress bar. One thing we don't want to do in the loading screen class
is use assets because, of course, they haven't yet
been loaded into memory. So instead, we'll use the shape render to draw
rectangles for the progress bar. First, we need to tell
the shaped render to use the loading
screen viewports camera. So below this I statement, I'll set the shape render to
use the viewports camera. Vinis type game,
that shape render, that subrojection matrix, viewports dot g
camera, dot combined. Now we can start
drawing the rectangles. I'll comment, draw
the progress bar using the current load progress. Then let's first call game
dot shape render, dot begin. We want to use filled shapes. So let's pass in shape type, pre center, then dot filled. Okay, for the background
rectangle, we'll use black. So let's set the shape renders color to black by typing game, dot shape render, dot side
color, 00, zero, one. Then we can draw the background
rectangle centered in the screen by typing
game, dot shape render, Direct, viewport dot G rolled width divided by two F minus progress
bar, background width. Divided by two F. Come a
viewport dot G world height, divided by F minus progress bar, background height, divided by two F. Come a progress
bar, background width. Come a progress bar,
background height. Next, for the progress
bar rectangle, we'll use the gold color that we defined in global variables. So let's type game,
dot shape render, D side color, global
variables dot gold. Then we can start drawing
it by typing game, dot shape render,
direct, viewport, do Geollwidth divided by two F minus progress
bar max width. Divided by F, viewports dot get road height divided by two F
minus progress bar height, divided by two F. Now to calculate the current
width of the progress bar, you need to get the
current loading progress from the asset manager. To do this, we call the
Git progress method, which returns a float 0-1. So to calculate the
progress bars width, we can simply do game
dot assets dot manager, dot get progress, times
progress bar max width. And for the height, we can
just use progress bar height. Finally, we need to call
game shape render end. And that's it for
the loading screen. Now we just need to go back to the main game class and
change some things in here. First, we need to create
a loading screen object. So at the top in the
screens category, let's type public
loading screen, price Center, then
loading screen. Now on the create method, let's get rid of all of these
calls to the asset manager. In their play SleCmt, initialize the loading
screen and switch to it. Let's do so by typing
loading screen, equals new loading screen, this and set screen
loading screen. Next, we need to move
all this other stuff to an assets loaded method, which will be called
by the loading screen when all of the assets
are finished loading. Let's select all of these lines and cut them with Control X. Then below the create method, let's type public void, assets loaded and
paste the lines. Okay, let's run the game
and see what happens. Alright, we get a progress bar, followed by a short delay. Then it switches to
the main menu screen. Excellent. Okay, that's
pretty much it for our game. So I'll see you in the
conclusion in the next video.
43. Conclusion: Congratulations on
completing this course and creating an entire game
using LibGDX and Java. We've come a long way over
the past few hours going from a simple little demo project
to a fully functional game. I hope this course inspires you to continue working with LibGDx. And if you decide to add
more to this game or create an entirely new game using what we learned, I would
love to check it out. So please send me pictures
and videos of the game or the code for the game or even better if you
publish a game, be sure to send me a link to it. Thank you again for
choosing my course, and I wish you good luck on your game development journey. Take care.
44. Install the Android SDK: Before we can start adding Android functionality
to our projects, we first need to
install the Android SDK or Android software
development kit. Similar to the JDK, the Android SDK is a comprehensive set of
tools used to develop applications for the
Android platform and we can actually install
it directly through IntellaJ. To do so, in the
IntellaJ welcome screen, we first want to
go to New Project and choose Android here
at the bottom left. It would then tell
us that in order to create an Android project, we need to have the
Android SDK installed, and we can install
it by clicking the Install SDK button here. If we don't already have
the Android SDK installed, it will tell us that no
Android SDK has been found. All right, so we can click next. We then get to
choose which Android SDK components we
want to install. We can just leave it on
the default options. We can also choose where we
want to install the SDK. Feel free to leave yours
on the default location, but I'm going to click
the Browse button here, go to the D Drive. Add a folder with this
button and call it Android. Then go inside the
Android folder and add another
folder called SDK. And with the SDK folder
chosen, I'll click Okay. We actually also want to copy this path by selecting it all and pressing Control C because we'll be needing it
in the next video. Right now, we can click next, then verify the settings, click next again and
accept the license here. And when we click Finish, it will download and install
all of the components. Once the Android SDK has finished installing, we
can click Finish again. And actually, what
we just did didn't download and install
the entire Android SDK. It only did a partial install. In order for our LibGDX
projects to work correctly, we need to do a full install. To do this, let's first
click Cancel here as we don't really
need to create a new project at the moment. And now on the welcome screen, let's click customize
here on the left, then all settings down here. And if we search for SDK in the search box
at the top left, it should find Android SDK
in the system settings here. With this chosen, we can now see our Android SDK
location on the right. And down here, we
can see a list of all available Android
SDK versions. The one we just now
installed should be the topmost one that has a
number for the API level. However, its status might say either not installed or
partially installed. To do a full installation, we first need to check the
box to the left of it, click Apply down here
and click Okay here. And it should start
the full installation, which could take a while. Okay, when that's done,
we can click Finish. Then click Apply here,
and it should now say installed for the status of the SDK version we
just installed. Now we can click Okay,
and close out of IntellaJ and I'll see
you in the next video where we'll use the
LibGDXPject generator to create a project with
Android functionality.
45. Create & Import a New Project: It's possible to add
Android functionality to an existing project
that doesn't have it, but I found that it's actually
much easier to create an empty LBGDXPject with Android functionality
than transfer over the code from
the other project. So right now, I have the
LbDXPject generator open, and we're going to
use it to create a project that's almost exactly the same as our previous SIC figure showdown project, but with Android
functionality added to it. Okay, for the project name, I'll go with SIC figure
Showdown Android. Now the package name needs
to be exactly the same as the other project
in order to not cause any problems when
we transfer the code. If you don't remember the
package name for your project, if you open it up in IntelliJ, you can find the
package name inside the source folder of
either the core folder or the desktop folder, minus comb grant dot SFS. So back in the
project generator, I'll put Com Grant dot
SFS for the package name. We also want to use the
same main game class. I use SFS for mine. For the output folder, I'll go to my main
LBGDXFolder then projects, right click and create
a new folder called stick figure Showdown Android. Press Center and click Open. Okay, next, for the
Android SDK location, yours is likely not showing the correct location for where you downloaded the Android
SDK in the previous video. The only reason it's
correct for me is that I used it to create an
Android project in the past. If you still have the
Android SDK location copied into the clipboard, you can paste it
into the box now. If not, if you rewatch the
end of the previous video, it will show you how to find
the Android SDK location. Okay, next, for
supported platforms, let's turn off everything
except desktop and Android. And for official extensions, we want to check free type. And now we're ready to
click the Generate button. And yes, we want to use
the more recent version of the Android Build Tools
and the Android API. Okay, so when
everything is finished, if you're using the
same version of the project generator as I am, it's likely telling you here
that the build has failed. The reason for this is that in newer versions of
the Android SDK, the names of two files that LibGDX requires
have been changed. Now the project generator
can't find the files. Hopefully, this will be
fixed in a future update. But anyway, to get
around the problem, we simply need to
change the names of the files back to
the original names. To find the files,
we need to go into the Android SDK download folder. I'm going to copy the location
into my clipboard and I'll leave everything as is in the project generator
and minimize it. Then I'll paste the
copied path into my file explorer
here and presenter. And here we need to go into
the Build Tools folder, then into the folder
of the highest Android SDK version
we have downloaded. And the first file we're looking for is the one
called D Eight here. This file was
originally called DX, and that's what the project
generator is looking for. So we need to change the
file's name back to DX. Okay, for the next file, we need to go into
the lib folder here, and now we see another
file called D Eight. We need to change this
one to DX as well. Alright, now, if we bring
the project generator back up with all of the
settings still the same, let's click the
Generate button again and choose, we want
to overwrite it. And once it's
finished generating, it should say that the
Build was successful. Awesome. Now we can close
out the project generator, and let's open up and tell a J. From the welcome screen,
we can click Open here, then browse to the folder for the project we just created, so stick figure Showdown
Android and click Okay. Now if we wait for everything
to finish loading, we might get a few messages
here, but we can ignore them. One problem we might
have, however, is that if we go to build, build project, we get this message about
being unable to make filled private,
et cetera, et cetera. It took me a while to
figure out how to fix this. And to do so, we first need to open up our main
project folder here, then double click this gradle
Properties file to open it. And here, at the end of the org dot gradle JV Margsine,
you need to add a space. Then types, add opens, equals Java dot BSE, fdlashjava dot IO equals, all unnamed, W this
part being in all caps. And now we need to
click this button on the right that says
Load Great Old changes. Okay, when that's
finished, if we go to build Build project again, I should say that the
Builder was successful. Alright, we can
close up the Great Old Dot properties file now, with all of that
stuff taken care of. In the next video, we'll
learn how we can run the Android version
of our project. See
46. Running LibGDX on Android: If we take a look at
the project structure, the main difference we'll
see between this project and our previous ones is that we
now have an Android folder. The Android folder contains
all of the files that are necessary for running
our application on an Android device. If we take a look inside
the source folder here, we can see that we have an
Android launcher class. This is basically the
Android version of the desktop launcher class in
the desktop source folder, and it's required
in order to run the Android version
of the application. We can open it up if we want. However, we can't
run it straight away like we can with
the desktop launcher. And just to make sure that
the desktop launcher works, let's go into the desktop
folder, then source, then right click
Desktop launcher and choose run desktop
launcher dot Main. Okay? And if it works, we should get the
square window with the red background and the bad logic image at
the bottom left. All right, we can
close this out now. To run the Android launcher,
we have two options. First, if we have an Android
device like a smartphone, we can plug the device
into our computer using a USB cable and install and run the Android version
of the application on it. To do this, with the
device plugged in, if we go up to the Run
configurations up here, where it currently
says desktop launcher, we can drop this down
and choose Android, and it might then show our Android device
here next to it. If it says now devices, it likely just means
that we haven't yet enabled developer
options on the device. The process for enabling developer options might be slightly different
depending on the device. But from what I've seen, most devices use
the same process. The first step is to go to
the device's settings menu. In the settings
menu, we should see an about device or about phone option, which
you want to click. Next, we should see either a
software information option or a build number option. If we see a software
information option, we want to click that, and then we should see a
build number option. Okay, so what we
want to do next is tap build number seven times. As we do that, it will
likely pop up a message saying that we're a
certain number of steps away from
being a developer. And after we tap it seven times, if we go back to settings, we should now see a developer option setting at the bottom. Now we just need
to click that and make sure that the switch
at the top is turned on. Right, after all of that is
finished, back in intell a J, as long as we have
Android chosen for the run configuration and
our device is plugged in, it should show the
device here next to it. We can now click the
Run button here and it will install and run the
application on the device. We, of course, won't be able to see the application
on the computer, but we should now be able to see it on the device's screen, and it should look mostly the same as a desktole
blancher version, except it will be full screen. All right, if you
are following along, let's go ahead and close
out the application on our device and
unplug the device. It should now say no devices next to the Run configuration. So the other way to run
the Android version of our application is by using
an Android virtual device. Android virtual devices or AVDs simulate particular
Android devices, and we can run them
on our computer using the Android
emulator and IntelliJ. To create an AVD, we use
the Android device manager, which we can find either
by going to tools, Android Device Manager or by dropping down the
box here that says no devices and choosing
device manager. Before we continue, inside
the Android folder over here, let's open up the
built Grado file. What we're mainly
concerned with in here is the default
config section. This shows us the minimum
Android SDK version that our application can run on, as well as the target version. When we create an AVD, we'll have to choose an Android
version for the device, and we'll want to choose a
version that isn't lower than Mint SDK version or higher
than target SDK version. All right, so back in
the device manager here, we can click Create
Device, and now we can choose the configuration that
we want to use for the ABD. First, we can choose
the device category, which includes
phones and tablets. We can choose a particular
device from that category. We can see the name
of the device, along with its size, resolution, and screen density, and we get a visual representation of
the device's screen here. For this ABD, let's go with the phone category and choose
the Nexus five x device. Now if we click Next, we get to choose the Android
version for the device. As I mentioned before, we
want to use something between the Mint SDK version and
the target SDK version. To make things
simple, let's just go with what we have for
the target SDK version. So for me, I'll choose the
one with API level 33 here. Because this is actually a
system image and not the same as the Android SDK version that we
downloaded earlier, we need to download this
as well by clicking the button here next to it
with a down arrow icon. Okay, when that finishes
downloading and installing, we can click Finish here,
and click Next here. Now we can change some settings
of the AVD if we want, such as by giving it a
different name or making it start up in either portrait
mode or landscape mode. We can actually
change this easily once we get the AVD
up and running, and it will remember the change
the next time we run it, so it's not necessary
to change it here. All right, so let's go
ahead and click Finish. Now, if we don't have a physical Android device plugged in, we should see our new AVD here. If you do have a
device plugged in, you could drop down this box and switch between it and the AVD. And by the way, we can create multiple AVDs to test the application on
multiple devices. So with the AVD chosen, we can minimize the
device manager. Then let's click the Run button. Okay? The Android emulator
panel should pop up, and if we give it a few minutes, the AVD should show
up in here and the application
should automatically be installed and start running. And if we want to change the
orientation of the device, we can click these
rotation buttons up here. To close out of the application, we can click the
Back button here. Now, you might like having
the Android emulator show up in this
little panel here, but we can actually
also make it come up as a standalone application. To do this, let's
first close out of the panel by
clicking the X here. Now let's go to File settings. And in the search box at the top left, let's
type Emulator. Okay, now we can
choose the Android emulator setting at the bottom. And here, we have this option that says Launch
and a tool window. If you uncheck this, then
click Apply and Okay. Now if we click the Run button, the Android emulator will launch as a standalone
application. And we can freely
move this around. We can close out of the
Android emulator if we want, but then until a J, we have to reload it whenever we click
the Run button again. So instead, we can
simply minimize it. And now if we click Run, we can bring the Android
emulator back up, and the application should
start up again straight away. Okay, let's go ahead
and close out of the application, and
in the next video, we'll transfer the code from our SIC figure showdown
project into this project. See
47. Transfer the Code: Okay, so what we'll
do is we'll replace everything in the assets
folder and the core and desktop source folders
of this project with the files from the original stick figure showdown project. So first, we want to show everything in the assets folder, as well as the
core source folder and the desktop source folder. And now we need to open up the previous project
by going to File, and we should be able to find
it under recent projects. Here it is. We want to open
this one in a new window. And we might need to
make the window a bit smaller and move it out of the way so we can
see the other one. Okay, first, let's go into the Assets folder of
the original project, and we need to close up
all of the folders inside. Then we can select the
first folder, hold Shift, and click the last folder to
select all of the folders, right click and choose Copy. Then let's go into the Assets
folder of our new project. Right click it, choose
paste, and click Okay here. And we can delete bad
logic dot JPG here. Okay, now let's go back
to the original project, and then the core source folder. Let's close up all the folders
inside the package folder. Then let's select
all the folders and the main game class at
the bottom and copy them. Then paste them into the same package folder and
the new project. Let's click ORight to
overwrite the main game class. Finally, in the desktop source
folder of the old project, we can copy the desktop
launcher class. Then right click the Desktop launcher and the new project, choose paste, then
Okay, then overwrite. And now we can close out
of the original project. Alright, now, let's go
ahead and test everything out to make sure it
all works like before. First, we can go up to
the run configurations and choose Desktop launcher. Let's click the Run button. Okay, it seems like everything
is working correctly. Awesome. Now, let's try it on the Android virtual
device we created earlier by clicking the Run
configuration box again and choosing Android. Let's make sure we have
the Nexus ABD selected and not a physical device just in case we have one plugged in. Okay, if we click Run,
we might have to first click Terminate here to stop
the previous application. Now if we bring up the
Android emulator window and let it finish installing, we can see that the game runs But one thing you'll
likely notice straightaway is that there is now a short pause between the repetitions
of the music. Other than that, everything seems to be working correctly. Okay, in the next video, we'll fix the super
annoying music issue. See.
48. Fix the Audio: As we saw in the previous video, if we run the Android
version of the game, we get an issue
where the music has a slight delay between
each repetition. This is actually
caused by a problem between the way Android handles audio and the way the music
class in LibGDx loops music. One solution to the
problem is to use the sound class instead of the
music class for the music. This only works, however,
if the music file is small, as the sound class
can only handle audio files that are
less than 1 megabyte. Fortunately for
us, our music file is quite small at only
about 9 kilobytes. If the file was over a megabyte, you would have to either find
a way to make it smaller or just use music that doesn't have to
sound good on loop. Okay, so to switch
our music asset to use the sound class
instead of the music class, let's first go into
the assets class, which is located
in the resources package of the core folder. And here, let's go down
to the load audio method. And when we load
the music asset, let's change music dot class
here to sound dot class. I'll press Control A to O to remove the
music class input. Next let's go into the
audio manager class, which is also located in
the resources package. At the top, where we create the music variable, let's
change it to private, final sound music,
and I'll press Control At O again to remove
the music class Import. And now we have a few
errors that we need to fix. First, at the bottom
of the constructor, there's no set looping
method in the sound class. Instead, to put a
sound object on loop, we call it loop method. However, this will
also immediately start playing the sound on loop, which we don't want to
do in the constructor. So let's just remove
these lines here. Now let's go down to
the nable music method. There's also no is playing
method for the sound class. So let's remove this I statement
along with this comment. What we're doing here
instead is simply start looping the music by
calling it loop method. So first I'll
comment loop music. Then let's call music do loop. Similarly, in the
disabled music method, let's remove these
lines at the bottom, and we'll simply
just stop the music. So I'll comment, stop music. Then let's call music do stop. Next in the play music method, we again have a call to I
playing, which isn't available. So let's remove these lines. And what we'll do instead is check if the music is enabled, and if so, we'll call
the music loop method. So I'll comment, I music
is enabled, loop it. Finish type if music
enabled. Music dot Loop. Next, for the pas music method, we unfortunately can't pause
a looping sound and resume it because calling
the loop method starts the audio
from the beginning. Because our music
audio is very short, however, this isn't
really a problem. So instead of a
pas music method, we're going to use
a stop music method and simply stop the music. We don't just want
to start typing a new name for the
method, though, because we're calling
this method from several other classes
in the project, and we'll get errors
in those classes about the pas music
method not being found. Instead, we want to right click the method name, go to refactor, then rename Type Stop
music and press Center. Renaming it this way
will make it so that all calls to the
previous method name now called the new method name. Now we can remove these lines, and we'll just stop the music if it's enabled. So I'll comment. If music is enabled, stop it. Then type I music
enabled. Music dot sop. That's it. Now if we run the game on the
Android emulator, you can hear that the music plays in a perfect loop again. One problem we do
have now, however, is that if we go
to the settings, another instance of the music starts playing on top
of the current one. This is because if we go into
the setting screen class, which is located inside
the screens package, and the show method of
this class, we have it, so if the music setting is on, it calls the music Toggle
buttons set checked method. The set checked method actually triggers the buttons
change listener. And in the music toggle buttons change listener, we have it. So if the music setting is on, it calls the audio Manager's
Enable music method. Now, if we go back to
the audio manager class, remember that in the
enabled music method, we call music dot Loop. The problem with this
is that when we call either the play method or the loop method on
a sound object, it actually starts
a new instance of the object's audio asset. So every time music
D Loop gets called, if the previous instances
haven't been stopped, we'll get another instance of the music looping
on top of them. To fix this, we can go up
to the top of the class. And in the music
variable section, we can add a private
Boolean music playing and initialize
it to false. Now and enable music, we only need to perform any of this code if the music
isn't currently playing. So at the top, I'll comment, I music is already playing,
don't do anything. Then this type, I
music playing, return. After calling music Dup here, we want to set music
playing to true. Next in disabled music, after stopping the music, we can simply set music
playing to false. Next on the play music method, we also only want to perform this code if the music
isn't already playing. So I'll copy the lines for checking this in the
Naval music method. Then paste them at the top
of the play music method. And after calling music D up, you want to set music
playing to true. Finally, in the
stop music method, after stopping the music here, we can add music
playing equals false. Okay, now if we run the
game on the emulator again, We might get a problem with the music not starting at all, but if we start the
game and go back to the main menu, the music starts. I've actually found this to only happen on the Android emulator. I've tried it on several
physical Android devices, and the problem didn't occur. Anyways, it seems
that the cause of it is that the music
asset doesn't get completely loaded into memory on the emulator before the main menu screen
comes up the first time, so it isn't able
to play the music. To fix this, we can try going to the assets class and
then the load method. We want to make it so that
the load audio method gets called before all
of the other methods. This will make it
more likely that all of the audio assets
get loaded in time. If we try running the game now, the music might start working when the
main menu comes up. If not, another thing we
can try is increasing the delay time between the loading screen and
the main menu screen. To do this, let's open up
the loading screen class. Let's change the delay time
variable here to something like F. Now if we run the game,
it should work correctly. But like I said, it
seems like this problem doesn't occur on actual
physical Android devices, so we could set the
delay time back to one if we wanted to, but
I'll leave mine on two. Alright and now if we
go to the settings, we can see that it doesn't cause another instance of
the music to start. So we've taken care of
that problem as well. However, we actually have
one more audio problem, which is that sometimes
the game will stutter when playing
multiple sounds at once. I found this issue to
be more noticeable on physical devices than
on the emulator. And the reason for the
issue is that in Android, the sounds play synchronously
on the game loop, forcing the rendering of
the game to get paused at times while waiting for a
sound to get processed. The solution to this problem is to play the sounds
asynchronously. To do this, let's go to the
Android launcher class. And here we need to override
the create audio method. So first, let's go
down a few lines and start typing create Audio. And when it shows up, we can
press Enter to override it. If we want to replace
this return line with return new asynchronous
Android audio, press Enter to import it, then context comic and fig. Now if we run the game, it
should work like normal. But if we were to play
it on a physical device, we shouldn't notice
any more stuttering. Okay, now if we start the game, we can still move around
using the arrow keys. But if we press, for example, the F key to punch, it brings up the onscreen
keyboard. This isn't good. And anyway, with mobile devices, we want to be able to do everything by
touching the screen. So we'll be putting some
buttons on the screen, both for moving our fighter and for attacking and blocking. But first, if we go back
to the setting screen, there are a couple of things
we want to change in here, and we'll do so in
the next video. See.
49. Change the Settings Screen: Before we change
the setting screen, if we run the game on a
physical Android device, you'll notice that
on the right side of the screen is showing
the navigation buttons. If we run it on the emulator, this might show up as a small bar at the bottom of the screen. For the most part, mobile
games will hide all of this so that it doesn't
get in the player's way. To do this in our game, we can go into the Android
launcher class, and after we initialize
the config variable, we can type Config, DU and
Mersv mode equals true. Now if we run it, the
navigation buttons or bar will be hidden. Okay. Now if we go
into the settings, toggling on and off the
full screen setting here doesn't actually
do anything. In Android, the game will
remain in full screen. Therefore, we can get rid of
this full screen box here altogether and replace it with a wider version
of the blood box. If we go into the
textures assets folder and open
menu items dot PNG, I actually created a wider
Show blood texture here, specifically for mobile
versions of the game. All right, so let's go to
the setting screen class. First, in the create
Images method, When we create the blood
setting background image, we want to first check
whether the game is being run on an Android
device, and if so, we'll use the longer
version of the texture, which has the region
name of Blood setting background
Long and the atlas. If the game isn't being
run on an Android device, we'll just use the normal
version of the texture. Okay, so to check if the game is being run on an
Android device, we can type a GDX
dot app dot Get Type is equal to application type. Press Ender to import it. Then if we put a dot, we get a list of all the available
application types, including Android,
desktop, IOS, and WebGL. We just want to check
if it's Android, so let's choose it here. And if we are using Android, let's blood setting background
image to new image, menu items Atlas, DfineRgion Blood setting
background long. Now we want to put an outs here. Then select this whole line
for initializing the image. Cut it with Control X and
paste it into the outs part. Alright, now let's go down
to the crate tables method. Down here where we add the four screen table to
the bottom table, we only want to do so if the application
type isn't Android. So first, I'll add
to the comment here, if not using Android, add the four screen table
to the bottom table. Then we can cut
this line out with Control X and type Fgdx
dot app dot Get Type. It is not equal to application dot application
type dot Android. Then paste the line back in. Okay, now, if we run the game on Android and go to
the setting screen, We just get the long
show blood image at the bottom with no
four screen option. Alright, and if we
run the desktop launcher and go to the settings, It's the same as it was before. Okay, in the next
video, we'll start putting the fighter control
buttons on the screen. See you there.
50. Fighter Controls 1: Set Up & Draw the Sprites: Start adding the
mobile UI to the game, we first need to go to
the assets class and load the mobile UI atlas
into the asset manager. If we go into the
textures Assets folder, we have a mobile it Atlas
file and a mobileitPnG file. If we open the PNG file, we have textures for each of the possible directions
for the joystick, which the player will use
to move their fighter, and we have textures
for a block button, a kick button, and
a punch button. Okay, so back in
the assets class, we first need to
add a variable that points to the location
of the mobile UI atlas. This is a gameplay asset, so we can add the variable to the bottom of the
gameplay assets section here by typing public
static final string, Mobile UI Atlas equals textures forward slash
mobile dot AtlaS. Next, we can go down to the
Load Game Play assets method. And at the bottom, we can load the mobile UI atlas by
typing manager dot Load, mobile UI Atlas, coma
texture Atlas dot class. Okay, now we need to go
to the game screen class and add some variables for
the mobile UI at the top. So after the blood section here, I'll add a new section
for mobile UI. Okay, so first, we'll
set a margin for how much space to put between the UI buttons and the
sides of the screen. For this, let's type
private static final float, mobile UI margin. Let's set it to 1.5 F
or 1.5 world units. Next, we'll need a
texture atlas object for getting the mobile UI atlas
from the asset manager. For this, let's type
private texture atlas, Mobile UI atlas. Next, we'll create some Sprite objects for each of the
mobile UI elements. Let's start with the
punch button by typing private Sprite,
Punch button Sprite. Then let's do private Sprite, kick Button Sprite, then private Sprite,
Block button Sprite. And finally, for the joystick, private sprite, joystick sprite. Okay. Now for the joystick, we actually need a
few more variables. First, for convenience,
we'll have a vector two object pointing to the
center of the joystick. For this, let's type
private final vector two, Joystick center equals
new vector two. We also need a vector two for indicating the current
direction of the joystick. Let's type private
final vector two, Joystick direction
equals new vector two. This will be similar to the
movement direction variable we created in the fighter class. Zero will indicate no movement. Negative one will indicate
left for X and down for Y, and one will indicate
right for X and up for Y. Okay, finally, we
need a variable to define the joysticks
drag threshold. This is basically the
sensitivity of the joystick. The lower the value, the
less the player has to drag their finger on the joystick in order to change
the direction. And the higher the value, the further they have to drag it. For this, let's do private
static final float, Joystick drag threshold. Equals, and I found that a
good value for this is one F, we can always try
other values later. Okay? And because we want to let the player use two fingers
on the screen at a time, one for moving their
fighter with the joystick, and one for making their
fighter attack or block, we need to keep
track of the fingers or pointers on the screen. And we also want to
know which action each pointer is performing. That sounds complicated, but
it's not too bad because LibGDX actually takes care of most of the pointer
tracking for us. Basically, we just need to
keep track of the actions, and the three possible
actions a pointer can have in our game are move,
attack, or block. So let's create an Enum for
these by typing private Enum, pointer action.
Move attack, block. And now we just need
to create an array of pointer actions to hold the action of each
pointer on the screen. In some games, the player is able to use many
pointers at once. But in our game, we only
care about two pointers, one for using the
joystick and one for clicking one of the
attack or block buttons. This is because the
player can only perform a single attack or block
action at one time. So for the array, let's type private final pointer
action brackets, pointer actions equals
new pointer action. Open bracket and give it a size of two for two
possible pointers. Okay, that's it
for the variables. Now we need to create
the mobile UI sprites. We'll do this in a method
called create mobile UI, which we'll call from
the constructor. So first, let's go
to the constructor. And after calling the create
Blood method down here, I'll comment, create
the mobile UI. Then let's call
create mobile UI. Then let's go down here below
the create blood method. And create the new
method by typing private void, create mobile UI. The first thing we need
to do in here is get the mobile UI Atlas
from the asset manager. So I'll comment, get
the mobile UI texture Atlas from the asset manager. And let's type mobile
UI Atlas equals game dot assets dot manager dot Git assets dot Mobile UI Atlas. Okay, next, let's create
the punch button sprite. I'll comment, create
the punch button. Then I'll start by
typing punch button, Sprite equals new Sprite. Mobile UI atlas
that Fine region. And the region name for the
punch button is Punch button. Now we need to scale the
size of the sprite by the world scale by typing
punch button sprite, that size, punch button sprite do get width times Glibbo
variables, that world scale. Come a punch button sprite. Dog height times quibble
variables that world scale. Next for the kick button, I'll copy and paste these lines. Change the comet to
create the kick button. Change the texture
region to kick button, and change all of this to
use the kick button sprite. Now I'll do the same
for the block button. For the joystick, I'll copy
and paste the lines again. Change this to
create the joystick. Change the region
name to joystick and change all of these
to joystick sprite. Some of the things we
want to do in here for the joystick is go ahead and set the joystick
sprites position, since it will always be located at the bottom left
of the screen with its X and Y coordinates both set to the mobile
UI margin variable. We also want to set the
Joystick center vector two object to the location of the Sprite center when
displayed on the screen. And we want to set the
Joystick direction vector two object to 00, which will mean
that the joystick is not being dragged
by the player. Okay, so to set the
position of the sprite, let's type Joystick
Sprite, dot set position. Mobile UI margin come
a mobile UI margin. Next for the center, let's
type Joystick center dot set, Joystick Sprite, dot get X, plus Joystickprt,
get W divided by F, joystick Sprite, I'll get
Y, plus Joystick sprite. I'll get height,
divided by F. Finally, for the direction, let's
do Joystick direction. Set, zero comma zero. Okay, now let's get these
sprites rendered to the screen. First, let's go to
the render method. After we call the Render
Pause button method here, we'll call it render
mobile UI method. We'll make it so the
method only draws the mobile UI sprites
if we're using Android. So I'll comment here, draw the mobile UI if using Android. Then let's call
Render mobile UI. Now let's go down here below the render Pause button method. Let's create the new
method by typing private void render mobile UI. First, if the application
type is an Android, we don't want to draw anything. So I'll comment, if not using Android, don't
draw anything. Then this type of gdx
dot app dot GEDIpe is not equal to
application type, preenter then Android, return. Okay? The first thing we'll
draw is the Joystick Sprite. So I'll comment
draw the joystick. And because we already
set its position, we can just call Joystick Sprite dot draw passingam dot batch. Now if we run the
Android version of the game and start the game, M we get the joy stick
at the bottom left. However, we have a
problem. We want to put the attack and block
buttons here on the right where the pause
button currently is, and we want to move the pause button to the bottom center. So let's go up to the render Pause button method
really quick. And here, for using Android, we want to set the position of the sprite to the bottom
center of the viewport. So at the top of the method, let's type Igdx dot
app dot GETPEs equal to application dot
application type dot Android. Pause button Sprite,
dot supposition, viewport dot G Rolled Width, divided by F minus Pause
button Sprite dot get Width, divided by F. And
for the Y position, we'll just use Pause
button margin. Now we can put notes here. Then cut this supposition line here and paste it
into the outs part. Now if we start the Android
version of the game, We get the pause
button in the center. And just to make sure we can
start the desktop version. And the pause button is still in the original location
and no joystick. Cool. Okay, back in the render mobile UI method,
we'll draw the buttons. First I'll comment,
draw the buttons. Let's first set a variable for the spacing between the buttons. For this, let's type float
button spacing equals 0.6 F, which we can change
later if necessary. And we're going to draw
the buttons in kind of a triangular shape with the
punch button at the left, the kick button
at the right, and the block button
at the top center. We'll start with
drawing the kick button at the right because we'll use its position to help us set the positions of
the other buttons. To get the position
of the kick button, we can take the width of
the viewport and subtract both the mobile UI margin and the width of
the kick button. And the Y position will simply
be the mobile UI margin. Okay, so let's set this
position by typing kick button Sprite,
that set position. Viewport, get rolled width, minus mobile UI immersion, minus kickbton
Sprite, do get width, coma mobile UI immersion. Then we can draw the button
by typing Kick button Sprite, dot draw game dot batch. Next, to get the exposition
of the punch button, we'll take the exposition
of the kick button, subtract the button spacing, and subtract the width
of the punch button. And as Y position will also
be the mobile UI immersion. So let's type punch button
Sprite dots position, Kickbton Sprite, dot get
X minus button spacing, minus punch button
Sprite dot get width. Come on mobile UI immersion.
Let's draw it by calling. Punch button spray do
draw game dot batch. Finally, for the block
button to get the position, we'll take the kick
buttons exposition, subtract half of
the button spacing, then subtract half of
the block buttons width. And for its Y position, we'll take the height
of the block button, add the mobile UI margin to it, then subtract the
button spacing. That sounds confusing,
but it works out well. So let's type block button,
sprite, do supposition, kick button, sprite, do get
X, minus button spacing, divided by F minus
block button sprite, do get width, divided by F, come a block button
sprite I'll get height, plus mobile UI Immersion,
minus button spacing. Then draw by calling
Block Button Sprite, D draw game do Batch. Right now, if we start with the Android version of the game. We get the joystick on the left and the
buttons on the right. Awesome. In the next video, we'll start to get
it all working. See you.
51. Fighter Controls 2: Attacking & Blocking: Our mobile UI working, let's first set down
to the touchdown method of the game screen class. One of the parameters
we have in this method is pointer here,
which is an integer. This indicates which of
the user's fingers or pointers touched the screen when the touchdown
method was called. LibGDX keeps track of how many pointers
are on the screen at one time and also keeps track of the location
of each pointer. And when a pointer first
touches the screen, it gets an index, which is given in the
pointer parameter. If the user has no
pointers on the screen, then they touch the
screen with a pointer. That pointer gets
an index of zero, and as long as that pointer
remains on the screen, it will continue to
have an index of zero. If they keep pointer zero on the screen and touch the
screen with another pointer, that pointer gets
an index of one, and the next one gets
a two and so on. However, if they release one of their pointers,
say pointer zero, then touch the screen with either the same pointer
again or a new pointer, LibGDx will give it the
smallest available index. So because they released
pointer zero earlier, the new pointer will
get an index of zero, even if pointers one, two, et cetera, are still
on the screen. Because of this and
because we only need to handle two pointers
in the game at once, the only pointer indexes
that matter to us, our index is zero and one. Before we check if one of the UI sprites has been pressed, we first need to make sure
the pointer parameter is either zero or one, or in other words, less
than the length of our pointer actions array,
which we set to two. Also, we only need to check
if the UI sprites are being touched when the game
is in the running state. So we'll add some more Asif blocks to the If statement here. Let's start with the
attack and block buttons, as they will be easier
than the joystick. First, for the punch
button sprite, after checking if the pause
button has been pressed, let's add an Osif here. We'll start by making
sure pointer is less than the length of the
pointer actions array by typing pointer, less than pointer
actions dot length. Now we also want to check if
the converted click position here is inside the punch button Sprites
bounding rectangle. Let's type and punch button Sprite Git bounding rectangle, that contains position dot
X come position dot Y. Now here, we want to make
the players fight or punch, and we want to set the
pointer's action to attack and the pointer
actions array. So first of all,
comment this out. If the pointer is included in
the pointer actions array, and the punch button
has been touched, make players fight or punch and set the pointer's
action to attack. Now we can call game
dot player dot punch. Then do pointer actions. Brackets pointer equals
pointer action dot attack. Next for the kick button,
let's add an SIF below this. Then type pointer, less
than pointer actions, dot length, and Kickbton
Sprite Get bounding rectangle. That contains position dot X, come on position dot Y. Okay, I'm going to
just copy and paste all the lines from inside
the punch button part. Change this to and the kick
button has been touched. Make player Spider kick. Now change this to game
dot player dot kick. Okay, for the block button,
let's add another SIF. Then type pointer, less than
pointer actions, dot length, and Block button Sprite
get bounding rectangle. Do contains position dot X. Come up position dot Y.
I'll paste the lines again. Change this to block button and make players fight or block. And for blocking, we want to set the pointers
action to block. Now, here we'll call game
dot player do block. And here we'll do
pointer actions pointer equals pointer action do block. Okay, if we start with the Android version
of the game now, We can press the punch
button to punch, the kick button to kick, and the block button to block. Whoever once we block, it
doesn't stop blocking. To fix this, let's go down
to the touch up method. Like in the touchdown method, we have a pointer parameter. In touch up, pointer refers to the index of the pointer that has been released
from the screen. So in order to make the
player stop blocking, we want to check if
pointer is inside the pointer actions array and that its action
is set to block. This means that the
pointer that pressed the block button
has been released, so we need to stop blocking. Now you might be wondering
why we don't just check if the pointer was inside the block button sprite
when released. The reason is that if the player presses
the block button, drags their finger
outside of the button, then releases the finger, the release position won't be located inside the block
buttons bounding rectangle. Okay, so first, we're
just going to check if the pointer is inside
the pointer actions array. So I'll comment.
Check if the pointer is included in the
pointer actions array. Then let's type if Pointer, less than pointer
actions dot length. Next, we want to check if the
pointer's action is block, and if so, we'll call the player fighter
stop blocking method. So let's type if
Pointer actions, brackets pointer is equal to
pointer action dot block. I'll comment. If
the pointer cause player's pinter to
block, stop blocking. Then it's call game dot
player do stop blocking. Next, outside of this
inner if statement, we want to set the
pointer's action to null to indicate that it
no longer has an action. So I'll comment the pointer
no longer has an action. Then it's type pointer actions, brackets pointer equals null. We're actually going
to add an Asif part in here for the joystick later, which is why we didn't just make this one big if statement. Can let's not forget
to return true down here to let it know we
handled the event ourselves. Now if we start the game, we can press the blocked
button to block, then release it
to stop blocking. And if we press the
button, then drag the cursor up here and
release, it still works. Okay? In the next video, we'll get the joystick working. See.
52. Fighter Controls 3: Joystick Movement: The joystick, let's first go back up here to the
touchdown method. And after checking the
block button here, let's set an Sif. Then type pointer, less than
pointer actions dot length, and joystick sprite, Dot
Get bounding rectangle. That contains position dot X, come on position dot Y. And here we simply need to set the pointer's
action to move. So I'll comment if the
pointer is included in the pointer actions array and the joystick
has been touched, set the pointers action to move. I then let's type
pointer actions, brackets pointer equals
pointer action move. The main code of
the joystick will be going down here in
the touch drag method. This method gets called whenever the user drags a pointer
across the screen, and like with touchdown
and touch up, lets us know the
index of the pointer. Screen ex and screen Y here refer to the current
position of the pointer. And the first thing we need
to do is convert these into world coordinates like we did in the touchdown method. So first, let's copy
these lines from the touchdown method and paste them at the top of
the touch drag method. Let's go ahead and change
this line to return true. Okay, before returning true,
we'll do a few checks. First, we want to make sure that the round is in
progress because we don't want the
player to be able to move during the start
and end round delays. And we also want to check
that the pointer is inside the pointer actions of array and that its action
is set to move. So first, I'll comment
all of this out. Check that the round
is in progress. The pointer is included in
the pointer actions array, and the pointer's
action is move. Then let's do all of this in
a NIF statement by typing I round state is equal to
roundstate.in progress. And pointer, less than
pointer actions dot length, and pointer actions brackets pointer equals pointer
action dot MOV. If all of this is true,
we want to check if the Joysticks drag threshold has been passed and that its
direction has changed. I'll comment, check if the Joysticks drag
threshold has been passed and its
direction has changed. Let's first create a Boolean
variable to indicate whether the Joysticks
direction has changed by typing Boolean,
direction changed. Let's set it to
false by default. So first, we'll compare the pointer's position to
the center of the joystick, and if the pointer
has moved away from the joystick center by at least the Joysticks
drag threshold amount, this means that the Joysticks drag threshold has been passed. Okay, let's start by comparing the X coordinates by
typing if position dot X greater than or equal to
Joystick center dot X plus Joystick drag threshold. If this is the case, the platter is moving the joystick
to the right, so we want to set the
joysticks X direction to one. However, we only
want to do so if the joysticks X direction
isn't already set to one. This is because when the
joystick changes direction, we need to change its texture to show the correct direction. And if we keep
setting the texture when it isn't
necessary to do so, it will just be a
waste of CPU power. Okay, so let's check this
by typing if Joystick direction X not equal to one. That will change the joysticks X direction to one or right. So I'll comment, change the joysticks X
direction to right. Then let's type
joystick direction, dot X equals one. And we also want to set
direction change to true. This will let us know
that we need to move the player's fighter in
a different direction, as well as change the
joystick sprites texture. Okay, now we need to
do all of this for the opposite direction or left. So let's type OsifPosition, dot x, less than or equal to Joystick center dot x minus
joystick drag threshold. Then if Joystick direction dot X is not equal to negative one, and I'll comment, change Joy
six x direction to left. Then let's type
joystick direction dot X equals negative one, and direction
changed equals true. Okay, when you
actually also need another Osifblock here for when the pointers X coordinate hasn't passed the Joysticks
drag threshold. This is because the
player might have dragged the joystick to the left or right, then back to the center. In which case, we need to change the joysticks direction to zero. So to do this, let's type Osif Joystick direction dot
X is not equal to zero. And I'll com it. Change the joysticks X
direction to center. Then let's type joystick
direction dot X equals zero, and direction
changed equals true. All right, we next need to
compare the Y coordinates. Because we want the
player to be able to move both horizontally
and vertically, we'll do this in a
separate I statement. So below this IF statement here, let's type if position dot Y, greater than or
equal to joystick center dot Y plus
joystick drag threshold. If joystick direction
that Y, not equal to one. And I'll comment, change the
joysticks Y direction to up. Then let's type
joystick direction, that Y equals one, and direction
changed equals true. Next, let's do O
sieve position dot Y. Less center or equal
to joystick center, do Y minus joystick
drag threshold. If joystick direction, do Y, not equal to negative one, and I'll comment, change the joysticks Y
direction to down. Then it's type
joystick direction do Y equals negative one. And direction
changed equals true. Finally, let's do out Sif. Joystick direction, that
Y, not equal to zero. And I'll come it.
Change joysticks Y direction to centered. Then let's do
joystick direction, that Y equals zero, and direction
changed equals true. Okay, after doing all
of these comparisons, we'll check if the Joysticks
direction has changed, and if so, we'll move
the player's fighter in the new direction and change the joystick sprites texture
to the correct texture. So first, below these
two nerf statements, let's type if direction changed. And I'll comment, if the
Joysticks direction has changed, move players fighter
in the new direction. Let's start with the
horizontal movement by typing I Joystick direction dot X is equal to one, game dot player dot Move Right. Elsiv Joystick direction dot
X is equal to negative one. Game dot player
dot move left Els. This means that the
X direction is zero, so we want to make the
player stop moving horizontally by calling
game dot player, do stop moving right, and game dot player do
stop moving left. Okay, under this IF statement, let's start a new one for
the Y direction by typing. If Joystick direction
dot Y is equal to one, game dot player dot move up. El SIF Joystick direction dot
Y is equal to negative one. Game dot player dot move down. Els game dot player
dot stop moving up. Game dot player dot
Stop Moving Down. Now below these
two F statements, we'll change the
joysticks texture. So I'll first comment here, if the Joysticks
direction has changed, set the joysticks texture to the texture for
the new direction. Now if we check out the
mobile ui Atlas vile, the way I name the texture
regions for the joystick is I put the word joystick
followed by the X direction, so left or right, followed by the Y direction, up or down. And the default centered region
is just called joystick. And we also just have
joystick down, joystick left, et cetera, for moving either only horizontally
or only vertically. Okay? So to get the correct region
named for the joystick, we can first create a
string variable here called Joystick region and by default, set it to joystick with a J. Next, we can check if the
Joysticks X direction is either a one or
a negative one, and if so, we can add
either the word right or the word left respectively to
the joystick region string. So to do this, this type, if Joystick direction
dot X, is equal to one. Joystick region,
plus equals, right. Else CIF, joystick direction
X is equal to negative one, Joystick Region,
plus equals left. If the Joysticks X
direction is zero, we don't want to add
anything to the region name. Now we can do the same
with the Y direction in a new F statement by typing, I Joystick direction
dot Y is equal to one, Joystick region,
plus equals quote up El CIF, joystick direction
Y is equal to negative one, Joystick region, plus
equals quote down. Finally, below these
two I statements, we can set the Joystick Sprites
new texture region using the Joystick region string
by typing Joystick Sprite, dot set region, mobile UI
atlas dot Fine Region. Joystick Region. And
before we try this out, we need to do a
couple of things with the joystick and the
touch up method. So after we check
if the pointer's action is set to block here, let's set an Sif and check if the pointers action is move
by typing pointer actions, Becket's pointer is equal
to pointer action dot move. And if so, the first thing
we want to do is make the player's fighter stop moving completely. So I'll comment. If the pointer cause players fighter to move, stop moving. Then it's called game dot
player dot Stop moving right. Game dot player do
stop moving left. Game dot player dot
stop moving up. And game dot player
stop moving down. Next, we want to set the
Joysticks direction to 00 and set the Sprites texture
region to the default. First I'll comment all this out. Reset the Joysticks direction and it's texture to the default. Finish type Joystick direction, dot set 00 and Joystick
Sprite dot set region. Mobile UI atlas Dt Fine Region. The default region name
is Joystick with a J. C now if we start up the game, We can use the joystick
to move around, and the texture of the joystick shows us which
direction we're moving. And if we release the mouse, we stop moving, and the joystick texture goes back
to the default. Now, unfortunately, unless we're using a computer
with a touchscreen, we can't check in the
emulator that we can use two different pointers for
the joystick and the buttons. If we run the game on a
physical Android device, however, we can see that
it all works correctly. We do have a slight
problem, though. If we drag the joystick so
that its texture changes, and while still keeping
our finger on the screen, if we press the
pause button with a different finger and
go to the main menu, then release the
joystick finger. If we now start the game, the joystickprt still uses
the previous texture. To fix this, we need to
go into the show method. And here, before calling
the Start game method, we need to check if the
joystick isn't centered, and if so, we'll reset its
direction and its texture. So first, I'll comment, if
the joystick isn't centered, reset its direction and texture. Finnis type, I Joystick
direction dot X is not equal to zero or joystick direction
dot Y is not equal to zero. Joystick direction, do
set, zero comma zero, Joystick Sprite D set region, mobile UI atlas
DFineRgion Joystick. And one more thing we need
to do in this show method is set all the actions in the
pointer actions array to null. So after this I
statement, I'll comment, set no action for
all the pointers in the pointer actions array. And to do this
easily, we can call the fill static method of the arrays class
by typing arrays, pressing Enter to import
it, then dot fill. We need to pass in the array, so pointer actions, followed by what value we want to
fill the array with, so no. Okay now if we run the game
on a physical Android device and go back to the main menu while holding the
joystick off center, when we start the game again, the joystick starts
back at the center. Perfect. All right, so
that's how we can add Android functionality
to a game in LibGDX. Thank you very
much for watching.