官术网_书友最值得收藏!

Creating a virtual joystick

We are going to improve the user input and we are going to do it by creating a virtual joystick.

A virtual joystick measures the distance from the touch position to its center and uses this information to set the values on the two axes. It behaves as a traditional analog joystick.

Since it is virtual, we are not constrained to have it at a specific position on the screen, so we can place it anywhere the player touches it.

We cannot, however, take the entire screen for the virtual joystick. There needs to be a fire button too.

We have experienced the frustration of small touch targets, so we are going to make the fire button as big as we can. This means that we are going to use half the screen for the virtual joystick and half the screen for the fire button.

The layout that we are going to use will have two views that fill the screen, each of them covering half of the width. We will name this layout view_vjoystick.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="horizontal"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <View android:id="@+id/vjoystick_main"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:layout_weight="1"
  />
  <View android:id="@+id/vjoystick_touch"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:layout_weight="1"
  />
</LinearLayout>

The interesting bit of this layout is the usage of Android:layout_weight to equally pide the screen into two halves. You can modify the weight value to make one view larger than the other if you want a larger space for the virtual joystick or the fire button.

We will create a class to handle this user InputController. We will call it VirtualJoystickInputController and it will, obviously, extend InputController.

To handle the events of this InputController, we are going to use two internal classes. One for each view we want to listen to the events to:

public VirtualJoystickInputController(View view) {
  view.findViewById(R.id.vjoystick_main)
    .setOnTouchListener(new VJoystickTouchListener());
  view.findViewById(R.id.vjoystick_touch)
    .setOnTouchListener(new VFireButtonTouchListener());

  double pixelFactor = view.getHeight() / 400d;
  mMaxDistance = 50*pixelFactor;
}

The mMaxDistance variable defines how far from the touch we consider the user to have reached the maximum. The value is, again, in screen units. You can imagine the maximum distance as the radius of the virtual gamepad. The smaller this distance is, the more sensitive the joystick is.

A small maximum distance will allow quick reactions, while a large one will allow better precision. Feel free to experiment with its size to make it work as you'd like.

The fire button is easier to handle than the virtual joystick. We use the same logic as in the previous example. Set mIsFiring to true when the event is a down action and set it to false when the event is an up action:

private class VFireButtonTouchListener implements View.OnTouchListener {
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    int action = event.getActionMasked();
    if (action == MotionEvent.ACTION_DOWN) {
      mIsFiring = true;
    }
    else if (action == MotionEvent.ACTION_UP) {
      mIsFiring = false;
    }
    return true;
  }
}

The listener for the virtual joystick is more interesting. We record the position of the touch when a down action is performed, we also reset the values when the touch goes up. But, as long as it moves, we update the values of mHorizontalFactor and mVerticalFactor based on the distance to the original touch:

private class VJoystickTouchListener implements View.OnTouchListener {
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    int action = event.getActionMasked();
    if (action == MotionEvent.ACTION_DOWN) {
      mStartingPositionX = event.getX(0);
      mStartingPositionY = event.getY(0);
    }
    else if (action == MotionEvent.ACTION_UP) {
      mHorizontalFactor = 0;
      mVerticalFactor = 0;
    }
    else if (action == MotionEvent.ACTION_MOVE) {
      // Get the proportion to the max
      mHorizontalFactor = (event.getX(0) - mStartingPositionX) / mMaxDistance;
      if (mHorizontalFactor > 1) {
        mHorizontalFactor = 1;
      }
      else if (mHorizontalFactor < -1) {
        mHorizontalFactor = -1;
      }
      mVerticalFactor = (event.getY(0) - mStartingPositionY) / mMaxDistance;
      if (mVerticalFactor > 1) {
        mVerticalFactor = 1;
      }
      else if (mVerticalFactor < -1) {
        mVerticalFactor = -1;
      }
    }
    return true;
  }
}

Please note that we want to keep mHorizontalFactor and mVerticalFactor between -1 and 1; thus, whenever the distance is larger than mMaxDistance, we do not consider it.

Finally, time to connect this new controller to the GameEngine. It is quite simple. We just have to update the layout for fragment_game.xml, including view_vjoystick.xml instead of view_keypad.xml, and then update the initialization of the GameEngine:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  android:paddingTop="@dimen/activity_vertical_margin"
  android:paddingLeft="@dimen/activity_horizontal_margin"
  android:paddingRight="@dimen/activity_horizontal_margin"
  tools:context="com.plattysoft.yass.counter.GameFragment">

  <TextView
    android:layout_gravity="top|left"
    android:id="@+id/txt_score"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hello_world" />

  <Button
    android:layout_gravity="top|right"
    android:id="@+id/btn_play_pause"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/pause" />

  <include layout="@layout/view_vjoystick" />

</FrameLayout>

As a reminder, the initialization of the GameEngine is done inside onViewCreated of the GameFragment. We only need to create an instance of the proper InputController:

mGameEngine = new GameEngine(getActivity());
mGameEngine.setInputController(new 
 VirtualJoystickInputController(get View()));
mGameEngine.addGameObject(new Player(getView()));
mGameEngine.startGame();

Time to run the game and try this controller.

General considerations and improvements

This input method is a huge improvement over the basic keypad we did before. The touch area is as big as the screen and the player does not need to look at this area of the screen to tap on small buttons. It will work anywhere.

This system handles diagonal as well as horizontal and vertical movements and also anything in between.

The player does not need to remove his/her finger from the screen to change the directions.

There is a lack of visual feedback, which can be solved by drawing the virtual gamepad as two circles when the player is using it. A big circle will show the range of the virtual joystick, while a smaller one will show the current touch pointer. On the other hand, you may not want to, since the lack of visual clutter makes the screen cleaner.

主站蜘蛛池模板: 开江县| 湘阴县| 大理市| 通州市| 北川| 东乡族自治县| 太仆寺旗| 昌都县| 封丘县| 余姚市| 辰溪县| 方山县| 崇州市| 屏边| 广水市| 寻甸| 江西省| 元谋县| 杭锦后旗| 平南县| 镇巴县| 兴国县| 阆中市| 额尔古纳市| 呈贡县| 梓潼县| 德格县| 黎平县| 阳曲县| 大城县| 丹东市| 淮安市| 集贤县| 黄冈市| 永修县| 肇东市| 临泉县| 磐安县| 肃南| 娄底市| 铜梁县|