Add a sample to demonstrate game controller usage.
Change-Id: I86a91916a39f3a211b06016b163f8d4a6d4a5a3b
This commit is contained in:
@@ -1961,6 +1961,13 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".view.GameControllerInput" android:label="Views/Game Controller Input">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.SAMPLE_CODE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- ************************************* -->
|
||||
<!-- GRAPHICS SAMPLES -->
|
||||
<!-- ************************************* -->
|
||||
|
||||
52
samples/ApiDemos/res/layout/game_controller_input.xml
Normal file
52
samples/ApiDemos/res/layout/game_controller_input.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Game controller input demo. -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/game_controller_input_description"
|
||||
android:padding="12dip" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dip"
|
||||
android:layout_weight="1"
|
||||
android:padding="12dip">
|
||||
<ListView
|
||||
android:id="@+id/summary"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:padding="3dip">
|
||||
</ListView>
|
||||
|
||||
<com.example.android.apis.view.GameView
|
||||
android:id="@+id/game"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="#000000"
|
||||
android:padding="3dip" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="6dip"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="6dip">
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:gravity="left"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
android:gravity="left"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
@@ -773,6 +773,20 @@
|
||||
dot will append the drag\'s textual conversion to the EditText.
|
||||
</string>
|
||||
|
||||
<string name="game_controller_input_description">
|
||||
This activity demonstrates how to process input events received from
|
||||
game controllers. Please connect your game controller now and try
|
||||
moving the joysticks or pressing buttons. If it helps, try to imagine
|
||||
that you are a lone space cowboy in hot pursuit of the aliens who kidnapped
|
||||
your favorite llama on their way back to Andromeda...
|
||||
</string>
|
||||
<string name="game_controller_input_heading_device">Input Device</string>
|
||||
<string name="game_controller_input_heading_axes">Axes</string>
|
||||
<string name="game_controller_input_heading_keys">Keys and Buttons</string>
|
||||
<string name="game_controller_input_label_device_name">Name</string>
|
||||
<string name="game_controller_input_key_pressed">Pressed</string>
|
||||
<string name="game_controller_input_key_released">Released</string>
|
||||
|
||||
<!-- ============================== -->
|
||||
<!-- GoogleLogin examples strings -->
|
||||
<!-- ============================== -->
|
||||
|
||||
@@ -0,0 +1,449 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.apis.view;
|
||||
|
||||
import com.example.android.apis.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.InputDevice;
|
||||
import android.view.InputEvent;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
|
||||
/**
|
||||
* Demonstrates how to process input events received from game controllers.
|
||||
*
|
||||
* This activity displays button states and joystick positions.
|
||||
* Also writes detailed information about relevant input events to the log.
|
||||
*
|
||||
* The game controller is also uses to control a very simple game. See {@link GameView}
|
||||
* for the game itself.
|
||||
*/
|
||||
public class GameControllerInput extends Activity {
|
||||
private static final String TAG = "GameControllerInput";
|
||||
|
||||
private SparseArray<InputDeviceState> mInputDeviceStates;
|
||||
private GameView mGame;
|
||||
private ListView mSummaryList;
|
||||
private SummaryAdapter mSummaryAdapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mInputDeviceStates = new SparseArray<InputDeviceState>();
|
||||
mSummaryAdapter = new SummaryAdapter(this, getResources());
|
||||
|
||||
setContentView(R.layout.game_controller_input);
|
||||
|
||||
mGame = (GameView) findViewById(R.id.game);
|
||||
|
||||
mSummaryList = (ListView) findViewById(R.id.summary);
|
||||
mSummaryList.setAdapter(mSummaryAdapter);
|
||||
mSummaryList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
mSummaryAdapter.onItemClick(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
|
||||
mGame.requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
// Update device state for visualization and logging.
|
||||
InputDeviceState state = getInputDeviceState(event);
|
||||
if (state != null) {
|
||||
switch (event.getAction()) {
|
||||
case KeyEvent.ACTION_DOWN:
|
||||
if (state.onKeyDown(event)) {
|
||||
mSummaryAdapter.show(state);
|
||||
}
|
||||
break;
|
||||
case KeyEvent.ACTION_UP:
|
||||
if (state.onKeyUp(event)) {
|
||||
mSummaryAdapter.show(state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchGenericMotionEvent(MotionEvent event) {
|
||||
// Check that the event came from a joystick since a generic motion event
|
||||
// could be almost anything.
|
||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0
|
||||
&& event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
// Update device state for visualization and logging.
|
||||
InputDeviceState state = getInputDeviceState(event);
|
||||
if (state != null && state.onJoystickMotion(event)) {
|
||||
mSummaryAdapter.show(state);
|
||||
}
|
||||
}
|
||||
return super.dispatchGenericMotionEvent(event);
|
||||
}
|
||||
|
||||
private InputDeviceState getInputDeviceState(InputEvent event) {
|
||||
final int deviceId = event.getDeviceId();
|
||||
InputDeviceState state = mInputDeviceStates.get(deviceId);
|
||||
if (state == null) {
|
||||
final InputDevice device = event.getDevice();
|
||||
if (device == null) {
|
||||
return null;
|
||||
}
|
||||
state = new InputDeviceState(device);
|
||||
mInputDeviceStates.put(deviceId, state);
|
||||
|
||||
Log.i(TAG, device.toString());
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the state of joystick axes and game controller buttons for a particular
|
||||
* input device for diagnostic purposes.
|
||||
*/
|
||||
private static class InputDeviceState {
|
||||
private final InputDevice mDevice;
|
||||
private final int[] mAxes;
|
||||
private final float[] mAxisValues;
|
||||
private final SparseIntArray mKeys;
|
||||
|
||||
public InputDeviceState(InputDevice device) {
|
||||
mDevice = device;
|
||||
mAxes = device.getMotionAxes();
|
||||
mAxisValues = new float[mAxes.length];
|
||||
mKeys = new SparseIntArray();
|
||||
}
|
||||
|
||||
public InputDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
public int getAxisCount() {
|
||||
return mAxes.length;
|
||||
}
|
||||
|
||||
public int getAxis(int axisIndex) {
|
||||
return mAxes[axisIndex];
|
||||
}
|
||||
|
||||
public float getAxisValue(int axisIndex) {
|
||||
return mAxisValues[axisIndex];
|
||||
}
|
||||
|
||||
public int getKeyCount() {
|
||||
return mKeys.size();
|
||||
}
|
||||
|
||||
public int getKeyCode(int keyIndex) {
|
||||
return mKeys.keyAt(keyIndex);
|
||||
}
|
||||
|
||||
public boolean isKeyPressed(int keyIndex) {
|
||||
return mKeys.valueAt(keyIndex) != 0;
|
||||
}
|
||||
|
||||
public boolean onKeyDown(KeyEvent event) {
|
||||
final int keyCode = event.getKeyCode();
|
||||
if (isGameKey(keyCode)) {
|
||||
if (event.getRepeatCount() == 0) {
|
||||
final String symbolicName = KeyEvent.keyCodeToString(keyCode);
|
||||
mKeys.put(keyCode, 1);
|
||||
Log.i(TAG, mDevice.getName() + " - Key Down: " + symbolicName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onKeyUp(KeyEvent event) {
|
||||
final int keyCode = event.getKeyCode();
|
||||
if (isGameKey(keyCode)) {
|
||||
int index = mKeys.indexOfKey(keyCode);
|
||||
if (index >= 0) {
|
||||
final String symbolicName = KeyEvent.keyCodeToString(keyCode);
|
||||
mKeys.put(keyCode, 0);
|
||||
Log.i(TAG, mDevice.getName() + " - Key Up: " + symbolicName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onJoystickMotion(MotionEvent event) {
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append(mDevice.getName()).append(" - Joystick Motion:\n");
|
||||
|
||||
final int historySize = event.getHistorySize();
|
||||
for (int i = 0; i < mAxes.length; i++) {
|
||||
final int axis = mAxes[i];
|
||||
final float value = event.getAxisValue(axis);
|
||||
mAxisValues[i] = value;
|
||||
message.append(" ").append(MotionEvent.axisToString(axis)).append(": ");
|
||||
|
||||
// Append all historical values in the batch.
|
||||
for (int historyPos = 0; historyPos < historySize; historyPos++) {
|
||||
message.append(event.getHistoricalAxisValue(axis, historyPos));
|
||||
message.append(", ");
|
||||
}
|
||||
|
||||
// Append the current value.
|
||||
message.append(value);
|
||||
message.append("\n");
|
||||
}
|
||||
Log.i(TAG, message.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check whether this is a key we care about.
|
||||
// In a real game, we would probably let the user configure which keys to use
|
||||
// instead of hardcoding the keys like this.
|
||||
private static boolean isGameKey(int keyCode) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
case KeyEvent.KEYCODE_SPACE:
|
||||
return true;
|
||||
default:
|
||||
return KeyEvent.isGamepadButton(keyCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list adapter that displays a summary of the device state.
|
||||
*/
|
||||
private static class SummaryAdapter extends BaseAdapter {
|
||||
private static final int BASE_ID_HEADING = 1 << 10;
|
||||
private static final int BASE_ID_DEVICE_ITEM = 2 << 10;
|
||||
private static final int BASE_ID_AXIS_ITEM = 3 << 10;
|
||||
private static final int BASE_ID_KEY_ITEM = 4 << 10;
|
||||
|
||||
private final Context mContext;
|
||||
private final Resources mResources;
|
||||
|
||||
private final SparseArray<Item> mDataItems = new SparseArray<Item>();
|
||||
private final ArrayList<Item> mVisibleItems = new ArrayList<Item>();
|
||||
|
||||
private final Heading mDeviceHeading;
|
||||
private final TextColumn mDeviceNameTextColumn;
|
||||
|
||||
private final Heading mAxesHeading;
|
||||
private final Heading mKeysHeading;
|
||||
|
||||
private InputDeviceState mState;
|
||||
|
||||
public SummaryAdapter(Context context, Resources resources) {
|
||||
mContext = context;
|
||||
mResources = resources;
|
||||
|
||||
mDeviceHeading = new Heading(BASE_ID_HEADING | 0,
|
||||
mResources.getString(R.string.game_controller_input_heading_device));
|
||||
mDeviceNameTextColumn = new TextColumn(BASE_ID_DEVICE_ITEM | 0,
|
||||
mResources.getString(R.string.game_controller_input_label_device_name));
|
||||
|
||||
mAxesHeading = new Heading(BASE_ID_HEADING | 1,
|
||||
mResources.getString(R.string.game_controller_input_heading_axes));
|
||||
mKeysHeading = new Heading(BASE_ID_HEADING | 2,
|
||||
mResources.getString(R.string.game_controller_input_heading_keys));
|
||||
}
|
||||
|
||||
public void onItemClick(int position) {
|
||||
if (mState != null) {
|
||||
Toast toast = Toast.makeText(
|
||||
mContext, mState.getDevice().toString(), Toast.LENGTH_LONG);
|
||||
toast.show();
|
||||
}
|
||||
}
|
||||
|
||||
public void show(InputDeviceState state) {
|
||||
mState = state;
|
||||
mVisibleItems.clear();
|
||||
|
||||
// Populate device information.
|
||||
mVisibleItems.add(mDeviceHeading);
|
||||
mDeviceNameTextColumn.setContent(state.getDevice().getName());
|
||||
mVisibleItems.add(mDeviceNameTextColumn);
|
||||
|
||||
// Populate axes.
|
||||
mVisibleItems.add(mAxesHeading);
|
||||
final int axisCount = state.getAxisCount();
|
||||
for (int i = 0; i < axisCount; i++) {
|
||||
final int axis = state.getAxis(i);
|
||||
final int id = BASE_ID_AXIS_ITEM | axis;
|
||||
TextColumn column = (TextColumn) mDataItems.get(id);
|
||||
if (column == null) {
|
||||
column = new TextColumn(id, MotionEvent.axisToString(axis));
|
||||
mDataItems.put(id, column);
|
||||
}
|
||||
column.setContent(Float.toString(state.getAxisValue(i)));
|
||||
mVisibleItems.add(column);
|
||||
}
|
||||
|
||||
// Populate keys.
|
||||
mVisibleItems.add(mKeysHeading);
|
||||
final int keyCount = state.getKeyCount();
|
||||
for (int i = 0; i < keyCount; i++) {
|
||||
final int keyCode = state.getKeyCode(i);
|
||||
final int id = BASE_ID_KEY_ITEM | keyCode;
|
||||
TextColumn column = (TextColumn) mDataItems.get(id);
|
||||
if (column == null) {
|
||||
column = new TextColumn(id, KeyEvent.keyCodeToString(keyCode));
|
||||
mDataItems.put(id, column);
|
||||
}
|
||||
column.setContent(mResources.getString(state.isKeyPressed(i)
|
||||
? R.string.game_controller_input_key_pressed
|
||||
: R.string.game_controller_input_key_released));
|
||||
mVisibleItems.add(column);
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mVisibleItems.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item getItem(int position) {
|
||||
return mVisibleItems.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).getItemId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
return getItem(position).getView(convertView, parent);
|
||||
}
|
||||
|
||||
private static abstract class Item {
|
||||
private final int mItemId;
|
||||
private final int mLayoutResourceId;
|
||||
private View mView;
|
||||
|
||||
public Item(int itemId, int layoutResourceId) {
|
||||
mItemId = itemId;
|
||||
mLayoutResourceId = layoutResourceId;
|
||||
}
|
||||
|
||||
public long getItemId() {
|
||||
return mItemId;
|
||||
}
|
||||
|
||||
public View getView(View convertView, ViewGroup parent) {
|
||||
if (mView == null) {
|
||||
LayoutInflater inflater = (LayoutInflater)
|
||||
parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mView = inflater.inflate(mLayoutResourceId, parent, false);
|
||||
initView(mView);
|
||||
}
|
||||
updateView(mView);
|
||||
return mView;
|
||||
}
|
||||
|
||||
protected void initView(View view) {
|
||||
}
|
||||
|
||||
protected void updateView(View view) {
|
||||
}
|
||||
}
|
||||
|
||||
private static class Heading extends Item {
|
||||
private final String mLabel;
|
||||
|
||||
public Heading(int itemId, String label) {
|
||||
super(itemId, R.layout.game_controller_input_heading);
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initView(View view) {
|
||||
TextView textView = (TextView) view;
|
||||
textView.setText(mLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private static class TextColumn extends Item {
|
||||
private final String mLabel;
|
||||
|
||||
private String mContent;
|
||||
private TextView mContentView;
|
||||
|
||||
public TextColumn(int itemId, String label) {
|
||||
super(itemId, R.layout.game_controller_input_text_column);
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
mContent = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initView(View view) {
|
||||
TextView textView = (TextView) view.findViewById(R.id.label);
|
||||
textView.setText(mLabel);
|
||||
|
||||
mContentView = (TextView) view.findViewById(R.id.content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateView(View view) {
|
||||
mContentView.setText(mContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
747
samples/ApiDemos/src/com/example/android/apis/view/GameView.java
Normal file
747
samples/ApiDemos/src/com/example/android/apis/view/GameView.java
Normal file
@@ -0,0 +1,747 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.apis.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* A trivial joystick based physics game to demonstrate joystick handling.
|
||||
*
|
||||
* @see GameControllerInput
|
||||
*/
|
||||
public class GameView extends View {
|
||||
private final long ANIMATION_TIME_STEP = 1000 / 60;
|
||||
private final int MAX_OBSTACLES = 12;
|
||||
|
||||
private final Random mRandom;
|
||||
private Ship mShip;
|
||||
private final List<Bullet> mBullets;
|
||||
private final List<Obstacle> mObstacles;
|
||||
|
||||
private long mLastStepTime;
|
||||
private InputDevice mLastInputDevice;
|
||||
|
||||
private static final int DPAD_STATE_LEFT = 1 << 0;
|
||||
private static final int DPAD_STATE_RIGHT = 1 << 1;
|
||||
private static final int DPAD_STATE_UP = 1 << 2;
|
||||
private static final int DPAD_STATE_DOWN = 1 << 3;
|
||||
|
||||
private int mDPadState;
|
||||
|
||||
private float mShipSize;
|
||||
private float mMaxShipThrust;
|
||||
private float mMaxShipSpeed;
|
||||
|
||||
private float mBulletSize;
|
||||
private float mBulletSpeed;
|
||||
|
||||
private float mMinObstacleSize;
|
||||
private float mMaxObstacleSize;
|
||||
private float mMinObstacleSpeed;
|
||||
private float mMaxObstacleSpeed;
|
||||
|
||||
private final Runnable mAnimationRunnable = new Runnable() {
|
||||
public void run() {
|
||||
animate();
|
||||
}
|
||||
};
|
||||
|
||||
public GameView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mRandom = new Random();
|
||||
mBullets = new ArrayList<Bullet>();
|
||||
mObstacles = new ArrayList<Obstacle>();
|
||||
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
|
||||
float baseSize = getContext().getResources().getDisplayMetrics().density * 5f;
|
||||
float baseSpeed = baseSize * 3;
|
||||
|
||||
mShipSize = baseSize * 3;
|
||||
mMaxShipThrust = baseSpeed * 0.25f;
|
||||
mMaxShipSpeed = baseSpeed * 12;
|
||||
|
||||
mBulletSize = baseSize;
|
||||
mBulletSpeed = baseSpeed * 12;
|
||||
|
||||
mMinObstacleSize = baseSize * 2;
|
||||
mMaxObstacleSize = baseSize * 12;
|
||||
mMinObstacleSpeed = baseSpeed;
|
||||
mMaxObstacleSpeed = baseSpeed * 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
|
||||
// Reset the game when the view changes size.
|
||||
reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
ensureInitialized();
|
||||
|
||||
// Handle DPad keys and fire button on initial down but not on auto-repeat.
|
||||
boolean handled = false;
|
||||
if (event.getRepeatCount() == 0) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
mShip.setHeadingX(-1);
|
||||
mDPadState |= DPAD_STATE_LEFT;
|
||||
handled = true;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
mShip.setHeadingX(1);
|
||||
mDPadState |= DPAD_STATE_RIGHT;
|
||||
handled = true;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
mShip.setHeadingY(-1);
|
||||
mDPadState |= DPAD_STATE_UP;
|
||||
handled = true;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
mShip.setHeadingY(1);
|
||||
mDPadState |= DPAD_STATE_DOWN;
|
||||
handled = true;
|
||||
break;
|
||||
default:
|
||||
if (isFireKey(keyCode)) {
|
||||
fire();
|
||||
handled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (handled) {
|
||||
step(event.getEventTime());
|
||||
return true;
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
ensureInitialized();
|
||||
|
||||
// Handle keys going up.
|
||||
boolean handled = false;
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
mShip.setHeadingX(0);
|
||||
mDPadState &= ~DPAD_STATE_LEFT;
|
||||
handled = true;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
mShip.setHeadingX(0);
|
||||
mDPadState &= ~DPAD_STATE_RIGHT;
|
||||
handled = true;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
mShip.setHeadingY(0);
|
||||
mDPadState &= ~DPAD_STATE_UP;
|
||||
handled = true;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
mShip.setHeadingY(0);
|
||||
mDPadState &= ~DPAD_STATE_DOWN;
|
||||
handled = true;
|
||||
break;
|
||||
default:
|
||||
if (isFireKey(keyCode)) {
|
||||
handled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (handled) {
|
||||
step(event.getEventTime());
|
||||
return true;
|
||||
}
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
private static boolean isFireKey(int keyCode) {
|
||||
return KeyEvent.isGamepadButton(keyCode)
|
||||
|| keyCode == KeyEvent.KEYCODE_DPAD_CENTER
|
||||
|| keyCode == KeyEvent.KEYCODE_SPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
ensureInitialized();
|
||||
|
||||
// Check that the event came from a joystick since a generic motion event
|
||||
// could be almost anything.
|
||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0
|
||||
&& event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
// Cache the most recently obtained device information.
|
||||
// The device information may change over time but it can be
|
||||
// somewhat expensive to query.
|
||||
if (mLastInputDevice == null || mLastInputDevice.getId() != event.getDeviceId()) {
|
||||
mLastInputDevice = event.getDevice();
|
||||
// It's possible for the device id to be invalid.
|
||||
// In that case, getDevice() will return null.
|
||||
if (mLastInputDevice == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore joystick while the DPad is pressed to avoid conflicting motions.
|
||||
if (mDPadState != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process all historical movement samples in the batch.
|
||||
final int historySize = event.getHistorySize();
|
||||
for (int i = 0; i < historySize; i++) {
|
||||
processJoystickInput(event, i);
|
||||
}
|
||||
|
||||
// Process the current movement sample in the batch.
|
||||
processJoystickInput(event, -1);
|
||||
return true;
|
||||
}
|
||||
return super.onGenericMotionEvent(event);
|
||||
}
|
||||
|
||||
private void processJoystickInput(MotionEvent event, int historyPos) {
|
||||
// Get joystick position.
|
||||
// Many game pads with two joysticks report the position of the second joystick
|
||||
// using the Z and RZ axes so we also handle those.
|
||||
// In a real game, we would allow the user to configure the axes manually.
|
||||
float x = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_X, historyPos);
|
||||
if (x == 0) {
|
||||
x = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_HAT_X, historyPos);
|
||||
}
|
||||
if (x == 0) {
|
||||
x = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_Z, historyPos);
|
||||
}
|
||||
|
||||
float y = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_Y, historyPos);
|
||||
if (y == 0) {
|
||||
y = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_HAT_Y, historyPos);
|
||||
}
|
||||
if (y == 0) {
|
||||
y = getCenteredAxis(event, mLastInputDevice, MotionEvent.AXIS_RZ, historyPos);
|
||||
}
|
||||
|
||||
// Set the ship heading.
|
||||
mShip.setHeading(x, y);
|
||||
step(historyPos < 0 ? event.getEventTime() : event.getHistoricalEventTime(historyPos));
|
||||
}
|
||||
|
||||
private static float getCenteredAxis(MotionEvent event, InputDevice device,
|
||||
int axis, int historyPos) {
|
||||
final InputDevice.MotionRange range = device.getMotionRange(axis);
|
||||
if (range != null) {
|
||||
final float flat = range.getFlat();
|
||||
final float value = historyPos < 0 ? event.getAxisValue(axis)
|
||||
: event.getHistoricalAxisValue(axis, historyPos);
|
||||
|
||||
// Ignore axis values that are within the 'flat' region of the joystick axis center.
|
||||
// A joystick at rest does not always report an absolute position of (0,0).
|
||||
if (Math.abs(value) > flat) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasWindowFocus) {
|
||||
// Turn on and off animations based on the window focus.
|
||||
// Alternately, we could update the game state using the Activity onResume()
|
||||
// and onPause() lifecycle events.
|
||||
if (hasWindowFocus) {
|
||||
getHandler().postDelayed(mAnimationRunnable, ANIMATION_TIME_STEP);
|
||||
mLastStepTime = SystemClock.uptimeMillis();
|
||||
} else {
|
||||
getHandler().removeCallbacks(mAnimationRunnable);
|
||||
|
||||
mDPadState = 0;
|
||||
if (mShip != null) {
|
||||
mShip.setHeading(0, 0);
|
||||
mShip.setVelocity(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
super.onWindowFocusChanged(hasWindowFocus);
|
||||
}
|
||||
|
||||
private void fire() {
|
||||
if (mShip != null && !mShip.isDestroyed()) {
|
||||
Bullet bullet = new Bullet();
|
||||
bullet.setPosition(mShip.getBulletInitialX(), mShip.getBulletInitialY());
|
||||
bullet.setVelocity(mShip.getBulletVelocityX(mBulletSpeed),
|
||||
mShip.getBulletVelocityY(mBulletSpeed));
|
||||
mBullets.add(bullet);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureInitialized() {
|
||||
if (mShip == null) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
mShip = new Ship();
|
||||
mBullets.clear();
|
||||
mObstacles.clear();
|
||||
}
|
||||
|
||||
void animate() {
|
||||
long currentStepTime = SystemClock.uptimeMillis();
|
||||
step(currentStepTime);
|
||||
|
||||
Handler handler = getHandler();
|
||||
if (handler != null) {
|
||||
handler.postAtTime(mAnimationRunnable, currentStepTime + ANIMATION_TIME_STEP);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void step(long currentStepTime) {
|
||||
float tau = (currentStepTime - mLastStepTime) * 0.001f;
|
||||
mLastStepTime = currentStepTime;
|
||||
|
||||
ensureInitialized();
|
||||
|
||||
// Move the ship.
|
||||
mShip.accelerate(tau, mMaxShipThrust, mMaxShipSpeed);
|
||||
if (!mShip.step(tau)) {
|
||||
reset();
|
||||
}
|
||||
|
||||
// Move the bullets.
|
||||
int numBullets = mBullets.size();
|
||||
for (int i = 0; i < numBullets; i++) {
|
||||
final Bullet bullet = mBullets.get(i);
|
||||
if (!bullet.step(tau)) {
|
||||
mBullets.remove(i);
|
||||
i -= 1;
|
||||
numBullets -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Move obstacles.
|
||||
int numObstacles = mObstacles.size();
|
||||
for (int i = 0; i < numObstacles; i++) {
|
||||
final Obstacle obstacle = mObstacles.get(i);
|
||||
if (!obstacle.step(tau)) {
|
||||
mObstacles.remove(i);
|
||||
i -= 1;
|
||||
numObstacles -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for collisions between bullets and obstacles.
|
||||
for (int i = 0; i < numBullets; i++) {
|
||||
final Bullet bullet = mBullets.get(i);
|
||||
for (int j = 0; j < numObstacles; j++) {
|
||||
final Obstacle obstacle = mObstacles.get(j);
|
||||
if (bullet.collidesWith(obstacle)) {
|
||||
bullet.destroy();
|
||||
obstacle.destroy();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for collisions between the ship and obstacles.
|
||||
for (int i = 0; i < numObstacles; i++) {
|
||||
final Obstacle obstacle = mObstacles.get(i);
|
||||
if (mShip.collidesWith(obstacle)) {
|
||||
mShip.destroy();
|
||||
obstacle.destroy();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn more obstacles offscreen when needed.
|
||||
// Avoid putting them right on top of the ship.
|
||||
OuterLoop: while (mObstacles.size() < MAX_OBSTACLES) {
|
||||
final float minDistance = mShipSize * 4;
|
||||
float size = mRandom.nextFloat() * (mMaxObstacleSize - mMinObstacleSize)
|
||||
+ mMinObstacleSize;
|
||||
float positionX, positionY;
|
||||
int tries = 0;
|
||||
do {
|
||||
int edge = mRandom.nextInt(4);
|
||||
switch (edge) {
|
||||
case 0:
|
||||
positionX = -size;
|
||||
positionY = mRandom.nextInt(getHeight());
|
||||
break;
|
||||
case 1:
|
||||
positionX = getWidth() + size;
|
||||
positionY = mRandom.nextInt(getHeight());
|
||||
break;
|
||||
case 2:
|
||||
positionX = mRandom.nextInt(getWidth());
|
||||
positionY = -size;
|
||||
break;
|
||||
default:
|
||||
positionX = mRandom.nextInt(getWidth());
|
||||
positionY = getHeight() + size;
|
||||
break;
|
||||
}
|
||||
if (++tries > 10) {
|
||||
break OuterLoop;
|
||||
}
|
||||
} while (mShip.distanceTo(positionX, positionY) < minDistance);
|
||||
|
||||
float direction = mRandom.nextFloat() * (float) Math.PI * 2;
|
||||
float speed = mRandom.nextFloat() * (mMaxObstacleSpeed - mMinObstacleSpeed)
|
||||
+ mMinObstacleSpeed;
|
||||
float velocityX = (float) Math.cos(direction) * speed;
|
||||
float velocityY = (float) Math.sin(direction) * speed;
|
||||
|
||||
Obstacle obstacle = new Obstacle();
|
||||
obstacle.setPosition(positionX, positionY);
|
||||
obstacle.setSize(size);
|
||||
obstacle.setVelocity(velocityX, velocityY);
|
||||
mObstacles.add(obstacle);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
// Draw the ship.
|
||||
if (mShip != null) {
|
||||
mShip.draw(canvas);
|
||||
}
|
||||
|
||||
// Draw bullets.
|
||||
int numBullets = mBullets.size();
|
||||
for (int i = 0; i < numBullets; i++) {
|
||||
final Bullet bullet = mBullets.get(i);
|
||||
bullet.draw(canvas);
|
||||
}
|
||||
|
||||
// Draw obstacles.
|
||||
int numObstacles = mObstacles.size();
|
||||
for (int i = 0; i < numObstacles; i++) {
|
||||
final Obstacle obstacle = mObstacles.get(i);
|
||||
obstacle.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
static float pythag(float x, float y) {
|
||||
return (float) Math.sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
static int blend(float alpha, int from, int to) {
|
||||
return from + (int) ((to - from) * alpha);
|
||||
}
|
||||
|
||||
static void setPaintARGBBlend(Paint paint, float alpha,
|
||||
int a1, int r1, int g1, int b1,
|
||||
int a2, int r2, int g2, int b2) {
|
||||
paint.setARGB(blend(alpha, a1, a2), blend(alpha, r1, r2),
|
||||
blend(alpha, g1, g2), blend(alpha, b1, b2));
|
||||
}
|
||||
|
||||
private abstract class Sprite {
|
||||
protected float mPositionX;
|
||||
protected float mPositionY;
|
||||
protected float mVelocityX;
|
||||
protected float mVelocityY;
|
||||
protected float mSize;
|
||||
protected boolean mDestroyed;
|
||||
protected float mDestroyAnimProgress;
|
||||
|
||||
public void setPosition(float x, float y) {
|
||||
mPositionX = x;
|
||||
mPositionY = y;
|
||||
}
|
||||
|
||||
public void setVelocity(float x, float y) {
|
||||
mVelocityX = x;
|
||||
mVelocityY = y;
|
||||
}
|
||||
|
||||
public void setSize(float size) {
|
||||
mSize = size;
|
||||
}
|
||||
|
||||
public float distanceTo(float x, float y) {
|
||||
return pythag(mPositionX - x, mPositionY - y);
|
||||
}
|
||||
|
||||
public float distanceTo(Sprite other) {
|
||||
return distanceTo(other.mPositionX, other.mPositionY);
|
||||
}
|
||||
|
||||
public boolean collidesWith(Sprite other) {
|
||||
// Really bad collision detection.
|
||||
return !mDestroyed && !other.mDestroyed
|
||||
&& distanceTo(other) <= Math.max(mSize, other.mSize)
|
||||
+ Math.min(mSize, other.mSize) * 0.5f;
|
||||
}
|
||||
|
||||
public boolean isDestroyed() {
|
||||
return mDestroyed;
|
||||
}
|
||||
|
||||
public boolean step(float tau) {
|
||||
mPositionX += mVelocityX * tau;
|
||||
mPositionY += mVelocityY * tau;
|
||||
|
||||
if (mDestroyed) {
|
||||
mDestroyAnimProgress += tau / getDestroyAnimDuration();
|
||||
if (mDestroyAnimProgress >= 1.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public abstract void draw(Canvas canvas);
|
||||
|
||||
public abstract float getDestroyAnimDuration();
|
||||
|
||||
protected boolean isOutsidePlayfield() {
|
||||
final int width = GameView.this.getWidth();
|
||||
final int height = GameView.this.getHeight();
|
||||
return mPositionX < 0 || mPositionX >= width
|
||||
|| mPositionY < 0 || mPositionY >= height;
|
||||
}
|
||||
|
||||
protected void wrapAtPlayfieldBoundary() {
|
||||
final int width = GameView.this.getWidth();
|
||||
final int height = GameView.this.getHeight();
|
||||
while (mPositionX <= -mSize) {
|
||||
mPositionX += width + mSize * 2;
|
||||
}
|
||||
while (mPositionX >= width + mSize) {
|
||||
mPositionX -= width + mSize * 2;
|
||||
}
|
||||
while (mPositionY <= -mSize) {
|
||||
mPositionY += height + mSize * 2;
|
||||
}
|
||||
while (mPositionY >= height + mSize) {
|
||||
mPositionY -= height + mSize * 2;
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
mDestroyed = true;
|
||||
step(0);
|
||||
}
|
||||
}
|
||||
|
||||
private class Ship extends Sprite {
|
||||
private static final float CORNER_ANGLE = (float) Math.PI * 2 / 3;
|
||||
private static final float TO_DEGREES = (float) (180.0 / Math.PI);
|
||||
|
||||
private float mHeadingX;
|
||||
private float mHeadingY;
|
||||
private float mHeadingAngle;
|
||||
private float mHeadingMagnitude;
|
||||
private final Paint mPaint;
|
||||
private final Path mPath;
|
||||
|
||||
|
||||
public Ship() {
|
||||
mPaint = new Paint();
|
||||
mPaint.setStyle(Style.FILL);
|
||||
|
||||
setPosition(getWidth() * 0.5f, getHeight() * 0.5f);
|
||||
setVelocity(0, 0);
|
||||
setSize(mShipSize);
|
||||
|
||||
mPath = new Path();
|
||||
mPath.moveTo(0, 0);
|
||||
mPath.lineTo((float)Math.cos(-CORNER_ANGLE) * mSize,
|
||||
(float)Math.sin(-CORNER_ANGLE) * mSize);
|
||||
mPath.lineTo(mSize, 0);
|
||||
mPath.lineTo((float)Math.cos(CORNER_ANGLE) * mSize,
|
||||
(float)Math.sin(CORNER_ANGLE) * mSize);
|
||||
mPath.lineTo(0, 0);
|
||||
}
|
||||
|
||||
public void setHeadingX(float x) {
|
||||
mHeadingX = x;
|
||||
updateHeading();
|
||||
}
|
||||
|
||||
public void setHeadingY(float y) {
|
||||
mHeadingY = y;
|
||||
updateHeading();
|
||||
}
|
||||
|
||||
public void setHeading(float x, float y) {
|
||||
mHeadingX = x;
|
||||
mHeadingY = y;
|
||||
updateHeading();
|
||||
}
|
||||
|
||||
private void updateHeading() {
|
||||
mHeadingMagnitude = pythag(mHeadingX, mHeadingY);
|
||||
if (mHeadingMagnitude > 0.1f) {
|
||||
mHeadingAngle = (float) Math.atan2(mHeadingY, mHeadingX);
|
||||
}
|
||||
}
|
||||
|
||||
private float polarX(float radius) {
|
||||
return (float) Math.cos(mHeadingAngle) * radius;
|
||||
}
|
||||
|
||||
private float polarY(float radius) {
|
||||
return (float) Math.sin(mHeadingAngle) * radius;
|
||||
}
|
||||
|
||||
public float getBulletInitialX() {
|
||||
return mPositionX + polarX(mSize);
|
||||
}
|
||||
|
||||
public float getBulletInitialY() {
|
||||
return mPositionY + polarY(mSize);
|
||||
}
|
||||
|
||||
public float getBulletVelocityX(float relativeSpeed) {
|
||||
return mVelocityX + polarX(relativeSpeed);
|
||||
}
|
||||
|
||||
public float getBulletVelocityY(float relativeSpeed) {
|
||||
return mVelocityY + polarY(relativeSpeed);
|
||||
}
|
||||
|
||||
public void accelerate(float tau, float maxThrust, float maxSpeed) {
|
||||
final float thrust = mHeadingMagnitude * maxThrust;
|
||||
mVelocityX += polarX(thrust);
|
||||
mVelocityY += polarY(thrust);
|
||||
|
||||
final float speed = pythag(mVelocityX, mVelocityY);
|
||||
if (speed > maxSpeed) {
|
||||
final float scale = maxSpeed / speed;
|
||||
mVelocityX = mVelocityX * scale;
|
||||
mVelocityY = mVelocityY * scale;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean step(float tau) {
|
||||
if (!super.step(tau)) {
|
||||
return false;
|
||||
}
|
||||
wrapAtPlayfieldBoundary();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void draw(Canvas canvas) {
|
||||
setPaintARGBBlend(mPaint, mDestroyAnimProgress,
|
||||
255, 63, 255, 63,
|
||||
0, 255, 0, 0);
|
||||
|
||||
canvas.save(Canvas.MATRIX_SAVE_FLAG);
|
||||
canvas.translate(mPositionX, mPositionY);
|
||||
canvas.rotate(mHeadingAngle * TO_DEGREES);
|
||||
canvas.drawPath(mPath, mPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDestroyAnimDuration() {
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private class Bullet extends Sprite {
|
||||
private final Paint mPaint;
|
||||
|
||||
public Bullet() {
|
||||
mPaint = new Paint();
|
||||
mPaint.setStyle(Style.FILL);
|
||||
|
||||
setSize(mBulletSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean step(float tau) {
|
||||
if (!super.step(tau)) {
|
||||
return false;
|
||||
}
|
||||
return !isOutsidePlayfield();
|
||||
}
|
||||
|
||||
public void draw(Canvas canvas) {
|
||||
setPaintARGBBlend(mPaint, mDestroyAnimProgress,
|
||||
255, 255, 255, 0,
|
||||
0, 255, 255, 255);
|
||||
canvas.drawCircle(mPositionX, mPositionY, mSize, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDestroyAnimDuration() {
|
||||
return 0.125f;
|
||||
}
|
||||
}
|
||||
|
||||
private class Obstacle extends Sprite {
|
||||
private final Paint mPaint;
|
||||
|
||||
public Obstacle() {
|
||||
mPaint = new Paint();
|
||||
mPaint.setARGB(255, 127, 127, 255);
|
||||
mPaint.setStyle(Style.FILL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean step(float tau) {
|
||||
if (!super.step(tau)) {
|
||||
return false;
|
||||
}
|
||||
wrapAtPlayfieldBoundary();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void draw(Canvas canvas) {
|
||||
setPaintARGBBlend(mPaint, mDestroyAnimProgress,
|
||||
255, 127, 127, 255,
|
||||
0, 255, 0, 0);
|
||||
canvas.drawCircle(mPositionX, mPositionY,
|
||||
mSize * (1.0f - mDestroyAnimProgress), mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDestroyAnimDuration() {
|
||||
return 0.25f;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user