Các tính năng bổ sung của tay điều khiển

Tay điều khiển trò chơi được trang bị các tính năng bổ sung giúp tăng cường đáng kể khả năng tương tác và trải nghiệm nhập vai của người chơi. Các chức năng xúc giác, cảm biến chuyển động và ánh sáng của tay điều khiển trò chơi trên Android đặc biệt hữu ích trong việc làm sâu sắc và phong phú thêm trải nghiệm chơi trò chơi. Mỗi tính năng đều kích thích các giác quan của người chơi theo cách riêng, thúc đẩy các hoạt động tương tác trực quan và có ý nghĩa hơn trong trò chơi.

Để khai báo rằng ứng dụng của bạn hỗ trợ tay điều khiển trò chơi, hãy thêm tính năng android.hardware.gamepad vào tệp kê khai ứng dụng:

<manifest ...>
  ...
  <uses-feature android:name="android.hardware.gamepad" android:required="false"/>
  ...
</manifest>

Xúc giác

Tính năng phản hồi xúc giác trong tay điều khiển trò chơi Android là một công nghệ quan trọng, cung cấp phản hồi xúc giác chân thực trong quá trình chơi trò chơi.

Công nghệ xúc giác mang lại cảm giác vật lý cho người dùng thông qua các rung động hoặc chuyển động. Ví dụ: khi xảy ra vụ nổ trong trò chơi, tay điều khiển sẽ rung, giúp người chơi cảm nhận được tác động một cách chân thực. Ngoài ra, bạn có thể đồng bộ hoá các rung động tinh tế với âm thanh của nhân vật đang đi bộ hoặc đang chạy, mang lại trải nghiệm chân thực hơn. Loại phản hồi xúc giác này cho phép người chơi cảm nhận được các sự kiện khác nhau xảy ra trong trò chơi.

Công nghệ này giúp người chơi đắm chìm tối đa, tăng cường phản ứng cảm xúc và làm phong phú thêm tính linh hoạt của trò chơi. Chế độ cài đặt phản hồi xúc giác trong tay điều khiển trò chơi Android không chỉ mở rộng khả năng sáng tạo cho nhà phát triển trò chơi mà còn mang đến cho người chơi trải nghiệm chơi trò chơi chân thực hơn bao giờ hết.

Kotlin

fun triggerVibrationMultiChannel(
  deviceId: Int, leftIntensity: Int, leftDuration: Int,
  rightIntensity: Int, rightDuration: Int) {
  val inputDevice = InputDevice.getDevice(deviceId)
  val vibratorManager = inputDevice!!.vibratorManager
  if (vibratorManager != null) {
    val vibratorIds = vibratorManager.vibratorIds
    val vibratorCount = vibratorIds.size
    if (vibratorCount > 0) {
      // We have an assumption that game controllers have two vibrators
      // corresponding to a left motor and a right motor, and the left
      // motor will be first.
      updateVibrator(vibratorManager.getVibrator(vibratorIds  [0]), leftIntensity, leftDuration)
      if (vibratorCount > 1) {
        updateVibrator(vibratorManager.getVibrator(vibratorIds[1]), rightIntensity, rightDuration)
      }
    }
  }
}

fun updateVibrator(vibrator: Vibrator?, intensity: Int, duration: Int) {
  if (vibrator != null) {
    if (intensity == 0) {
      vibrator.cancel()
    } else if (duration > 0) {
      vibrator.vibrate(VibrationEffect.createOneShot(duration.toLong(), intensity))
    }
  }
}

Java

public void triggerVibrationMultiChannel(
    int deviceId, int leftIntensity, int leftDuration,
    int rightIntensity, int rightDuration) {

    InputDevice inputDevice = InputDevice.getDevice(deviceId);

    // Check if device exists to avoid NullPointerException
    if (inputDevice == null) {
      return;
    }

    VibratorManager vibratorManager = inputDevice.getVibratorManager();
    if (vibratorManager != null) {
        int[] vibratorIds = vibratorManager.getVibratorIds();
        int vibratorCount = vibratorIds.length;

        if (vibratorCount > 0) {
            // We have an assumption that game controllers have two vibrators
            // corresponding to a left motor and a right motor, and the left
            // motor will be first.
            updateVibrator(vibratorManager.getVibrator(vibratorIds[0]), leftIntensity, leftDuration);

            if (vibratorCount > 1) {
                updateVibrator(vibratorManager.getVibrator(vibratorIds[1]), rightIntensity, rightDuration);
            }
        }
    }
}

public void updateVibrator(Vibrator vibrator, int intensity, int duration) {
    if (vibrator != null) {
        if (intensity == 0) {
            vibrator.cancel();
        } else if (duration > 0) {
            vibrator.vibrate(VibrationEffect.createOneShot((long) duration, intensity));
        }
    }
}

Để sử dụng tính năng rung, ứng dụng này đặt một tính năng và quyền.

<manifest ...>
  ...
  <uses-permission android:name="android.permission.VIBRATE"/>
  ...
</manifest>

Để biết thêm thông tin, hãy xem bài viết VibratorManager và Tệp kê khai ứng dụng.

Cảm biến chuyển động

Một trong những công nghệ tiên tiến nhất giúp nâng cao trải nghiệm chơi trò chơi là tay điều khiển trò chơi Android được trang bị cảm biến chuyển động. Công nghệ này phát hiện chính xác các chuyển động thể chất của người dùng và chuyển dữ liệu đó thành hành động trong trò chơi, mang lại trải nghiệm chơi trò chơi trực quan và sống động hơn. Trong phần giới thiệu này, chúng ta sẽ tìm hiểu cách hoạt động của các tính năng cảm biến chuyển động trong tay điều khiển trò chơi Android.

Cảm biến chuyển động thường kết hợp con quay hồi chuyển và gia tốc kế để phát hiện chuyển động và hướng của người dùng.

Ứng dụng cần triển khai các lớp trình nghe gia tốc và con quay hồi chuyển, đồng thời đăng ký các trình nghe này với trình quản lý cảm biến của bộ điều khiển.

Kotlin

fun setIntegratedAccelerometerActive(deviceId: Int) {
  val device = InputDevice.getDevice(deviceId)
  val sensorManager = device?.sensorManager
  val accelerometer = sensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
  if (accelerometer != null) {
    val accelerometerListener =
      GameControllerAccelerometerListener(accelerometer)
    sensorManager?.registerListener(
      accelerometerListener, accelerometer,
      SensorManager.SENSOR_DELAY_GAME
    )
  }
}

fun setIntegratedGyroscopeActive(deviceId: Int) {
  val device = InputDevice.getDevice(deviceId)
  val sensorManager = device?.sensorManager
  val gyroscope = sensorManager?.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
  if (gyroscope != null) {
    val gyroscopeListener = GameControllerGyroscopeListener(gyroscope)
    sensorManager?.registerListener(
      gyroscopeListener, gyroscope,
      SensorManager.SENSOR_DELAY_GAME
    )
  }
}

class GameControllerAccelerometerListener(private val listenerAccelerometer: Sensor?) :
  SensorEventListener {
  override fun onSensorChanged(event: SensorEvent) {
    if (listenerAccelerometer != null) {
      synchronized(listenerAccelerometer) {
        if (event.sensor == listenerAccelerometer) {
          Log.d("Accelerometer",
            "onSensorChanged " + event.values[0] + ", "
            + event.values[1] + ", " + event.values[2])
        }
      }
    }
  }

  override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
  }
}

class GameControllerGyroscopeListener(private val listenerGyroscope: Sensor?) :
  SensorEventListener {
  override fun onSensorChanged(event: SensorEvent) {
    if (listenerGyroscope != null) {
      synchronized(listenerGyroscope) {
        if (event.sensor == listenerGyroscope) {
          Log.d("Gyroscope",
            "onSensorChanged " + event.values[0] + ", " +
            event.values[1] + ", " + event.values[2])
        }
      }
    }
 }

  override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
  }
}

Java


public void setIntegratedAccelerometerActive(int deviceId) {
    InputDevice device = InputDevice.getDevice(deviceId);
    // Safe handling for null device or sensor manager
    if (device == null) {
      return;
    }
    SensorManager sensorManager = device.getSensorManager();
    if (sensorManager == null) {
      return;
    }

    Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    if (accelerometer != null) {
      GameControllerAccelerometerListener   accelerometerListener =
          new GameControllerAccelerometerListener(accelerometer);
        sensorManager.registerListener(
          accelerometerListener, accelerometer,
          SensorManager.SENSOR_DELAY_GAME
        );
    }
}

public void setIntegratedGyroscopeActive(int deviceId) {
    InputDevice device = InputDevice.getDevice(deviceId);
    if (device == null) {
        return;
    }
    SensorManager sensorManager = device.getSensorManager();
    if (sensorManager == null) {
        return;
    }
    Sensor gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
    if (gyroscope != null) {
        GameControllerGyroscopeListener gyroscopeListener =
          new GameControllerGyroscopeListener(gyroscope);
        sensorManager.registerListener(
          gyroscopeListener, gyroscope,
          SensorManager.SENSOR_DELAY_GAME
        );
    }
}

public static class GameControllerAccelerometerListener implements SensorEventListener {
    private final Sensor listenerAccelerometer;
    public GameControllerAccelerometerListener(Sensor   listenerAccelerometer) {
        this.listenerAccelerometer = listenerAccelerometer;
    }
    @Override
    public void onSensorChanged(SensorEvent event) {
        if (listenerAccelerometer != null) {
            synchronized (listenerAccelerometer) {
                if (event.sensor == listenerAccelerometer) {
                    Log.d("Accelerometer",
                      "onSensorChanged " + event.values[0] + ", "
                      + event.values[1] + ", " + event.values[2]);
                }
            }
        }
    }
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
}

public static class GameControllerGyroscopeListener implements SensorEventListener {
    private final Sensor listenerGyroscope;

    public GameControllerGyroscopeListener(Sensor listenerGyroscope) {
        this.listenerGyroscope = listenerGyroscope;
    }
    @Override
    public void onSensorChanged(SensorEvent event) {
        if (listenerGyroscope != null) {
            synchronized (listenerGyroscope) {
                if (event.sensor == listenerGyroscope) {
                    Log.d("Gyroscope",
                      "onSensorChanged " + event.values[0] +  ", " +
                        event.values[1] + ", " + event.values  [2]);
                }
            }
        }
    }
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
}

Để biết thêm thông tin về Cảm biến chuyển động và SensorEventListener.

Đèn

Chế độ cài đặt màu đèn trên tay điều khiển trò chơi Android giúp tăng thêm trải nghiệm đắm chìm vào trò chơi thông qua các yếu tố hình ảnh.

Tính năng màu đèn sử dụng đèn LED tích hợp trong tay điều khiển để hiển thị nhiều màu sắc, phản hồi linh hoạt theo các tình huống chơi trò chơi. Ví dụ: đèn có thể nhấp nháy màu đỏ khi máu của người chơi ở mức nguy kịch hoặc sáng màu xanh lục khi hoàn thành một nhiệm vụ cụ thể, cung cấp phản hồi trực quan dựa trên các sự kiện trong trò chơi. Những chế độ cài đặt màu sáng này giúp tăng mức độ tương tác của người dùng, tăng sự hồi hộp và thích thú khi chơi trò chơi, đồng thời giúp người chơi đắm chìm hoàn toàn vào thế giới trò chơi.

Tính năng màu đèn trong tay điều khiển trò chơi trên Android không chỉ có mục đích trang trí mà còn đóng vai trò quan trọng trong việc tạo không khí cho trò chơi và cải thiện trải nghiệm người dùng.

Kotlin

fun changeControllerLightColor(deviceId: Int, color: Int) {
  val device = InputDevice.getDevice(deviceId)
  device?.let {
    if (it.sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK) {
      val lightsManager = device.lightsManager
      lightsManager?.let { manager ->
        manager.lights.forEach { light ->
          val stateBuilder = LightState.Builder()
          stateBuilder.setColor(color)
          val requestBuilder = LightsRequest.Builder()
          requestBuilder.addLight(light, stateBuilder.build())
          val lightsSession = lightsManager.openSession()
          lightsSession.requestLights(requestBuilder.build())
        }
      }
    }
  }
}

Java

public void changeControllerLightColor(int deviceId, int  color) {
    InputDevice device = InputDevice.getDevice(deviceId);

    if (device != null) {
      // Check if the device is a joystick.
      // Note: Parentheses are required around the bitwise AND operation in Java
      // because == has higher precedence than &.
        if ((device.getSources() & InputDevice.  SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK) {
            LightsManager lightsManager = device.getLightsManager();

            if (lightsManager != null) {
                for (Light light : lightsManager.getLights()) {
                    LightState.Builder stateBuilder = new   LightState.Builder();
                    stateBuilder.setColor(color);

                    LightsRequest.Builder requestBuilder = new LightsRequest.Builder();
                    requestBuilder.addLight(light, stateBuilder.build());

                    LightsManager.Session lightsSession =   lightsManager.openSession();
                    lightsSession.requestLights(requestBuilder.build());
                }
            }
        }
    }
}

Để sử dụng đèn của tay điều khiển, hãy khai báo tính năng tay điều khiển trò chơi và quyền LIGHTS trong tệp kê khai:

<manifest ...>
  ...
  <uses-permission android:name="android.permission.LIGHTS" />
  ...
</manifest>

Để biết thêm thông tin, hãy xem bài viết LightsManager và Tệp kê khai ứng dụng.

Bàn di chuột của bộ điều khiển

Một số tay điều khiển trò chơi có bàn di chuột có thể dùng cho nhiều hành động trong trò chơi, chẳng hạn như di chuyển trong trình đơn hoặc điều khiển nhân vật trong trò chơi theo cách trực quan hơn.

Bàn di chuột trên tay điều khiển trò chơi
Hình 1. Bàn di chuột trên tay điều khiển trò chơi.

Tay điều khiển trò chơi có bàn di chuột tích hợp cho phép điều khiển thiết bị trực tiếp trên Android. Khi bạn chạm vào bàn di chuột, một con trỏ chuột sẽ xuất hiện trên màn hình, cho phép bạn thao tác trực quan như khi dùng chuột.