Android遊戲開發教程—-遊戲控制

遊戲控制是我們的遊戲技術基礎的最後一部分,在Android 遊戲中我們可以通過按鍵,觸摸屏還有傳感器來控制遊戲。

按鍵控制

 按鍵並非收有安卓手機都擁有,實際上Android平板和新版本的Android操作系統中,我們能發現實體按鍵正有逐漸消失的趨勢。

 按鍵事件包括onKeyUp和onKeyDown等事件,下面通過一個按鍵事件的使用案例來瞭解按鍵控制技術。

[java]  public class KeyInputActivity extends Activity { 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(new TextView(this)); 
    } 
  @Override 
   public boolean onKeyDown(int keyCode, KeyEvent event) { 
      switch(keyCode){   
      case KeyEvent.KEYCODE_DPAD_CENTER:   
          Log.v("onKeyDown", "按下:中鍵");   
          break;   
      case KeyEvent.KEYCODE_DPAD_UP:   
         Log.v("onKeyDown", "按下:上方向鍵");   
          break;   
      case KeyEvent.KEYCODE_DPAD_DOWN:   
          Log.v("onKeyDown", "按下:下方向鍵");  
          break;   
      case KeyEvent.KEYCODE_DPAD_LEFT:   
         Log.v("onKeyDown", "按下:左方向鍵");  
          break;   
      case KeyEvent.KEYCODE_DPAD_RIGHT:   
          Log.v("onKeyDown", "按下:右方向鍵");  
          break;   
   }  
    return super.onKeyDown(keyCode, event); 
  } 
  @Override 
   public boolean onKeyUp(int keyCode, KeyEvent event) { 
       switch(keyCode){   
        case KeyEvent.KEYCODE_DPAD_CENTER:   
            Log.v("onKeyDown", "釋放:中鍵");   
            break;   
        case KeyEvent.KEYCODE_DPAD_UP:   
             Log.v("onKeyDown", "釋放:上方向鍵");   
            break;   
        case KeyEvent.KEYCODE_DPAD_DOWN:   
            Log.v("onKeyDown", "釋放:下方向鍵");  
            break;   
        case KeyEvent.KEYCODE_DPAD_LEFT:   
             Log.v("onKeyDown", "釋放:左方向鍵");  
            break;   
        case KeyEvent.KEYCODE_DPAD_RIGHT:   
            Log.v("onKeyDown", "釋放:右方向鍵");  
            break;   
     } 
    return super.onKeyUp(keyCode, event); 
  } 
  }    

  public class KeyInputActivity extends Activity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(new TextView(this));
   }
  @Override
   public boolean onKeyDown(int keyCode, KeyEvent event) {
      switch(keyCode){ 
      case KeyEvent.KEYCODE_DPAD_CENTER: 
          Log.v("onKeyDown", "按下:中鍵"); 
          break; 
      case KeyEvent.KEYCODE_DPAD_UP: 
        Log.v("onKeyDown", "按下:上方向鍵"); 
          break; 
      case KeyEvent.KEYCODE_DPAD_DOWN: 
          Log.v("onKeyDown", "按下:下方向鍵");
          break; 
      case KeyEvent.KEYCODE_DPAD_LEFT: 
        Log.v("onKeyDown", "按下:左方向鍵");
          break; 
      case KeyEvent.KEYCODE_DPAD_RIGHT: 
          Log.v("onKeyDown", "按下:右方向鍵");
          break; 
   }
   return super.onKeyDown(keyCode, event);
  }
  @Override
   public boolean onKeyUp(int keyCode, KeyEvent event) {
      switch(keyCode){ 
       case KeyEvent.KEYCODE_DPAD_CENTER: 
           Log.v("onKeyDown", "釋放:中鍵"); 
           break; 
       case KeyEvent.KEYCODE_DPAD_UP: 
         Log.v("onKeyDown", "釋放:上方向鍵"); 
           break; 
       case KeyEvent.KEYCODE_DPAD_DOWN: 
           Log.v("onKeyDown", "釋放:下方向鍵");
           break; 
       case KeyEvent.KEYCODE_DPAD_LEFT: 
         Log.v("onKeyDown", "釋放:左方向鍵");
           break; 
       case KeyEvent.KEYCODE_DPAD_RIGHT: 
           Log.v("onKeyDown", "釋放:右方向鍵");
           break; 
    }
   return super.onKeyUp(keyCode, event);
  }
  }  

說明:

keyCode為鍵值,手機中每一個按鈕都擁有一個完全獨立的鍵值,通過按鍵鍵值就可以確定當前按下的是哪一個按鍵。

KeyEvent為按鍵事件,該對象中保存著當前按鍵的所有信息。比如:按鍵發生的時間,按鍵發生的次數,按鍵發生的類型等等。

觸摸控制

 在Android平臺中,觸摸控制是基礎,這也是移動平臺優勢所在,因為觸摸能提供直觀的更加人性化的操作。觸摸就分為單點觸摸控制與多點觸摸控制,下面通過實例代碼來分別演示如何使用觸摸控制技術。

演示:單點觸摸事件

[java]  public class STouchActivity extends Activity { 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(new TextView(this)); 
    } 
  @Override 
   public boolean onTouchEvent(MotionEvent event) { 
     int action = event.getAction(); 
     float mPosX = event.getX();//觸摸點X坐標  
     float mPosY = event.getY();//觸摸點y坐標  
      switch (action) { 
      // 觸摸按下的事件  
      case MotionEvent.ACTION_DOWN: 
          Log.v("test", "ACTION_DOWN"); 
          break; 
      // 觸摸移動的事件  
      case MotionEvent.ACTION_MOVE: 
          Log.v("test", "ACTION_MOVE"); 
          break; 
      // 觸摸抬起的事件  
      case MotionEvent.ACTION_UP: 
          Log.v("test", "ACTION_UP"); 
          break; 
      } 
      return true; 
  } 
}  

  public class STouchActivity extends Activity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(new TextView(this));
   }
  @Override
   public boolean onTouchEvent(MotionEvent event) {
     int action = event.getAction();
     float mPosX = event.getX();//觸摸點X坐標
     float mPosY = event.getY();//觸摸點y坐標
      switch (action) {
      // 觸摸按下的事件
      case MotionEvent.ACTION_DOWN:
          Log.v("test", "ACTION_DOWN");
          break;
      // 觸摸移動的事件
      case MotionEvent.ACTION_MOVE:
          Log.v("test", "ACTION_MOVE");
          break;
      // 觸摸抬起的事件
      case MotionEvent.ACTION_UP:
          Log.v("test", "ACTION_UP");
          break;
      }
      return true;
  }
} 覆蓋Activity的onTouchEvent方法,實現觸摸事件處理

多點觸摸

多點觸摸並不是所有手機都支持,有些手機支持很多點觸摸,有些手機可能隻支持單點觸摸。

Android2.2版本後對多點觸摸做瞭修改,添加瞭新的方法和常量,甚至重命名瞭常量。這些改變可能會讓處理多點觸摸容易些。不過隻支持Android2.2以後的版本,為瞭支持Android2.0–Android2.21版本,我們使用Android2.0的API。

當處理多點觸摸事件時,我們使用重載的方法,它們帶有一個所謂的指針索引,如event.getX(pointerIndex);

pointIndex是MotionEvent的內部數組中的一個索引,它包含特定手指觸摸屏幕事件的坐標值。而真正識別屏幕上的一根手指是指針ID。指針ID是一個任意數字,可以唯一標識觸摸屏幕的一個指針的實例。有一個方法MotionEvent.getPointerIdentifier(int pointerIndex),它返回一個基於指針索引的指針ID。隻要手指還觸摸在屏幕上,一個指針ID就與一根手指保持相同,而指針索引就不一定這樣瞭。先來看看是如何獲取指針索引:

int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;

ACTION_POINTER_ID_MASK的常量的值是0xff00,因此低8位為0,高8位為15,用於保存事件的指針索引。整數的低8位可從event.getAction()方法返回得到,用於保存事件類型的值。我們通過MotionEvent.ACTION_POINTER_ID_SHIFT來移位,該值為8,因此實際上是將第15位移到第8位,第7位移到第0位。註意我們是獲取pointerIndex而常量卻是XXX_POINTER_ID_XXX而不是XXX_POINTER_INDEX_XXX

獲取事件類型,我們隻需屏蔽指針索引:

int action = event.getAction() & MotionEvent.ACTION_MASK;

這裡我們會遇到新的事件類型,

MotionEvent.ACTION_POINTER_DOWN:除瞭第一根手指外的任何手指觸摸屏幕,都將發生該事件,而第一根手指仍然產生MotionEvent.ACTION_DOWN事件。

MotionEvent.ACTION_POINTER_UP:多根手指觸摸屏幕而一根手指離開屏幕時,將發生該事件。最後一根手指離開屏幕將產生MotionEvent.ACTION_UP事件,而該手指不一定是第一個觸摸屏幕的手指。為瞭檢查單個MotionEvent中包含幾個事件,可使用MotionEvent.getPointerCount()方法,它會告訴我們MotionEvent中包含多少根手指的坐標。然後我們可通過MotionEvent.getX()、MotionEvent.getY()和MotionEvent.getPointerId()方法來得到指針ID和從指針索引0到MotionEvent.getPointerCount() – 1的坐標值。

     好吧,如果上述理論介紹讓你抓狂,不必擔心,你隻需明白如何應用就行瞭,請看實例代碼.   

 多點觸摸案例:

以下代碼支持最多10個點的觸摸,其實我們也正好隻有10根手指,觸摸坐標被保存在數組x和y裡面,觸摸狀態被保存在touched 數組裡面。

[java]  public class MTouchActivity extends Activity { 
  float[] x = new float[10];//保存10個觸摸點的x坐標  
  float[] y = new float[10];//保存10個觸摸點的y坐標  
  boolean[] touched = new boolean[10];//保存10個觸摸點的按下狀態  
  StringBuilder builder = new StringBuilder(); 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(new TextView(this)); 
    } 
  @Override 
  public boolean onTouchEvent(MotionEvent event) { 
  int action = event.getAction() & MotionEvent.ACTION_MASK; 
  int pointerIndex =  
  (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> 
  MotionEvent.ACTION_POINTER_ID_SHIFT; 
  int pointerId = event.getPointerId(pointerIndex); 
  switch (action) { 
  case MotionEvent.ACTION_DOWN: 
  case MotionEvent.ACTION_POINTER_DOWN://第二根手指按下時才會觸發  
  touched[pointerId] = true; 
  x[pointerId] = (int)event.getX(pointerIndex); 
  y[pointerId] = (int)event.getY(pointerIndex); 
  break; 
  case MotionEvent.ACTION_UP: 
  case MotionEvent.ACTION_POINTER_UP://最後一根手指抬起時不會觸發  
  case MotionEvent.ACTION_CANCEL: 
  touched[pointerId] = false; 
  x[pointerId] = (int)event.getX(pointerIndex); 
  y[pointerId] = (int)event.getY(pointerIndex); 
  break; 
  case MotionEvent.ACTION_MOVE: 
  int pointerCount = event.getPointerCount(); 
  for (int i = 0; i < pointerCount; i++) { 
  pointerIndex = i; 
  pointerId = event.getPointerId(pointerIndex); 
  x[pointerId] = (int)event.getX(pointerIndex); 
  y[pointerId] = (int)event.getY(pointerIndex); 
  } 
  break; 
  } 
  //生成觸摸日志  
  builder.setLength(0); 
  for(int i = 0; i < 10; i++) { 
  builder.append(touched[i]); 
  builder.append(", "); 
  builder.append(x[i]); 
  builder.append(", "); 
  builder.append(y[i]); 
  builder.append("\n"); 
  } 
  //打印觸摸日志  
   Log.v("test", builder.toString()); 
  return true; 
  } 
  } 

  public class MTouchActivity extends Activity {
  float[] x = new float[10];//保存10個觸摸點的x坐標
  float[] y = new float[10];//保存10個觸摸點的y坐標
  boolean[] touched = new boolean[10];//保存10個觸摸點的按下狀態
  StringBuilder builder = new StringBuilder();
   @Override
   protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(new TextView(this));
   }
  @Override
  public boolean onTouchEvent(MotionEvent event) {
  int action = event.getAction() & MotionEvent.ACTION_MASK;
  int pointerIndex =
  (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >>
  MotionEvent.ACTION_POINTER_ID_SHIFT;
  int pointerId = event.getPointerId(pointerIndex);
  switch (action) {
  case MotionEvent.ACTION_DOWN:
  case MotionEvent.ACTION_POINTER_DOWN://第二根手指按下時才會觸發
  touched[pointerId] = true;
  x[pointerId] = (int)event.getX(pointerIndex);
  y[pointerId] = (int)event.getY(pointerIndex);
  break;
  case MotionEvent.ACTION_UP:
  case MotionEvent.ACTION_POINTER_UP://最後一根手指抬起時不會觸發
  case MotionEvent.ACTION_CANCEL:
  touched[pointerId] = false;
  x[pointerId] = (int)event.getX(pointerIndex);
  y[pointerId] = (int)event.getY(pointerIndex);
  break;
  case MotionEvent.ACTION_MOVE:
  int pointerCount = event.getPointerCount();
  for (int i = 0; i < pointerCount; i++) {
  pointerIndex = i;
  pointerId = event.getPointerId(pointerIndex);
  x[pointerId] = (int)event.getX(pointerIndex);
  y[pointerId] = (int)event.getY(pointerIndex);
  }
  break;
  }
  //生成觸摸日志
  builder.setLength(0);
  for(int i = 0; i < 10; i++) {
  builder.append(touched[i]);
  builder.append(", ");
  builder.append(x[i]);
  builder.append(", ");
  builder.append(y[i]);
  builder.append("\n");
  }
  //打印觸摸日志
   Log.v("test", builder.toString());
  return true;
  }
  }

傳感器控制技術

加速器

遊戲中一個有趣的輸入方法是加速計,所有的Android設備都要求有一個3D加速計。

為瞭獲取加速計信息,我們註冊一個偵聽器,需要實現的接口名為SensorEventListener,它具有兩個方法:

public void onSensorChanged(SensorEvent event);

public void onAccuracyChanged(Sensor sensor, int accuracy);

當一個新的加速計事件發生時會調用第一個方法,而當加速計的精度發生變化時會調用第二個方法。一般我們可以放心的忽略第二個方法。為瞭實現這個功能,首先我們需要確認設備上是否具有一個加速計。盡管現在所有的Android設備都應該有一個加速計,但也許將來會有變化。我們必須百分之百確保該輸入方法是可用的。首先我們需要做的是獲得一個SensorManager實例,它將會表明設備是否安裝瞭加速計,以及在哪裡註冊偵聽器。我們可以通過Context接口的一個方法來獲取SensorManager實例:

SensorManage manager =

(SensorManager)context.getSystemService(Context.SENSOR_SERVICE);

SensorManager是Android系統提供的一個系統服務,Android具有多個系統服務,每一個服務可向我們提供不同的系統信息。一旦獲取SensorManager實例,就能檢查加速計是否可用:

boolean hasAccel = manager.getSensorList(Sensor.TYPE_ACCELEROMETER).size() > 0;

通過這段代碼,我們查詢管理器以瞭解所有安裝的accelerometer類型加速計。這就意味著一個設備可具有多個加速計,不過,實際上隻返回一個加速計傳感器。

如果已經安裝瞭一個加速計,我們可通過SensorManager來獲取它並向其註冊SensorEventListener,如下所示:

Sensor sensor = manager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0);

boolean sucess =

manager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_GAME);

參數SensorManager.SENSOR_DELAY_GAME用於指定偵聽器更新的頻率,其更新內容來自加速計的最新狀態。

SensorManager.registerListener()方法返回一個Boolean變量,以表明註冊過程是否成功。

一旦成功註冊偵聽器,我們就可以通過SensorEventListener.onSensorChanged()方法來接收SensorEvent。該方法隻有在傳感器的狀態發生變化時才會被調用。

現在該處理SensorEvent瞭。該SensorEvent有一個公有浮點型數組成員變量SensorEvent.values,它存儲瞭加速計當前3個軸的加速度值。SensorEvent.values[0]保存X軸的值,SensorEvent.values[1]保存Y軸的值,SensorEvent.values[2]保存Z軸的值。

總體來說X、Y軸方向位於你的手機屏幕所在的平面,而Z軸垂直於手機屏幕所在平面,X軸一般位於手機短的那條邊的方向,其他的坐標軸大傢可以充分發揮你的空間想象能力。

 

說瞭這麼多,其實看看代碼就一切都簡單明瞭。

加速計的使用案例:

以下代碼將在textView中實時顯示出加速傳感器x,y,z的三個值

[java]  public class AccelerometerActivity extends Activity implements SensorEventListener{ 
    TextView textView; 
    StringBuilder builder = new StringBuilder(); 
   
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        // TODO Auto-generated method stub  
        super.onCreate(savedInstanceState); 
        textView = new TextView(this); 
        setContentView(textView); 
         
        SensorManager manager = (SensorManager)getSystemService( 
                Context.SENSOR_SERVICE); 
    if(manager.getSensorList(Sensor.TYPE_ACCELEROMETER).size() == 0){ 
            textView.setText("加速器未安裝"); 
        }else{ 
            Sensor accelerometer = manager.getSensorList( 
                    Sensor.TYPE_ACCELEROMETER).get(0); 
            if(!manager.registerListener(this, accelerometer, 
                    SensorManager.SENSOR_DELAY_GAME)){ 
                textView.setText("申請監聽器失敗"); 
            } 
        } 
    } 
    @Override 
    public void onAccuracyChanged(Sensor sensor, int accuracy) { 
    } 
    @Override 
    public void onSensorChanged(SensorEvent event) { 
        // TODO Auto-generated method stub  
        builder.setLength(0); 
        builder.append("x: "); 
        builder.append(event.values[0]); 
        builder.append(", y: "); 
        builder.append(event.values[1]); 
        builder.append(", z: "); 
        builder.append(event.values[2]); 
        textView.setText(builder.toString()); 
    } 
  } 

  public class AccelerometerActivity extends Activity implements SensorEventListener{
   TextView textView;
   StringBuilder builder = new StringBuilder();
  
   @Override
   protected void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    textView = new TextView(this);
    setContentView(textView);
    
    SensorManager manager = (SensorManager)getSystemService(
      Context.SENSOR_SERVICE);
   if(manager.getSensorList(Sensor.TYPE_ACCELEROMETER).size() == 0){
     textView.setText("加速器未安裝");
    }else{
     Sensor accelerometer = manager.getSensorList(
       Sensor.TYPE_ACCELEROMETER).get(0);
     if(!manager.registerListener(this, accelerometer,
       SensorManager.SENSOR_DELAY_GAME)){
      textView.setText("申請監聽器失敗");
     }
    }
   }
   @Override
   public void onAccuracyChanged(Sensor sensor, int accuracy) {
   }
   @Override
   public void onSensorChanged(SensorEvent event) {
    // TODO Auto-generated method stub
    builder.setLength(0);
    builder.append("x: ");
    builder.append(event.values[0]);
    builder.append(", y: ");
    builder.append(event.values[1]);
    builder.append(", z: ");
    builder.append(event.values[2]);
    textView.setText(builder.toString());
   }
  }

方向傳感器

方向傳感器是用加速計和磁力計虛擬出來的,用於計算手機的俯仰角度。

方向傳感器包括下面三個角度:

Roll:左右傾斜角度,也叫滾轉角 范圍為-90°至90°

Pitch:前後傾斜,也叫俯仰角  范圍為-180°至180°

Yaw:左右搖擺,也叫偏航角  范圍為0°至360°。

 

方向傳感器的使用案例:

以下代碼將在textView中實時顯示出方向傳感器roll,pitch,yaw的三個值

[java]  public class OrientationActivity extends Activity 
   implements SensorEventListener{ 
    TextView textView; 
    StringBuilder builder = new StringBuilder(); 
    float yaw; 
    float pitch; 
    float roll; 
   
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        // TODO Auto-generated method stub  
        super.onCreate(savedInstanceState); 
        textView = new TextView(this); 
        setContentView(textView); 
         
SensorManager manager = (SensorManager)getSystemService( 
                Context.SENSOR_SERVICE); 
         
        if(manager.getSensorList(Sensor.TYPE_ORIENTATION).size() == 0){ 
            textView.setText("方向傳感器未安裝"); 
        }else{ 
            Sensor compass = manager.getDefaultSensor( 
                    Sensor.TYPE_ORIENTATION); 
            if(!manager.registerListener(this, compass, 
                    SensorManager.SENSOR_DELAY_GAME)){ 
                textView.setText("申請監聽器失敗"); 
            } 
        } 
    } 
   
    @Override 
    public void onAccuracyChanged(Sensor sensor, int accuracy) { 
        // TODO Auto-generated method stub  
         
    } 
   
    @Override 
    public void onSensorChanged(SensorEvent event) { 
        // TODO Auto-generated method stub  
        yaw = event.values[0]; 
        pitch = event.values[1]; 
        roll = event.values[2]; 
   
        calculateOrientation();  
    } 
     
    public void calculateOrientation(){ 
        builder.setLength(0); 
        builder.append("yaw: "); 
        builder.append(yaw + "\n"); 
        builder.append("pitch: " ); 
        builder.append(pitch + "\n"); 
        builder.append("roll: "); 
        builder.append(roll); 
        textView.setText(builder.toString()); 
    } 
  } 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *