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!
 @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;i110 && 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;
 }
A lot to process, I know. I'll explain some of the more interesting parts for you. First off, button binding!

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!

No comments:

Post a Comment