Hey folks, Cody here. Sorry I haven't been posting a whole lot, but I'll be spending the next 4 days in San Francisco for Game Developers Conference! This of course means things may not progress too much this week, but I figured I'd drop in and talk about a few of the mechanics I'm working on for world 2.
The first, and most complex thing being added is the new power! As you may have seen in the previous post, world 2 will feature a robot power that allows the player to fire bullets. However, Robot Shadow will be unable to jump! This allows us to focus more on puzzles going into world 2. The robot power is actually being drawn with two separate bitmaps: one for the body and one for the arm. This lets me move shadow around on screen while independently rotating his arm to face whichever direction he's aiming. I'll be adding controls to let the player touch a position on screen to aim shadows arm, then press the fire button (which replaces Shadow's jump button) to shoot! Bullets will both kill certain enemies and hit buttons, which leads into the next mechanic I'm working on.
Buttons! World 2 will introduce buttons and shutters which are closed until the buttons are hit with a bullet. We will be using this to design puzzles in which the player needs to find a robot power and reach the button with it's limited mobility to proceed. The tricky aspect here is that in the past, all collidable objects have been a part of the level. The introduction of shutters means editing the tile map itself, which should be much more efficient than things like the light gates which utilize an independent object which must be updated. My current plan is to iterate through the tile map matrix when the button is pushed, looking for shutter tiles and replacing them with open tiles.
Finally, if all goes well, world 2 will have moving platforms. These are a must for moving robot shadow across gaps, and Marty has been pushing for them since world 1. Since these platforms will need to be animated, it's not really practical to make them a part of the tile map. That means they will be the first collidable object, which means checking their position when they are present. I'll also need to add some code to be sure the player moves with the platform if they are standing on it, so that should be fun!
That's all for now, look forward to all these fun things as soon as world 2 is finished!
Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts
Monday, March 5, 2012
Wednesday, February 8, 2012
Boss Fights
Hey everybody! Cody here, sorry for the lack of updates. Trying to balance a game, work, and a full engineering course load is difficult at best. But worth it!
I wanted to talk about the boss fight. The first world's boss is done, and it took a lot of thinking to figure out exactly how I wanted to do it. I think I found a decent solution though, and Ill explain why it was so much harder than an enemy or something.
Good programming is all about modularity. You find ways to represent general cases, arbitrary values, etc. Why make a screwdriver when you can make a Swiss army knife? For example, the enemy class is a class that can represent any enemy we have created and (in theory) any enemy we will create. I do this by assuming very little about the enemy being created and letting many aspects such as size, behavior, and sprite sheet be defined when I create the enemy.
So why couldn't I take this approach with bosses and make a general Boss class? Well, I definitely juggled with the idea, but the one boss that has been designed is much more complex than an enemy. Combine that with the fact that we don't even know what the other bosses will do yet and it just wasn't going to work out. However, I still need to classify all of my bosses under some distinct label that I can pass into my level. Things get really messy if I can't file all these bosses under some category. And that, my friends, is what Interfaces are for!
There's an Interface, short and simple. And yes, I left the comment in which I lament over past mistakes. Hindsight is 20/20.. Anyways, think of this as a skeleton for a class. I can now create separate classes for every boss I ever want to create that implement this interface. That means they are required to define the 4 methods seen above, and I can call those methods without necessarily knowing what sort of boss I'm dealing with, but I can also write completely separate code for each boss. Cool, huh?
So since the boss is only being used once in one level under one circumstance, I more or less wrapped every detail concerning it into it's class. Most enemies are given a position as you saw above, the boss actually has his position defined inside of his own code. He goes there, everyone else deals with it. Then I just had to whip up some code drawing a huge static body, two animated arms, and animated eyelids. The arms switch animation when he charges up, and then bullets fly in every direction! Whenever we start making a new boss, we aren't bound to any of the limitations on how I developed this first guy; I can essentially rewrite everything and pass it to anything looking for a Boss object. So without further ado, here he is!
As a neat side note, absolutely no collision is defined for this dude. We instead created 2 new tiles, both transparent. One acted like a wall, other like a spike. I then pasted the entire boss onto the map, traced over him with the tiles (wall tiles for the black region, spike tiles for the white), and then deleted him off of it. Worked like a charm!
Hopefully we'll have the public build updated soon with the boss fight and the nifty screen shake and fade to black preceding it. And a new cutscene for good measure. Also, I'm working on rooting my phone so I can record some gameplay for those without Androids who want to see what the game actually looks like. Hang tight!
I wanted to talk about the boss fight. The first world's boss is done, and it took a lot of thinking to figure out exactly how I wanted to do it. I think I found a decent solution though, and Ill explain why it was so much harder than an enemy or something.
Good programming is all about modularity. You find ways to represent general cases, arbitrary values, etc. Why make a screwdriver when you can make a Swiss army knife? For example, the enemy class is a class that can represent any enemy we have created and (in theory) any enemy we will create. I do this by assuming very little about the enemy being created and letting many aspects such as size, behavior, and sprite sheet be defined when I create the enemy.
public Enemy(Assets assets, int type, int height, int width, int cols, int numFrames, int xPos, int yPos, int xSpeed, int ySpeed) { //values initialized here }
So why couldn't I take this approach with bosses and make a general Boss class? Well, I definitely juggled with the idea, but the one boss that has been designed is much more complex than an enemy. Combine that with the fact that we don't even know what the other bosses will do yet and it just wasn't going to work out. However, I still need to classify all of my bosses under some distinct label that I can pass into my level. Things get really messy if I can't file all these bosses under some category. And that, my friends, is what Interfaces are for!
//Boss interface gives me a standard format for creating and storing a variety of //bosses for the different levels. In retrospect, I should have implemented the //player as an interface. public interface Boss { public void draw(Canvas c); public void updatePhysics(double elapsed); public void update(); public void killBoss(); }
There's an Interface, short and simple. And yes, I left the comment in which I lament over past mistakes. Hindsight is 20/20.. Anyways, think of this as a skeleton for a class. I can now create separate classes for every boss I ever want to create that implement this interface. That means they are required to define the 4 methods seen above, and I can call those methods without necessarily knowing what sort of boss I'm dealing with, but I can also write completely separate code for each boss. Cool, huh?
So since the boss is only being used once in one level under one circumstance, I more or less wrapped every detail concerning it into it's class. Most enemies are given a position as you saw above, the boss actually has his position defined inside of his own code. He goes there, everyone else deals with it. Then I just had to whip up some code drawing a huge static body, two animated arms, and animated eyelids. The arms switch animation when he charges up, and then bullets fly in every direction! Whenever we start making a new boss, we aren't bound to any of the limitations on how I developed this first guy; I can essentially rewrite everything and pass it to anything looking for a Boss object. So without further ado, here he is!
As a neat side note, absolutely no collision is defined for this dude. We instead created 2 new tiles, both transparent. One acted like a wall, other like a spike. I then pasted the entire boss onto the map, traced over him with the tiles (wall tiles for the black region, spike tiles for the white), and then deleted him off of it. Worked like a charm!
Hopefully we'll have the public build updated soon with the boss fight and the nifty screen shake and fade to black preceding it. And a new cutscene for good measure. Also, I'm working on rooting my phone so I can record some gameplay for those without Androids who want to see what the game actually looks like. Hang tight!
Sunday, January 29, 2012
Enemies
I haven't posted about game mechanics in a bit, so I guess I'll talk about how I implemented enemies! This will also contain a modification I made to my asset system, as well as another one that I would like to make in the future. So here we go!
An instance of the Enemy class is created for almost everything in the game that has the potential to kill the player. The exceptions to this are doors and light gates (implemented with another class called Static) and the spikes that line the edges of some levels.
Now in order to create a single Enemy class that can represent any type of enemy, I had to use a lot of switch statements. For the non programmers out there, a switch statement lets you define several different cases and execute a different block of code depending on which case fits. For my purposes, I could check which type of enemy a particular instance of the Enemy class is and make sure it behaves correctly.
This setup makes it very simple for me to add new enemies in. I simply need to add cases defining it's hitbox and movement pattern and I'm set!
Another consideration I needed to make was how I manage the bitmaps containing spritesheets for these enemies. My original implementation had all of the enemy sprite sheets aliased correctly such that each sheet was loaded once per type of enemy. However, whenever an enemy switched directions or was flipped from on to off, the change was handled within the enemy class. This meant that eventually, every enemy instance would have it's own sprite sheet. Terribly inefficient, and unavoidable as long as sprite sheet changes are handled by the enemy itself.
To fix this, I modified my Assets structure (yet again) to contain a bitmap manager for the Enemy class. A data structure was built that stores two sprite sheets per enemy type, labeled by the enemy's type name. The reason I stored two per enemy is that if we have some slimes facing left and some facing right, they can't be read off of the same sheet! Whenever an Enemy is created, it checks the data structure to see if it's bitmaps are loaded in and loads them if needed. When a switch is flipped, the two bitmaps for that enemy are overwritten with the new versions. This way, I can have 2 slimes or 200 slimes in a level with virtually the same memory footprint!
In the future, I'd like to move ALL bitmap allocations into the Assets class. Right now the Player sets up its own bitmaps, the Background sets up a bitmap, etc. A much more elegant and less bug-prone setup would be for the Assets instance created for a level to have a single data structure with all of the bitmaps used for the level. It can then pass every single asset a reference to it's bitmaps, and requires much less work when I want to clean out all of the bitmaps before loading a new level.
An instance of the Enemy class is created for almost everything in the game that has the potential to kill the player. The exceptions to this are doors and light gates (implemented with another class called Static) and the spikes that line the edges of some levels.
Now in order to create a single Enemy class that can represent any type of enemy, I had to use a lot of switch statements. For the non programmers out there, a switch statement lets you define several different cases and execute a different block of code depending on which case fits. For my purposes, I could check which type of enemy a particular instance of the Enemy class is and make sure it behaves correctly.
//Conditional x collision depending on enemy type if(type==SLIME){ if(assets.getBg().checkCollisionX(collision, yPos) || assets.getBg().checkGap(collision, yPos+spriteHeight)){ collided=true; } }else if(type==STINGER){ if(assets.getBg().checkCollisionX(collision, yPos) || assets.getBg().checkCollisionX(collision, yPos+spriteHeight-assets.getBg().tileSize) || (xPos<=xBoundL && xSpeed<0) || (xPos>=xBoundR&&xSpeed>0)){ collided=true; }I again use a switch statement to define different hitboxes depending on the type of enemy
public Rect getLoc(){ if(type==STINGER){ return new Rect(xPos+40,yPos+40,xPos+spriteWidth-20,yPos+spriteHeight-25); }else if(type==LIGHT_TRAP){ return new Rect(xPos,yPos+15,xPos+spriteWidth,yPos+spriteHeight); }else{ return new Rect(xPos,yPos,xPos+spriteWidth,yPos+spriteHeight); } }
This setup makes it very simple for me to add new enemies in. I simply need to add cases defining it's hitbox and movement pattern and I'm set!
Another consideration I needed to make was how I manage the bitmaps containing spritesheets for these enemies. My original implementation had all of the enemy sprite sheets aliased correctly such that each sheet was loaded once per type of enemy. However, whenever an enemy switched directions or was flipped from on to off, the change was handled within the enemy class. This meant that eventually, every enemy instance would have it's own sprite sheet. Terribly inefficient, and unavoidable as long as sprite sheet changes are handled by the enemy itself.
To fix this, I modified my Assets structure (yet again) to contain a bitmap manager for the Enemy class. A data structure was built that stores two sprite sheets per enemy type, labeled by the enemy's type name. The reason I stored two per enemy is that if we have some slimes facing left and some facing right, they can't be read off of the same sheet! Whenever an Enemy is created, it checks the data structure to see if it's bitmaps are loaded in and loads them if needed. When a switch is flipped, the two bitmaps for that enemy are overwritten with the new versions. This way, I can have 2 slimes or 200 slimes in a level with virtually the same memory footprint!
In the future, I'd like to move ALL bitmap allocations into the Assets class. Right now the Player sets up its own bitmaps, the Background sets up a bitmap, etc. A much more elegant and less bug-prone setup would be for the Assets instance created for a level to have a single data structure with all of the bitmaps used for the level. It can then pass every single asset a reference to it's bitmaps, and requires much less work when I want to clean out all of the bitmaps before loading a new level.
Sunday, January 22, 2012
Levels
This is kind of a fun one to talk about. The level system is the subject of the single largest and most complicated overhaul I had to make in the course of this project! It was a tedious and terrifying process, but the results are awesome. I'll start with the old system.
In our original level system, the Background object mentioned in a previous post held a Bitmap. This Bitmap was essentially the entire level. Here's what one looks like
As a fun aside, here's the image Marty would give me to place the enemies and such.
The player and enemies would actually refer to this bitmap in memory, looking up pixel positions to see if they were black. This is how collision was managed, and it wasn't a very good way for a few reasons. First off, we were limited to black platforms/walls/etc and nothing else that was a part of the background layer could be black. However, it lead to a MUCH more significant issue. Memory management.
Alright, this is going to require a hardcore digression so skip this paragraph if you aren't interested in how memory is managed. Image files on a computer can be saved in several formats: GIF, JPEG, BMP, etc. However, this file extension only refers to how the image is compressed to store on a hard drive. While actually working with the picture (viewing it or manipulating it, for example), it is expanded and stored in RAM, which I'll refer to as memory here. The images size in memory has nothing to do with what format it's saved as; in fact, it's pretty simple to figure out it's size. Multiply the number of pixels by the bit depth (typically 32) and divide it by 8 bits in a byte to get the number of bytes. One megabyte is a million bytes (sort of), so the rather small level depicted above is 1600*1966 pixels, or about 12.5 MB. On Android, these images are stored in an area of memory called the native heap which is as small as 24MB on some devices and has no problem crashing your app if you try to go past that. Since I'm also loading player sprites, enemies, and all sorts of other fun images, this level crashed phones.
So I made a new system, that didn't suck. In the new system, I used tiles. Each tile is loaded only once, and a level file is loaded in that is used to create a massive data structure describing what kind of tile goes where. We only needed to load around 20 or 30 different 32x32 pixel tiles into memory, and we could create massive levels! This was stress tested with a 200x200 tile level, which ran perfectly but was absolutely miserable to navigate (thanks Alex Mateik..) In this fancy new system, levels are made like this.
Marty loads in a tile set, creates a level, and sends me the tile set and .tmx file produced. The level is parsed out using a nice little chunk of code called TMXLoader (Thanks David Iserovich) and I create a bitmap of what the player can currently see. The bitmap is constantly recreated, and all of our memory issues are solved!
Collision is also a bit easier in this system. Rather than checking pixels, I can look up what tile the player is entering and decide if it is a collision tile or not. To determine what tile number each type of tile is, I open up the .tmx file in a text editor and I am greeted by something like this.
Slightly eye-melting, I admit. However, squint your eyes and you should see a level amidst these numbers! 0 is a transparent tile (or lack of tile), 1 is a solid block, 2 is a platform, and all of the other numbers are pieces of the curved cliff edges. With this, I can decide what tiles should cause the player to react in what ways, and keep the player from randomly falling through floors! Well..sometimes.
In our original level system, the Background object mentioned in a previous post held a Bitmap. This Bitmap was essentially the entire level. Here's what one looks like
As a fun aside, here's the image Marty would give me to place the enemies and such.
The player and enemies would actually refer to this bitmap in memory, looking up pixel positions to see if they were black. This is how collision was managed, and it wasn't a very good way for a few reasons. First off, we were limited to black platforms/walls/etc and nothing else that was a part of the background layer could be black. However, it lead to a MUCH more significant issue. Memory management.
Alright, this is going to require a hardcore digression so skip this paragraph if you aren't interested in how memory is managed. Image files on a computer can be saved in several formats: GIF, JPEG, BMP, etc. However, this file extension only refers to how the image is compressed to store on a hard drive. While actually working with the picture (viewing it or manipulating it, for example), it is expanded and stored in RAM, which I'll refer to as memory here. The images size in memory has nothing to do with what format it's saved as; in fact, it's pretty simple to figure out it's size. Multiply the number of pixels by the bit depth (typically 32) and divide it by 8 bits in a byte to get the number of bytes. One megabyte is a million bytes (sort of), so the rather small level depicted above is 1600*1966 pixels, or about 12.5 MB. On Android, these images are stored in an area of memory called the native heap which is as small as 24MB on some devices and has no problem crashing your app if you try to go past that. Since I'm also loading player sprites, enemies, and all sorts of other fun images, this level crashed phones.
So I made a new system, that didn't suck. In the new system, I used tiles. Each tile is loaded only once, and a level file is loaded in that is used to create a massive data structure describing what kind of tile goes where. We only needed to load around 20 or 30 different 32x32 pixel tiles into memory, and we could create massive levels! This was stress tested with a 200x200 tile level, which ran perfectly but was absolutely miserable to navigate (thanks Alex Mateik..) In this fancy new system, levels are made like this.
Marty loads in a tile set, creates a level, and sends me the tile set and .tmx file produced. The level is parsed out using a nice little chunk of code called TMXLoader (Thanks David Iserovich) and I create a bitmap of what the player can currently see. The bitmap is constantly recreated, and all of our memory issues are solved!
Collision is also a bit easier in this system. Rather than checking pixels, I can look up what tile the player is entering and decide if it is a collision tile or not. To determine what tile number each type of tile is, I open up the .tmx file in a text editor and I am greeted by something like this.
Slightly eye-melting, I admit. However, squint your eyes and you should see a level amidst these numbers! 0 is a transparent tile (or lack of tile), 1 is a solid block, 2 is a platform, and all of the other numbers are pieces of the curved cliff edges. With this, I can decide what tiles should cause the player to react in what ways, and keep the player from randomly falling through floors! Well..sometimes.
Thursday, January 19, 2012
Asset Management
In today's episode of offswitch development nonsense, I'm going to talk about the asset management system I made! Rather, I'm going to talk about the old system and how terrible it was before describing the new system and how much better it is! This post will have pictures instead of code, so that's exciting.
When I say asset, I'm referring to elements of the game: backgrounds/gradients, buttons, enemies, statics (doors, levers, etc), and the player. Just to clarify, background and gradient are kind of misleading names.. the gradient object I refer to is the scene painted behind the level, and the background object is the platforms and walls of the actual level. I should REALLY get around to renaming those things before I get called out on it.
So I'll explain the old system. For fellow java programmers out there, I created an Asset object with some generic methods/variables, then made all six of the game objects I listed extend Asset. My "Asset Management System" was just an Asset array. Since all of the different things extended Asset, I could throw them all into the array. In English, this is the equivalent to dumping everything into a large box. Whenever I needed to access an objects, I had to go digging through the box and check what each thing was. And just about everything in the box needed to access just about everything else in the box. If the player needed to access the background to check for collision, It had to search through the box to find the background and then check the pixel needed. This led to a lot of weird dependencies and channels of communication between all of the different objects. I drew a picture, both to illustrate the unnecessary complexity of this system and to demonstrate why Marty is doing ALL of the drawings.
It doesn't take a programmer to tell that this system redefines terrible. So I decided to rip it all out and redesign it!
The new system creates an Asset object, which is a custom data structure containing all of the individual objects and a few methods for them to interact. Each object is given a handle to the Asset object, so if they need to talk to each other they can trace up to the Asset and access the object they need. This is both entirely more efficient and WAY easier to understand, as the following picture demonstrates.
Way better, right?
When I say asset, I'm referring to elements of the game: backgrounds/gradients, buttons, enemies, statics (doors, levers, etc), and the player. Just to clarify, background and gradient are kind of misleading names.. the gradient object I refer to is the scene painted behind the level, and the background object is the platforms and walls of the actual level. I should REALLY get around to renaming those things before I get called out on it.
So I'll explain the old system. For fellow java programmers out there, I created an Asset object with some generic methods/variables, then made all six of the game objects I listed extend Asset. My "Asset Management System" was just an Asset array. Since all of the different things extended Asset, I could throw them all into the array. In English, this is the equivalent to dumping everything into a large box. Whenever I needed to access an objects, I had to go digging through the box and check what each thing was. And just about everything in the box needed to access just about everything else in the box. If the player needed to access the background to check for collision, It had to search through the box to find the background and then check the pixel needed. This led to a lot of weird dependencies and channels of communication between all of the different objects. I drew a picture, both to illustrate the unnecessary complexity of this system and to demonstrate why Marty is doing ALL of the drawings.
It doesn't take a programmer to tell that this system redefines terrible. So I decided to rip it all out and redesign it!
The new system creates an Asset object, which is a custom data structure containing all of the individual objects and a few methods for them to interact. Each object is given a handle to the Asset object, so if they need to talk to each other they can trace up to the Asset and access the object they need. This is both entirely more efficient and WAY easier to understand, as the following picture demonstrates.
Way better, right?
Tuesday, January 17, 2012
Controls
To start things off from the programming side, I'm going to post some of my favorite code: the controls! Implementing good, functional multi-touch controls was harder than I had anticipated, as there are a lot of things we take for granted when playing video games. For example, what happens if I slide my finger off of the button before lifting it from the screen? How about if I slide my finger from the right button to the left? I'll talk about how I dealt with some of these cases, but first let me show you the code!
When implementing game controls, the natural first thought is to set a button to "pressed" when you put your finger on it and "unpressed" when you lift your finger. However, this leaves one major problem. If your finger is not on the button still when you lift it, it won't be lifted! An additional problem I ran into was that it's easy to get your fingers mixed up when programming multi-touch. If you aren't careful, lifting your left thumb may cause the button under your right thumb to lift.. which I claimed made the game more interesting, but was shot down in the end.
I fixed both of these problems with one Hashmap, a fairly basic java structure that stores data using a key in the same way the post office uses your address to determine which house to bring your mail to. So after the first bit of code with all the STATE garbage (all that does is start the game if it isn't started when I touch anywhere on screen), I have a nicely labeled block of code for every button that checks to see if the finger went down on top of one of the buttons. If so, it binds the pointer ID of that finger (guaranteed not to change, where the pointer INDEX can change whenever it feels like it) to the button it is pressing. This way, when the finger is lifted I don't even have to care where it is at the time. I just look up what button it was bound to, lift the button, and unbind it. Fun, right?
The next bit of code worth talking about is the block under ACTION_MOVE, which is what I call the d-pad sliding code. You may not think about it, but gamers typically slide their thumb between the left and right buttons of the d-pad on a controller to quickly switch directions. Initially my code required the player to lift their thumb from the left button before pressing right and vice-versa, which felt a bit clunky. To fix that, I check if a finger that is bound to the left button hovers over the right button or if a finger bound to right hovers over left, and unbind/rebind the finger appropriately. This small addition makes a HUGE difference to gameplay.
Finally, you might be wondering what happens when I call that doKeyDown thing. What I'm doing there is sending a signal to the actual game thread, which then does two things. The first thing it does is talk to the Button class, telling it to redraw the button as a depressed button (slightly greyed out version) to give the player some feedback that their button press was received. The second thing it does is update a variable in the Player class telling the player that the button is pressed. I've considered moving this variable outside of the Player, but it seems to be fine there for now since the player is the only thing that has to read in a button press and respond to it.
Anyways, this was a bit lengthy but I hope it offers some insight into what it takes to develop controls for a touch screen game. Next time I'll talk about something more headache inducing, like collision!
@SuppressWarnings("unchecked") public boolean onTouchEvent(MotionEvent event){ //Binds the key to a hashmap so we can watch for it to lift, then proceed to call doKeyUp/Down int actionCode = event.getActionMasked(); switch (actionCode){ case(MotionEvent.ACTION_DOWN): case(MotionEvent.ACTION_POINTER_DOWN): if(event.getPointerCount()==4){ mAssets.getPlayer().invincible=!mAssets.getPlayer().invincible; } if(thread.getCurrentState()==STATE_PAUSE){ thread.unpause(); return true; }else if(thread.getCurrentState()==STATE_WIN){ level=0; thread.doStart(); }else if(thread.getCurrentState()!=STATE_RUNNING && level==0){ level=1; thread.doStart(); } for(int i = 0; i < event.getPointerCount(); i++){ if( event.getActionIndex()!=i){ //This pointer has not gone down, or the button is already bound to something }else if (event.getX(i)>10 && event.getX(i)<100 && event.getY(i)>380 && event.getY(i)<470 && mPointers.containsValue(Button.BUTTON_LEFT)==false) { //Left button mPointers.put(event.getPointerId(i),Button.BUTTON_LEFT); onKeyDown(Button.BUTTON_LEFT); //Right button } else if (event.getX(i)>110 && event.getX(i)<200 && event.getY(i)>380 && event.getY(i)<470&& mPointers.containsValue(Button.BUTTON_RIGHT)==false) { mPointers.put(event.getPointerId(i),Button.BUTTON_RIGHT); onKeyDown(Button.BUTTON_RIGHT); //A button } else if (event.getX(i)>600 && event.getX(i)<690 && event.getY(i)>380 && event.getY(i)<470&& mPointers.containsValue(Button.BUTTON_A)==false) { mPointers.put(event.getPointerId(i),Button.BUTTON_A); onKeyDown(Button.BUTTON_A); //B button } else if (event.getX(i)>700 && event.getX(i)<790 && event.getY(i)>380 && event.getY(i)<470&& mPointers.containsValue(Button.BUTTON_B)==false) { mPointers.put(event.getPointerId(i),Button.BUTTON_B); onKeyDown(Button.BUTTON_B); } } break; case (MotionEvent.ACTION_UP): case (MotionEvent.ACTION_POINTER_UP): for(int i = 0; i < event.getPointerCount(); i++){ if(event.getActionIndex()==i && mPointers.containsKey(event.getPointerId(i))){ onKeyUp((Integer)mPointers.get(event.getPointerId(i))); mPointers.remove(event.getPointerId(i)); } } break; case (MotionEvent.ACTION_MOVE): for(int i=0;iA lot to process, I know. I'll explain some of the more interesting parts for you. First off, button binding!110 && event.getX(i)<200 && event.getY(i)>380 && event.getY(i)<470 && mPointers.containsValue(Button.BUTTON_RIGHT)==false){ onKeyUp(Button.BUTTON_LEFT); mPointers.remove(event.getPointerId(i)); mPointers.put(event.getPointerId(i),Button.BUTTON_RIGHT); onKeyDown(Button.BUTTON_RIGHT); }else if((Integer)mPointers.get(event.getPointerId(i))==Button.BUTTON_RIGHT && event.getX(i)>10 && event.getX(i)<100 && event.getY(i)>380 && event.getY(i)<470 && mPointers.containsValue(Button.BUTTON_LEFT)==false){ onKeyUp(Button.BUTTON_RIGHT); mPointers.remove(event.getPointerId(i)); mPointers.put(event.getPointerId(i),Button.BUTTON_LEFT); onKeyDown(Button.BUTTON_LEFT); } } } break; } return true; }
When implementing game controls, the natural first thought is to set a button to "pressed" when you put your finger on it and "unpressed" when you lift your finger. However, this leaves one major problem. If your finger is not on the button still when you lift it, it won't be lifted! An additional problem I ran into was that it's easy to get your fingers mixed up when programming multi-touch. If you aren't careful, lifting your left thumb may cause the button under your right thumb to lift.. which I claimed made the game more interesting, but was shot down in the end.
I fixed both of these problems with one Hashmap, a fairly basic java structure that stores data using a key in the same way the post office uses your address to determine which house to bring your mail to. So after the first bit of code with all the STATE garbage (all that does is start the game if it isn't started when I touch anywhere on screen), I have a nicely labeled block of code for every button that checks to see if the finger went down on top of one of the buttons. If so, it binds the pointer ID of that finger (guaranteed not to change, where the pointer INDEX can change whenever it feels like it) to the button it is pressing. This way, when the finger is lifted I don't even have to care where it is at the time. I just look up what button it was bound to, lift the button, and unbind it. Fun, right?
The next bit of code worth talking about is the block under ACTION_MOVE, which is what I call the d-pad sliding code. You may not think about it, but gamers typically slide their thumb between the left and right buttons of the d-pad on a controller to quickly switch directions. Initially my code required the player to lift their thumb from the left button before pressing right and vice-versa, which felt a bit clunky. To fix that, I check if a finger that is bound to the left button hovers over the right button or if a finger bound to right hovers over left, and unbind/rebind the finger appropriately. This small addition makes a HUGE difference to gameplay.
Finally, you might be wondering what happens when I call that doKeyDown thing. What I'm doing there is sending a signal to the actual game thread, which then does two things. The first thing it does is talk to the Button class, telling it to redraw the button as a depressed button (slightly greyed out version) to give the player some feedback that their button press was received. The second thing it does is update a variable in the Player class telling the player that the button is pressed. I've considered moving this variable outside of the Player, but it seems to be fine there for now since the player is the only thing that has to read in a button press and respond to it.
Anyways, this was a bit lengthy but I hope it offers some insight into what it takes to develop controls for a touch screen game. Next time I'll talk about something more headache inducing, like collision!
Subscribe to:
Posts (Atom)