The Return of Talking Time

Go Back   The Return of Talking Time > Talking about other things > Talking about creating

Reply
 
Thread Tools Display Modes
  #1  
Old 02-18-2016, 10:01 PM
Balrog's Avatar
Balrog Balrog is online now
Hungry in Dungeon
 
Join Date: Jun 2007
Location: Oklahoma
Posts: 13,224
Default Intro to Game Programming (with C# and XNA)

I've been wanting to make a game making tutorial because most of the ones that were around when I first started weren't great so...

Originally I had written this long post going into setting up your development environment and all that but I decided it might be better to talk about a game we're all probably familiar with and then we'll go about cloning it (sorta). Game Maker and Unity are supposed to be really good for beginners to use but I don't have much experience in them. I do have a little experience in C# (the programming language) and XNA (the game framework) so I'm going to use those. The downside to this is that XNA isn't an engine (like Unity) so we'll have to roll our own, the upside is that we aren't go to do something incredibly complex. Another downside to XNA is that it's kinda dead (as in Microsoft no longer supports it) but it's still usable thanks to FNA and Monogame (don't worry about those for now). So let's talk about Space Invaders:



For starters, let's just list everything we see:
Code:
The word "SCORE<1>" 
The word "HI-SCORE"
The word "SCORE<2>"
The player 1 score (4 digits long)
The high score (4 digits long)
The player 2 score (4 digits long)
55 aliens
4 shields
1 player
The number of lives left (3 currently)
2 ship icons
The word "CREDIT"
The number of credits (2 digits long)
BONUS:
The UFO that shows up periodically
Bullets the aliens shoot
Bullets the player shoots
So I'm probably forgetting something but this seems good enough to start talking about how these things are going to be represented within the program. Things are represented in programs with variables so let's talk about variables. In C# (the language we're going to use later on), variables have a data type and a name. For example, you could represent someone's age like this:



The data type indicates how much memory you'll use, I picked byte because it has a range of 0 to 255 (like rupees in the Legend of Zelda!) and you generally want to pick the smallest data type you can to conserve memory.

Fun fact: I only used int, float, string, and bool for everything I did in college and a good portion of my career and no one complained, lol.

So anyway, let's look at our list of stuff on the screen and pair it up with a data type (as best we can).
https://msdn.microsoft.com/en-us/lib...(v=vs.90).aspx

Code:
The word "SCORE<1>" - string
The word "HI-SCORE" - string
The word "SCORE<2>" - string
The player 1 (4 digits long) - uint or string
The high score (4 digits long) - uint or string
The player 2 (4 digits long) - uint or string
55 aliens - nothing fits :(
4 shields - nothing fits :(
1 player  - nothing fits :(
The number of lives left (3 currently) - byte or string
2 ship icons - nothing fits :(
The word "CREDIT" - string
The number of credits (2 digits long) - byte or string 
BONUS:
The UFO that shows up periodically - nothing fits :(
Bullets the aliens shoot - nothing fits :(
Bullets the player shoots - nothing fits :(
So anything that's a word is a string and anything that's a number is a byte or an uint. At this point you could think of any of the numeric values as either numeric data types (like int, uint, byte, etc.) or as a string. As far as I'm concerned they are strings because they're something rendered to the screen other than an image. They're a number under the sheets but on screen, they're treated as strings. The ones we don't have built in data types for are all the ones that are represented with a picture. XNA has a built in class (for now just think of it as a bigger data type) for 2 dimensional pictures called Texture2D so we'll use that. Our list looks like this now:

Code:
The word "SCORE<1>" - string
The word "HI-SCORE" - string
The word "SCORE<2>" - string
The player 1 (4 digits long) - string
The high score (4 digits long) - string 
The player 2 (4 digits long) - string
55 aliens - Texture2D
4 shields - Texture2D
1 player  - Texture2D
The number of lives left (3 currently) - string
2 ship icons - Texture2D
The word "CREDIT" - string
The number of credits (2 digits long) - string 
BONUS:
The UFO that shows up periodically - Texture2D
Bullets the aliens shoot - Texture2D
Bullets the player shoots - Texture2D
The next thing we're going to do is convert this list to C# for use in our game. The format for variables is data type name semicolon. So it'll look like this:

Code:
string score1Lbl;
string hiScoreLbl;
string score2Lbl;
string player1Score;
string highScore;
string player2Score;
Texture2D alien;
Texture2D shield;
Texture2D player;
string numLives;
Texture2D shipLifeIcon;
string creditLbl;
string numCredit;
Texture2D ufo;
Texture2D alienBullet;
Texture2D playerBullet;
So you may or may not have noticed but we'll need multiples of certain things (like the shields and aliens) but for now, to keep things simple, we're just going to do one of each. The next thing we're going to do is assign some of these variables. The way we assign variables in C# is like this:

// this is declaring the variable age and assigning it the value 25
byte age = 25;

// this is assigning to a variable that is known (and is in scope but we'll talk scope later)
age = 26;

so let's assign where we can:
Code:
string score1Lbl = "SCORE<1>";
string hiScoreLbl = "HI-SCORE";
string score2Lbl = "SCORE<2>";
string player1Score = "0000";
string highScore = "0000";
string player2Score = "0000";
Texture2D alien;
Texture2D shield;
Texture2D player;
string numLives = "3";
Texture2D shipLifeIcon;
string creditLbl = "CREDIT";
string numCredit = "00";
Texture2D ufo;
Texture2D alienBullet;
Texture2D playerBullet;
The numeric variables are pretty straightforward. The string variables require a double quote at the start and end (the char data type requires a single quote but we're not using it here). Since the Texture2Ds aren't simple data types like the others, they require a different process we'll go into later.

So if we pasted in the current code into our program and ran it, it would run but it wouldn't display anything yet because we haven't specified where we want everything displayed, in addition to the fact that we haven't told the program to display anything. The first step in doing this is coming up with variables for the location of everything we want to display. So for laughs, I'm going to draw a box around each individual thing we are going to display.



Now that I've done that I'm going to briefly go off on a tangent about XNA's 2D coordinate system. In XNA, the upper left corner of the screen is coordinate x:0, y:0. The bottom right corner of the screen is coordinate x:width of the screen, y: height of the screen. So if you're moving right, the x value is going up and left the x value is going down.

Similarly, if you are going up, the y is doing down and down the y value is going up. We could use individual int values for the x and y value like:

int scorePositionX = 50;
int scorePositionY = 95:

We /could/ do that but instead we are going to use a built in data type in XNA called a Vector2. A Vector2 holds an X and a Y value and some other junk we're not going to worry about right now. Initializing a Vector2 looks like this:



There are two new things here that we haven't seen before: the "new" keyword and the "constructor". The "new" keyword is used to create new objects. The constructor specifies what kind of object is being created and with which values. The Vector2 structure has several constructors we could use, they look like this:

// this is the default constructor, it will assign x and y to zero
Vector2()

// this is a constructor that will set x and y to the value passed in (I've never used this one and had to google it)
Vector2(float value)

// this is a constructor that will set x to the first value and y to the second value passed in
Vector2(float x, float y)

I'm going to use the last constructor to set all the locations to 0,0 for now.
Code:
string score1Lbl = "SCORE<1>";
string hiScoreLbl = "HI-SCORE";
string score2Lbl = "SCORE<2>";
string player1Score = "0000";
string highScore = "0000";
string player2Score = "0000";
Texture2D alien;
Texture2D shield;
Texture2D player;
string numLives = "3";
Texture2D shipLifeIcon;
string creditLbl = "CREDIT";
string numCredit = "00";
Texture2D ufo;
Texture2D alienBullet;
Texture2D playerBullet;

Vector2 score1Pos = new Vector2(0, 0);
Vector2 hiScorePos = new Vector2(0, 0);
Vector2 score2Pos = new Vector2(0, 0);
Vector2 player1ScorePos = new Vector2(0, 0);
Vector2 highScorePos = new Vector2(0, 0);
Vector2 player2ScorePos = new Vector2(0, 0);
Vector2 alienPos = new Vector2(0, 0);
Vector2 shieldPos = new Vector2(0, 0);
Vector2 playerPos = new Vector2(0, 0);
Vector2 numLivesPos = new Vector2(0, 0);
Vector2 shipLifeIconPos = new Vector2(0, 0);
Vector2 creditPos = new Vector2(0, 0);
Vector2 numCreditPos = new Vector2(0, 0);
Vector2 ufoPos = new Vector2(0, 0);
Vector2 alienBulletPos = new Vector2(0, 0);
Vector2 playerBulletPos = new Vector2(0, 0);
I've defaulted everything to x:0, y:0, I'll make some guesses later on about their actual position. So for each piece of text or number or image we plan on displaying, we have a corresponding Vector2 position. It seems pretty straightforward but since there's a position and a related thing, it would be nice to have something that could hold both because I'm likely to mismatch the image of the UFO with the position of the player or something. Fortunately we can make something that can hold both by making our own data type called a "class". I'm going to make the image one first and the text/number/whatever one after that. Here's the class I came up with that holds and image and a position, I'm calling it a Sprite.

Code:
public class Sprite
{
      Vector2 position;
      Texture2D image;

      // constructor
      public Sprite(Vector2 p, Texture2D i)
      {
           position = p;
           image = i;
      }
}
Here's one that holds a string and a position. I'm calling it the DisplayText class:
Code:
public class DisplayText
{
      Vector2 position;
      string text;

      // constructor
      public DisplayText(Vector2 p, string t)
      {
           position = p;
           text = t;
      }
}
For now I'm going to replace all the Texture2D and their associated positions with Sprites, the same goes for strings that have positions. I put in ???? for the Texture2D variable. We'll get into that in the next tutorial. Our list of variables now looks like this:

Code:
DisplayText score1Lbl = new DisplayText(new Vector2(0,0), "SCORE<1>");
DisplayText hiScoreLbl = new DisplayText(new Vector2(0,0), "HI-SCORE");
DisplayText score2Lbl = new DisplayText(new Vector2(0,0), "SCORE<2>");
DisplayText player1Score = new DisplayText(new Vector2(0,0), "0000");
DisplayText highScore = new DisplayText(new Vector2(0,0), "0000");
DisplayText player2Score = new DisplayText(new Vector2(0,0), "0000");
Sprite alien = new Sprite(new Vector2(0,0), ????);
Sprite shield =  new Sprite(new Vector2(0,0), ????);
Sprite player = new Sprite(new Vector2(0,0), ????);
DisplayText numLives = new DisplayText(new Vector2(0,0), "3");
Sprite shipLifeIcon = new Sprite(new Vector2(0,0), ????);
DisplayText creditLbl = new DisplayText(new Vector2(0,0), "CREDIT");
DisplayText numCredit = new DisplayText(new Vector2(0,0), "00");
Sprite ufo = new Sprite(new Vector2(0,0), ????);
Sprite alienBullet = new Sprite(new Vector2(0,0), ????);
Sprite playerBullet = new Sprite(new Vector2(0,0), ????);
End of part 1...

Part 2 coming ????

Last edited by Balrog; 02-19-2016 at 11:22 PM.
Reply With Quote
  #2  
Old 02-19-2016, 06:42 AM
ASandoval's Avatar
ASandoval ASandoval is offline
Baby, I WANT Klonoa.
 
Join Date: Jul 2008
Location: Blackwood, NJ
Posts: 4,791
Default

Thanks for this. I'll be following along when I get home from MAGfest.
Reply With Quote
  #3  
Old 02-19-2016, 06:51 AM
narcodis's Avatar
narcodis narcodis is online now
Senior Member
 
Join Date: Aug 2011
Location: Salt Lake City UT
Posts: 3,915
Default

This is a cool tutorial and a good idea.

Couple questions.. does XNA code port directly over to Monogame? I have an old XNA 4.0 book, and ran through a few chapters of, but stopped after I heard the library was no longer supported. C# is a really solid language though, so I'd be into figuring out how to work with the library. Maybe you'd want to cover how to set up an XNA/Monogame project or something...?
Reply With Quote
  #4  
Old 02-19-2016, 08:50 AM
Kirin's Avatar
Kirin Kirin is offline
Not a Beer
 
Join Date: Jun 2007
Location: NC
Posts: 20,324
Default

I'm totally down for this sort of topic in general, though since I have, uh, a couple decades of C++ development under my belt what you're covering here is stuff I'm familiar with. But it looks like a good intro for people who aren't! I assume if/when I get into actually mucking about with game design I'll be looking into Unity, but you can't go wrong with a grounding in the basics.

Quote:
Fun fact: I only used int, float, string, and bool for everything I did in college and a good portion of my career and no one complained, lol.
Any modern platform (even phones these days) have enough memory that nobody is going to care how efficient a type is that you're using for a one-off variable. Now, if your program is gonna have a few million instances of that variable (which can happen with arrays of map data or what have you), that's another matter. And I have programs on my phone that I really wish were optimized better 'cause I know they're taking up way more space than they really need to be. But for a small game like this, the only reason it would make any noticeable difference is if you're trying to port it onto a GameBoy cart for retro cred or something.
Reply With Quote
  #5  
Old 02-19-2016, 08:51 AM
Falselogic's Avatar
Falselogic Falselogic is online now
Threadcromantosaurus Rex
 
Join Date: Aug 2007
Location: California
Posts: 31,675
Default

woot!
Reply With Quote
  #6  
Old 02-19-2016, 08:54 AM
That Old Chestnut's Avatar
That Old Chestnut That Old Chestnut is offline
you work for ME now
 
Join Date: Nov 2007
Location: Alabama
Posts: 2,655
Default

Oh HELL yeah!
Reply With Quote
  #7  
Old 02-19-2016, 09:49 AM
Balrog's Avatar
Balrog Balrog is online now
Hungry in Dungeon
 
Join Date: Jun 2007
Location: Oklahoma
Posts: 13,224
Default

Quote:
Originally Posted by narcodis View Post
Couple questions.. does XNA code port directly over to Monogame? I have an old XNA 4.0 book, and ran through a few chapters of, but stopped after I heard the library was no longer supported. C# is a really solid language though, so I'd be into figuring out how to work with the library. Maybe you'd want to cover how to set up an XNA/Monogame project or something...?
I used Monogame to port 2 games to Playstation Mobile (which is dead now ) and it was relatively painless. A year ago there was a small difference in how XNA and Monogame handled 2D rendering with layers and I had to hack it to work but it was close. FNA on the other hand worked exactly like XNA right out of the box and works on Windows/Mac/Linux. I might do a XNA -> FNA thing at the end but we'll see.
Reply With Quote
  #8  
Old 02-19-2016, 12:07 PM
Kirin's Avatar
Kirin Kirin is offline
Not a Beer
 
Join Date: Jun 2007
Location: NC
Posts: 20,324
Default

Huh, checked out the FNA page and that "single build process of Windows, OSX, and Linux" thing along with all-free-OSS implementation sounds quite attractive. Of course the drawback since they focus on open platforms is it looks like there's no easy port to iOS and Android. Hmm.

Of course, depending on what I decide to do, I may want Unity's engines to start with anyway.
Reply With Quote
  #9  
Old 02-19-2016, 12:39 PM
Ted Ted is offline
Senior Member
 
Join Date: Aug 2010
Posts: 4,508
Default

This is cool! As someone who writes code all day to make businesses run, I'm excited to see code that draws pretty pictures and responds to user input in real-time.

But I'm confused by something: in the last code section player1Score is constructed as "new DisplayText()", but player2Score is constructed as "new UI()". Is there a difference?

Also, at risk of being nit-picky, having two vars named "hiScore" and "highScore" might cause some confusion down the line. How about something like "hiScoreLabel" and "hiScoreVal" instead?
Reply With Quote
  #10  
Old 02-19-2016, 01:09 PM
Kirin's Avatar
Kirin Kirin is offline
Not a Beer
 
Join Date: Jun 2007
Location: NC
Posts: 20,324
Default

I bet UI() a copy-paste error from a later revision, with a new structure Balrog will introduce for the bits of text that change in response to the game state as opposed to the static strings.
Reply With Quote
  #11  
Old 02-19-2016, 01:48 PM
MooMoo's Avatar
MooMoo MooMoo is offline
Butternut Squash
 
Join Date: Sep 2014
Location: YooKay
Posts: 1,337
Default

Holy piss this is amazing. Thank you so much, you've explained things so clearly and helpfully too. I'm a learner programmer so seeing how to program something I actually give a damn about (muh gaemz) is really helpful and intuitive for me.
Reply With Quote
  #12  
Old 02-19-2016, 07:30 PM
Torzelbaum's Avatar
Torzelbaum Torzelbaum is offline
????? LV 13 HP 292/
 
Join Date: Feb 2011
Location: Central Illinois
Posts: 11,433
Default

Quote:
Originally Posted by Kirin View Post
I'm totally down for this sort of topic in general, though since I have, uh, a couple decades of C++ development under my belt what you're covering here is stuff I'm familiar with. But it looks like a good intro for people who aren't! I assume if/when I get into actually mucking about with game design I'll be looking into Unity, but you can't go wrong with a grounding in the basics.
Someone in another thread mentioned LibGDX. I've bought some ebooks about it but unfortunately haven't been able to get too far into them.
Reply With Quote
  #13  
Old 02-19-2016, 07:40 PM
Balrog's Avatar
Balrog Balrog is online now
Hungry in Dungeon
 
Join Date: Jun 2007
Location: Oklahoma
Posts: 13,224
Default

Quote:
Originally Posted by Kirin View Post
Huh, checked out the FNA page and that "single build process of Windows, OSX, and Linux" thing along with all-free-OSS implementation sounds quite attractive. Of course the drawback since they focus on open platforms is it looks like there's no easy port to iOS and Android. Hmm.

Of course, depending on what I decide to do, I may want Unity's engines to start with anyway.
If you're doing iOS and Android, Unity is probably the better choice. I think Monogame supports those somehow but the kind of stuff I make currently doesn't translate to touchscreen very well so I've never messed with it

Quote:
Originally Posted by Ted View Post
This is cool! As someone who writes code all day to make businesses run, I'm excited to see code that draws pretty pictures and responds to user input in real-time.

But I'm confused by something: in the last code section player1Score is constructed as "new DisplayText()", but player2Score is constructed as "new UI()". Is there a difference?

Also, at risk of being nit-picky, having two vars named "hiScore" and "highScore" might cause some confusion down the line. How about something like "hiScoreLabel" and "hiScoreVal" instead?
That's a copy paste error and I'll change those variables names, that's sloppy, lol.
Reply With Quote
  #14  
Old 02-20-2016, 09:56 AM
Kirin's Avatar
Kirin Kirin is offline
Not a Beer
 
Join Date: Jun 2007
Location: NC
Posts: 20,324
Default

Honestly I'm not sure if the particular games I have in my head right now would be workable on mobile either. But I feel like if I'm learning new stuff anyway it would be most helpful to learn something that at least has a viable path to iOS/Android, since being able to do stuff there seems like such a generally useful skill in the current gaming aand general programming environment.
Reply With Quote
  #15  
Old 02-23-2016, 10:34 PM
Balrog's Avatar
Balrog Balrog is online now
Hungry in Dungeon
 
Join Date: Jun 2007
Location: Oklahoma
Posts: 13,224
Default

The next thing that we need to do is install XNA (https://www.microsoft.com/en-us/down....aspx?id=23714) DirectX 9c (https://www.microsoft.com/en-us/down....aspx?id=34429) and some flavor of Visual Studio Express, I'm still using 2010 (http://download.microsoft.com/downlo...10Express1.iso) but I've heard that the newest one works with XNA with some modifications (info here: http://stackoverflow.com/questions/2...o-2015-preview).

Once you've installed all of that and restarted you computer, you should be ready to go.
So to start, once you've installed Visual Studio, open the program, click "New Project...", select "XNA Game Studio 4.0" from the left hand options (it's under Visual C#), select "Windows Game (4.0)" from the middle options, and name the game TalkingInvaders in the name field at the bottom and click the OK button. It should look something like this:

Visual Studio has created the program and given us some files to start off with. You can run the program now to see what it does by pressing the "F5" key or by selecting Debug->Start Debugging from the top menu. It should look like this:

So it doesn't look much like a game, in fact it doesn't look like much at all but there's a lot of stuff going on underneath that we sorta need to understand to move forward so I'll attempt to explain that now. Open up the Game1.cs file on the right side and I'll go over the important stuff as best I can. Game1.cs has the class definition (think of it as a blueprint) for our game. Here's the highlights:

public Game1()
Game1 is the default name for the game, and when the Game1() method (or function if you want to call it that) is called inside the Program.cs file, it creates an instance of our game. If you look at the function, it says
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
...we're not going to worry about those now.

https://msdn.microsoft.com/en-us/lib...nitialize.aspx
protected override void Initialize()
This function will be called once after the Game1() constructor gets called. We can initialize variables (kinda like the graphics = new GraphicsDeviceManager(this); thing above). Generally this is used to initialize non-graphic variables.

https://msdn.microsoft.com/en-us/lib...adcontent.aspx
protected override void LoadContent()
You've seen loading screens before, right? That's what this function does. It loads graphics, music, sounds, etc. into memory. Since most of the stuff we'll create relies on graphics later on, I'll initialize some variables here. This gets called only once (usually).

https://msdn.microsoft.com/en-us/lib...adcontent.aspx
protected override void UnloadContent()
This does the opposite of LoadContent. That funcion loads things into memory and this one unloads it. This will get called once when the game ends.

https://msdn.microsoft.com/en-us/lib...me.update.aspx
protected override void Update(GameTime gameTime)
There's a boolean variable that's set behind the scenes called "IsFixedTimeStep", it's true by default and it causes the game to call the Update method every 16 milliseconds. This method will eventually handle our user input, moving things around, updating animations, shooting bullets, etc. If you manually set " IsFixedTimeStep" to false Update and Draw will get called sequentially as often as possible. I've never set it to false so I don't know much about it.

https://msdn.microsoft.com/en-us/lib...game.draw.aspx
protected override void Draw(GameTime gameTime)
This method renders things to the screen. It will get called as often as it can (when IsFixedTimeStep is set to true). If the update method is taking too long to run, things will look laggy and crappy.

So to recap, Game1(), Initialize(), LoadContent(), UnloadContent() get called only once currently where Update gets called every 16 milliseconds, and Draw gets called as quickly as it can. Also, if your Update takes too long to run, your Draw won't have time to run and the game will start stuttering and look crappy.


The next thing we're going to do is start hacking some basic details into place so we can start positioning things correctly. Some light googling tells me that the resolution for Space Invaders is 224x256. That's pretty small by modern standards but I'm going to go with it. We're going to set those values in the Game1() method inside of the Game1.cs file. Our Game1 method should look like this:
Code:
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferWidth = 224;
            graphics.PreferredBackBufferHeight = 256;

            Content.RootDirectory = "Content";
        }
If we run this (by pressing F5 again) it should look like this:

It's so tiny and precious. We'll scale the whole thing up later but for now let's leave it. Next let's change the background color. If you look in the Draw method you'll see this:
Code:
GraphicsDevice.Clear(Color.CornflowerBlue);
We're going to change that to black. There's a couple ways of doing this, we could change it to this:
Code:
GraphicsDevice.Clear(Color.Black);
or we could do this:
Code:
GraphicsDevice.Clear(new Color(0,0,0));
That "Color.Black" is a built in data type in XNA, it's an enumeration. We'll make an enumeration of our own later. Both of those pieces of code do the same thing but the first one is clearer so I'll stick with that. The second one is manually setting RGB (Red, Green, Blue) values, you can do either one.

Next, let's paste in the code we wrote in the last tutorial. Let's start with the two classes, Sprite and DisplayText. In the right menu, right click on TalkingInvaders (the one with the Windows symbol next to it) and then Add and then Class. It should default to Class in the middle, type in "Sprite.cs" in the bottom textbox and click the Add button. Replace the class Sprite and the two curly braces below with this:
Code:
public class Sprite
{
      Vector2 position;
      Texture2D image;

      // constructor
      public Sprite(Vector2 p, Texture2D i)
      {
           position = p;
           image = i;
      }
}
Now that we've done that we'll get some errors, you can either click on them in the window below or hover over the red squiggly lines in the file. At the moment the file doesn't know what a Vector2 is or a Texture2D. Those are built into XNA so the way we import them is to right click the red squiggly line over Vector2 and then Resolve then using Microsoft.Xna.Framework. Do the same for Texture2D but this time it's Microsoft.Xna.Framework.Graphics. You'll notice that it added this to the top of the file:
Code:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
All that does is give us access to all the classes, structures, etc. in those folders.
Repeat the same steps for the DisplayText class we wrote in the last tutorial. After that you should be able to hit F5 and run it again. If nothing pops up, you've got errors we'll have to figure out

The next thing we're going to do is paste in our variables from the first tutorial. Where you declare a variable is what determines how long it lives and who can access it. Consider the following:
Code:
public class Person
{
     public byte getAge()
     {
         int age = 0;
         age = age + 1;
         return age;
     }
}
The variable age is declared inside a function so it's only visible inside of that function. Also, no other functions know anything about age. That function will always return a 1 since it starts at zero and adds 1 to it. So if we do this:
Code:
public class Person
{
	int age = 0;

      public byte getAge()
     { 
         age = age + 1;
         return age;
     }
}
The age variable is now outside the function. Since it's inside the same class (Person) the getAge function can knows about it and can access it. The function will now return age + 1 every time you call that function. That's kinda dumb but it's just an example. So where's the right place for all the variables we made in the first tutorial? It might make sense to have some of them placed in the Draw method since they're being rendered and in a regular program I might do that but since the Draw method is going to be called frequently it's kinda goofy to keep declaring the variables over and over again.
Here's some more info about scope and lifetime: https://msdn.microsoft.com/en-us/library/awt60xs1.aspx

Also, a crappy diagram:

For now I'm just going to put them inside of the Game1.cs file, at the top right under "SpriteBatch spriteBatch;", paste in the code. It should look like this:

I might move a few of these around later but if we put them here, we can access them in the Update and Draw methods so it's just easier for now.
There's errors because I put in those ???? as placeholders. For now, let's comment those out so it will compile. To comment something out either put a "//" in front of it or wrap it like this /* this is a comment */. Your code should look like this:

We can hit F5 at this point to run it but it's not going to render anything because we haven't told it to render anything yet. I'm going to tackle rendering the text first, then I'll move to rendering images. To render text in XNA you need a string (which we have) and a SpriteFont. A SpriteFont is just a font that's imported into your project. Get this font here: http://www.fonts2u.com/space-invaders-regular.font and unzip it wherever, then right click on the space_invaders.ttf file and click Install. It sucks but Visual Studio only looks for new fonts when it starts so you'll have to shut down Visual Studio and restart it now. Open the TalkingInvaders project back up.

Now right click on TalkingInvadersContent (Content) on the right side, go to Add, then New Item, click on Sprite Font in the middle, and change the Name at the bottom to SpaceFont.spritefont and click the Add button in the bottom right. It should automatically add and open up the SpaceFont.spritefont file. This file is an xml format file that has some metadata about the font. Inside the <FontName> replace the "Segoe UI Mono" with "Space Invaders", change the size to 6, the Spacing to 3, and UseKerning to false.
FUN FACT: I just guessed on that font size, spacing, and kerning by eyeballing a screenshot. It's probably not too accurate.
The file should look like this:

At this point the font is in our project but it's not in the game yet because we haven't loaded it yet. To do this, we'll need to declare a SpriteFont variable and load the font. Let's just declare a new SpriteFont variable called "font" like this:
Code:
SpriteFont font;
Place that right underneath the SpriteBatch spriteBatch; at the top of our file. Next we'll load that variable in the LoadContent() method by typing out this:
Code:
font = Content.Load<SpriteFont>("SpaceFont");
Now that we've got it loaded we can use it. Inside the Draw method place the following:
Code:
spriteBatch.Begin();
spriteBatch.DrawString(font, "TEST", new Vector2(0,0), Color.White);
spriteBatch.End();
The spriteBatch Begin and End just tell it when to start and stop rendering. You can have multiple begin and ends, one after another, and XNA will sorta treat each one of those like a layer in Photoshop. For this game we'll just need one Begin and one End, I'm pretty sure.
You can press F5 to test this out. It should look like this:

Since our test string worked, I want to start working in the real strings we're going to use. Inside the DisplayText class there's two variables, position and text. By default, variables inside classes are private and we can't access them directly but we can access them through public methods (we could also make the variables public but that's frowned upon, there's also another way to access private variables but I'll go into that later). So we're going to write some methods (or functions) to access the text and position variables.
Code:
public class DisplayText
    {
          Vector2 position;
          string text;

          // constructor
          public DisplayText(Vector2 p, string t)
          {
               position = p;
               text = t;
          }

          public string getText()
          {
              return text;
          }

          public Vector2 getPosition()
          {
              return position;
          }
    }
I've added two new functions so we can access those two variables. Both of them are public (so they can be accessed), one returns a string and the other a Vector2. Now I'm going to go back and replace that "TEST" and new Vector2(0, 0) in the Draw method with function calls to the scoreLbl variable. The draw method should look like this now:
Code:
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(new Color(0,0,0));

            spriteBatch.Begin();
            spriteBatch.DrawString(font, score1Lbl.getText(), score1Lbl.getPosition(), Color.White);
            spriteBatch.End();

            // TODO: Add your drawing code here

            base.Draw(gameTime);
        }
The way that we call a method on a variable is by using the dot notation. It's basically variable name then a period then the method we want to call. Whenever you type out a variable name and then type out a period, Visual Studio will pop up a list of public methods you can call. I went ahead and did the same for all the other variables in there. I went ahead and added a DrawString call for each DisplayText variable we have. The Draw method looks like this now.
Code:
            spriteBatch.DrawString(font, score1Lbl.getText(), score1Lbl.getPosition(), Color.White);
            spriteBatch.DrawString(font, hiScoreLbl.getText(), hiScoreLbl.getPosition(), Color.White);
            spriteBatch.DrawString(font, score2Lbl.getText(), score2Lbl.getPosition(), Color.White);
            spriteBatch.DrawString(font, player1Score.getText(), player1Score.getPosition(), Color.White);
            spriteBatch.DrawString(font, highScore.getText(), highScore.getPosition(), Color.White);
            spriteBatch.DrawString(font, player2Score.getText(), player2Score.getPosition(), Color.White);
            spriteBatch.DrawString(font, numLives.getText(), numLives.getPosition(), Color.White);
            spriteBatch.DrawString(font, creditLbl.getText(), creditLbl.getPosition(), Color.White);
            spriteBatch.DrawString(font, numCredit.getText(), numCredit.getPosition(), Color.White);
If you hit F5 and run this, you'll get a garbled mess. That's because it's rendering all those strings in the same position (0,0) so I'm going to fix those now. I used a screenshot of Space Invaders to figure out all the positions, so these numbers probably aren't exact but they're close enough.
Code:
        DisplayText score1Lbl = new DisplayText(new Vector2(6,1), "SCORE<1>");
        DisplayText hiScoreLbl = new DisplayText(new Vector2(79,1), "HI-SCORE");
        DisplayText score2Lbl = new DisplayText(new Vector2(151,1), "SCORE<2>");
        DisplayText player1Score = new DisplayText(new Vector2(23,16), "0000");
        DisplayText highScore = new DisplayText(new Vector2(87,16), "0000");
        DisplayText player2Score = new DisplayText(new Vector2(160,16), "0000");
        //Sprite alien = new Sprite(new Vector2(0,0), ????);
        //Sprite shield =  new Sprite(new Vector2(0,0), ????);
        //Sprite player = new Sprite(new Vector2(0,0), ????);
        DisplayText numLives = new DisplayText(new Vector2(7,232), "3");
        //Sprite shipLifeIcon = new Sprite(new Vector2(0,0), ????);
        DisplayText creditLbl = new DisplayText(new Vector2(135,232), "CREDIT");
        DisplayText numCredit = new DisplayText(new Vector2(191,232), "00");
        //Sprite ufo = new Sprite(new Vector2(0,0), ????);
        //Sprite alienBullet = new Sprite(new Vector2(0,0), ????);
        //Sprite playerBullet = new Sprite(new Vector2(0,0), ????);
When I went looking for their position in the screenshot I picked the upper left corner of where the text started. If you hit F5 it should look like this now:

End of Round 2!
Reply With Quote
  #16  
Old 02-24-2016, 05:51 PM
ASandoval's Avatar
ASandoval ASandoval is offline
Baby, I WANT Klonoa.
 
Join Date: Jul 2008
Location: Blackwood, NJ
Posts: 4,791
Default



Yay I'm learning! Thanks again, Balrog
Reply With Quote
  #17  
Old 02-24-2016, 09:37 PM
Balrog's Avatar
Balrog Balrog is online now
Hungry in Dungeon
 
Join Date: Jun 2007
Location: Oklahoma
Posts: 13,224
Default

That's awesome! High fives!
Reply With Quote
  #18  
Old 02-26-2016, 09:13 AM
narcodis's Avatar
narcodis narcodis is online now
Senior Member
 
Join Date: Aug 2011
Location: Salt Lake City UT
Posts: 3,915
Default

Really awesome tutorial. Thanks for doing this, following along and enjoying myself so far.

One issue I ran into: when rendering my SpriteFont, it wound up looking incredibly blurry and compressed. I figured this was due to some graphics driver issue or something, since I was running Visual Studio in a bootcamp partition on my Mac. I migrated everything on to my desktop PC, which took a few hours since I had to install Visual Studio/XNA etc., and much to my dismay the issue was still happening.

I eventually resolved it. I think the issue might have something to do with my environment (XNA 4.0.4, DirectX 11.3, Visual Studio 2013 Pro). I read that somewhere down the line, XNA decided to render their SpriteFonts in a different way that involves compressing them before they are rendered. This leads to a blurry/sloppy/jaggy looking font, rather than the crisp, clean, pixelated look this tutorial showcases.

The solution was a framework called Nuclex -- specifically, the SpriteFont portion of the framework (installation instructions here). Once I added that reference to my Content package, and then updated the Content Processor in the properties of my spritefont file, the font rendered perfectly.

Keep this tutorial going, really liking it so far.
Reply With Quote
  #19  
Old 02-26-2016, 11:00 AM
Balrog's Avatar
Balrog Balrog is online now
Hungry in Dungeon
 
Join Date: Jun 2007
Location: Oklahoma
Posts: 13,224
Default

I've had the blurry text issue with Monogame before but not with XNA. I didn't know how portable Nuclex was so I used this font converter found here: http://blogs.msdn.com/b/shawnhar/arc...ts-in-xna.aspx to use bitmap fonts.
Reply With Quote
  #20  
Old 03-02-2016, 09:46 PM
Balrog's Avatar
Balrog Balrog is online now
Hungry in Dungeon
 
Join Date: Jun 2007
Location: Oklahoma
Posts: 13,224
Default

The next thing we're going to do it get images up on the screen. The process is going to be sorta similar to how we did the text. I'll start with the player's ship. Download the file here:
https://dl.dropboxusercontent.com/u/...orial/Ship.png
Inside the project, right click on TalkingInvadersContent (Content) on the right hand side, then Add, then Existing item and browse for the Ship.png you just downloaded. For laughs, click on the Ship.png that it added. It should look something like this:

I'll briefly go over this becuase it gave me heartburn because I glazed over it when I was first learning XNA. The Asset Name is the name you'll reference this content with, it strips out the .png so if you have two files like Ship.png and Ship.jpg, it'll complain. The Build Action is set to Compile which is weird. Our code gets compiled into an executable but it kinda seems weird to do that for graphics/music/etc. XNA will compile those things into another format that it can use. Content Importer is set to texture since this is a png but it will try to autodetect what you've got but it's not always right, the same goes for Content Processor. For example in my other games I have a font that comes from a .bmp file, XNA will try to use the Texture Content Processor but I have to change it to Sprite Font Texture. Copy to Output Directory says whether or not the file will be copied to the /bin folder with the executable. I change that to always copy for level files and stuff that I didn't bother to write a custom Content Processor for D:
The "Ship.png" is going to be a Texture2D like I talked about in the first tutorial. So let's declare a Texture2D variable called shipSprite in the Game1.cs file towards the top under "SpriteFont font;". It should look like this:
Code:
Texture2D shipSprite;
Since this is content and not just a regular variable, we'll load it in the LoadContent method. It will look sorta like the font we loaded before. It should look like this:
Code:
shipSprite = Content.Load<Texture2D>("Ship");
Like the font, we put the Asset Name in quotes at the end and the type inside the < > symbols. Before we tie this content to our player variable, let's test it out. Rendering the image is similar to how we did the text except that instead of calling the DrawString method, we're going to call the regular old spriteBatch.Draw method. We'll do this inside the Draw method. There are several different options given when you type in "spriteBatch.Draw(", I'm going to go with the second option which lets us pass in a Texture2D, a Vector2 (for position), and a Color. Also, I'm going to position it at 50, 50 just to test it.

The Color option at the end seems kinda unneccessary and weird but for for now just pretend it's the color of light shining on the sprite. If you change it to red or yellow, it'll take on a red or yellow tint. If you pick black, the sprite won't show up at all (on a black background).

FUN FACT: You can also play with the transparency of the sprite by making a custom color like Color(red, green, blue, alpha transparency).

Your draw method should look like this now:
Code:
protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(new Color(0,0,0));

            spriteBatch.Begin();

            // render UI
            spriteBatch.DrawString(font, score1Lbl.getText(), score1Lbl.getPosition(), Color.White);
            spriteBatch.DrawString(font, hiScoreLbl.getText(), hiScoreLbl.getPosition(), Color.White);
            spriteBatch.DrawString(font, score2Lbl.getText(), score2Lbl.getPosition(), Color.White);
            spriteBatch.DrawString(font, player1Score.getText(), player1Score.getPosition(), Color.White);
            spriteBatch.DrawString(font, highScore.getText(), highScore.getPosition(), Color.White);
            spriteBatch.DrawString(font, player2Score.getText(), player2Score.getPosition(), Color.White);
            spriteBatch.DrawString(font, numLives.getText(), numLives.getPosition(), Color.White);
            spriteBatch.DrawString(font, creditLbl.getText(), creditLbl.getPosition(), Color.White);
            spriteBatch.DrawString(font, numCredit.getText(), numCredit.getPosition(), Color.White);

            // render player
            spriteBatch.Draw(shipSprite, new Vector2(50, 50), Color.White);

            spriteBatch.End();

            base.Draw(gameTime);
        }
Hitting F5 should produce this:

Now that we've tested it out, let's expose the variables inside the Sprite class like we did with the DisplayText class. Place these two functions in the Sprite class. These two functions will return the variables that we passed in with the Sprite's constructor.
Code:
        public Texture2D getImage()
        {
            return image;
        }

        public Vector2 getPosition()
        {
            return position;
        }
The next thing we need to do is get our Sprite instances initialized in the right spot. Since we're passing in a Texture2D to create a Sprite, we'll need a texture that's already loaded so we'll need to do it in our LoadContent method in the Game1.cs file. Let's go ahead and copy the other Sprite instances in there now too. We'll copy the commented out ones from the top of the Game1.cs file and put them in the LoadContent method after the font and shipSprite Content.Load calls. I'm going to leave the declaration of those sprites at the top because they'll be needed in more than just the LoadContent method, they'll eventually be needed in the LoadContent, Update, and Draw methods. If we take out the initialization of those variables, we can safely comment those variables back in. With the Sprite instances just being declared, the top of the Game1.cs file should look like this:
Code:
        DisplayText score1Lbl = new DisplayText(new Vector2(6,1), "SCORE<1>");
        DisplayText hiScoreLbl = new DisplayText(new Vector2(79,1), "HI-SCORE");
        DisplayText score2Lbl = new DisplayText(new Vector2(151,1), "SCORE<2>");
        DisplayText player1Score = new DisplayText(new Vector2(23,16), "0000");
        DisplayText highScore = new DisplayText(new Vector2(87,16), "0000");
        DisplayText player2Score = new DisplayText(new Vector2(160,16), "0000");      
        DisplayText numLives = new DisplayText(new Vector2(7,232), "3");       
        DisplayText creditLbl = new DisplayText(new Vector2(135,232), "CREDIT");
        DisplayText numCredit = new DisplayText(new Vector2(191,232), "00");

        Sprite player;
        Sprite alien;
        Sprite shield;
        Sprite shipLifeIcon;
        Sprite ufo;
        Sprite alienBullet;
        Sprite playerBullet;
I moved the variables around a little because it was bugging me having them all together, lol.

Our LoadContent should look like this now (I set the position to 103, 208 by looking at a screenshot, it may not be accurate):
Code:
protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            font = Content.Load<SpriteFont>("SpaceFont");
            shipSprite = Content.Load<Texture2D>("Ship");

            player = new Sprite(new Vector2(103, 208), shipSprite);
            // alien = new Sprite(new Vector2(0,0), ????);
            // shield =  new Sprite(new Vector2(0,0), ????);
            // shipLifeIcon = new Sprite(new Vector2(0,0), ????);
            // ufo = new Sprite(new Vector2(0,0), ????);
            // alienBullet = new Sprite(new Vector2(0,0), ????);
            // playerBullet = new Sprite(new Vector2(0,0), ????);
        }
And change this:
Code:
spriteBatch.Draw(shipSprite, new Vector2(50, 50), Color.White);
To this:
Code:
spriteBatch.Draw(player.getImage(), player.getPosition(), Color.White);
Next, we're going to move the player around. XNA has built in support for 360 controllers and keyboard and mouse. I'm just going to do keyboard for now. If you look in the existing Update function we're given, they're already doing a little bit of controller handling and the keyboard handling will look like that. To get which keys are being pressed, we'll get the entire state of the keyboard, store it in a variable and do specific checks on the keys we care about (for this we'll do left arrow, right arrow, and space (to fire)). To get the state of the keyboard we'll add this to Update:
Code:
KeyboardState keyboardState = Keyboard.GetState();
Then we'll do checks (using if/else statements) to see which keys are down like this:
Code:
            if (keyboardState.IsKeyDown(Keys.Left)) 
            {

            }
            else if (keyboardState.IsKeyDown(Keys.Right))
            {

            }
            else
            {

            }

            if (keyboardState.IsKeyDown(Keys.Space))
            {

            }
If you're unfamiliar with if/else statements, it's basically if (test), then do something, else if (test) do something, else do something or nothing. This explains it better than I can: https://msdn.microsoft.com/en-us/library/5011f09h.aspx
In Space Invaders, the player can only move left or right and they're stopped on both sides (about the width of the ship from the side of the screen). First we'll code the moving around, then the stopping at both sides. Unfortunately, at this point, we can get the position of a sprite but we can't set it (outside of the constructor). We'll have to write a quick function to do that. Since the player can only move right to left, let's just write a function that does that.
Place this in your Sprite.cs file:
Code:
        public void moveXAxis(float distance)
        {
            position.X = position.X + distance;
        }
So whatever value for distance we pass in, it will assign the X value to the current X value plus that distance. (NOTE: I made distance an int here initially but 1 was too slow and 2 was too fast, so I made it a float like I probably should have to begin with). Also, you'll notice that the data type being returned here is "void", that just means nothing is returned. Next we'll call the same function to move our ship around. Make your Update code look like this:
Code:
            if (keyboardState.IsKeyDown(Keys.Left))
            {
                player.moveXAxis(-1.5f);
            }
            else if (keyboardState.IsKeyDown(Keys.Right))
            {
                player.moveXAxis(1.5f);
            }
Remember in XNA 0,0 is the upper left of the screen so going left on the X-axis means going negative and going right the opposite. Also, I had to put an f at the end of the number since it's a float, any decimal numbers are assumed to be doubles unless specified otherwise. If you hit F5 and run it you should be able to move right or left, even offscreen. We'll fix that with a new method. We could add a function to the Sprite class that looks like this:
Code:
public void limitLocationX(int minimum, int maximum)
{
            if (position.X < minimum)
            {
                position.X = minimum;
            }
            else if (position.X > maximum)
            {
                position.X = maximum;
            }

}
Instead we're going to use a built in function in the MathHelper class called Clamp. MathHelper has a bunch of, uh, helpful math functions. So add this code to our Sprite.cs file:
Code:
public void limitLocationX(int minimum, int maximum)
        {
           position.X = MathHelper.Clamp(position.X, minimum, maximum);
        }
We're going to limit the player about 1 ship width from the left side and 1 ship width from the right. The ship's width is 13 pixels so that'll give us our left bounds but the right bounds are a little trickier. The width of the screen is 224, so it should be 224 - 13, right? Well, it's not Remember that each sprite is positioned from the upper left corner, so it would be 224 (width of the screen) - 13 (our distance from the edge of the screen) - 13 (the actual width of our ship). So underneath our if/else statement in the Update method, add this:
Code:
player.limitLocationX(13, 224 - 13 - 13);
If you hit F5 now, you should be able to move right and left and stop 13 pixels from the edge of the screen.
The next thing we're going to tackle is shooting the bullet. It's pretty similar to the player. First, download this https://dl.dropboxusercontent.com/u/...ial/Bullet.png

Like before, right click TalkingInvadersContent (Content) on the right side of the project, selected Add, then Existing Item and browse to wherever you downloaded the bullet image. Next, declare a variable of type Texture2D called playerBulletSprite like this:
Code:
Texture2D playerBulletSprite;
Load that variable in the LoadContent() method like this:
Code:
playerBulletSprite = Content.Load<Texture2D>("Bullet");
We've already got a variable for the Sprite called playerBullet, so we'll use that. If we were cloning Gradius or some other game where you could have multiple player bullets on screen at once, we would make an array (or list) of bullets but since there's only one player bullet on screen at a time, we'll just use one variable. There's a couple ways we could go about doing the bullet. We could do something like this in the Update method:
Code:
            if (keyboardState.IsKeyDown(Keys.Space))
            {
                playerBullet = new Sprite(player.getPosition(), playerBulletSprite);
            }
The problem with doing it this way is that every time we call "new" we are allocating a chunk of memory, so if we shot 100 times, we would do 100 allocations. In C#, those allocations that are no longer used are cleaned up by something called Garbage Collection which is time consuming and will wreck the performance of our game periodically. So instead of calling "new" Sprite every time, we'll simply reuse an existing allocation. First, let's create a bullet offscreen in the LoadContent() method.
Code:
playerBullet = new Sprite(new Vector2(0, -1000), playerBulletSprite);
The 0, -1000 is just 1000 pixels offscreen from the top. What we'll do first is position the bullet right on top of the player when they press the space button. Outside of the constructor, we don't have a way to set the position of sprites so we'll write a new method in the Sprite class that does that.
Code:
public void setPosition(Vector2 p)
        {
            position = p;
        }
In the Update method, I'll fill in the check to see if the space button is pressed down with a call to set the bullet position like this:
Code:
if (keyboardState.IsKeyDown(Keys.Space))
            {
                if (playerBullet.getPosition().Y < -4) // completely off screen?
                {
                    // position the bullet on top of the player
                    playerBullet.setPosition(new Vector2(player.getPosition().X + 6, // + 6 is the middle of a 13 wide sprite
                                                         player.getPosition().Y - 4)); // - 4 is on top of the player sprite
                }
            }
I added another if statement in there to check that checks if the bullet is off screen. If we didn't check that we could reset the bullet back to the player every time. Also, I moved it over so that it shoots out of the middle of the player sprite. Next, we'll render the bullet like we did with the player like this:
Code:
spriteBatch.Draw(playerBullet.getImage(), playerBullet.getPosition(), Color.White);
If you press F5 and run it, you'll see a bullet come out when you press the spacebar. Unfortunately, it doesn't move so we'll need to add that. Let's start by adding a new method to change the position along the Y axis like we did for the X axis in the Sprite class. In the Update method we're going to add a new function to change the position on the Y axis like this:
Code:
public void moveYAxis(float distance)
        {
            position.Y = position.Y + distance;
        }
I just copied the moveXAxis method and reworked the name and variables. Now inside the Update method add this:
Code:
if (playerBullet.getPosition().Y >= -4) 
            {
                playerBullet.moveYAxis(-4);
            }
It doesn't really matter where you put that code in the Update method but I like to put control stuff first and other stuff last. What this will do is move the bullet up 4 pixels every 16 milliseconds. I'm just guessing on the 4 pixels, it might move faster than that. Something to keep in mind though is that if the bullet moves faster that the size of the smallest enemy, we might have to figure out a solution. For instance, if an enemy was 3 pixels tall, our bullet was 1 pixel tall and we were moving 5 pixels at a time, the bullet might jump over the enemy. But we don't have to worry about that
The new hotness should look like this:


In the next tutorial, I'm going to add the enemies and maybe the shields (I can't think of a easy way to do the shields yet).
Reply With Quote
  #21  
Old 03-03-2016, 12:44 PM
Kirin's Avatar
Kirin Kirin is offline
Not a Beer
 
Join Date: Jun 2007
Location: NC
Posts: 20,324
Default

Just a heads-up, your inline images in the latest post aren't showing up from here.
Reply With Quote
  #22  
Old 03-03-2016, 07:28 PM
Balrog's Avatar
Balrog Balrog is online now
Hungry in Dungeon
 
Join Date: Jun 2007
Location: Oklahoma
Posts: 13,224
Default

Looks like they're there now. Might've been a dropbox thing
Reply With Quote
  #23  
Old 03-03-2016, 08:46 PM
ASandoval's Avatar
ASandoval ASandoval is offline
Baby, I WANT Klonoa.
 
Join Date: Jul 2008
Location: Blackwood, NJ
Posts: 4,791
Default

Just posting to say I'm excited to get back to this, which probably won't be until Sunday, at which point I'll have forgotten everything I've learned and have to start over from the top.
Reply With Quote
  #24  
Old 03-09-2016, 01:45 PM
ASandoval's Avatar
ASandoval ASandoval is offline
Baby, I WANT Klonoa.
 
Join Date: Jul 2008
Location: Blackwood, NJ
Posts: 4,791
Default

Aaaand done. I had a brief issue at the very beginning with the draw function because it didn't like that I added the ship and was saying I needed to Begin the SpriteBatch before I could end it, even though I hadn't deleted or touched SpriteBatch.Begin(); I had to move the SpriteBatch.End(); line a couple of times before it eventually took it. Not sure why, I kept it at the end of the code the whole time. Weird! Anyway it works now and I didn't have any trouble after that. Thanks Balrog.
Reply With Quote
  #25  
Old 03-09-2016, 03:52 PM
Balrog's Avatar
Balrog Balrog is online now
Hungry in Dungeon
 
Join Date: Jun 2007
Location: Oklahoma
Posts: 13,224
Default

Yeah, all of the spriteBatch.Draw method calls have to have a spriteBatch.Begin(); before and a spriteBatch.End(); afterwards.
Reply With Quote
  #26  
Old 03-10-2016, 05:38 AM
ThornGhost's Avatar
ThornGhost ThornGhost is offline
That bug is NUTS!
 
Join Date: May 2008
Posts: 4,353
Default

This looks amazing and I'm definitely going to follow along with it soon. Thanks, Balrog!
Reply With Quote
  #27  
Old 03-10-2016, 10:34 AM
Mightyblue's Avatar
Mightyblue Mightyblue is offline
Are You Sure About That?
 
Join Date: Jun 2007
Location: Somewhere cold. And frosty.
Posts: 21,934
Default

Chiming in late to note that the shields are probably easiest handled as separate sprite chunks to make up each "damage" section of the shield (since you could shoot them from underneath as well). Positioning and collision will be a pain in the ass, but you should be able to reuse the player/enemy collision code to do it.
Reply With Quote
  #28  
Old 03-10-2016, 11:13 AM
Kirin's Avatar
Kirin Kirin is offline
Not a Beer
 
Join Date: Jun 2007
Location: NC
Posts: 20,324
Default

Alternately you can have the shields each be a customizable pixel array that keeps track of the "live" parts of the shield, and then a custom collision-detection routine that gets called whenever a shot intersects with the rectangle-box of the shield to see whether it actually hits the currently active shield and removes some active pixels as appropriate. Of course, you'd also need a custom drawing routine, or just write out a new sprite every time one's damaged. All that's quite a bit advanced beyond the current lesson. But it's probably what I'd do for realsies?
Reply With Quote
  #29  
Old 03-13-2016, 03:26 PM
Balrog's Avatar
Balrog Balrog is online now
Hungry in Dungeon
 
Join Date: Jun 2007
Location: Oklahoma
Posts: 13,224
Default

The next thing we're going to tackle is the aliens. First, download these:
https://dl.dropboxusercontent.com/u/...rial/Small.png
https://dl.dropboxusercontent.com/u/...ial/Medium.png
https://dl.dropboxusercontent.com/u/...rial/Large.png

Like before, right click on TalkingInvadersContent (Content) on the right hand side and then Add, then Existing Item, then browse to where you downloaded those. Next we'll make three new Texture2Ds to handle these. Put this towards the top of your Game1.cs file with the other Texture2Ds:
Code:
Texture2D smallSprite, mediumSprite, largeSprite;
You can declare multiple variables of the same type like this. Add this to your LoadContent() method like we did before:
Code:
            smallSprite = Content.Load<Texture2D>("Small");
            mediumSprite = Content.Load<Texture2D>("Medium");
            largeSprite = Content.Load<Texture2D>("Large");
Also, let's initialize that Sprite alien variable we made earlier like this (inside the LoadContent method after where we load the smallSprite variable):
Code:
alien = new Sprite(new Vector2(32,64), smallSprite);
And render it in the Draw method:
Code:
spriteBatch.Draw(alien.getImage(), alien.getPosition(), Color.White);
If you hit F5, it should look like this:

We can shoot at the alien but nothing happens We'll go over that in a bit. Next we'll tackle putting all 55 of the aliens on screen. We could make 55 different variables but it's cleaner and easier to just make an array or list of them. I think when I wrote a Space Invaders clone in college I put the aliens into a 2D array (think a grid) but I don't see the benefit of doing that at the moment so we'll just do a one dimensional array for the moment. For a one dimensional array you simply put a [] after the variable type and before the variable name, like this:
Code:
int[] ages;
So let's change that singular Sprite alien variable declaration into this:
Code:
Sprite[] aliens;
We broke our code from before but we'll clean it up in a minute. Next change the alien = new Sprite(new Vector2(32,64), smallSprite);
into this:
Code:
aliens = new Sprite[55];
Next comment out the code that renders the alien (remove the spriteBatch.Draw(alien....). We now have an array of aliens but nothing is in that list until we populate it with something. We could populate that array one at a time like this:
Code:
aliens[0] = new Sprite(new Vector2(32,64), smallSprite); // array indexes in C# start at 0!
aliens[1] = new Sprite(new Vector2(40,64), smallSprite);
aliens[2] = new Sprite(new Vector2(48,64), smallSprite);
// etc.
But we'd have to do that 55 times and it's just not worth it. Instead we'll use a for loop (or two) to populate the list. Here's the documentation on for loops in C#: https://msdn.microsoft.com/en-us/library/ch45axte.aspx For loops are good for doing something over and over again for a set amount of times. I'll start by populating the top row like this:
Code:
aliens = new Sprite[55]; // this has to be called first, otherwise the code below will crash :(
            for (int x = 0; x < 11; x++)
            {
                aliens[x] = new Sprite(new Vector2(32 + (x * 16), 64), smallSprite);
            }
So, I declared an int called "x", set it to zero, then it checks if x is less than 11 (the number of aliens per row), then it does the line below which creates an instance of the Sprite class and assigns it to the aliens array at position x (which is zero to start out), after that it does x++ which increases x by 1. Then it checks if it's still less than 11 and repeats until that x < 11 check fails then it finishes the for loop.
I did a little mathemagic to get the sprites positioned right, I start at x:32 then move over 16 pixels to the right to set the next position.
So we have a for loop that makes a row of 11 enemies and we need 5 of those, so we'll wrap the for loop in another for loop and have it go 5 times like this:
Code:
for (int y = 0; y < 5; y++)
{
            for (int x = 0; x < 11; x++)
            {
                aliens[x + (y * 11)] = new Sprite(new Vector2(32 + (x * 16), 64), smallSprite);
            }
}
Next, we'll up the count of aliens we're drawing in the Draw method. Before we were just going to 11, so change that count in the Draw method to 55.

Code:
for (int i = 0; i < 55; i++)
            {
                    spriteBatch.Draw(aliens[i].getImage(), aliens[i].getPosition(), Color.White);
            }
If you hit F5 at this point, you'll only get a single row. The reason is I forgot to stagger the new sprites vertically when we added the for loop so change our previous for loop to this:
Code:
for (int y = 0; y < 5; y++)
{
            for (int x = 0; x < 11; x++)
            {
                aliens[x + (y * 11)] = new Sprite(new Vector2(32 + (x * 16), 64 + (y * 16)), smallSprite); // note the 64 + ( y * 16)
            }
}
Like before, I'm just guessing that it's 16 pixels below the previous row. /shrugs
Unfortunately, the sprites are all the same. So I'm going to add some if/else statements in here to change that to the right ones.
Code:
for (int y = 0; y < 5; y++)
            {
                for (int x = 0; x < 11; x++)
                {
                    if (y == 0) // first row
                    {
                        aliens[x + (y * 11)] = new Sprite(new Vector2(32 + (x * 16), 64 + (y * 16)), smallSprite);
                    }
                    else if (y == 1 || y == 2) // second or third row
                    {
                        aliens[x + (y * 11)] = new Sprite(new Vector2(32 + (x * 16), 64 + (y * 16)), mediumSprite);
                    }
                    else // everything else
                    {
                        aliens[x + (y * 11)] = new Sprite(new Vector2(32 + (x * 16), 64 + (y * 16)), largeSprite);
                    }
                }
            }
This isn't too different from the if/else statements we made before except for a new symbol, the double "||" symbol. This means "or". If we wanted "and" we would use the "&&" symbol. If you use a single | or &, you're doing something else, reference here: https://msdn.microsoft.com/en-us/library/6a71f45d.aspx
At this point, if you hit F5, you should get this:

The next thing we're going to do is make the enemies disappear when we hit them. Probably the simplest way of doing this is to add a new variable to our Sprite class that acts as an on/off switch for rendering. The best fit data type-wise for on/off is a bool which has a value of true or false (like a light switch...no dimmer!). So inside our Sprite class we're going to add a new bool variable called visible and create getVisible(), setVisible(...) functions for it. Add this within the Sprite.cs file:
Code:
        bool visible;

        public bool getVisible()
        {
            return visible;
        }

        public void setVisible(bool v)
        {
            visible = v;
        }
        // constructor
        public Sprite(Vector2 p, Texture2D i, bool v)
        {
            position = p;
            image = i;
            visible = v;
        }
Also, you can take out the existing Sprite constructor or leave it, it doesn't matter. I took mine out because 1) we aren't going to use that one and 2) it doesn't set the value of visible so it will always be false (unless we call the setVisible method). If you took out the old constructor, you'll notice that we've got 5 errors now because the constructor we're using in the Game1.cs file only passes in two arguments and our new contructor requires three. So let's fix those errors. Double click on the first error and add a ", true" to the end of the constructor. The Sprite constructors should look something like this:
Code:
            player = new Sprite(new Vector2(103, 208), shipSprite, true);
            playerBullet = new Sprite(new Vector2(-1000, -1000), playerBulletSprite, true);

            aliens = new Sprite[55];
            for (int y = 0; y < 5; y++)
            {
                for (int x = 0; x < 11; x++)
                {
                    if (y == 0) // first row
                    {
                        aliens[x + (y * 11)] = new Sprite(new Vector2(32 + (x * 16), 64 + (y * 16)), smallSprite, true);
                    }
                    else if (y == 1 || y == 2) // second or third row
                    {
                        aliens[x + (y * 11)] = new Sprite(new Vector2(32 + (x * 16), 64 + (y * 16)), mediumSprite, true);
                    }
                    else // everything else
                    {
                        aliens[x + (y * 11)] = new Sprite(new Vector2(32 + (x * 16), 64 + (y * 16)), largeSprite, true);
                    }
                }
            }
Next we're going do check if the sprite is visible before calling the Draw function like this:
Code:
if (aliens[i].getVisible() == true)
                {
                    spriteBatch.Draw(aliens[i].getImage(), aliens[i].getPosition(), Color.White);
                }
You can test it by hitting F5 but it will just draw everything like it did before. The next thing we're going to do is check if the player's bullet is overlapping an enemy and if it is we're going to 1) make the enemy invisible and 2) move the bullet offscreen so it can be fired again (and not continue hitting enemies). Texture2D's have a built in Rectangle size called Bounds and we'll just use that for now, even though it won't work later on because reasons. So we'll combine two pieces of information, the Sprite's position and the Texture2D's Bounds and test if they're intersecting. Luckily, the Rectangle structure has a built in function called Intersects. Reference here: https://msdn.microsoft.com/en-us/lib...e_members.aspx
Let's start by making a new function in the Sprite class that returns a Rectangle called hitbox like this:
Code:
        public Rectangle hitbox()
        {
            return new Rectangle((int)position.X, (int)position.Y, image.Width, image.Height);
        }
This will return a Rectangle at the Sprite's current position, the same size as it's Texture2D. The Rectangle's constructor requires 4 ints and the Sprites position is a float (or decimal) number so we have to cast it. Here's some info about casting: https://msdn.microsoft.com/en-us/library/ms173105.aspx The gist of it is that certain data type can be cast as other ones or converted. In our case, we might be at position 1.5 (a decimal number), so we cast that to an int, which turns it into 1 (a whole number).
Now that we have a way to get a Sprite's hitbox, we need to check the bullet's hitbox against the enemy's hitbox. We'll use a for loop like before but this time in the Update method in the Game1.cs file.
Code:
            for (int i = 0; i < aliens.Length; i++)
            {
                if (playerBullet.hitbox().Intersects(aliens[i].hitbox()))
                {
                    // hide the alien
                    aliens[i].setVisible(false);

                    // move the bullet offscreen
                    playerBullet.setPosition(new Vector2(-1000, -1000));
                }
            }
There's one new thing I introduced here, the "aliens.Length". In C#, arrays know how big they are so you can use the Length function to do that. You could put in 55 but this is a little cleaner. If you press F5, you'll be able to make the aliens disappear by shooting them but there's a bug The bullet is still colliding with aliens even if they're invisible so do a visibility check before testing collision.
Code:
            for (int i = 0; i < aliens.Length; i++)
            {
                if (aliens[i].getVisible() == true && playerBullet.hitbox().Intersects(aliens[i].hitbox()))
                {
                    // hide the alien
                    aliens[i].setVisible(false);

                    // move the bullet offscreen
                    playerBullet.setPosition(new Vector2(-1000, -1000));
                }
            }
There's a couple inefficiencies in this code but I'm going to ignore them for now. 1. We call player.hitbox(), which has to be calculated each time. 2. We don't break out of the for loop once we find an alien the bullet collides with.
The next thing we need to do is move the aliens back and forth around the screen. They either move left or right and move down when 1 enemy hits the side of the screen. Since the aliens move left and right in unison we don't really need a isMovingRight variable in the Sprite class and we can't put it inside the Update method because it will get reset each time so we'll put it at the top of the Game1.cs file with the rest of the variables like this:
Code:
bool isMovingRight = true;
Next, we'll check if isMovingRight is true and if it is, we'll move all the aliens a little to the right, if not we'll move them a little to the left. Change the for loop to look like this:
Code:
for (int i = 0; i < aliens.Length; i++)
            {
                if (aliens[i].getVisible() && playerBullet.hitbox().Intersects(aliens[i].hitbox()))
                {
                    // hide the alien
                    aliens[i].setVisible(false);

                    // move the bullet offscreen
                    playerBullet.setPosition(new Vector2(-1000, -1000));
                }

                if (isMovingRight)
                {
                    aliens[i].moveXAxis(.05f);
                }
                else
                {
                    aliens[i].moveXAxis(-.05f);
                }
            }
The .05f is some arbitrary number I picked. Later on we'll make that a variable since the aliens' speed varies. Next we're going to add a check to see if any of the aliens go offscreen and if they do, we'll move them back on screen and switch the isMovingRight variable to the opposite. So replace the code above with this:
Code:
bool atLeastOneOffscreen = false;
            for (int i = 0; i < aliens.Length; i++)
            {
                if (aliens[i].getVisible() && playerBullet.hitbox().Intersects(aliens[i].hitbox()))
                {
                    // hide the alien
                    aliens[i].setVisible(false);

                    // move the bullet offscreen
                    playerBullet.setPosition(new Vector2(-1000, -1000));
                }

                if (isMovingRight)
                {
                    aliens[i].moveXAxis(.05f);
                }
                else 
                {
                    aliens[i].moveXAxis(-.05f);
                }

                // check if anyone moved offscreen
                if (!atLeastOneOffscreen &&
                    aliens[i].getVisible() &&
                    (aliens[i].getPosition().X < 0 || aliens[i].getPosition().X > 224 - aliens[i].hitbox().Width))
                {
                    atLeastOneOffscreen = true;
                    isMovingRight = !isMovingRight;
                }
            }

            if (atLeastOneOffscreen)
            {
                for (int i = 0; i < aliens.Length; i++)
                {
                    aliens[i].moveYAxis(1); // move down
                    aliens[i].limitLocationX(0, 224 - aliens[i].hitbox().Width); // don't let them go off screen
                }
            }
The only new thing here is the ! symbol, reference here: https://msdn.microsoft.com/en-us/library/f2kd6eb2.aspx
It turns true into false and false into true. You can also use it with the equal sign like this "5 != 6" which means 5 does not equal 6. So this statement "isMovingRight = !isMovingRight;" switches whatever the value of isMovingRight is to it's opposite. If it's true it becomes false, if it's false it becomes true. If you hit F5, you can test it out. It should look like this after you've taken some aliens out:

Next we're going to add point values to the aliens. I could just add a point value to the Sprite class but then the player would have a point value and bullets would too and that kinda doesn't make sense. So we're going to make a new class that inherits from Sprite called Alien. When a class inherits from another class it receives all the variables and functions from the parent class. If you want you can override those parent functions or just use the ones you get. So right click on TalkingInvaders on the right hand side, then Add, then Class..., then type in Alien.cs in the texbox towards the bottom, then click the Add button in the bottom right. Erase whatever they give you for the Alien.cs class and put this in:
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace TalkingInvaders
{
    public class Alien : Sprite
    {
        int points;
        public int getPoints()
        {
            return points;
        }

        public Alien(Vector2 p, Texture2D i, bool v, int pnts)
            : base(p, i, v)
        {
            points = pnts;
        }
    }
}
If you compare this to the Sprite class, a couple things will jump out. 1) There's a ": Sprite" at the top, that just means that we're inheriting from the Sprite class. 2) The Alien constructor has this :base(...) thing. Whenever the Alien constructor gets called it has to also call the Sprite constructor in order to initialize those variables. Next we're going to change our array of Sprites called aliens to an array of Aliens like this:
Code:
Alien[] aliens;
This will break our code but they're easy fixes. Change this:
Code:
aliens = new Sprite[55];
to this:
Code:
aliens = new Alien[55];
Change this:
Code:
                    if (y == 0) // first row
                    {
                        aliens[x + (y * 11)] = new Sprite(new Vector2(32 + (x * 16), 64 + (y * 16)), smallSprite, true);
                    }
                    else if (y == 1 || y == 2) // second or third row
                    {
                        aliens[x + (y * 11)] = new Sprite(new Vector2(32 + (x * 16), 64 + (y * 16)), mediumSprite, true);
                    }
                    else // everything else
                    {
                        aliens[x + (y * 11)] = new Sprite(new Vector2(32 + (x * 16), 64 + (y * 16)), largeSprite, true);
                    }
to this:
Code:
if (y == 0) // first row
                    {
                        aliens[x + (y * 11)] = new Alien(new Vector2(32 + (x * 16), 64 + (y * 16)), smallSprite, true, 10); // 10 points
                    }
                    else if (y == 1 || y == 2) // second or third row
                    {
                        aliens[x + (y * 11)] = new Alien(new Vector2(32 + (x * 16), 64 + (y * 16)), mediumSprite, true, 20); // 20 points
                    }
                    else // everything else
                    {
                        aliens[x + (y * 11)] = new Alien(new Vector2(32 + (x * 16), 64 + (y * 16)), largeSprite, true, 30); // 30 points
                    }
We've now initialized the scores to 10,20,30 respectively. The next thing to do is update the score on the screen. The right thing to do is probably make a Player class that inherits from Sprite but I don't want to, lol. So I'm just going to add an int to the Game1.cs file called player1ScoreVal like this:
Code:
int player1ScoreVal;
And we'll add to that every time the player's bullet hits an enemy. Inside the Update method, where we set the alien to be not visible, add this line:
Code:
// up the score
                    player1ScoreVal += aliens[i].getPoints();
                    // could also be written like this
                    // player1ScoreVal = player1ScoreVal + aliens[i].getPoints();
This will add the point value of the alien to our player1ScoreVal variable. That "+=" is just a shortcut, you can also do that with -=, %=, *=, /=. Whatever. So we've added it to a internal score variable but it's not being reflected on screen yet. To do this we'll assign the string value of player1ScoreVal to the player1Score from the first tutorial. I forgot to add a function that sets the value of the text in the DisplayText class so I'll do that now:
Code:
public void setText(string t)
          {
              text = t;
          }
Add this below the code that ups the value of player1ScoreVal:
Code:
player1Score.setText(player1ScoreVal.ToString());
Since our setText function expects a string to be passed in, we have to call .ToString() on our int value. Most things in C# have a built in .ToString() function but what they produce can vary wildly. Also, you might have noticed but early on we had 4 digits for score. So we need to pad that will some zeroes if the score isn't high enough. So change the code we just added to this:
Code:
string scoreVal = player1ScoreVal.ToString();
                    player1Score.setText(scoreVal.PadLeft(4, '0')); // score is 4 digits long
I'm not sure what happens in Space Invaders when you go past 9999, it probably rolls the score back to zero. If you feel like coding that, be my guest, it's getting late.

Last edited by Balrog; 03-14-2016 at 08:28 PM.
Reply With Quote
  #30  
Old 03-14-2016, 10:44 AM
ASandoval's Avatar
ASandoval ASandoval is offline
Baby, I WANT Klonoa.
 
Join Date: Jul 2008
Location: Blackwood, NJ
Posts: 4,791
Default

Got to inserting For Loops to display rows of aliens when I got this error in the Draw function:



I only get this error when I type aliens.getImage and aliens.getPosition. I don't get it with alien.getImage or alien.getPosition but instead get 'alien does not exist in the current context'. I noticed I had been using alien as a variable instead of aliens at one point and thought I missed one somewhere in one of the classes but find and replace hasn't found anything. As far as I can tell all instances of 'alien' have been replaced to 'aliens'. Not sure what's going on.
Reply With Quote
Reply

Tags
diy , programming

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 08:59 PM.


Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Your posts İyou, 2007