• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

development


Commit MetaInfo

Revision5cfbd84e5006b931ff5682a3d6505cbad7b27805 (tree)
Zeit2011-02-25 06:22:52
AutorJeff Brown <jeffbrown@goog...>
CommiterAndroid (Google) Code Review

Log Message

Merge "Add a sample to demonstrate game controller usage."

Ändern Zusammenfassung

Diff

--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -1961,6 +1961,13 @@
19611961 </intent-filter>
19621962 </activity>
19631963
1964+ <activity android:name=".view.GameControllerInput" android:label="Views/Game Controller Input">
1965+ <intent-filter>
1966+ <action android:name="android.intent.action.MAIN" />
1967+ <category android:name="android.intent.category.SAMPLE_CODE" />
1968+ </intent-filter>
1969+ </activity>
1970+
19641971 <!-- ************************************* -->
19651972 <!-- GRAPHICS SAMPLES -->
19661973 <!-- ************************************* -->
--- /dev/null
+++ b/samples/ApiDemos/res/layout/game_controller_input.xml
@@ -0,0 +1,52 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<!-- Copyright (C) 2011 The Android Open Source Project
3+
4+ Licensed under the Apache License, Version 2.0 (the "License");
5+ you may not use this file except in compliance with the License.
6+ You may obtain a copy of the License at
7+
8+ http://www.apache.org/licenses/LICENSE-2.0
9+
10+ Unless required by applicable law or agreed to in writing, software
11+ distributed under the License is distributed on an "AS IS" BASIS,
12+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ See the License for the specific language governing permissions and
14+ limitations under the License.
15+-->
16+
17+<!-- Game controller input demo. -->
18+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
19+ android:orientation="vertical"
20+ android:layout_width="match_parent"
21+ android:layout_height="match_parent">
22+
23+ <TextView
24+ android:id="@+id/description"
25+ android:layout_width="match_parent"
26+ android:layout_height="wrap_content"
27+ android:text="@string/game_controller_input_description"
28+ android:padding="12dip" />
29+
30+ <LinearLayout
31+ android:orientation="horizontal"
32+ android:layout_width="match_parent"
33+ android:layout_height="0dip"
34+ android:layout_weight="1"
35+ android:padding="12dip">
36+ <ListView
37+ android:id="@+id/summary"
38+ android:layout_width="0dip"
39+ android:layout_height="match_parent"
40+ android:layout_weight="1"
41+ android:padding="3dip">
42+ </ListView>
43+
44+ <com.example.android.apis.view.GameView
45+ android:id="@+id/game"
46+ android:layout_width="0dip"
47+ android:layout_height="match_parent"
48+ android:layout_weight="1"
49+ android:background="#000000"
50+ android:padding="3dip" />
51+ </LinearLayout>
52+</LinearLayout>
--- /dev/null
+++ b/samples/ApiDemos/res/layout/game_controller_input_heading.xml
@@ -0,0 +1,21 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<!-- Copyright (C) 2011 The Android Open Source Project
3+
4+ Licensed under the Apache License, Version 2.0 (the "License");
5+ you may not use this file except in compliance with the License.
6+ You may obtain a copy of the License at
7+
8+ http://www.apache.org/licenses/LICENSE-2.0
9+
10+ Unless required by applicable law or agreed to in writing, software
11+ distributed under the License is distributed on an "AS IS" BASIS,
12+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ See the License for the specific language governing permissions and
14+ limitations under the License.
15+-->
16+
17+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
18+ android:layout_width="wrap_content"
19+ android:layout_height="match_parent"
20+ android:padding="6dip"
21+ android:textAppearance="?android:attr/textAppearanceMedium" />
--- /dev/null
+++ b/samples/ApiDemos/res/layout/game_controller_input_text_column.xml
@@ -0,0 +1,34 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<!-- Copyright (C) 2011 The Android Open Source Project
3+
4+ Licensed under the Apache License, Version 2.0 (the "License");
5+ you may not use this file except in compliance with the License.
6+ You may obtain a copy of the License at
7+
8+ http://www.apache.org/licenses/LICENSE-2.0
9+
10+ Unless required by applicable law or agreed to in writing, software
11+ distributed under the License is distributed on an "AS IS" BASIS,
12+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ See the License for the specific language governing permissions and
14+ limitations under the License.
15+-->
16+
17+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
18+ android:layout_width="match_parent"
19+ android:layout_height="wrap_content"
20+ android:orientation="horizontal"
21+ android:padding="6dip">
22+ <TextView
23+ android:id="@+id/label"
24+ android:gravity="left"
25+ android:layout_width="0dip"
26+ android:layout_height="match_parent"
27+ android:layout_weight="1" />
28+ <TextView
29+ android:id="@+id/content"
30+ android:gravity="left"
31+ android:layout_width="0dip"
32+ android:layout_height="match_parent"
33+ android:layout_weight="1" />
34+</LinearLayout>
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -773,6 +773,20 @@
773773 dot will append the drag\'s textual conversion to the EditText.
774774 </string>
775775
776+ <string name="game_controller_input_description">
777+ This activity demonstrates how to process input events received from
778+ game controllers. Please connect your game controller now and try
779+ moving the joysticks or pressing buttons. If it helps, try to imagine
780+ that you are a lone space cowboy in hot pursuit of the aliens who kidnapped
781+ your favorite llama on their way back to Andromeda...
782+ </string>
783+ <string name="game_controller_input_heading_device">Input Device</string>
784+ <string name="game_controller_input_heading_axes">Axes</string>
785+ <string name="game_controller_input_heading_keys">Keys and Buttons</string>
786+ <string name="game_controller_input_label_device_name">Name</string>
787+ <string name="game_controller_input_key_pressed">Pressed</string>
788+ <string name="game_controller_input_key_released">Released</string>
789+
776790 <!-- ============================== -->
777791 <!-- GoogleLogin examples strings -->
778792 <!-- ============================== -->
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/view/GameControllerInput.java
@@ -0,0 +1,449 @@
1+/*
2+ * Copyright (C) 2011 The Android Open Source Project
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+package com.example.android.apis.view;
18+
19+import com.example.android.apis.R;
20+
21+import android.app.Activity;
22+import android.content.Context;
23+import android.content.res.Resources;
24+import android.os.Bundle;
25+import android.util.AttributeSet;
26+import android.util.Log;
27+import android.util.SparseArray;
28+import android.util.SparseIntArray;
29+import android.view.InputDevice;
30+import android.view.InputEvent;
31+import android.view.KeyEvent;
32+import android.view.LayoutInflater;
33+import android.view.MotionEvent;
34+import android.view.View;
35+import android.view.ViewGroup;
36+import android.widget.AdapterView;
37+import android.widget.BaseAdapter;
38+import android.widget.ListView;
39+import android.widget.TextView;
40+import android.widget.Toast;
41+
42+import java.util.ArrayList;
43+import java.util.concurrent.atomic.AtomicLong;
44+
45+
46+/**
47+ * Demonstrates how to process input events received from game controllers.
48+ *
49+ * This activity displays button states and joystick positions.
50+ * Also writes detailed information about relevant input events to the log.
51+ *
52+ * The game controller is also uses to control a very simple game. See {@link GameView}
53+ * for the game itself.
54+ */
55+public class GameControllerInput extends Activity {
56+ private static final String TAG = "GameControllerInput";
57+
58+ private SparseArray<InputDeviceState> mInputDeviceStates;
59+ private GameView mGame;
60+ private ListView mSummaryList;
61+ private SummaryAdapter mSummaryAdapter;
62+
63+ @Override
64+ protected void onCreate(Bundle savedInstanceState) {
65+ super.onCreate(savedInstanceState);
66+
67+ mInputDeviceStates = new SparseArray<InputDeviceState>();
68+ mSummaryAdapter = new SummaryAdapter(this, getResources());
69+
70+ setContentView(R.layout.game_controller_input);
71+
72+ mGame = (GameView) findViewById(R.id.game);
73+
74+ mSummaryList = (ListView) findViewById(R.id.summary);
75+ mSummaryList.setAdapter(mSummaryAdapter);
76+ mSummaryList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
77+ @Override
78+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
79+ mSummaryAdapter.onItemClick(position);
80+ }
81+ });
82+ }
83+
84+ @Override
85+ public void onWindowFocusChanged(boolean hasFocus) {
86+ super.onWindowFocusChanged(hasFocus);
87+
88+ mGame.requestFocus();
89+ }
90+
91+ @Override
92+ public boolean dispatchKeyEvent(KeyEvent event) {
93+ // Update device state for visualization and logging.
94+ InputDeviceState state = getInputDeviceState(event);
95+ if (state != null) {
96+ switch (event.getAction()) {
97+ case KeyEvent.ACTION_DOWN:
98+ if (state.onKeyDown(event)) {
99+ mSummaryAdapter.show(state);
100+ }
101+ break;
102+ case KeyEvent.ACTION_UP:
103+ if (state.onKeyUp(event)) {
104+ mSummaryAdapter.show(state);
105+ }
106+ break;
107+ }
108+ }
109+ return super.dispatchKeyEvent(event);
110+ }
111+
112+ @Override
113+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
114+ // Check that the event came from a joystick since a generic motion event
115+ // could be almost anything.
116+ if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0
117+ && event.getAction() == MotionEvent.ACTION_MOVE) {
118+ // Update device state for visualization and logging.
119+ InputDeviceState state = getInputDeviceState(event);
120+ if (state != null && state.onJoystickMotion(event)) {
121+ mSummaryAdapter.show(state);
122+ }
123+ }
124+ return super.dispatchGenericMotionEvent(event);
125+ }
126+
127+ private InputDeviceState getInputDeviceState(InputEvent event) {
128+ final int deviceId = event.getDeviceId();
129+ InputDeviceState state = mInputDeviceStates.get(deviceId);
130+ if (state == null) {
131+ final InputDevice device = event.getDevice();
132+ if (device == null) {
133+ return null;
134+ }
135+ state = new InputDeviceState(device);
136+ mInputDeviceStates.put(deviceId, state);
137+
138+ Log.i(TAG, device.toString());
139+ }
140+ return state;
141+ }
142+
143+ /**
144+ * Tracks the state of joystick axes and game controller buttons for a particular
145+ * input device for diagnostic purposes.
146+ */
147+ private static class InputDeviceState {
148+ private final InputDevice mDevice;
149+ private final int[] mAxes;
150+ private final float[] mAxisValues;
151+ private final SparseIntArray mKeys;
152+
153+ public InputDeviceState(InputDevice device) {
154+ mDevice = device;
155+ mAxes = device.getMotionAxes();
156+ mAxisValues = new float[mAxes.length];
157+ mKeys = new SparseIntArray();
158+ }
159+
160+ public InputDevice getDevice() {
161+ return mDevice;
162+ }
163+
164+ public int getAxisCount() {
165+ return mAxes.length;
166+ }
167+
168+ public int getAxis(int axisIndex) {
169+ return mAxes[axisIndex];
170+ }
171+
172+ public float getAxisValue(int axisIndex) {
173+ return mAxisValues[axisIndex];
174+ }
175+
176+ public int getKeyCount() {
177+ return mKeys.size();
178+ }
179+
180+ public int getKeyCode(int keyIndex) {
181+ return mKeys.keyAt(keyIndex);
182+ }
183+
184+ public boolean isKeyPressed(int keyIndex) {
185+ return mKeys.valueAt(keyIndex) != 0;
186+ }
187+
188+ public boolean onKeyDown(KeyEvent event) {
189+ final int keyCode = event.getKeyCode();
190+ if (isGameKey(keyCode)) {
191+ if (event.getRepeatCount() == 0) {
192+ final String symbolicName = KeyEvent.keyCodeToString(keyCode);
193+ mKeys.put(keyCode, 1);
194+ Log.i(TAG, mDevice.getName() + " - Key Down: " + symbolicName);
195+ }
196+ return true;
197+ }
198+ return false;
199+ }
200+
201+ public boolean onKeyUp(KeyEvent event) {
202+ final int keyCode = event.getKeyCode();
203+ if (isGameKey(keyCode)) {
204+ int index = mKeys.indexOfKey(keyCode);
205+ if (index >= 0) {
206+ final String symbolicName = KeyEvent.keyCodeToString(keyCode);
207+ mKeys.put(keyCode, 0);
208+ Log.i(TAG, mDevice.getName() + " - Key Up: " + symbolicName);
209+ }
210+ return true;
211+ }
212+ return false;
213+ }
214+
215+ public boolean onJoystickMotion(MotionEvent event) {
216+ StringBuilder message = new StringBuilder();
217+ message.append(mDevice.getName()).append(" - Joystick Motion:\n");
218+
219+ final int historySize = event.getHistorySize();
220+ for (int i = 0; i < mAxes.length; i++) {
221+ final int axis = mAxes[i];
222+ final float value = event.getAxisValue(axis);
223+ mAxisValues[i] = value;
224+ message.append(" ").append(MotionEvent.axisToString(axis)).append(": ");
225+
226+ // Append all historical values in the batch.
227+ for (int historyPos = 0; historyPos < historySize; historyPos++) {
228+ message.append(event.getHistoricalAxisValue(axis, historyPos));
229+ message.append(", ");
230+ }
231+
232+ // Append the current value.
233+ message.append(value);
234+ message.append("\n");
235+ }
236+ Log.i(TAG, message.toString());
237+ return true;
238+ }
239+
240+ // Check whether this is a key we care about.
241+ // In a real game, we would probably let the user configure which keys to use
242+ // instead of hardcoding the keys like this.
243+ private static boolean isGameKey(int keyCode) {
244+ switch (keyCode) {
245+ case KeyEvent.KEYCODE_DPAD_UP:
246+ case KeyEvent.KEYCODE_DPAD_DOWN:
247+ case KeyEvent.KEYCODE_DPAD_LEFT:
248+ case KeyEvent.KEYCODE_DPAD_RIGHT:
249+ case KeyEvent.KEYCODE_DPAD_CENTER:
250+ case KeyEvent.KEYCODE_SPACE:
251+ return true;
252+ default:
253+ return KeyEvent.isGamepadButton(keyCode);
254+ }
255+ }
256+ }
257+
258+ /**
259+ * A list adapter that displays a summary of the device state.
260+ */
261+ private static class SummaryAdapter extends BaseAdapter {
262+ private static final int BASE_ID_HEADING = 1 << 10;
263+ private static final int BASE_ID_DEVICE_ITEM = 2 << 10;
264+ private static final int BASE_ID_AXIS_ITEM = 3 << 10;
265+ private static final int BASE_ID_KEY_ITEM = 4 << 10;
266+
267+ private final Context mContext;
268+ private final Resources mResources;
269+
270+ private final SparseArray<Item> mDataItems = new SparseArray<Item>();
271+ private final ArrayList<Item> mVisibleItems = new ArrayList<Item>();
272+
273+ private final Heading mDeviceHeading;
274+ private final TextColumn mDeviceNameTextColumn;
275+
276+ private final Heading mAxesHeading;
277+ private final Heading mKeysHeading;
278+
279+ private InputDeviceState mState;
280+
281+ public SummaryAdapter(Context context, Resources resources) {
282+ mContext = context;
283+ mResources = resources;
284+
285+ mDeviceHeading = new Heading(BASE_ID_HEADING | 0,
286+ mResources.getString(R.string.game_controller_input_heading_device));
287+ mDeviceNameTextColumn = new TextColumn(BASE_ID_DEVICE_ITEM | 0,
288+ mResources.getString(R.string.game_controller_input_label_device_name));
289+
290+ mAxesHeading = new Heading(BASE_ID_HEADING | 1,
291+ mResources.getString(R.string.game_controller_input_heading_axes));
292+ mKeysHeading = new Heading(BASE_ID_HEADING | 2,
293+ mResources.getString(R.string.game_controller_input_heading_keys));
294+ }
295+
296+ public void onItemClick(int position) {
297+ if (mState != null) {
298+ Toast toast = Toast.makeText(
299+ mContext, mState.getDevice().toString(), Toast.LENGTH_LONG);
300+ toast.show();
301+ }
302+ }
303+
304+ public void show(InputDeviceState state) {
305+ mState = state;
306+ mVisibleItems.clear();
307+
308+ // Populate device information.
309+ mVisibleItems.add(mDeviceHeading);
310+ mDeviceNameTextColumn.setContent(state.getDevice().getName());
311+ mVisibleItems.add(mDeviceNameTextColumn);
312+
313+ // Populate axes.
314+ mVisibleItems.add(mAxesHeading);
315+ final int axisCount = state.getAxisCount();
316+ for (int i = 0; i < axisCount; i++) {
317+ final int axis = state.getAxis(i);
318+ final int id = BASE_ID_AXIS_ITEM | axis;
319+ TextColumn column = (TextColumn) mDataItems.get(id);
320+ if (column == null) {
321+ column = new TextColumn(id, MotionEvent.axisToString(axis));
322+ mDataItems.put(id, column);
323+ }
324+ column.setContent(Float.toString(state.getAxisValue(i)));
325+ mVisibleItems.add(column);
326+ }
327+
328+ // Populate keys.
329+ mVisibleItems.add(mKeysHeading);
330+ final int keyCount = state.getKeyCount();
331+ for (int i = 0; i < keyCount; i++) {
332+ final int keyCode = state.getKeyCode(i);
333+ final int id = BASE_ID_KEY_ITEM | keyCode;
334+ TextColumn column = (TextColumn) mDataItems.get(id);
335+ if (column == null) {
336+ column = new TextColumn(id, KeyEvent.keyCodeToString(keyCode));
337+ mDataItems.put(id, column);
338+ }
339+ column.setContent(mResources.getString(state.isKeyPressed(i)
340+ ? R.string.game_controller_input_key_pressed
341+ : R.string.game_controller_input_key_released));
342+ mVisibleItems.add(column);
343+ }
344+
345+ notifyDataSetChanged();
346+ }
347+
348+ @Override
349+ public boolean hasStableIds() {
350+ return true;
351+ }
352+
353+ @Override
354+ public int getCount() {
355+ return mVisibleItems.size();
356+ }
357+
358+ @Override
359+ public Item getItem(int position) {
360+ return mVisibleItems.get(position);
361+ }
362+
363+ @Override
364+ public long getItemId(int position) {
365+ return getItem(position).getItemId();
366+ }
367+
368+ @Override
369+ public View getView(int position, View convertView, ViewGroup parent) {
370+ return getItem(position).getView(convertView, parent);
371+ }
372+
373+ private static abstract class Item {
374+ private final int mItemId;
375+ private final int mLayoutResourceId;
376+ private View mView;
377+
378+ public Item(int itemId, int layoutResourceId) {
379+ mItemId = itemId;
380+ mLayoutResourceId = layoutResourceId;
381+ }
382+
383+ public long getItemId() {
384+ return mItemId;
385+ }
386+
387+ public View getView(View convertView, ViewGroup parent) {
388+ if (mView == null) {
389+ LayoutInflater inflater = (LayoutInflater)
390+ parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
391+ mView = inflater.inflate(mLayoutResourceId, parent, false);
392+ initView(mView);
393+ }
394+ updateView(mView);
395+ return mView;
396+ }
397+
398+ protected void initView(View view) {
399+ }
400+
401+ protected void updateView(View view) {
402+ }
403+ }
404+
405+ private static class Heading extends Item {
406+ private final String mLabel;
407+
408+ public Heading(int itemId, String label) {
409+ super(itemId, R.layout.game_controller_input_heading);
410+ mLabel = label;
411+ }
412+
413+ @Override
414+ public void initView(View view) {
415+ TextView textView = (TextView) view;
416+ textView.setText(mLabel);
417+ }
418+ }
419+
420+ private static class TextColumn extends Item {
421+ private final String mLabel;
422+
423+ private String mContent;
424+ private TextView mContentView;
425+
426+ public TextColumn(int itemId, String label) {
427+ super(itemId, R.layout.game_controller_input_text_column);
428+ mLabel = label;
429+ }
430+
431+ public void setContent(String content) {
432+ mContent = content;
433+ }
434+
435+ @Override
436+ public void initView(View view) {
437+ TextView textView = (TextView) view.findViewById(R.id.label);
438+ textView.setText(mLabel);
439+
440+ mContentView = (TextView) view.findViewById(R.id.content);
441+ }
442+
443+ @Override
444+ public void updateView(View view) {
445+ mContentView.setText(mContent);
446+ }
447+ }
448+ }
449+}
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/view/GameView.java
@@ -0,0 +1,747 @@
1+/*
2+ * Copyright (C) 2011 The Android Open Source Project
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+package com.example.android.apis.view;
18+
19+import android.content.Context;
20+import android.graphics.Canvas;
21+import android.graphics.Paint;
22+import android.graphics.Path;
23+import android.graphics.Paint.Style;
24+import android.os.Handler;
25+import android.os.SystemClock;
26+import android.util.AttributeSet;
27+import android.view.InputDevice;
28+import android.view.KeyEvent;
29+import android.view.MotionEvent;
30+import android.view.View;
31+
32+import java.util.ArrayList;
33+import java.util.List;
34+import java.util.Random;
35+
36+/**
37+ * A trivial joystick based physics game to demonstrate joystick handling.
38+ *
39+ * @see GameControllerInput
40+ */
41+public class GameView extends View {
42+ private final long ANIMATION_TIME_STEP = 1000 / 60;
43+ private final int MAX_OBSTACLES = 12;
44+
45+ private final Random mRandom;
46+ private Ship mShip;
47+ private final List<Bullet> mBullets;
48+ private final List<Obstacle> mObstacles;
49+
50+ private long mLastStepTime;
51+ private InputDevice mLastInputDevice;
52+
53+ private static final int DPAD_STATE_LEFT = 1 << 0;
54+ private static final int DPAD_STATE_RIGHT = 1 << 1;
55+ private static final int DPAD_STATE_UP = 1 << 2;
56+ private static final int DPAD_STATE_DOWN = 1 << 3;
57+
58+ private int mDPadState;
59+
60+ private float mShipSize;
61+ private float mMaxShipThrust;
62+ private float mMaxShipSpeed;
63+
64+ private float mBulletSize;
65+ private float mBulletSpeed;
66+
67+ private float mMinObstacleSize;
68+ private float mMaxObstacleSize;
69+ private float mMinObstacleSpeed;
70+ private float mMaxObstacleSpeed;
71+
72+ private final Runnable mAnimationRunnable = new Runnable() {
73+ public void run() {
74+ animate();
75+ }
76+ };
77+
78+ public GameView(Context context, AttributeSet attrs) {
79+ super(context, attrs);
80+
81+ mRandom = new Random();
82+ mBullets = new ArrayList<Bullet>();
83+ mObstacles = new ArrayList<Obstacle>();
84+
85+ setFocusable(true);
86+ setFocusableInTouchMode(true);
87+
88+ float baseSize = getContext().getResources().getDisplayMetrics().density * 5f;
89+ float baseSpeed = baseSize * 3;
90+
91+ mShipSize = baseSize * 3;
92+ mMaxShipThrust = baseSpeed * 0.25f;
93+ mMaxShipSpeed = baseSpeed * 12;
94+
95+ mBulletSize = baseSize;
96+ mBulletSpeed = baseSpeed * 12;
97+
98+ mMinObstacleSize = baseSize * 2;
99+ mMaxObstacleSize = baseSize * 12;
100+ mMinObstacleSpeed = baseSpeed;
101+ mMaxObstacleSpeed = baseSpeed * 3;
102+ }
103+
104+ @Override
105+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
106+ super.onSizeChanged(w, h, oldw, oldh);
107+
108+ // Reset the game when the view changes size.
109+ reset();
110+ }
111+
112+ @Override
113+ public boolean onKeyDown(int keyCode, KeyEvent event) {
114+ ensureInitialized();
115+
116+ // Handle DPad keys and fire button on initial down but not on auto-repeat.
117+ boolean handled = false;
118+ if (event.getRepeatCount() == 0) {
119+ switch (keyCode) {
120+ case KeyEvent.KEYCODE_DPAD_LEFT:
121+ mShip.setHeadingX(-1);
122+ mDPadState |= DPAD_STATE_LEFT;
123+ handled = true;
124+ break;
125+ case KeyEvent.KEYCODE_DPAD_RIGHT:
126+ mShip.setHeadingX(1);
127+ mDPadState |= DPAD_STATE_RIGHT;
128+ handled = true;
129+ break;
130+ case KeyEvent.KEYCODE_DPAD_UP:
131+ mShip.setHeadingY(-1);
132+ mDPadState |= DPAD_STATE_UP;
133+ handled = true;
134+ break;
135+ case KeyEvent.KEYCODE_DPAD_DOWN:
136+ mShip.setHeadingY(1);
137+ mDPadState |= DPAD_STATE_DOWN;
138+ handled = true;
139+ break;
140+ default:
141+ if (isFireKey(keyCode)) {
142+ fire();
143+ handled = true;
144+ }
145+ break;
146+ }
147+ }
148+ if (handled) {
149+ step(event.getEventTime());
150+ return true;
151+ }
152+ return super.onKeyDown(keyCode, event);
153+ }
154+
155+ @Override
156+ public boolean onKeyUp(int keyCode, KeyEvent event) {
157+ ensureInitialized();
158+
159+ // Handle keys going up.
160+ boolean handled = false;
161+ switch (keyCode) {
162+ case KeyEvent.KEYCODE_DPAD_LEFT:
163+ mShip.setHeadingX(0);
164+ mDPadState &= ~DPAD_STATE_LEFT;
165+ handled = true;
166+ break;
167+ case KeyEvent.KEYCODE_DPAD_RIGHT:
168+ mShip.setHeadingX(0);
169+ mDPadState &= ~DPAD_STATE_RIGHT;
170+ handled = true;
171+ break;
172+ case KeyEvent.KEYCODE_DPAD_UP:
173+ mShip.setHeadingY(0);
174+ mDPadState &= ~DPAD_STATE_UP;
175+ handled = true;
176+ break;
177+ case KeyEvent.KEYCODE_DPAD_DOWN:
178+ mShip.setHeadingY(0);
179+ mDPadState &= ~DPAD_STATE_DOWN;
180+ handled = true;
181+ break;
182+ default:
183+ if (isFireKey(keyCode)) {
184+ handled = true;
185+ }
186+ break;
187+ }
188+ if (handled) {
189+ step(event.getEventTime());
190+ return true;
191+ }
192+ return super.onKeyUp(keyCode, event);
193+ }
194+
195+ private static boolean isFireKey(int keyCode) {
196+ return KeyEvent.isGamepadButton(keyCode)
197+ || keyCode == KeyEvent.KEYCODE_DPAD_CENTER
198+ || keyCode == KeyEvent.KEYCODE_SPACE;
199+ }
200+
201+ @Override
202+ public boolean onGenericMotionEvent(MotionEvent event) {
203+ ensureInitialized();
204+
205+ // Check that the event came from a joystick since a generic motion event
206+ // could be almost anything.
207+ if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0
208+ && event.getAction() == MotionEvent.ACTION_MOVE) {
209+ // Cache the most recently obtained device information.
210+ // The device information may change over time but it can be
211+ // somewhat expensive to query.
212+ if (mLastInputDevice == null || mLastInputDevice.getId() != event.getDeviceId()) {
213+ mLastInputDevice = event.getDevice();
214+ // It's possible for the device id to be invalid.
215+ // In that case, getDevice() will return null.
216+ if (mLastInputDevice == null) {
217+ return false;
218+ }
219+ }
220+
221+ // Ignore joystick while the DPad is pressed to avoid conflicting motions.
222+ if (mDPadState != 0) {
223+ return true;
224+ }
225+
226+ // Process all historical movement samples in the batch.
227+ final int historySize = event.getHistorySize();
228+ for (int i = 0; i < historySize; i++) {
229+ processJoystickInput(event, i);
230+ }
231+
232+ // Process the current movement sample in the batch.
233+ processJoystickInput(event, -1);
234+ return true;
235+ }
236+ return super.onGenericMotionEvent(event);
237+ }
238+
239+ private void processJoystickInput(MotionEvent event, int historyPos) {
240+ // Get joystick position.
241+ // Many game pads with two joysticks report the position of the second joystick
242+ // using the Z and RZ axes so we also handle those.
243+ // In a real game, we would allow the user to configure the axes manually.
244+ float x = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_X, historyPos);
245+ if (x == 0) {
246+ x = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_HAT_X, historyPos);
247+ }
248+ if (x == 0) {
249+ x = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_Z, historyPos);
250+ }
251+
252+ float y = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_Y, historyPos);
253+ if (y == 0) {
254+ y = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_HAT_Y, historyPos);
255+ }
256+ if (y == 0) {
257+ y = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_RZ, historyPos);
258+ }
259+
260+ // Set the ship heading.
261+ mShip.setHeading(x, y);
262+ step(historyPos < 0 ? event.getEventTime() : event.getHistoricalEventTime(historyPos));
263+ }
264+
265+ private static float getCenteredAxis(MotionEvent event, InputDevice device,
266+ int axis, int historyPos) {
267+ final InputDevice.MotionRange range = device.getMotionRange(axis);
268+ if (range != null) {
269+ final float flat = range.getFlat();
270+ final float value = historyPos < 0 ? event.getAxisValue(axis)
271+ : event.getHistoricalAxisValue(axis, historyPos);
272+
273+ // Ignore axis values that are within the 'flat' region of the joystick axis center.
274+ // A joystick at rest does not always report an absolute position of (0,0).
275+ if (Math.abs(value) > flat) {
276+ return value;
277+ }
278+ }
279+ return 0;
280+ }
281+
282+ @Override
283+ public void onWindowFocusChanged(boolean hasWindowFocus) {
284+ // Turn on and off animations based on the window focus.
285+ // Alternately, we could update the game state using the Activity onResume()
286+ // and onPause() lifecycle events.
287+ if (hasWindowFocus) {
288+ getHandler().postDelayed(mAnimationRunnable, ANIMATION_TIME_STEP);
289+ mLastStepTime = SystemClock.uptimeMillis();
290+ } else {
291+ getHandler().removeCallbacks(mAnimationRunnable);
292+
293+ mDPadState = 0;
294+ if (mShip != null) {
295+ mShip.setHeading(0, 0);
296+ mShip.setVelocity(0, 0);
297+ }
298+ }
299+
300+ super.onWindowFocusChanged(hasWindowFocus);
301+ }
302+
303+ private void fire() {
304+ if (mShip != null && !mShip.isDestroyed()) {
305+ Bullet bullet = new Bullet();
306+ bullet.setPosition(mShip.getBulletInitialX(), mShip.getBulletInitialY());
307+ bullet.setVelocity(mShip.getBulletVelocityX(mBulletSpeed),
308+ mShip.getBulletVelocityY(mBulletSpeed));
309+ mBullets.add(bullet);
310+ }
311+ }
312+
313+ private void ensureInitialized() {
314+ if (mShip == null) {
315+ reset();
316+ }
317+ }
318+
319+ private void reset() {
320+ mShip = new Ship();
321+ mBullets.clear();
322+ mObstacles.clear();
323+ }
324+
325+ void animate() {
326+ long currentStepTime = SystemClock.uptimeMillis();
327+ step(currentStepTime);
328+
329+ Handler handler = getHandler();
330+ if (handler != null) {
331+ handler.postAtTime(mAnimationRunnable, currentStepTime + ANIMATION_TIME_STEP);
332+ invalidate();
333+ }
334+ }
335+
336+ private void step(long currentStepTime) {
337+ float tau = (currentStepTime - mLastStepTime) * 0.001f;
338+ mLastStepTime = currentStepTime;
339+
340+ ensureInitialized();
341+
342+ // Move the ship.
343+ mShip.accelerate(tau, mMaxShipThrust, mMaxShipSpeed);
344+ if (!mShip.step(tau)) {
345+ reset();
346+ }
347+
348+ // Move the bullets.
349+ int numBullets = mBullets.size();
350+ for (int i = 0; i < numBullets; i++) {
351+ final Bullet bullet = mBullets.get(i);
352+ if (!bullet.step(tau)) {
353+ mBullets.remove(i);
354+ i -= 1;
355+ numBullets -= 1;
356+ }
357+ }
358+
359+ // Move obstacles.
360+ int numObstacles = mObstacles.size();
361+ for (int i = 0; i < numObstacles; i++) {
362+ final Obstacle obstacle = mObstacles.get(i);
363+ if (!obstacle.step(tau)) {
364+ mObstacles.remove(i);
365+ i -= 1;
366+ numObstacles -= 1;
367+ }
368+ }
369+
370+ // Check for collisions between bullets and obstacles.
371+ for (int i = 0; i < numBullets; i++) {
372+ final Bullet bullet = mBullets.get(i);
373+ for (int j = 0; j < numObstacles; j++) {
374+ final Obstacle obstacle = mObstacles.get(j);
375+ if (bullet.collidesWith(obstacle)) {
376+ bullet.destroy();
377+ obstacle.destroy();
378+ break;
379+ }
380+ }
381+ }
382+
383+ // Check for collisions between the ship and obstacles.
384+ for (int i = 0; i < numObstacles; i++) {
385+ final Obstacle obstacle = mObstacles.get(i);
386+ if (mShip.collidesWith(obstacle)) {
387+ mShip.destroy();
388+ obstacle.destroy();
389+ break;
390+ }
391+ }
392+
393+ // Spawn more obstacles offscreen when needed.
394+ // Avoid putting them right on top of the ship.
395+ OuterLoop: while (mObstacles.size() < MAX_OBSTACLES) {
396+ final float minDistance = mShipSize * 4;
397+ float size = mRandom.nextFloat() * (mMaxObstacleSize - mMinObstacleSize)
398+ + mMinObstacleSize;
399+ float positionX, positionY;
400+ int tries = 0;
401+ do {
402+ int edge = mRandom.nextInt(4);
403+ switch (edge) {
404+ case 0:
405+ positionX = -size;
406+ positionY = mRandom.nextInt(getHeight());
407+ break;
408+ case 1:
409+ positionX = getWidth() + size;
410+ positionY = mRandom.nextInt(getHeight());
411+ break;
412+ case 2:
413+ positionX = mRandom.nextInt(getWidth());
414+ positionY = -size;
415+ break;
416+ default:
417+ positionX = mRandom.nextInt(getWidth());
418+ positionY = getHeight() + size;
419+ break;
420+ }
421+ if (++tries > 10) {
422+ break OuterLoop;
423+ }
424+ } while (mShip.distanceTo(positionX, positionY) < minDistance);
425+
426+ float direction = mRandom.nextFloat() * (float) Math.PI * 2;
427+ float speed = mRandom.nextFloat() * (mMaxObstacleSpeed - mMinObstacleSpeed)
428+ + mMinObstacleSpeed;
429+ float velocityX = (float) Math.cos(direction) * speed;
430+ float velocityY = (float) Math.sin(direction) * speed;
431+
432+ Obstacle obstacle = new Obstacle();
433+ obstacle.setPosition(positionX, positionY);
434+ obstacle.setSize(size);
435+ obstacle.setVelocity(velocityX, velocityY);
436+ mObstacles.add(obstacle);
437+ }
438+ }
439+
440+ @Override
441+ protected void onDraw(Canvas canvas) {
442+ super.onDraw(canvas);
443+
444+ // Draw the ship.
445+ if (mShip != null) {
446+ mShip.draw(canvas);
447+ }
448+
449+ // Draw bullets.
450+ int numBullets = mBullets.size();
451+ for (int i = 0; i < numBullets; i++) {
452+ final Bullet bullet = mBullets.get(i);
453+ bullet.draw(canvas);
454+ }
455+
456+ // Draw obstacles.
457+ int numObstacles = mObstacles.size();
458+ for (int i = 0; i < numObstacles; i++) {
459+ final Obstacle obstacle = mObstacles.get(i);
460+ obstacle.draw(canvas);
461+ }
462+ }
463+
464+ static float pythag(float x, float y) {
465+ return (float) Math.sqrt(x * x + y * y);
466+ }
467+
468+ static int blend(float alpha, int from, int to) {
469+ return from + (int) ((to - from) * alpha);
470+ }
471+
472+ static void setPaintARGBBlend(Paint paint, float alpha,
473+ int a1, int r1, int g1, int b1,
474+ int a2, int r2, int g2, int b2) {
475+ paint.setARGB(blend(alpha, a1, a2), blend(alpha, r1, r2),
476+ blend(alpha, g1, g2), blend(alpha, b1, b2));
477+ }
478+
479+ private abstract class Sprite {
480+ protected float mPositionX;
481+ protected float mPositionY;
482+ protected float mVelocityX;
483+ protected float mVelocityY;
484+ protected float mSize;
485+ protected boolean mDestroyed;
486+ protected float mDestroyAnimProgress;
487+
488+ public void setPosition(float x, float y) {
489+ mPositionX = x;
490+ mPositionY = y;
491+ }
492+
493+ public void setVelocity(float x, float y) {
494+ mVelocityX = x;
495+ mVelocityY = y;
496+ }
497+
498+ public void setSize(float size) {
499+ mSize = size;
500+ }
501+
502+ public float distanceTo(float x, float y) {
503+ return pythag(mPositionX - x, mPositionY - y);
504+ }
505+
506+ public float distanceTo(Sprite other) {
507+ return distanceTo(other.mPositionX, other.mPositionY);
508+ }
509+
510+ public boolean collidesWith(Sprite other) {
511+ // Really bad collision detection.
512+ return !mDestroyed && !other.mDestroyed
513+ && distanceTo(other) <= Math.max(mSize, other.mSize)
514+ + Math.min(mSize, other.mSize) * 0.5f;
515+ }
516+
517+ public boolean isDestroyed() {
518+ return mDestroyed;
519+ }
520+
521+ public boolean step(float tau) {
522+ mPositionX += mVelocityX * tau;
523+ mPositionY += mVelocityY * tau;
524+
525+ if (mDestroyed) {
526+ mDestroyAnimProgress += tau / getDestroyAnimDuration();
527+ if (mDestroyAnimProgress >= 1.0f) {
528+ return false;
529+ }
530+ }
531+ return true;
532+ }
533+
534+ public abstract void draw(Canvas canvas);
535+
536+ public abstract float getDestroyAnimDuration();
537+
538+ protected boolean isOutsidePlayfield() {
539+ final int width = GameView.this.getWidth();
540+ final int height = GameView.this.getHeight();
541+ return mPositionX < 0 || mPositionX >= width
542+ || mPositionY < 0 || mPositionY >= height;
543+ }
544+
545+ protected void wrapAtPlayfieldBoundary() {
546+ final int width = GameView.this.getWidth();
547+ final int height = GameView.this.getHeight();
548+ while (mPositionX <= -mSize) {
549+ mPositionX += width + mSize * 2;
550+ }
551+ while (mPositionX >= width + mSize) {
552+ mPositionX -= width + mSize * 2;
553+ }
554+ while (mPositionY <= -mSize) {
555+ mPositionY += height + mSize * 2;
556+ }
557+ while (mPositionY >= height + mSize) {
558+ mPositionY -= height + mSize * 2;
559+ }
560+ }
561+
562+ public void destroy() {
563+ mDestroyed = true;
564+ step(0);
565+ }
566+ }
567+
568+ private class Ship extends Sprite {
569+ private static final float CORNER_ANGLE = (float) Math.PI * 2 / 3;
570+ private static final float TO_DEGREES = (float) (180.0 / Math.PI);
571+
572+ private float mHeadingX;
573+ private float mHeadingY;
574+ private float mHeadingAngle;
575+ private float mHeadingMagnitude;
576+ private final Paint mPaint;
577+ private final Path mPath;
578+
579+
580+ public Ship() {
581+ mPaint = new Paint();
582+ mPaint.setStyle(Style.FILL);
583+
584+ setPosition(getWidth() * 0.5f, getHeight() * 0.5f);
585+ setVelocity(0, 0);
586+ setSize(mShipSize);
587+
588+ mPath = new Path();
589+ mPath.moveTo(0, 0);
590+ mPath.lineTo((float)Math.cos(-CORNER_ANGLE) * mSize,
591+ (float)Math.sin(-CORNER_ANGLE) * mSize);
592+ mPath.lineTo(mSize, 0);
593+ mPath.lineTo((float)Math.cos(CORNER_ANGLE) * mSize,
594+ (float)Math.sin(CORNER_ANGLE) * mSize);
595+ mPath.lineTo(0, 0);
596+ }
597+
598+ public void setHeadingX(float x) {
599+ mHeadingX = x;
600+ updateHeading();
601+ }
602+
603+ public void setHeadingY(float y) {
604+ mHeadingY = y;
605+ updateHeading();
606+ }
607+
608+ public void setHeading(float x, float y) {
609+ mHeadingX = x;
610+ mHeadingY = y;
611+ updateHeading();
612+ }
613+
614+ private void updateHeading() {
615+ mHeadingMagnitude = pythag(mHeadingX, mHeadingY);
616+ if (mHeadingMagnitude > 0.1f) {
617+ mHeadingAngle = (float) Math.atan2(mHeadingY, mHeadingX);
618+ }
619+ }
620+
621+ private float polarX(float radius) {
622+ return (float) Math.cos(mHeadingAngle) * radius;
623+ }
624+
625+ private float polarY(float radius) {
626+ return (float) Math.sin(mHeadingAngle) * radius;
627+ }
628+
629+ public float getBulletInitialX() {
630+ return mPositionX + polarX(mSize);
631+ }
632+
633+ public float getBulletInitialY() {
634+ return mPositionY + polarY(mSize);
635+ }
636+
637+ public float getBulletVelocityX(float relativeSpeed) {
638+ return mVelocityX + polarX(relativeSpeed);
639+ }
640+
641+ public float getBulletVelocityY(float relativeSpeed) {
642+ return mVelocityY + polarY(relativeSpeed);
643+ }
644+
645+ public void accelerate(float tau, float maxThrust, float maxSpeed) {
646+ final float thrust = mHeadingMagnitude * maxThrust;
647+ mVelocityX += polarX(thrust);
648+ mVelocityY += polarY(thrust);
649+
650+ final float speed = pythag(mVelocityX, mVelocityY);
651+ if (speed > maxSpeed) {
652+ final float scale = maxSpeed / speed;
653+ mVelocityX = mVelocityX * scale;
654+ mVelocityY = mVelocityY * scale;
655+ }
656+ }
657+
658+ @Override
659+ public boolean step(float tau) {
660+ if (!super.step(tau)) {
661+ return false;
662+ }
663+ wrapAtPlayfieldBoundary();
664+ return true;
665+ }
666+
667+ public void draw(Canvas canvas) {
668+ setPaintARGBBlend(mPaint, mDestroyAnimProgress,
669+ 255, 63, 255, 63,
670+ 0, 255, 0, 0);
671+
672+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
673+ canvas.translate(mPositionX, mPositionY);
674+ canvas.rotate(mHeadingAngle * TO_DEGREES);
675+ canvas.drawPath(mPath, mPaint);
676+ canvas.restore();
677+ }
678+
679+ @Override
680+ public float getDestroyAnimDuration() {
681+ return 1.0f;
682+ }
683+ }
684+
685+ private class Bullet extends Sprite {
686+ private final Paint mPaint;
687+
688+ public Bullet() {
689+ mPaint = new Paint();
690+ mPaint.setStyle(Style.FILL);
691+
692+ setSize(mBulletSize);
693+ }
694+
695+ @Override
696+ public boolean step(float tau) {
697+ if (!super.step(tau)) {
698+ return false;
699+ }
700+ return !isOutsidePlayfield();
701+ }
702+
703+ public void draw(Canvas canvas) {
704+ setPaintARGBBlend(mPaint, mDestroyAnimProgress,
705+ 255, 255, 255, 0,
706+ 0, 255, 255, 255);
707+ canvas.drawCircle(mPositionX, mPositionY, mSize, mPaint);
708+ }
709+
710+ @Override
711+ public float getDestroyAnimDuration() {
712+ return 0.125f;
713+ }
714+ }
715+
716+ private class Obstacle extends Sprite {
717+ private final Paint mPaint;
718+
719+ public Obstacle() {
720+ mPaint = new Paint();
721+ mPaint.setARGB(255, 127, 127, 255);
722+ mPaint.setStyle(Style.FILL);
723+ }
724+
725+ @Override
726+ public boolean step(float tau) {
727+ if (!super.step(tau)) {
728+ return false;
729+ }
730+ wrapAtPlayfieldBoundary();
731+ return true;
732+ }
733+
734+ public void draw(Canvas canvas) {
735+ setPaintARGBBlend(mPaint, mDestroyAnimProgress,
736+ 255, 127, 127, 255,
737+ 0, 255, 0, 0);
738+ canvas.drawCircle(mPositionX, mPositionY,
739+ mSize * (1.0f - mDestroyAnimProgress), mPaint);
740+ }
741+
742+ @Override
743+ public float getDestroyAnimDuration() {
744+ return 0.25f;
745+ }
746+ }
747+}
\ No newline at end of file