遊戲控制是我們的遊戲技術基礎的最後一部分,在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());
}
}