I will continue my previous post with adding different weapons to Asteroid Runner and my TileImageLoader class, which came about from wanting to add the new weapons. Who knows where it will go from there... (Warning: more nerdy talk below)
As with most games with weapons, more is generally a good thing. :D Because Asteroid Runner began as nothing more than drawing tests, my ship had only a single projectile to shoot. After developing the game further, I realized that I wanted to have a few more weapons. I was a bit afraid to look at my code, because I knew there was no simple way to add anything different.
I started to code up a weapon class before I stopped myself and took a step back. So, I need new weapons, and weapons shoot projectiles. These should clearly be separate things; the weapon defines how the projectile(s) are fired, and the projectile determines movement and behavior. While obviously related, they are quite different, and following the practice of keeping code elements specific, I decided to create a Weapon abstract class and a Projectile abstract class. I knew that once these were implemented, I should be able to easily subclass them to fit my needs.
The first thing I had to do was remove all of the weapon firing and projectile related methods and properties from the main part of the game and find a way to implement them into my new classes. It took a bit of rethinking but I managed to separate elements into the 2 abstract classes while creating the first subclasses for the Blaster. I implemented the new Blaster classes into the main code with a bit of generic practices and had them working within the game! On to the fun part: more weapons!
I had a few weapon ideas when I started rebuilding the weapon system so I started by implementing the first 2 interesting ones that became the Pulsar and the Laser. The idea for the Pulsar was 2 green balls that orbit around a central point and fire fairly quickly, and the idea for the Laser was to have a relatively slow projectile that would pass through asteroids while continuing to damage them.
Creating the Pulsar caused me to change a few things with my code. I decided that the weapon would create multiple projectiles, so there obviously had to be some modifier to differentiate one projectile from the other, otherwise they would appear the same. I added a 3rd variable to my Create method (it previously had only the x & y position) that allowed me to do anything I wanted based on the implementation.
For the Pulsar, I knew the standard 2D design for the type of projectile movement I envisioned was a wave pattern; trigonometry to the rescue! I did a bit of fiddling around with the math and finally got a wave shape that I liked. I had the Create method passing in a 0 and a 1 for the modifier, so I had to figure out a way to use this to create the difference. The 2 projectiles were going to be in line with each other but moving back in forth; I decided to modify the phase of the wave to accomplish this. I added a simple element to my wave function (t * Math.PI) that allowed me to easily modify the phase. At a 0, the sine wave would be normal; and at a 1, the phase would be shifted by Pi, which results in an exactly opposite wave. (I did not pull all of this from high school math memory, I did a bit of googling and testing to get the final results.)
The Laser was a pretty simple implementation except for one change I had to add: a hit method. Previously, when a projectile hit an asteroid, it was simply removed from the game by changing a boolean. The issue, of course, was that I wanted all other projectiles to still do that. Instead of only a boolean change, I created a hit method in the Projectile class that performs the change. This allows me to override the method inside the Laser projectile class so that it does nothing. Now, it behaves the way I want.
To explain what I want to about images, I need to take a step back near the beginning. My initial plan was to have all of the images on a single image of 10 x 10 cells, each cell being 16 x 16 pixels (simply the resolution I chose). Through my finagling (haha! that's a real word!), I was able to read specific portions of this image as I wanted for my game. When I got the idea to start adding weapons, I realized how quick things would get unorganized, and certain things drive me insane if they are unorganized. I decided to separate each type of image (projectiles, obstacles, etc) into their own file that was a single strip (horizontal) of 10 cells.
While it is not hard to import the files individually and select the images I need, I knew I could make the process smoother, so I created another small utility class I named TileImageLoader. Currently, it only supports a strip of images, but I will probably change that later. When you instantiate this class, you give it the path to the folder of images (all of my images are in a single folder) and it determines the size of each cell. Once that is established, you can simply call getArt with a file name and image index (starting at 0) to retrieve what you want. (This class will be embedded at the end of this post.)
Up until this point, all of the BufferedImage variables were inside the GUI class since that had been my primary place for anything graphics related. I decided to pull all of the variables and methods relating to images out of that class and create an Images class with static variables. While the Images class does nothing more than hold variables and call the methods to create them, it is convenient for having all of that information in a single location without having it clutter up other classes. To use an image, I simply reference Images.asteroidArt for example. (This class will also be embedded at the end of this post)
I have been doing a few things with images that seem as if they would be unconventional, but they have taught me things and I believe it to be an interesting approach. For example, my asteroids have a visible effect when they are hit; this is not a separate image in an art file. When the Asteroid class is instantiated, the image for the asteroid is passed in. Immediately, the image is read pixel by pixel (only 256 steps for my default resolution) to get the RGB values, these values are modified, and they are saved to an alternate image. When an asteroid is hit, a timer is created; while the timer is greater than 0, the alternate image is displayed.
This allows me to only create 1 image for the asteroid, reducing the number of art that must be created. While it is not necessarily an efficient way to program, it does allow for interesting possibilities. I am currently creating more of the GUI for the game, primarily a display of available weapons. I am creating 1 image for each icon and creating the alternate images within the code of the game.
I completely understand that this could potentially slow down my game, but the limited use of this idea has not caused any performance issues. If my idea for the weapon icons does not work out, I will change my approach and draw the individual images. I don't mind the art aspect at all (even if I'm not that good at it), but I enjoy the concept of building game elements at runtime. One idea of my game is to support any resolution images as long as they are square tiles and keep the same object proportions. Modifying images within the game would be even less that would have to be created for alternate designs.
I do enjoy implementing new ideas and trying things that are not necessarily the "right" way to do something. Sometimes being "wrong" is more fun. :-P I am going to give an attempt at animated sprites or at least a different method of altering the current image. Until next time, I will leave you with my TileImageLoader and Images classes.
P.S. Wow. I just realized how long this is...
import java.awt.image.BufferedImage; import java.io.IOException; import javax.imageio.ImageIO; public class TileImageLoader { //art is created in single row of tiles, generally 10 images long //directory where images are stored private String path; public TileImageLoader(String path) { this.path = path; } //get art based on file name and image index number public BufferedImage getArt(String file, int index) { try { BufferedImage art = ImageIO.read(this.getClass().getResource(path + file)); int size = art.getHeight(); return art.getSubimage(index * size, 0, size, size); } catch(IOException ex) {ex.printStackTrace();} //required return - image not loaded properly return null; } }
import java.awt.image.BufferedImage; import astroidrunner.util.TileImageLoader; public class Images { String path = "/tex/"; TileImageLoader il = new TileImageLoader(path); //size of art tiles (for game scaling) private static int tileSize; //art for the ship and asteroids public static BufferedImage shipArt; public static BufferedImage asteroidArt; //art for the projectiles public static BufferedImage pBlasterArt; public static BufferedImage pPulsarArt; public static BufferedImage pLaserArt; public static BufferedImage pRocketArt; public static BufferedImage pTripleArt; //art for the weapon selection graphics public static BufferedImage gBlasterArt; public static BufferedImage gPulsarArt; public static BufferedImage gLaserArt; public static BufferedImage gRocketArt; public static BufferedImage gTripleArt; //CONSTRUCTOR public Images() { //load images and get tileSize shipArt = il.getArt("ship.png", 0); tileSize = shipArt.getHeight(); //the ship art is used as a basis, all art should be the same height asteroidArt = il.getArt("obstacles.png", 0); pBlasterArt = il.getArt("projectiles.png", 0); pPulsarArt = il.getArt("projectiles.png", 1); pLaserArt = il.getArt("projectiles.png", 2); pRocketArt = il.getArt("projectiles.png", 3); pTripleArt = il.getArt("projectiles.png", 4); gBlasterArt = il.getArt("ui_weapons.png", 0); gPulsarArt = il.getArt("ui_weapons.png", 1); gLaserArt = il.getArt("ui_weapons.png", 2); gRocketArt = il.getArt("ui_weapons.png", 3); gTripleArt = il.getArt("ui_weapons.png", 4); } //return the size of a tile public static int getTileSize() { return tileSize; } }