Professional Documents
Culture Documents
All the hard work. The hours of dedication. The years of dreaming. It culminates here. Welcome to Unit 2. By the time you walk away from this Unit, you will have made a fully playable game. Before we get into the actual coding, let's talk about logistics. 1. You will be making a Java based applet. The purpose of this is to make it easy to embed into HTML, so you will be seeing live examples on this page. 2. Your voice will matter. Use the comments box below. We will be adding nonessential features as you guys deem necessary. 3. If you have not followed Unit 1, we will be using Eclipse throughout this series. So I suggest that you go read the first few lessons in the first Unit to get yourself ready to develop. In the past two days, many of you (nearly a hundred) participated in the survey that was available on this page, and I read each response. Thank you so much for your feedback. Game Idea: The most popular game idea was a platforming shooter. So that is the direction we will be headed. Like I said before, YOU decide where we will be at the end of the unit. I will simply lead you there. Changes: The biggest complaint was that the lessons were too far apart. I will be changing that this Unit. Expect more frequent updates (most likely 2 lessons a week). I hope to finish this Unit in about a month. In addition, thanks to donations from users like you, we were able to upgrade our website with premium features. So, whenever I use a game asset (graphics, sound, etc), I will be posting them here for you to download and incorporate into your own project. You don't need to start up Photoshop and throw together artwork for this Unit. Of course to maintain this, Kilobolt has to pay a monthly fee. So if you donate any amount (yes even a dollar) it will help us retain this service and bring more exciting features to you in the coming lessons. In addition we are offering an advertising service. Kilobolt.com, although a very new website, receives significant traffic from very dedicated users. If you want to promote your business, contact us. We are offering two weeks of free advertising to qualifying partners.
Lesson #2-1: Setting up a Project Open up Eclipse and create a Java project. You can do this by right clicking on the Package Explorer >> New >> Java Project:
Click to Enlarge
Call it KiloboltGame (consistency will ensure that we won't have errors due to naming).
Inside this project's src folder (remember this is where we store all our code): 1. Create a Package. Name it kiloboltgame. By convention its the package name begins lowercase. Packages are ways of organizing classes into folder-like structures. Right click on your newly created package and create a class: StartingClass.
Now we can start coding. Lesson #2-2: Adding Methods and extending Applet
By this time, I trust that you are all experienced Java programmers capable of discerning which brace corresponds with which.
As such I will no longer be color coding unless I believe it is absolutely necessary. 1. Add extends Applet after "StartingClass". Recall that we can borrow methods from a superclass by inheriting it. 2. You will see a red line under Applet. Import Applet by pressing Ctrl+Shift+O or by manually selecting the import by mouseover. 3. You can now add the following methods: init(), start(), stop(), destroy(). These are part of the Applet superclass, and by importing Applet, you gain the ability to use them. 4. An easy way to add a pre-defined method (as in one that is already written for you), is to use the auto-complete function. Begin typing "init" and then press Ctrl + Space. It will open an auto-complete suggestions box, from which you can choose the first init() function. Proceed to do this with all four methods and you will see this:
5. Press Ctrl + Shift + F to auto-organize your code. Shortcuts such as these will help you save a lot of time.
FIGURE 2-1
package kiloboltgame; import java.applet.Applet;
public class StartingClass extends Applet{ @Override public void init() { // TODO Auto-generated method stub super.init(); } @Override public void start() { //TODO Auto-generated method stub super.start(); } @Override public void stop() { // TODO Auto-generated method stub super.stop(); } @Override public void destroy() { // TODO Auto-generated method stub super.destroy(); } } Now, as always, we will talk about each of these methods. The four methods: init(), start(), stop(), and destroy() are frameworks for execution provided by the Applet class. In this Applet's life cycle, major events will call one of these methods and execute it. The @Override tests for errors upon compilation. In this case, we are using it to annotate that we are overriding methods from a parent class. It informs you when you make mistakes. More information on this can be found on Google. But for now, don't worry about it. :) Within each method, you will see a "super." This "super" is referring to the superclass (in this case Applet). You might also see a "this" in the future, which will refer to the current class. Don't worry about this for now. We can safely delete each "super" line:
@Override public void init() { // TODO Auto-generated method stub } @Override public void start() { //TODO Auto-generated method stub } @Override public void stop() { // TODO Auto-generated method stub } @Override public void destroy() { // TODO Auto-generated method stub } } Before we move on! Mini Quiz: 1. How can you make a program keep running? How do you repeat statements? 2. What tool do we use for simultaneous processes in Java? 3. What is a game loop?
Answers: 1. We utilize a loop. 2. A thread. 3. A game loop is the central component of a game. People refer to it as the heartbeat of the game. To put simply, it is a loop that will continuously check for changes in the game and make necessary updates. Graphics, controls, movement, physics all rely on the game loop in some way. Here's a fake game loop that will help you understand:
FIGURE 2-3
while (playerIsAlive){ updatePosition(); drawCharacterAtCurrentLocation(); applyGravity(); } Figure 2-3 incorporates three pseudo-methods (they are simply there for illustration).
We have a condition that while the playerIsAlive is true, we update his position, redraw his character at that new position, and apply gravity. This is a simplified example of a game loop. As we get into more advanced gaming concepts, we will talk about creating a proper game loop. But for now, a simple one will have to suffice (And on modern computers, a little bit of slacking here won't hurt us). But wait until we start developing on Android. Technical considerations can be PAINFUL. Day 1 will stop here. Day 2 will be up in an hour or two. In Day 2, we will talk about applying threads and loops to create the heartbeat of our upcoming game. Be excited. Oh and also, please post a good game name as a comment down below. :) We will see what kind of names come up. Thank you for being an awesome audience, and please share our page on Facebook. Help us reach 1,000 likes by November! I've been thinking about the direction that I want to go with this game, and as of now, I am considering making it a game about a Q-bot (cube robot)* going around shooting things and jumping across platforms. Two games that I am taking inspiration from are Metal Slug and Megaman. As we get deeper into the series, I want to add character upgrades, different types of platforms and power ups, and much more. But first, let us talk about the game thread and the game loop. (*Name not final)
Simple right? The first line creates a new instance of a thread (a Thread object called "thread"), and the second line starts the thread. When you call: thread.start(); , however, nothing will happen. Why? Refer to this example from Lesson #1-21:
This tool tip describes that when we implement interface, we can extract the run method from the Thread that we created. So to "bridge" or connect this newly created run() method and the thread that we have created above: 3. Go back to this statement in the start method: Thread thread = new Thread(); and add "this": Thread thread = new Thread(this); We will talk more about "this" in the future. It is quite a flexible keyword, and therefore I think it's best if we talk about it from another perspective with more understanding. The final result:
public class StartingClass extends Applet implements Runnable{ @Override public void init() {
} @Override public void start() { Thread thread = new Thread(this); thread.start(); } @Override public void stop() { // TODO Auto-generated method stub } @Override public void destroy() { // TODO Auto-generated method stub } @Override public void run() { // TODO Auto-generated method stub } }
be disastrous when the machine slows down (hence this is a bad game loop), but for the purposes of our simple Java game on your modern day computer, it is simple, effective, easy to implement, and very intuitive. So first we create a game loop in the run() method of the class: 1. This loop will be a while loop, and will run indefinitely. To make a while loop run forever, we just writetrue as the condition for the loop: while(true){
} 2. Within this add the following segment of code: repaint(); try { Thread.sleep(17); } catch (InterruptedException e) { e.printStackTrace(); }
What a try/catch does is the following. 1. In try, we attempt to do something, like sleep. 2. If something goes wrong, then the catch will save information regarding errors, and will print it to a console. Don't worry too much about it. It's just a built-in fail-safe system. It won't fail here. Just click the suggested quick fix. At this point the StartingClass.java will look like this:
} @Override public void run() { while (true) { repaint(); try { Thread.sleep(17); } catch (InterruptedException e) { e.printStackTrace(); } } } }
To use a constant from the Color superclass, we must import the Color superclass.
3. setFocusable(true); This statement makes sure that when the game starts, the applet takes focus and that our input goes directly into it. If this is not enabled, then you would have to click inside the applet before it starts handling keyboard events. 4. Frame frame = (Frame) this.getParent().getParent(); frame.setTitle("Q-Bot Alpha");
Here again, you must import Frame to create a Frame object called frame. This is slightly complicated, but just know that the first line assigns the applet window to the frame variable an the second line just sets the title to be Q-Bot Alpha. At this point, you will have this:
setBackground(Color.BLACK); setFocusable(true); Frame frame = (Frame) this.getParent().getParent(); frame.setTitle("Q-Bot Alpha"); } @Override public void start() { Thread thread = new Thread(this); thread.start(); } @Override public void stop() { // TODO Auto-generated method stub } @Override public void destroy() { // TODO Auto-generated method stub } @Override public void run() { while (true) { repaint(); try { Thread.sleep(17); } catch (InterruptedException e) { e.printStackTrace(); } } } } Short Cut Reminders: Ctrl+Shift+O: Auto import code (may import the wrong classes) Ctrl+Shift+F: Auto format code into proper indents Ctrl+Space: Open auto complete suggestions. Thank you everyone for reading, and feel free to leave comments and questions below!
From now on, I will be including the project folder as a source code for people who don't want to write their own code, or just want to examine their own.
So let's get started by implementing the KeyListener. 1. Go to your StartingClass.java and examine your class declaration:
public class StartingClass extends Applet implements Runnable{ You will notice that you are already implementing the Runnable interface, so you simply add: ", KeyListener" to the end like so: public class StartingClass extends Applet implements Runnable, KeyListener{
2. When you add this, Java will give you an error saying "KeyListener cannot be resolved to a type". To resolve this error, just import KeyListener.
You need to import KeyListener before you can implement the interface.
3. You will now see an error: "The type StartingClass must implement the inherited abstract method KeyListener.keyReleased(KeyEvent)." Recall that when you implement an interface, you must take all of its methods and declare them in your class. So you can easily resolve this by choosing: Add unimplemented methods.
4. Adding unimplemented methods will do two things: First, it will add the three methods: keyPressed(), keyReleased(), and keyTyped() to your code. These three methods require a KeyEvent object as a parameter. Java is smart enough to import KeyEvent for you. 5. Finally, add this implemented KeyListener to the current Applet by adding to the init() method: addKeyListener(this);
public class StartingClass extends Applet implements Runnable, KeyListener{ @Override public void init() { setSize(800, 480); setBackground(Color.BLACK); setFocusable(true); addKeyListener(this); Frame frame = (Frame) this.getParent().getParent(); frame.setTitle("Q-Bot Alpha"); } @Override public void start() { Thread thread = new Thread(this); thread.start(); } @Override public void stop() { // TODO Auto-generated method stub } @Override public void destroy() { // TODO Auto-generated method stub }
@Override public void run() { while (true) { repaint(); try { Thread.sleep(17); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void keyPressed(KeyEvent e) { // TODO Auto-generated method stub } @Override public void keyReleased(KeyEvent e) { // TODO Auto-generated method stub } @Override public void keyTyped(KeyEvent e) { // TODO Auto-generated method stub } } The first two methods are self-explanatory. keyTyped is slightly more complicated, but you can learn more about it here: http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/event/KeyEvent.html
case KeyEvent.VK_DOWN: break; case KeyEvent.VK_LEFT: break; case KeyEvent.VK_RIGHT: break; case KeyEvent.VK_SPACE: break; } } Let's examine this in detail. We learned about switches in Unit 1. It is an alternative to a long list of if statements. A switch will compare the key, which is the variable we are checking for, with values, and then carry out the appropriate response. In this case, the key is e.getKeyCode(). e.getKeyCode() will return the code of the button that you press on the keyboard. (If you were to type System.out.println(e.getKeyCode()); , each time that you press a button, it will display the key code on the console). What the switch is doing here, then, is comparing the e.getKeyCode() returned from your button presses, and comparing it to multiple cases of values.
At the present, we have no character to actually move or stop. Therefore, we will just output some text to the console like so:
} catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Move up"); break; case KeyEvent.VK_DOWN: System.out.println("Move down"); break; case KeyEvent.VK_LEFT: System.out.println("Move left"); break; case KeyEvent.VK_RIGHT: System.out.println("Move right"); break; case KeyEvent.VK_SPACE: System.out.println("Jump"); break; } } @Override public void keyReleased(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Stop moving up"); break; case KeyEvent.VK_DOWN: System.out.println("Stop moving down"); break; case KeyEvent.VK_LEFT: System.out.println("Stop moving left"); break; case KeyEvent.VK_RIGHT:
System.out.println("Stop moving right"); break; case KeyEvent.VK_SPACE: System.out.println("Stop jumping"); break; } } @Override public void keyTyped(KeyEvent e) { // TODO Auto-generated method stub } } That's today's lesson! On Friday, we will be adding the background, our main character, and then add some movement! Thanks for following my tutorials, and let me know if I can help you in any way!
Download File
3. Helper methods that retrieve and change the value of variables in the class. Let's get started. We begin by creating a new class called Robot.java in the kiloboltgame package (or whatever you named your package). Copy and paste the following:
speedY += 1; if (centerY + speedY >= 382) { centerY = 382; speedY = 0; jumped = false; } } // Prevents going beyond X coordinate of 0 if (centerX + speedX <= 60) { centerX = 61; } } public void moveRight() { speedX = 6; } public void moveLeft() { speedX = -6; } public void stop() { speedX = 0; } public void jump() { if (jumped == false) { speedY = -15; jumped = true; } } }
through it one more time and try to make sense of the if statements. *When examining the update() method again, Keep the following in mind. 1. Speed can be negative, which means that a character with negative speedX would move to the left. 2. The Origin (0,0) pixel is at the TOP LEFT. I will talk about this below. This means that if a character has a positive speedY, he is FALLING, not RISING.
3. Recall the meaning of += : x += 1; is equivalent to: x = x + 1; 4. We are arbitrarily defining the ground at about 440 pixels. That means if the character's centerY is at about 382, his feet would reach the ground at ~440 pixels. 5. If the character's centerX is lesser than 60, his left hand will be outside the left edge of the screen. Now have a look at a diagram:
Basic Diagram
//382 is where the character's centerY would be if he were standing on the ground. centerY = 382; }else{ centerY += speedY; //Add speedY to centerY to determine its new position } // Handles Jumping if (jumped == true) { speedY += 1; //While the character is in the air, add 1 to his speedY. //NOTE: This will bring the character downwards! if (centerY + speedY >= 382) { centerY = 382; speedY = 0; jumped = false; } } // Prevents going beyond X coordinate of 0 if (centerX + speedX <= 60) { //If speedX plus centerX would bring the //outside the screen, centerX = 61; //Fix the character's centerX at 60 pixels. }
character
= 382; to manually set the character at a height that will stop him from moving. The Handles jumping section will check the current speed and position to test whether the character is in mid-jump or on the ground. Finally, let's discuss the section labeled:// Prevents going beyond X coordinate of 0 This section just checks if the character is moving beyond the left edge of the screen and fixes his centerX coordinate at 61 if he tries to move off the screen. Now that we have discussed the update() method, we will move on to the other methods: 2. Methods called upon input:
moveRight(), which sets the character's horizontal speed (speedX) as 6. moveLeft(), which sets the character's speedX as -6. stop(), which sets the speedX as zero. jump(), which sets the vertical speed as -15.
each time that moveRight() is called, the character's speed will be given a value of 6 pixels. In the case of the robot, the update() method, will add this speed of 6 to the centerX value. Changing the centerX value will mean that the drawRobot(centerX, centerY) statement above will now "move" the robot to a new location with its new centerX. If this happens on a speed that is fast enough, we get the illusion of smooth motion.
The update() method is implicitly called automatically, and will loop over and over again. The paint() will similarly be always called, with the repaint() statement within the run() method. I. Defining the update() Method: Let's first deal with the update() method. We will use this method for double buffering - a technique that is used to prevent tearing and flickering. Feel free to read up more on this subject, as I am not an expert in this topic. The basic concept is that it works by retaining the previous position of the screen's current image for a short amount of time, so that the movement of the image looks smooth and natural. 1. Begin by declaring (below private Robot robot): private Image image; private Graphics second;
As class variables (accessible by all methods within the StartingClass). 2. If you receive an error on either Graphics or Images, press Ctrl+Shift+O to auto-import them. 3. Now go down the the update() method and add the following:
g.drawImage(image, 0, 0, this);
} The individual statements of this segment of code is easy to understand. What is difficult is discerning how each of these pieces fit together as a whole to create a double buffering system. Feel free to look at this in detail, but my advice to you: ignore it, accept it, and move on. :) II. Defining the paint() Method: The paint() method will be used to draw our graphics to the screen. For now, we only need to draw the robot, so it will have one statement inside:
An Image variable, the x and y coordinates where you want to draw the Image, and an ImageObserver (which is slightly beyond the scope of this lesson). In our example, we will use the character variable to represent our robot image, and then draw the top left corner of the robot 61 pixels to the left, and 63 pixels above the (centerX, centerY), and then use the "this" keyword as our ImageObserver. At this point, you will have a lot of errors. We will address them each one at a time.
Frame frame = (Frame) this.getParent().getParent(); frame.setTitle("Q-Bot Alpha"); try { base = getDocumentBase(); } catch (Exception e) { // TODO: handle exception }
// Image Setups character = getImage(base, "data/character.png"); } 4. Finally, we must create a data folder by right clicking on src and creating a folder named data. Inside it, download, drag, and drop this image:
Download File
That should solve the character errors. The StartingClass.java will look like this now: (If you have errors, try Rebuilding by doing the following: On the toolbar above, click on Project > Clean > OK).
private Image image, character; private Graphics second; private URL base; @Override public void init() { setSize(800, 480);
setBackground(Color.BLACK); setFocusable(true); addKeyListener(this); Frame frame = (Frame) this.getParent().getParent(); frame.setTitle("Q-Bot Alpha"); try { base = getDocumentBase(); } catch (Exception e) { // TODO: handle exception } // Image Setups character = getImage(base, "data/character.png"); } @Override public void start() { robot = new Robot(); Thread thread = new Thread(this); thread.start(); } @Override public void stop() { // TODO Auto-generated method stub } @Override public void destroy() { // TODO Auto-generated method stub } @Override public void run() { while (true) { repaint(); try { Thread.sleep(17); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void update(Graphics g) { if (image == null) {
image = createImage(this.getWidth(), this.getHeight()); second = image.getGraphics(); } second.setColor(getBackground()); second.fillRect(0, 0, getWidth(), getHeight()); second.setColor(getForeground()); paint(second); g.drawImage(image, 0, 0, this); } @Override public void paint(Graphics g) { g.drawImage(character, robot.getCenterX() - 61, robot.getCenterY() - 63, this); } @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Move up"); break; case KeyEvent.VK_DOWN: System.out.println("Move down"); break; case KeyEvent.VK_LEFT: System.out.println("Move left"); break; case KeyEvent.VK_RIGHT: System.out.println("Move right"); break; case KeyEvent.VK_SPACE: System.out.println("Jump"); break; } } @Override public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Stop moving up"); break; case KeyEvent.VK_DOWN: System.out.println("Stop moving down"); break; case KeyEvent.VK_LEFT: System.out.println("Stop moving left"); break; case KeyEvent.VK_RIGHT: System.out.println("Stop moving right"); break; case KeyEvent.VK_SPACE: System.out.println("Stop jumping"); break; } } @Override public void keyTyped(KeyEvent e) { // TODO Auto-generated method stub } }
In my previous description of the Robot class, I mentioned that we would be creating: 1. Constantly updated methods that are called on each iteration of the game loop. 2. Methods that are only called upon player input. 3. Helper methods that retrieve and change the value of variables in the class.
We never did create the helper methods. So here we do that. 1. Open Robot.java again.
2. Right click anywhere on the white space, click Source >> and select Generate Getters and Setters.
3. Select all, sort by getters then setters, and then press OK. What are getters and setters? Again, in Java, it is common practice to set class-wide variables as private. For other classes to access these private variables, they must use helper functions known as getters and setters. Let's have a look at a pair: public int getSpeedX() { return speedX; }
and public void setSpeedX(int speedX) { this.height = speedX; } Whenever a getter method is called, it returns the value that you "get." When I say: myNewVariable = getSpeedX(), my new variable will get the value of speedX. If I say: setSpeed(10); then my speedX will now have a value of 10. With these three things finished, the result is:
} @Override public void start() { robot = new Robot(); Thread thread = new Thread(this); thread.start(); } @Override public void stop() { // TODO Auto-generated method stub } @Override public void destroy() { // TODO Auto-generated method stub } @Override public void run() { while (true) { repaint(); try { Thread.sleep(17); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void update(Graphics g) { if (image == null) { image = createImage(this.getWidth(), this.getHeight()); second = image.getGraphics(); } second.setColor(getBackground()); second.fillRect(0, 0, getWidth(), getHeight()); second.setColor(getForeground()); paint(second); g.drawImage(image, 0, 0, this); }
@Override public void paint(Graphics g) { g.drawImage(character, robot.getCenterX() - 61, robot.getCenterY() - 63, this); } @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Move up"); break; case KeyEvent.VK_DOWN: System.out.println("Move down"); break; case KeyEvent.VK_LEFT: System.out.println("Move left"); break; case KeyEvent.VK_RIGHT: System.out.println("Move right"); break; case KeyEvent.VK_SPACE: System.out.println("Jump"); break; } } @Override public void keyReleased(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Stop moving up"); break; case KeyEvent.VK_DOWN: System.out.println("Stop moving down"); break; case KeyEvent.VK_LEFT: System.out.println("Stop moving left"); break;
case KeyEvent.VK_RIGHT: System.out.println("Stop moving right"); break; case KeyEvent.VK_SPACE: System.out.println("Stop jumping"); break; } } @Override public void keyTyped(KeyEvent e) { // TODO Auto-generated method stub } }
Now we will make our final set of changes to make this character move. First: In StartingClass go down to the keyPressed() method, and replace: 1. System.out.println("Move left"); with robot.moveLeft(); 2. System.out.println("Move right"); with robot.moveRight(); 3. System.out.println("Jump"); with robot.jump(); And in the keyReleased() method, replace: 1. System.out.println("Stop moving left"); and 2. System.out.println("Stop moving right"); with robot.stop(); Second: Within the run() method, we need to call robot.update();
} catch (Exception e) { // TODO: handle exception } // Image Setups character = getImage(base, "data/character.png"); } @Override public void start() { robot = new Robot(); Thread thread = new Thread(this); thread.start(); } @Override public void stop() { // TODO Auto-generated method stub } @Override public void destroy() { // TODO Auto-generated method stub } @Override public void run() { while (true) { robot.update(); repaint(); try { Thread.sleep(17); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void update(Graphics g) { if (image == null) { image = createImage(this.getWidth(), this.getHeight()); second = image.getGraphics(); } second.setColor(getBackground()); second.fillRect(0, 0, getWidth(), getHeight()); second.setColor(getForeground());
paint(second); g.drawImage(image, 0, 0, this); } @Override public void paint(Graphics g) { g.drawImage(character, robot.getCenterX() - 61, robot.getCenterY() - 63, this); } @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Move up"); break; case KeyEvent.VK_DOWN: System.out.println("Move down"); break; case KeyEvent.VK_LEFT: robot.moveLeft(); break; case KeyEvent.VK_RIGHT: robot.moveRight(); break; case KeyEvent.VK_SPACE: robot.jump(); break; } } @Override public void keyReleased(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Stop moving up"); break; case KeyEvent.VK_DOWN: System.out.println("Stop moving down");
break; case KeyEvent.VK_LEFT: robot.stop(); break; case KeyEvent.VK_RIGHT: robot.stop(); break; case KeyEvent.VK_SPACE: System.out.println("Stop jumping"); break; } } @Override public void keyTyped(KeyEvent e) { // TODO Auto-generated method stub } }
public void update() { // Moves Character or Scrolls Background accordingly. if (speedX < 0) { centerX += speedX; } else if (speedX == 0) { System.out.println("Do not scroll the background.");
} else { if (centerX <= 150) { centerX += speedX; } else { System.out.println("Scroll Background Here"); } } // Updates Y Position if (centerY + speedY >= 382) { centerY = 382; }else{ centerY += speedY; } // Handles Jumping if (jumped == true) { speedY += 1; if (centerY + speedY >= 382) { centerY = 382; speedY = 0; jumped = false; } } // Prevents going beyond X coordinate of 0 if (centerX + speedX <= 60) { centerX = 61; } } public void moveRight() { speedX = 6; } public void moveLeft() { speedX = -6; } public void stop() { speedX = 0; } public void jump() { if (jumped == false) { speedY = -15;
jumped = true; } } public int getCenterX() { return centerX; } public int getCenterY() { return centerY; } public boolean isJumped() { return jumped; } public int getSpeedX() { return speedX; } public int getSpeedY() { return speedY; } public void setCenterX(int centerX) { this.centerX = centerX; } public void setCenterY(int centerY) { this.centerY = centerY; } public void setJumped(boolean jumped) { this.jumped = jumped; } public void setSpeedX(int speedX) { this.speedX = speedX; } public void setSpeedY(int speedY) { this.speedY = speedY; }
} That's it for Day 4. This was a tough lesson. Please let me know in the comments section if you need me to clarify anything at all.
If your code is not working for any reason, try Rebuilding by doing the following: On the toolbar above, click on Project > Clean > OK. As a general rule, if your code does not work and you cannot find any errors, your first step should be to rebuild the project! Thanks for reading and please support us if you are learning! Every dollar helps us out tremendously!
Download File
Instructions on importing projects can be found here. Welcome to Day 5! I hope you are enjoying the changes to our website! I think they should make these tutorials more readable and therefore easier to follow. Also, as I do these twice a week, I am learning how to format things better. So even as the code gets more complex, hopefully the organization will get simpler so that you never get lost! Today we will apply some of the concepts we covered in the previous lessons to add a scrolling background. In addition, we will be adjusting the movement so that it does not "stick" like it used to. Lastly, we will change the player's sprite (character image) depending on his actions.
Inside this Background class, we will be doing the following. 1. Creating variables that we will be using to manipulate the background. 2. Defining a constructor (which allows us to create objects (instances) with the Background class (remember that classes are blueprints for objects. By instance we refer to an object that was created using a class and therefore has its properties). 3. Creating an update() method that will let us move the background. 4. Adding getters/setters (recall from Day 4 that getters/setters are helper methods that allow us to retrieve and manipulate private variables) that deal with the variables created in step 1. Let's begin!
I. CREATING VARIABLES
We will be creating bgX, bgY, and speedX. The first two of these represent the x and y coordinates of the background's upper left corner. So all we need to do is declare (below the class declaration: public class Background { :) private int bgX, bgY, speedX;
want to do so at a specific location that we choose, so here I will add two parameters - one for the x coordinate, another for the y: public Background (int x, int y){ bgX = x; bgY = y; speedX = 0; }
The variables bgX, bgY and SpeedX that we defined above will now take the following values: x, y, and 0, respectively. This means that whatever two values that we feed the constructor in the StartingClassto create a Background object will become the values of bgX and bgY. We want the background to start static, so we give it a speed of zero.
one in a superclass." When you create a method with Eclipse's help (using Ctrl + Space), READ the tool tip. It will often offer more insight. Quick Note #2: Be a thoughtful programmer. Think about how things work. Don't just take what I say and accept it without scrutiny. Try to conceptually understand why everything works the way it does. That is the only way you will get better to the point where you can make your own programs. If you have any questions, ask in the comments section. I love answering questions and explaining things in more depth. Okay. Now that you thought about the update() method, let's see what it does. bgX += speedX - this will change the bgX by the speed in the x direction. That makes sense. Now, the if statement: When the x coordinate of the background is at -2160 or below (it is no longer visible), we move it up by 4320 pixels (so that it can go 2160 pixels ahead of the currently visible image. If this is confusing, it will make more sense when we start running this code at the end). Question: Why does the if statement say: bgX <= -2160 and not bgX == - 2160? There is a very important reason. Hint: Think about what would happen if the speed of the Background was not a factor of 2160. Ask yourself these questions: What is the smallest unit of change in X position? Can bgX change instantly from 2161 to 2159? If so, will the condition still be satisfied?
bgY = y; speedX = 0; } public void update() { bgX += speedX; if (bgX <= -2160){ bgX += 4320; } } public int getBgX() { return bgX; } public int getBgY() { return bgY; } public int getSpeedX() { return speedX; } public void setBgX(int bgX) { this.bgX = bgX; } public void setBgY(int bgY) { this.bgY = bgY; } public void setSpeedX(int speedX) { this.speedX = speedX; }
method). 2. Within the run() method, we must call the object's update() method. 3. We must paint the new object in the paint() method. Let's start with number 1 (we finished number 0 above).
This will create two new Background objects separate from each other that you can refer to as bg1 andbg2. Recall that we required two integer values as parameters in the Background constructor, so we feed it x, y coordinates for each newly created Background object.
bg1.update(); bg2.update();
Download File
1. Make changes to the line: private Image image, character; like so: private Image image, character, background; 2. Now below where we defined the character image:
character = getImage(base, "data/character.png"); Define background: background = getImage(base, "data/background.png"); 3. Then scroll to the paint method and add this: g.drawImage(background, bg1.getBgX(), bg1.getBgY(), this); g.drawImage(background, bg2.getBgX(), bg2.getBgY(), this); Images are painted in the order they appear. So if you want the character to be above the background, you need to put these two lines above the line that paints the character! The resulting StartingClass:
character = getImage(base, "data/character.png"); background = getImage(base, "data/background.png"); } @Override public void start() { bg1 = new Background(0,0); bg2 = new Background(2160, 0); robot = new Robot();
Thread thread = new Thread(this); thread.start(); } @Override public void stop() { // TODO Auto-generated method stub } @Override public void destroy() { // TODO Auto-generated method stub } @Override public void run() { while (true) { robot.update(); bg1.update(); bg2.update(); repaint(); try { Thread.sleep(17); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void update(Graphics g) { if (image == null) { image = createImage(this.getWidth(), this.getHeight()); second = image.getGraphics(); } second.setColor(getBackground()); second.fillRect(0, 0, getWidth(), getHeight());
second.setColor(getForeground()); paint(second); g.drawImage(image, 0, 0, this); } @Override public void paint(Graphics g) { g.drawImage(background, bg1.getBgX(), bg1.getBgY(), this); g.drawImage(background, bg2.getBgX(), bg2.getBgY(), this); g.drawImage(character, robot.getCenterX() - 61, robot.getCenterY() - 63, this); } @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Move up"); break; case KeyEvent.VK_DOWN: System.out.println("Move down"); break; case KeyEvent.VK_LEFT: robot.moveLeft(); break; case KeyEvent.VK_RIGHT: robot.moveRight(); break; case KeyEvent.VK_SPACE: System.out.println("Jump"); robot.jump(); break; } } @Override public void keyReleased(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Stop moving up");
break; case KeyEvent.VK_DOWN: System.out.println("Stop moving down"); break; case KeyEvent.VK_LEFT: robot.stop(); break; case KeyEvent.VK_RIGHT: robot.stop(); break; case KeyEvent.VK_SPACE: System.out.println("Stop jumping"); break; } } @Override public void keyTyped(KeyEvent e) { // TODO Auto-generated method stub } public static Background getBg1() { return bg1; } public static Background getBg2() { return bg2; }
As I feel like all the changes I made to the Robot class can be understood when you examine them line by line, I will now paste the full Robot class here, instead of showing its creation step by step. If you have any specific questions, do let me know.
bg1.setSpeedX(-MOVESPEED); bg2.setSpeedX(-MOVESPEED); } // Updates Y Position centerY += speedY; if (centerY + speedY >= GROUND) { centerY = GROUND; } // Handles Jumping if (jumped == true) { speedY += 1; if (centerY + speedY >= GROUND) { centerY = GROUND; speedY = 0; jumped = false; } } // Prevents going beyond X coordinate of 0 if (centerX + speedX <= 60) { centerX = 61; } } public void moveRight() { if (ducked == false) { speedX = MOVESPEED; } } public void moveLeft() { if (ducked == false) { speedX = -MOVESPEED; } } public void stopRight() { setMovingRight(false); stop(); } public void stopLeft() { setMovingLeft(false); stop(); }
private void stop() { if (isMovingRight() == false && isMovingLeft() == false) { speedX = 0; } if (isMovingRight() == false && isMovingLeft() == true) { moveLeft(); } if (isMovingRight() == true && isMovingLeft() == false) { moveRight(); } } public void jump() { if (jumped == false) { speedY = JUMPSPEED; jumped = true; } } public int getCenterX() { return centerX; } public int getCenterY() { return centerY; } public boolean isJumped() { return jumped; } public int getSpeedX() { return speedX; } public int getSpeedY() { return speedY; } public void setCenterX(int centerX) { this.centerX = centerX; } public void setCenterY(int centerY) { this.centerY = centerY; }
public void setJumped(boolean jumped) { this.jumped = jumped; } public void setSpeedX(int speedX) { this.speedX = speedX; } public void setSpeedY(int speedY) { this.speedY = speedY; } public boolean isDucked() { return ducked; } public void setDucked(boolean ducked) { this.ducked = ducked; } public boolean isMovingRight() { return movingRight; } public void setMovingRight(boolean movingRight) { this.movingRight = movingRight; } public boolean isMovingLeft() { return movingLeft; } public void setMovingLeft(boolean movingLeft) { this.movingLeft = movingLeft; } } Dissecting the changes:
1. I never talked about constants before, so let me spend a few minutes talking about them. Constants are types of variables. They just are variables that have permanent values. To indicate a constant, we just use the keyword final (which cannot be used as a variable name for this reason), and by convention (not law) we CAPITALIZE ITS NAME. The lines: final int JUMPSPEED = -15;
Are three constants. Previously, I had these hard-coded, but creating constants for each of these values makes it easy to make changes to some of the basics of the game. In each location that I used -15, 5, and 382, I replaced the integers with these constants. 2. I replaced most of the System.out.println() placeholders with functional code. Where we previously indicated the background would scroll or stop, I added code that does exactly that. 3. You will get an error in the StartingClass when you replace your Robot.java with the code above. We will fix these soon, so do not panic!
robot.moveRight(); robot.setMovingRight(true); break; case KeyEvent.VK_SPACE: robot.jump(); break; } } @Override public void keyReleased(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Stop moving up"); break; case KeyEvent.VK_DOWN: currentSprite = character; robot.setDucked(false); break; case KeyEvent.VK_LEFT: robot.stopLeft(); break; case KeyEvent.VK_RIGHT: robot.stopRight(); break; case KeyEvent.VK_SPACE: break; } } You will have errors, but we will fix them in this next section:
Download File
Download File
Download File
Please download the three images and place them inside your data folder. Make sure you overwrite the existing character.png! 1. We want to create a Image object for the down and jumped png's (we already did character.png), so add characterDown and characterJumped to the variable declaration statement like so: "private Image image, currentSprite, character, characterDown, characterJumped, background;" Note: Order of the declaration does not matter, but I like to organize them. What is currentSprite, you ask? At the present, we are painting the Robot robot (Robot object named robot) with the image character, but we now want to paint currentSprite, which will dynamically change to character, characterDown or characterJumped according to what the Robot robot is doing. 2. Next, update the // Image Setups section of the code: character = getImage(base, "data/character.png"); characterDown = getImage(base, "data/down.png"); characterJumped = getImage(base, "data/jumped.png"); currentSprite = character; background = getImage(base, "data/background.png"); 3. Make the following changes (in bold) to the run() method:
} } } These changes will check the current state of the robot and make necessary changes to currentSprite. 4. The last thing you need to do is change the Image variable that is being painted for the robot: g.drawImage(character, robot.getCenterX() - 61, robot.getCenterY() - 63, this); should now be: g.drawImage(currentSprite, robot.getCenterX() - 61, robot.getCenterY() - 63, this);
That should fix all the problems in your code, and we have the final StartingClass:
} catch (Exception e) { // TODO: handle exception } // Image Setups character = getImage(base, "data/character.png"); characterDown = getImage(base, "data/down.png"); characterJumped = getImage(base, "data/jumped.png"); currentSprite = character; background = getImage(base, "data/background.png"); } @Override public void start() { bg1 = new Background(0,0); bg2 = new Background(2160, 0); Thread thread = new Thread(this); thread.start(); } @Override public void stop() { // TODO Auto-generated method stub } @Override public void destroy() { // TODO Auto-generated method stub } @Override public void run() { while (true) { robot.update(); if (robot.isJumped()){ currentSprite = characterJumped; }else if (robot.isJumped() == false && robot.isDucked() == false){ currentSprite = character; } bg1.update(); bg2.update(); repaint(); try { Thread.sleep(17); } catch (InterruptedException e) { e.printStackTrace(); } }
} @Override public void update(Graphics g) { if (image == null) { image = createImage(this.getWidth(), this.getHeight()); second = image.getGraphics(); } second.setColor(getBackground()); second.fillRect(0, 0, getWidth(), getHeight()); second.setColor(getForeground()); paint(second); g.drawImage(image, 0, 0, this); } @Override public void paint(Graphics g) { g.drawImage(background, bg1.getBgX(), bg1.getBgY(), this); g.drawImage(background, bg2.getBgX(), bg2.getBgY(), this); g.drawImage(currentSprite, robot.getCenterX() - 61, robot.getCenterY() - 63, this); } @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Move up"); break; case KeyEvent.VK_DOWN: currentSprite = characterDown; if (robot.isJumped() == false){ robot.setDucked(true); robot.setSpeedX(0); } break; case KeyEvent.VK_LEFT: robot.moveLeft(); robot.setMovingLeft(true); break; case KeyEvent.VK_RIGHT: robot.moveRight();
robot.setMovingRight(true); break; case KeyEvent.VK_SPACE: robot.jump(); break; } } @Override public void keyReleased(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Stop moving up"); break; case KeyEvent.VK_DOWN: currentSprite = character; robot.setDucked(false); break; case KeyEvent.VK_LEFT: robot.stopLeft(); break; case KeyEvent.VK_RIGHT: robot.stopRight(); break; case KeyEvent.VK_SPACE: break; } } @Override public void keyTyped(KeyEvent e) { // TODO Auto-generated method stub } public static Background getBg1() { return bg1; } public static Background getBg2() { return bg2;
} } That's it for Day 5! I hope that you guys learned a lot and that I did not proceed too quickly. Thank you for reading my tutorials and supporting Kilobolt Studios! Got questions? Ask away!
Hello everyone, and welcome to Day 6 of Unit 2. In today's lesson, we will be adding an enemy to our game, because our main char Our first enemy will look like this:
Too add Heliboy to our game, we will first create an Enemy class (which will serve as the superclass for most enemy types), and als
The Enemy class will be pretty simple. Since this will be a superclass, we will define commonly used variables and methods, so that
1. Right-click on our kiloboltgame package >> New >> Class. 2. Name it "Enemy". 3. Press OK!
In addition, whenever the background scrolls, the enemy should move in the same direction. So we will create a reference to the bg Within the class definition, declare the following:
private int maxHealth, currentHealth, power, speedX, centerX, centerY; private Background bg = StartingClass.getBg1();
As with all other game objects, we will need to first create a method that will constantly be running: update(); In addition, we will crea
TIP: In Eclipse, you can indent multiple lines of code at once by selecting multiple lines and pressing Tab. You can also dedent by p TIP #2: You can auto format your code (fix indents and such) by pressing Ctrl + Shift + F.
public int getMaxHealth() { return maxHealth; } public int getCurrentHealth() { return currentHealth; } public int getPower() { return power; } public int getSpeedX() { return speedX; } public int getCenterX() { return centerX; } public int getCenterY() { return centerY; } public Background getBg() { return bg; } public void setMaxHealth(int maxHealth) { this.maxHealth = maxHealth; } public void setCurrentHealth(int currentHealth) { this.currentHealth = currentHealth; } public void setPower(int power) { this.power = power; }
public void setSpeedX(int speedX) { this.speedX = speedX; } public void setCenterX(int centerX) { this.centerX = centerX; } public void setCenterY(int centerY) { this.centerY = centerY; } public void setBg(Background bg) { this.bg = bg; }
And you are done with the Enemy class for now! It should now look like this:
private int maxHealth, currentHealth, power, speedX, centerX, centerY; private Background bg = StartingClass.getBg1();
We will now be creating the Heliboy class. We will be creating this class slightly differently, so even if you know how to make classes in Eclipse, read the instructions
By taking steps 4 and 6 : choosing Enemy as a superclass and adding a constructor, Eclipse automatically generated the code in bo Replace the //TODO message with the following code: setCenterX(centerX);
setCenterY(centerY); And add the following parameters to the constructor so that we can use centerX and centerY:
public Heliboy(int centerX, int centerY) { And now you have the completed Heliboy class. Whenever you create a new Heliboy object, you will pass in two parameters, which
1. Download the above image, place it in your data folder, and rebuild the project by going to Project >> Clean >> Clean All Projects 2. Open StatingClass.java, and add heliboy to the Image declarations. private Image image, currentSprite, character, characterDown, characterJumped, background, heliboy; 3. In the // Image Setups section, define the heliboy Image object: heliboy = getImage(base, "data/heliboy.png");
2. Within the start() method, add the two bolded lines BELOW the Background creation (the Enemy superclass looks for these back bg1 = new Background(0, 0); ... hb = new Heliboy(340, 360); hb2 = new Heliboy(700, 360); ... Thread thread = new Thread(this); ... This will create two Heliboy objects centered at (340, 360) and (700, 360).
if (robot.isJumped()){ currentSprite = characterJumped; }else if (robot.isJumped() == false && robot.isDucked() == false){ currentSprite = character; } hb.update(); hb2.update(); bg1.update(); bg2.update(); repaint(); try { Thread.sleep(17); } catch (InterruptedException e) { e.printStackTrace(); } } }
You might notice that we never defined update() in the Heliboy class; however, since the Heliboy class inherited the Enemy class, c class. This is inheritance in action. :)
The Heliboy.png image you downloaded has dimensions 96 x 96. So, if we paint 48 pixels lower in both X and Y (by subtracting 48), Heliboy(340, 360);) will represent the centerX and centerY coordinates of the newly drawn images.
And that's it for Day 6! I hope you guys are beginning to pick up on the patterns, so you can add your own ideas to the game as they Thank you for supporting Kilobolt and tell your friends about us! :)
Download File
Go to Day 7: Shooting B
Welcome to Day 7. Thank you to those of you who submitted ideas on the story of this game. We will be implementing some of them
This lesson will be dedicated to arming our character, so that next week, when we go over collision detection, we will have more to w
This is how we will approach today's task: 1. We will first create a Projectiles class, which we will use as a blueprint for our bullet objects. 2. Within the Robot class, we will create a method that handles shooting. 3. Finally, in the StartingClass, we will paint the bullets to the screen, and allow the user to shoot with the Control button.
Lesson #2-20: Creating the Projectile Class The Projectile class, as mentioned, will be a blueprint for creating bullets! As with many of the previous classes, we will do the following: 1. Create the class. 2. Declare variables 3. Create an update() method and create helper Getters and Setters methods I. CREATING THE PROJECTILE CLASS 1. Right click on the kiloboltgame package >> New >> Class 2. Name it "Projectile". 3. Check the box labeled "Constructors from superclass:" 4. Press OK! You should now see the following: package kiloboltgame; public class Projectile { public Projectile() { // TODO Auto-generated constructor stub } } II. DECLARING VARIABLES Let's now declare the variables that we will be using. private int x, y, speedX; private boolean visible; These are class-wide variables, so they should be below the class declaration: public class Projectile{ private int x, y, speedX; private boolean visible; Now, we need to make some changes to the constructor: public Projectile(){ } As of now, it takes in no parameters. We want it to take in two values, a starting X coordinate and a starting Y coordinate, which will 1. So we add the two parameters: public Projectile(int startX, int startY){ }
2. To set these startX and startY variables into the class-wide x and y variables, and to set the speedXof the bullet and to initialize th constructor: x = startX; y = startY; speedX = 7; visible = true;
Your constructor should now look like this: public Projectile(int startX, int startY){
} III. CREATING THE UPDATE() AND HELPER METHODS The update() method in this class, as in the other classes, will be called with each update of the game. Naturally, then, we should change the location of the bullet here with respect to speed, and react when the bullet collides or goes off 1. So first create an update method: public void update(){
} 2. Add the following statements: x += speedX; if (x > 800) { visible = false; } - The x += speedX; statement will continually update the x coordinate by adding to it the speed in the x direction.
- The if statement checks if the bullet is off the screen, and makes it invisible. In the other classes, we will remove these bullets so th 3. Now create Getters and Setters: - Right click on the code, go to Source >> Generate Getters and Setters - Select All - Press OK You should get the following: public int getX() { return x; }
That should be it for the Projectile class for now. Here's the final code: FIGURE 2-29: PROJECTILE CLASS package kiloboltgame; public class Projectile { private int x, y, speedX; private boolean visible; public Projectile(int startX, int startY){ x = startX; y = startY; speedX = 7; visible = true; } public void update(){ x += speedX; if (x > 800){ visible = false; } } public int getX() { return x; } public int getY() { return y; } public int getSpeedX() { return speedX; } public boolean isVisible() { return visible; } public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } public void setSpeedX(int speedX) { this.speedX = speedX; } public void setVisible(boolean visible) { this.visible = visible; }
} Lesson #2-21: Creating an ArrayList in the Robot Class It would be extremely time consuming and inefficient if we were to manually create bullets each time that the player pressed "shoot."
us to store our Projectile objects in a "container" of increasing size. 1. To do so, we first declare (below all the other variable declarations): private ArrayList<Projectile> projectiles = new ArrayList<Projectile>(); (Make sure you import ArrayList, Ctrl+Shift+O).
Doing this creates an ArrayList (of Projectiles) called projectiles. 2. Now, below the jump() method, I will add the shoot() method: public void shoot() { Projectile p = new Projectile(centerX + 50, centerY - 25); projectiles.add(p); }
This method simply creates a new Projectile, labels it p, and adds it to the projectiles ArrayList. We create this 50 pixels to the right a 3. Create a Getter method as follows: public ArrayList getProjectiles() { return projectiles; } This will allow us to reference this newly created ArrayList from the other classes. Your finished Robot class will look like this: FIGURE 2-30: ROBOT CLASS (CHANGES IN BOLD) package kiloboltgame; import java.util.ArrayList; public class Robot { // Constants are Here final int JUMPSPEED = -15; final int MOVESPEED = 5; final int GROUND = 382; private int centerX = 100; private int centerY = GROUND; private boolean jumped = false; private boolean movingLeft = false; private boolean movingRight = false; private boolean ducked = false; private int speedX = 0; private int speedY = 1; private Background bg1 = StartingClass.getBg1(); private Background bg2 = StartingClass.getBg2(); private ArrayList<Projectile> projectiles = new ArrayList<Projectile>(); public void update() { // Moves Character or Scrolls Background accordingly. if (speedX < 0) { centerX += speedX; } if (speedX == 0 || speedX < 0) { bg1.setSpeedX(0); bg2.setSpeedX(0); } if (centerX <= 200 && speedX > 0) {
centerX += speedX; } if (speedX > 0 && centerX > 200){ bg1.setSpeedX(-MOVESPEED); bg2.setSpeedX(-MOVESPEED); } // Updates Y Position centerY += speedY; if (centerY + speedY >= GROUND) { centerY = GROUND; } // Handles Jumping if (jumped == true) { speedY += 1; if (centerY + speedY >= GROUND) { centerY = GROUND; speedY = 0; jumped = false; } } // Prevents going beyond X coordinate of 0 if (centerX + speedX <= 60) { centerX = 61; } } public void moveRight() { if (ducked == false) { speedX = MOVESPEED; } } public void moveLeft() { if (ducked == false) { speedX = -MOVESPEED; } } public void stopRight() { setMovingRight(false); stop(); } public void stopLeft() { setMovingLeft(false); stop(); } private void stop() { if (isMovingRight() == false && isMovingLeft() == false) { speedX = 0; }
public void jump() { if (jumped == false) { speedY = JUMPSPEED; jumped = true; } } public void shoot() { Projectile p = new Projectile(centerX + 50, centerY - 25); projectiles.add(p); }
public int getCenterX() { return centerX; } public int getCenterY() { return centerY; }
} Lesson #2-22: Painting the Bullets in the StartingClass I. CHANGING THE RUN() METHOD Make the changes in bold to your run() method: @Override public void run() { while (true) { robot.update(); if (robot.isJumped()) { currentSprite = characterJumped; } else if (robot.isJumped() == false && robot.isDucked() == false) { currentSprite = character; } ArrayList projectiles = robot.getProjectiles(); for (int i = 0; i < projectiles.size(); i++) { Projectile p = (Projectile) projectiles.get(i); if (p.isVisible() == true) { p.update(); } else { projectiles.remove(i); } } hb.update(); hb2.update(); bg1.update(); bg2.update(); repaint(); try { Thread.sleep(17); } catch (InterruptedException e) { e.printStackTrace(); } }
} (Make sure you import ArrayList: Ctrl+Shift+O) Let's talk about what we did here: We created a new ArrayList called projectiles and gave it the value of the projectiles ArrayList in robot. We then created a for loop, which runs as long as the index i is lesser than the size of the projectiles ArrayList (which is the number Then we check which Projectile object is in the i-th (4th, 5th, etc). place in the ArrayList (the Projectile object with an index of i).
To illustrate indexes, here's another example: List = [1, 2, 3, 5, 7] The indexes of each of these are: 0, 1, 2, 3, and 4. This means that the for loop will go through each object in the ArrayList of Projec
After that, it checks if this p is on the screen (isVisible()). If true, it updates it. Else, it removes this p by removing the i-th index from t
To summarize all this, we create a for loop with an index called i which goes up by 1 each time. We set the i-th Projectile in the proje on screen). II. CHANGING THE PAINT() METHOD Make the changes in bold in the paint() method: @Override public void paint(Graphics g) { g.drawImage(background, bg1.getBgX(), bg1.getBgY(), this); g.drawImage(background, bg2.getBgX(), bg2.getBgY(), this); ArrayList projectiles = robot.getProjectiles(); for (int i = 0; i < projectiles.size(); i++) { Projectile p = (Projectile) projectiles.get(i); g.setColor(Color.YELLOW); g.fillRect(p.getX(), p.getY(), 10, 5); } g.drawImage(currentSprite, robot.getCenterX() - 61, robot.getCenterY() - 63, this); g.drawImage(heliboy, hb.getCenterX() - 48, hb.getCenterY() - 48, this); g.drawImage(heliboy, hb2.getCenterX() - 48, hb2.getCenterY() - 48, this); } The same concepts apply here.
We created an ArrayList called projectiles (the one from the update() method is only usable by the update() method, so this is a uniq class.
We used a for loop and painted each Projectile object called p, which represents the i-th Projectile in the ArrayList at the current itera at the X and Y coordinate of the p Projectile. Lesson #2-23: Shooting on Keypress We want to use the Ctrl button to shoot our bullets. Navigate to the keyPressed() method in StartingClass, and make the following changes in bold: @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Move up"); break; case KeyEvent.VK_DOWN: currentSprite = characterDown; if (robot.isJumped() == false) { robot.setDucked(true); robot.setSpeedX(0); } break; case KeyEvent.VK_LEFT:
robot.moveLeft(); robot.setMovingLeft(true); break; case KeyEvent.VK_RIGHT: robot.moveRight(); robot.setMovingRight(true); break; case KeyEvent.VK_SPACE: robot.jump(); break;
All we did was add another case to the switch, so that when CONTROL is pressed, the robot calls itsshoot() method. We also check We can now run the game, and you should be able to shoot with the Control button! The final StartingClass follows (changes in bold): FIGURE 2-31: STARTINGCLASS, END OF DAY 7 package kiloboltgame; import java.applet.Applet; import java.awt.Color; import java.awt.Frame; import java.awt.Graphics; import java.awt.Image; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.net.URL; import java.util.ArrayList; public class StartingClass extends Applet implements Runnable, KeyListener { private Robot robot; private Heliboy hb, hb2; private Image image, currentSprite, character, characterDown, characterJumped, background, heliboy; private Graphics second; private URL base; private static Background bg1, bg2; @Override public void init() { setSize(800, 480); setBackground(Color.BLACK); setFocusable(true); addKeyListener(this); Frame frame = (Frame) this.getParent().getParent(); frame.setTitle("Q-Bot Alpha"); try { base = getDocumentBase(); } catch (Exception e) { // TODO: handle exception } // Image Setups
character = getImage(base, "data/character.png"); characterDown = getImage(base, "data/down.png"); characterJumped = getImage(base, "data/jumped.png"); currentSprite = character; heliboy = getImage(base, "data/heliboy.png"); background = getImage(base, "data/background.png"); } @Override public void start() { bg1 = new Background(0, 0); bg2 = new Background(2160, 0); hb = new Heliboy(340, 360); hb2 = new Heliboy(700, 360); robot = new Robot(); Thread thread = new Thread(this); thread.start(); } @Override public void stop() { // TODO Auto-generated method stub } @Override public void destroy() { // TODO Auto-generated method stub } @Override public void run() { while (true) { robot.update(); if (robot.isJumped()) { currentSprite = characterJumped; } else if (robot.isJumped() == false && robot.isDucked() == false) { currentSprite = character; }
ArrayList projectiles = robot.getProjectiles(); for (int i = 0; i < projectiles.size(); i++) { Projectile p = (Projectile) projectiles.get(i); if (p.isVisible() == true) { p.update(); } else { projectiles.remove(i); } } hb.update(); hb2.update(); bg1.update(); bg2.update(); repaint(); try { Thread.sleep(17); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void update(Graphics g) { if (image == null) {
image = createImage(this.getWidth(), this.getHeight()); second = image.getGraphics(); } second.setColor(getBackground()); second.fillRect(0, 0, getWidth(), getHeight()); second.setColor(getForeground()); paint(second); g.drawImage(image, 0, 0, this); } @Override public void paint(Graphics g) { g.drawImage(background, bg1.getBgX(), bg1.getBgY(), this); g.drawImage(background, bg2.getBgX(), bg2.getBgY(), this); ArrayList projectiles = robot.getProjectiles(); for (int i = 0; i < projectiles.size(); i++) { Projectile p = (Projectile) projectiles.get(i); g.setColor(Color.YELLOW); g.fillRect(p.getX(), p.getY(), 10, 5); } g.drawImage(currentSprite, robot.getCenterX() - 61, robot.getCenterY() - 63, this); g.drawImage(heliboy, hb.getCenterX() - 48, hb.getCenterY() - 48, this); g.drawImage(heliboy, hb2.getCenterX() - 48, hb2.getCenterY() - 48, this); } @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Move up"); break; case KeyEvent.VK_DOWN: currentSprite = characterDown; if (robot.isJumped() == false) { robot.setDucked(true); robot.setSpeedX(0); } break; case KeyEvent.VK_LEFT: robot.moveLeft(); robot.setMovingLeft(true); break; case KeyEvent.VK_RIGHT: robot.moveRight(); robot.setMovingRight(true); break; case KeyEvent.VK_SPACE: robot.jump(); break; case KeyEvent.VK_CONTROL: if (robot.isDucked() == false && robot.isJumped() == false) { robot.shoot(); } break;
} } @Override public void keyReleased(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: System.out.println("Stop moving up"); break; case KeyEvent.VK_DOWN: currentSprite = character; robot.setDucked(false); break; case KeyEvent.VK_LEFT: robot.stopLeft(); break; case KeyEvent.VK_RIGHT: robot.stopRight(); break; case KeyEvent.VK_SPACE: break; } } @Override public void keyTyped(KeyEvent e) { // TODO Auto-generated method stub } public static Background getBg1() { return bg1; } public static Background getBg2() { return bg2; } } Thank you for reading this lesson! Things are starting to get a bit repetitive, so we will be mixing things up next time around. As always, we appreciate your support, and we're extremely pleased that we broke 750 Facebook Likes!
Download File
Go to Day 8: Anima
Welcome to Day 9! Today's lesson will be short, but full of information. We are building towards loading pre-made Level designs and rendering tiles to the screen. To do so, we must first go over some basic fundamentals. Therefore, we will be creating a new project to practice and study these new topics! Lesson #2-26: Arrays One of the most powerful tools in a programmer's arsenal is the array. You can think of an array as a list of items. Let's say you had three favorite things: 1. Playing 2. Eating 3. Sleeping These three things could easily be categorized into one list so that you can refer people to this list rather than three separate variables. That's what an array does, in principle. 1. To see this in action, create a new Project, and name it whatever you want. 2. Within the src folder, create a new Java class called: Array. 3. Add the main method: public static void main(String args[]) { } 4. Within the main method, declare the following: String[] favoriteThings = new String [3]; This creates an Array (list) of String objects called stringArray, and allocates memory to accommodate three (3) items. Alternatively, this same line can be written as below, by shifting the brackets: String favoriteThings[] = new String [3]; Astute observers will recognize this syntax. If you examine the parameter of the main method: String args[], you will now see that the main method takes in an array of Strings as a parameter. These arrays are passed in at a lower level, and you will not encounter them in Eclipse.
This code introduces nothing new. It is just application of previously-covered topics. Make sure you understand this before moving on! If we place this for loop at the bottom of the main method, we will see the following output:
Understand how it works? Then you are ready for the next level. Lesson #2-27: Two-Dimensional Arrays The most practical application of a two-dimensional Array is when you need to store a list of values with (x, y) positions. For our game's purposes, we will use a 2D array to hold a value at each position, and then paint the object/character/platform associated with said value at its (x, y) coordinate.
2D Arrays are very similar to single dimensional arrays, but we add a second dimension (like adding a width). Two create a 2D array, we simply state: int[][] intArray = new int[30][50]; //Creates a 2D array of integers with height 30 and width 50. 1. Create a new class called Tilemap. 2. Add the following code:
public static void main(String args[]) { Tilemap tp = new Tilemap(); } public Tilemap() { int[][] tilemap = new int[30][50]; System.out.println("New Tilemap created."); Random r = new Random();
int rows = tilemap.length; int columns = tilemap[1].length; printTiles(rows, columns, tilemap, r); } private void printTiles(int rows, int columns, int[][] tilemap, Random r) { for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { tilemap[i][j] = r.nextInt(5); System.out.print(" " + tilemap[i][j]); } System.out.println(""); } } } Before you run this code, let's talk about what each segment of code does. 1. We import the Random class (to be used later) 2. In the main method, we create a new Tilemap object called tp. This calls the constructor below the main method. 3. In the constructor, we create a new two dimensional array called tilemap, with height 30, and width 50. - We also create a Random object called r Two-dimensional arrays can be thought of as Arrays of Arrays. Meaning that it is a list of lists. The larger list has 30 empty spots. Each of those 30 spots has 50 spots across. Therefore, when we refer to tilemap.length, we get the length of the larger list, which is 30. When we refer to tilemap[1].length, we get the length of the small list with index 1 in the large list, which is the second small list in the large list. This will have a value of 50. 4. We then call the printTiles() method, which takes in various parameters, uses two for loops (one for the columns and another for the rows). This printTiles() method gives each space a value between 0 to 4 (at random). Then we print each row and column. The result should look something like this:
In the next lesson, we will associate each integer from 0 to 4 with a colored rectangle and fill the screen. This will then segue into a Unit 3, beginning with a series of lessons on Level design/rendering and collision physics!
Hello and welcome! This is the last lesson of Unit 2. More fun is just around the corner in Unit 3. Let's finish strong! In today's lesson, we will be building upon what we covered in Day 9. If you have not looked at Day 9 yet, please do so before proceeding! What we will be covering: 1. We will be creating a two-dimensional Array that holds a random set of values. 2. Using this two-dimensional array, we will print colored squares to the screen.
What is the significance of this lesson? When we continue our 2D game development, we will be creating levels (maps) using text files. In each text file, we will represent ob with keyboard characters, and our game will parse the file to create levels that we can interact with. This lesson will be an application of what we learned in Day 9. After this, we should be able to accomplish the above task with ease
lessons. Note: We will be creating a NEW class today. You do not need to open any previous files. Let's begin.
3. Override the init() and paint() methods. Since we have extended an Applet (inheritance), Java will look for the paint() and init() methods. As we will be defining these method than using the one in the Applet method, so we use the " @Override" keyword. The two methods will look like this (Make sure to import Graphics) (changes in Bold): import java.applet.Applet; import java.awt.Graphics; public class Renderer extends Applet{ @Override public void init() { } @Override public void paint(Graphics g) { } } 4. Define the init() method.
In the init() method, we want to do the following: - Set the size of the window. - Set the color of the Background - Create the Tilemap. So we add the following lines of code: setSize(800, 480); setBackground(Color.BLACK); createTilemap(); Eclipse will give you an error as follows:
Since we have never created a createTilemap() method, we must do so right now. 5. Click on the 1 quickfix: Create method 'createTilemap()'. This should create a method as follows:
static int[][] tilemap; static int rows, columns; This creates a static (class-wide) two-dimensional Array called tilemap and two integers called rowsand columns. Scroll back inside the createTilemap() method. We will now define the three variables by declaring the following:
- The first line creates a new 2D Array with 50 elements, which each contain 30 elements. - tilemap.length is 50, as tilemap contains 50 elements. - Each of these 50 elements contain 30 elements. Therefore, tilemap[1] refers to the element with index 1 in this 50 element list (the fifty). Note: Remember that indexes start at 0. Note 2: We can refer to all 50 elements in tilemap using the indexes: 0 through 49.
- To fill the tilemap Array randomly, we first create a new Random object: Random r = new Random();
- Then we use two for loops to fill each index with a random number between 0 and 4, inclusive: for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { tilemap[i][j] = r.nextInt(5); } }
Note: Recall that tilemap[i][j] refers to the spot with coordinates (i, j) inside the 2D array. Refer to this simplified illustration if you are confused!
for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { tilemap[i][j] = r.nextInt(5); } } } @Override public void paint(Graphics g) { } } With the tilemap created, we can now paint it to the screen!
This is the same nested for loop we saw in the createTilemap() method. We will use i and j to refer to locations in the 2D Array.
We want to translate this (i, j) location into a coordinate system of (16*i, 16*j), because we want to represent, for example, (50, 30), a So within the inner for loop, we add the following in BOLD: for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; int mod_i = 16*i; int mod_j = 16*j; } }
Note: The underscore "_" does not have any meaning. It is just part of the variable names, which stand for modified I and m
Now directly below the two variable declarations (and still within the inner for loop), we need to create a switch that takes tilemap[i][ that switches are used to compare the value of a key against cases) and compare with the integers 0 through 4, inclusive.
For each case, we will set the color of g, which needs to be set directly before you paint an object, and then paint a rectangle on the coordinates (mod_i, mod_j). The full class will now look like this:
import java.util.Random; public class Renderer2 extends Applet { static int[][] tilemap; static int rows, columns; @Override public void init() { setSize(800, 480); setBackground(Color.BLACK); createTilemap(); } @Override public void paint(Graphics g) { for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { int mod_i = 16*i; int mod_j = 16*j; switch (tilemap[i][j]) { case 0: g.setColor(Color.RED); g.fillRect(mod_i, mod_j, 16, 16); break; case 1: g.setColor(Color.BLUE); g.fillRect(mod_i, mod_j, 16, 16); break; case 2: g.setColor(Color.YELLOW); g.fillRect(mod_i, mod_j, 16, 16); break; case 3: g.setColor(Color.WHITE); g.fillRect(mod_i, mod_j, 16, 16); break; case 4: g.setColor(Color.GREEN); g.fillRect(mod_i, mod_j, 16, 16); break; } } } } private void createTilemap() { tilemap = new int[50][30]; rows = tilemap.length; columns = tilemap[49].length;
Random r = new Random(); for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { tilemap[i][j] = r.nextInt(5); }
} } } Now press Play! You should see a beautiful painting like this:
And with that, our mini-break from 2D game development (Days 9 and 10) is over, and so concludes Unit 2! In the next lessons, we will be applying these techniques to create our levels, and eventually introduce collision detection.
I hope that this lesson made sense to everyone! I know that everyone is at different levels of Java experience and understanding, an balance as to not bore people and to not leave people behind. I might assume that you remember something from a previous lesson forgotten it already. So let me know if you need anything better explained. I'd be glad to do that for you.
And thank you to everyone who liked Kilobolt Studios on Facebook! We have surpassed 1,000 likes and it is all because of your h In the next few months, Kilobolt will expand and offer more tutorials, better games, and a super-cool website. If you would like to hel donating!
Continue to U