اقدامات کنترلر را کنترل کنید

کنترل‌کننده‌ها دو نوع اقدام دارند:

  • KeyEvent برای هر دکمه‌ای که حالت دودویی "روشن" و "خاموش" دارد، استفاده می‌شود.
  • MotionEvent برای هر محوری که محدوده‌ای از مقادیر را برمی‌گرداند، استفاده می‌شود. مانند -۱ تا ۱ برای آنالوگ استیک‌ها یا ۰ تا ۱ برای تریگرهای آنالوگ.

شما می‌توانید این ورودی‌ها را از View که focus دارد بخوانید.

کاتلین

override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
  if (event.isFromSource(SOURCE_GAMEPAD)
      && event.repeatCount == 0
  ) {
      Log.d("GameView", "Gamepad key pressed: $keyCode")
      return true
  }

  return super.onKeyDown(keyCode, event)
}

override fun onGenericMotionEvent(event: MotionEvent): Boolean {
  if (event.isFromSource(SOURCE_JOYSTICK)) {
      Log.d("GameView", "Gamepad event: $event")
      return true
  }

  return super.onGenericMotionEvent(event)
}

جاوا

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
  if (event.isFromSource(SOURCE_GAMEPAD)
          && event.getRepeatCount() == 0
  ) {
      Log.d("GameView", "Gamepad key pressed: " + keyCode);
      return true;
  }

  return super.onKeyDown(keyCode, event);
}

@Override
public boolean onGenericMotionEvent(MotionEvent event) {
  if (event.isFromSource(SOURCE_JOYSTICK)) {
      Log.d("GameView", "Gamepad event: " + event);
      return true;
  }
  return super.onGenericMotionEvent(event);
}

در صورت نیاز، می‌توانید رویدادها را مستقیماً از Activity بخوانید.

تأیید کنید که دسته بازی متصل است

هنگام گزارش رویدادهای ورودی، اندروید از شناسه‌های کلید یا محور یکسان برای انواع مختلف دستگاه‌های ورودی استفاده مجدد می‌کند. برای مثال، یک اکشن صفحه لمسی یک رویداد AXIS_X تولید می‌کند که نشان دهنده مختصات X سطح لمسی است، اما یک گیم‌پد یک رویداد AXIS_X تولید می‌کند که نشان دهنده موقعیت X دسته بازی سمت چپ است. این بدان معناست که برای تفسیر صحیح رویدادهای ورودی، باید نوع منبع را بررسی کنید.

برای تأیید اینکه InputDevice متصل شده یک کنترلر بازی است، از تابع supportsSource(int) استفاده کنید:

  • نوع منبع SOURCE_GAMEPAD نشان می‌دهد که دستگاه ورودی دارای دکمه‌های کنترلی است (برای مثال، KEYCODE_BUTTON_A ). توجه داشته باشید که این نوع منبع دقیقاً نشان نمی‌دهد که آیا کنترل‌کننده بازی دارای دکمه‌های D-pad است یا خیر، اگرچه اکثر کنترل‌کننده‌ها معمولاً دارای کنترل‌های جهتی هستند.
  • نوع منبع SOURCE_DPAD نشان می‌دهد که دستگاه ورودی دارای دکمه‌های D-pad است (برای مثال، DPAD_UP ).
  • نوع منبع SOURCE_JOYSTICK نشان می‌دهد که دستگاه ورودی دارای دسته‌های کنترل آنالوگ است (برای مثال، یک دسته کنترل که حرکات را در امتداد AXIS_X و AXIS_Y ثبت می‌کند).

قطعه کد زیر یک متد کمکی را نشان می‌دهد که به شما امکان می‌دهد بررسی کنید که آیا دستگاه‌های ورودی متصل، کنترل‌کننده‌های بازی هستند یا خیر. در این صورت، این متد شناسه‌های دستگاه را برای کنترل‌کننده‌های بازی بازیابی می‌کند. سپس می‌توانید هر شناسه دستگاه را با یک بازیکن در بازی خود مرتبط کنید و اقدامات بازی را برای هر بازیکن متصل به طور جداگانه پردازش کنید. برای کسب اطلاعات بیشتر در مورد پشتیبانی از چندین کنترل‌کننده بازی که به طور همزمان در یک دستگاه مبتنی بر اندروید متصل هستند، به بخش «پشتیبانی از چندین کنترل‌کننده بازی» مراجعه کنید.

کاتلین

fun getGameControllerIds(): List<Int> {
  val gameControllerDeviceIds = mutableListOf<Int>()
  val deviceIds = InputDevice.getDeviceIds()
  deviceIds.forEach { deviceId ->
      InputDevice.getDevice(deviceId)?.apply {

          // Verify that the device has gamepad buttons, control sticks, or both.
          if (supportsSource(SOURCE_GAMEPAD)
              || supportsSource(SOURCE_JOYSTICK)) {
              // This device is a game controller. Store its device ID.
              gameControllerDeviceIds
                  .takeIf { !it.contains(deviceId) }
                  ?.add(deviceId)
          }
      }
  }
  return gameControllerDeviceIds
}

جاوا

 public ArrayList<Integer> getGameControllerIds() {
  ArrayList<Integer> gameControllerDeviceIds = new ArrayList<Integer>();
  int[] deviceIds = InputDevice.getDeviceIds();
  for (int deviceId : deviceIds) {
      InputDevice dev = InputDevice.getDevice(deviceId);

      if (dev == null) {
          continue;
      }

      // Verify that the device has gamepad buttons, control sticks, or both.
      if (dev.supportsSource(SOURCE_GAMEPAD) || dev.supportsSource(SOURCE_JOYSTICK)) {
          // This device is a game controller. Store its device ID.
          if (!gameControllerDeviceIds.contains(deviceId)) {
              gameControllerDeviceIds.add(deviceId);
          }
      }
  }
  return gameControllerDeviceIds;
}

ورودی‌های کنترل‌کننده فرآیند

این بخش انواع دسته‌های بازی پشتیبانی‌شده در اندروید را شرح می‌دهد.

توسعه‌دهندگان ++C باید از کتابخانه کنترلر بازی (Game Controller Library ) استفاده کنند. این کتابخانه، تمام کنترلرها را با رایج‌ترین زیرمجموعه ویژگی‌ها یکپارچه می‌کند و یک رابط کاربری سازگار بین آنها، از جمله قابلیت تشخیص طرح دکمه‌ها، فراهم می‌کند.

این شکل نشان می‌دهد که یک توسعه‌دهنده بازی اندروید می‌تواند انتظار داشته باشد که یک دسته بازی رایج در اندروید چه شکلی باشد.

دسته بازی عمومی با ورودی‌های برچسب‌گذاری شده شامل D-Pad، دسته‌های آنالوگ و دکمه‌ها
شکل ۱. پروفایل یک دسته بازی عمومی.

جدول، نام‌ها و انواع رویدادهای استاندارد برای کنترل‌کننده‌های بازی را فهرست می‌کند. برای فهرست کامل رویدادها، به انواع رایج مراجعه کنید. سیستم، رویدادهای MotionEvent را از طریق onGenericMotionEvent و رویدادهای KeyEvent از طریق onKeyDown و onKeyUp ارسال می‌کند.

ورودی کنترلر رویداد کلیدی رویداد حرکتی
۱. دی-پد
AXIS_HAT_X
(ورودی افقی)
AXIS_HAT_Y
(ورودی عمودی)
۲. دسته آنالوگ سمت چپ
KEYCODE_BUTTON_THUMBL
(هنگام فشار دادن به داخل)
AXIS_X
(حرکت افقی)
AXIS_Y
(حرکت عمودی)
۳. دسته آنالوگ سمت راست
KEYCODE_BUTTON_THUMBR
(هنگام فشار دادن به داخل)
AXIS_Z
(حرکت افقی)
AXIS_RZ
(حرکت عمودی)
۴. دکمه ضربدر KEYCODE_BUTTON_X
۵. یک دکمه KEYCODE_BUTTON_A
۶. دکمه Y KEYCODE_BUTTON_Y
۷. دکمه B KEYCODE_BUTTON_B
۸. سپر راست
KEYCODE_BUTTON_R1
۹. ماشه سمت راست
AXIS_RTRIGGER
۱۰. ماشه چپ AXIS_LTRIGGER
۱۱. سپر چپ KEYCODE_BUTTON_L1
۱۲. شروع KEYCODE_BUTTON_START
۱۳. انتخاب کنید KEYCODE_BUTTON_SELECT

فشار دادن دکمه‌های دسته

از آنجایی که اندروید فشار دکمه‌های کنترلر را دقیقاً مانند فشار دکمه‌های کیبورد گزارش می‌دهد، باید:

  • تأیید کنید که رویداد از یک SOURCE_GAMEPAD می‌آید.
  • مطمئن شوید که فقط یک بار دکمه را با KeyEvent.getRepeatCount() دریافت می‌کنید، اندروید رویدادهای کلید تکراری را درست مانند زمانی که یک کلید صفحه کلید را نگه داشته‌اید، ارسال می‌کند.
  • با برگرداندن true نشان می‌دهد که یک رویداد مدیریت می‌شود.
  • رویدادهای مدیریت نشده را به super ارسال کنید تا تأیید شود که لایه‌های سازگاری مختلف اندروید به درستی کار می‌کنند.

    کاتلین

    class GameView : View {
    // ...
    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        event.apply {
            var handled = false
    
            // make sure we're handling gamepad events
            if (isFromSource(SOURCE_GAMEPAD)) {
    
                // avoid processing the keycode repeatedly
                if (repeatCount == 0) {
                    when (keyCode) {
                        // handle the "A" button
                        KEYCODE_BUTTON_A -> {
                          handled = true
                        }
                    }
                    // ...
                }
            }
            if (handled) {
                return true
            }
       }
       return super.onKeyDown(keyCode, event)
      }
    }
    

    جاوا

    public class GameView extends View {
    // ...
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean handled = false;
        // make sure we're handling gamepad events
        if (event.isFromSource(SOURCE_GAMEPAD)) {
            // avoid processing the keycode repeatedly
            if (event.getRepeatCount() == 0) {
                switch (keyCode) {
                    case KEYCODE_BUTTON_A:
                        // handle the "A" button
                        handled = true;
                        break;
                    // ...
                }
            }
            // mark this event as handled
            if (handled) {
                return true;
            }
        }
        // Always do this instead of "return false"
        // it allows Android's input compatibility layers to work
        return super.onKeyDown(keyCode, event);
      }
    }
    

ورودی پد جهت‌دار فرآیند

پد جهت‌دار چهار جهته یا D-pad، یک کنترل فیزیکی رایج در بسیاری از دسته‌های بازی است. اندروید فشردن D-pad برای جهت‌های بالا و پایین را به عنوان رویدادهای AXIS_HAT_Y گزارش می‌دهد که -1.0 نشان دهنده جهت بالا و 1.0 نشان دهنده جهت پایین است. فشردن D-pad برای جهت چپ یا راست را به عنوان رویدادهای AXIS_HAT_X گزارش می‌کند که -1.0 نشان دهنده جهت چپ و 1.0 نشان دهنده جهت راست است.

بعضی از کنترلرها در عوض، فشردن دکمه‌های جهت‌نما را با یک کد کلید گزارش می‌دهند. اگر بازی شما به فشردن دکمه‌های جهت‌نما اهمیت می‌دهد، باید رویدادهای محور کلاه و کدهای کلید جهت‌نما را به عنوان رویدادهای ورودی یکسان، همانطور که در جدول ۲ توصیه شده است، در نظر بگیرید.

جدول ۲. اقدامات پیش‌فرض بازی پیشنهادی برای کدهای کلید D-pad و مقادیر محور کلاه.

اکشن بازی کد کلید D-pad کد محور کلاه
حرکت به بالا KEYCODE_DPAD_UP AXIS_HAT_Y (برای مقادیر ۰ تا -۱.۰)
حرکت به پایین KEYCODE_DPAD_DOWN AXIS_HAT_Y (برای مقادیر ۰ تا ۱.۰)
حرکت به چپ KEYCODE_DPAD_LEFT AXIS_HAT_X (برای مقادیر ۰ تا -۱.۰)
حرکت به راست KEYCODE_DPAD_RIGHT AXIS_HAT_X (برای مقادیر ۰ تا ۱.۰)

قطعه کد زیر یک کلاس کمکی را نشان می‌دهد که به شما امکان می‌دهد محور کلاه و مقادیر کد کلید را از یک رویداد ورودی بررسی کنید تا جهت D-pad را تعیین کنید.

کاتلین

class Dpad {

    private var directionPressed = -1 // initialized to -1

    fun getDirectionPressed(event: InputEvent): Int {
        if (!isDpadDevice(event)) {
            return -1
        }

        // If the input event is a MotionEvent, check its hat axis values.
        (event as? MotionEvent)?.apply {

            // Use the hat axis value to find the D-pad direction
            val xaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_X)
            val yaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_Y)

            directionPressed = when {
                // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
                // LEFT and RIGHT direction accordingly.
                xaxis.compareTo(-1.0f) == 0 -> Dpad.LEFT
                xaxis.compareTo(1.0f) == 0 -> Dpad.RIGHT
                // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
                // UP and DOWN direction accordingly.
                yaxis.compareTo(-1.0f) == 0 -> Dpad.UP
                yaxis.compareTo(1.0f) == 0 -> Dpad.DOWN
                else -> directionPressed
            }
        }
        // If the input event is a KeyEvent, check its key code.
        (event as? KeyEvent)?.apply {

            // Use the key code to find the D-pad direction.
            directionPressed = when(event.keyCode) {
                KeyEvent.KEYCODE_DPAD_LEFT -> Dpad.LEFT
                KeyEvent.KEYCODE_DPAD_RIGHT -> Dpad.RIGHT
                KeyEvent.KEYCODE_DPAD_UP -> Dpad.UP
                KeyEvent.KEYCODE_DPAD_DOWN -> Dpad.DOWN
                KeyEvent.KEYCODE_DPAD_CENTER ->  Dpad.CENTER
                else -> directionPressed
            }
        }
        return directionPressed
    }

    companion object {
        internal const val UP = 0
        internal const val LEFT = 1
        internal const val RIGHT = 2
        internal const val DOWN = 3
        internal const val CENTER = 4

        fun isDpadDevice(event: InputEvent): Boolean =
            // Check that input comes from a device with directional pads.
            return event.isFromSource(InputDevice.SOURCE_DPAD)
    }
}

جاوا

public class Dpad {
    final static int UP       = 0;
    final static int LEFT     = 1;
    final static int RIGHT    = 2;
    final static int DOWN     = 3;
    final static int CENTER   = 4;

    int directionPressed = -1; // initialized to -1

    public int getDirectionPressed(InputEvent event) {
        if (!isDpadDevice(event)) {
           return -1;
        }

        // If the input event is a MotionEvent, check its hat axis values.
        if (event instanceof MotionEvent) {

            // Use the hat axis value to find the D-pad direction
            MotionEvent motionEvent = (MotionEvent) event;
            float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X);
            float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y);

            // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
            // LEFT and RIGHT direction accordingly.
            if (Float.compare(xaxis, -1.0f) == 0) {
                directionPressed =  Dpad.LEFT;
            } else if (Float.compare(xaxis, 1.0f) == 0) {
                directionPressed =  Dpad.RIGHT;
            }
            // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
            // UP and DOWN direction accordingly.
            else if (Float.compare(yaxis, -1.0f) == 0) {
                directionPressed =  Dpad.UP;
            } else if (Float.compare(yaxis, 1.0f) == 0) {
                directionPressed =  Dpad.DOWN;
            }
        }

        // If the input event is a KeyEvent, check its key code.
        else if (event instanceof KeyEvent) {

           // Use the key code to find the D-pad direction.
            KeyEvent keyEvent = (KeyEvent) event;
            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
                directionPressed = Dpad.LEFT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
                directionPressed = Dpad.RIGHT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
                directionPressed = Dpad.UP;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
                directionPressed = Dpad.DOWN;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
                directionPressed = Dpad.CENTER;
            }
        }
        return directionPressed;
    }

    public static boolean isDpadDevice(InputEvent event) {
        // Check that input comes from a device with directional pads.
        return event.isFromSource(InputDevice.SOURCE_DPAD);
     }
}

شما می‌توانید از این کلاس کمکی در بازی خود، هر جایی که می‌خواهید ورودی D-pad را پردازش کنید، استفاده کنید (برای مثال، در فراخوانی‌های onGenericMotionEvent() یا onKeyDown() ).

برای مثال:

کاتلین

private val dpad = Dpad()
...
override fun onGenericMotionEvent(event: MotionEvent): Boolean {
    if (Dpad.isDpadDevice(event)) {
        when (dpad.getDirectionPressed(event)) {
            Dpad.LEFT -> {
                // Do something for LEFT direction press
                ...
                return true
            }
            Dpad.RIGHT -> {
                // Do something for RIGHT direction press
                ...
                return true
            }
            Dpad.UP -> {
                // Do something for UP direction press
                ...
                return true
            }
            ...
        }
    }

    // Check if this event is from a joystick movement and process accordingly.
    ...
}

جاوا

Dpad dpad = new Dpad();
...
@Override
public boolean onGenericMotionEvent(MotionEvent event) {

    // Check if this event if from a D-pad and process accordingly.
    if (Dpad.isDpadDevice(event)) {

       int press = dpad.getDirectionPressed(event);
       switch (press) {
            case LEFT:
                // Do something for LEFT direction press
                ...
                return true;
            case RIGHT:
                // Do something for RIGHT direction press
                ...
                return true;
            case UP:
                // Do something for UP direction press
                ...
                return true;
            ...
        }
    }

    // Check if this event is from a joystick movement and process accordingly.
    ...
}

حرکات جوی‌استیک را پردازش کنید

وقتی بازیکنان جوی‌استیک را روی دسته‌های بازی خود حرکت می‌دهند، اندروید یک MotionEvent گزارش می‌دهد که شامل کد اکشن ACTION_MOVE و موقعیت‌های به‌روزرسانی‌شده‌ی محورهای جوی‌استیک است. بازی شما می‌تواند از داده‌های ارائه‌شده توسط MotionEvent برای تعیین اینکه آیا حرکت جوی‌استیک مورد نظرش اتفاق افتاده است یا خیر، استفاده کند.

توجه داشته باشید که رویدادهای حرکتی جوی‌استیک ممکن است چندین نمونه حرکت را در یک شیء واحد دسته‌بندی کنند. شیء MotionEvent شامل موقعیت فعلی برای هر محور جوی‌استیک و همچنین چندین موقعیت تاریخی برای هر محور است. هنگام گزارش رویدادهای حرکتی با کد عملیاتی ACTION_MOVE (مانند حرکات جوی‌استیک)، اندروید مقادیر محور را برای کارایی دسته‌بندی می‌کند. مقادیر تاریخی برای یک محور شامل مجموعه‌ای از مقادیر متمایز قدیمی‌تر از مقدار فعلی محور و جدیدتر از مقادیر گزارش شده در هر رویداد حرکتی قبلی است. برای جزئیات بیشتر به مرجع MotionEvent مراجعه کنید.

برای رندر دقیق حرکت یک شیء بازی بر اساس ورودی جوی‌استیک، می‌توانید از اطلاعات تاریخی ارائه شده توسط اشیاء MotionEvent استفاده کنید.

شما می‌توانید مقادیر فعلی و تاریخی را با استفاده از روش‌های زیر بازیابی کنید:

قطعه کد زیر نشان می‌دهد که چگونه می‌توانید تابع فراخوانی onGenericMotionEvent() را برای پردازش ورودی جوی‌استیک نادیده بگیرید. ابتدا باید مقادیر تاریخی یک محور و سپس موقعیت فعلی آن را پردازش کنید.

کاتلین

class GameView(...) : View(...) {

    override fun onGenericMotionEvent(event: MotionEvent): Boolean {

        // Check that the event came from a game controller
        return if (event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
                && event.action == MotionEvent.ACTION_MOVE) {

            // Process the movements starting from the
            // earliest historical position in the batch
            (0 until event.historySize).forEach { i ->
                // Process the event at historical position i
                processJoystickInput(event, i)
            }

            // Process the current movement sample in the batch (position -1)
            processJoystickInput(event, -1)
            true
        } else {
            super.onGenericMotionEvent(event)
        }
    }
}

جاوا

public class GameView extends View {

    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {

        // Check that the event came from a game controller
        if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) ==
                InputDevice.SOURCE_JOYSTICK &&
                event.getAction() == MotionEvent.ACTION_MOVE) {

            // Process all historical movement samples in the batch
            final int historySize = event.getHistorySize();

            // Process the movements starting from the
            // earliest historical position in the batch
            for (int i = 0; i < historySize; i++) {
                // Process the event at historical position i
                processJoystickInput(event, i);
            }

            // Process the current movement sample in the batch (position -1)
            processJoystickInput(event, -1);
            return true;
        }
        return super.onGenericMotionEvent(event);
    }
}

قبل از استفاده از ورودی جوی‌استیک، باید مشخص کنید که آیا جوی‌استیک در مرکز قرار دارد یا خیر، سپس حرکات محور آن را بر این اساس محاسبه کنید. جوی‌استیک‌ها معمولاً یک ناحیه مسطح دارند، یعنی محدوده‌ای از مقادیر نزدیک به مختصات (0،0) که در آن محور به عنوان مرکز در نظر گرفته می‌شود. اگر مقدار محور گزارش شده توسط اندروید در ناحیه مسطح قرار گیرد، باید کنترلر را در حالت سکون (یعنی بی‌حرکت در امتداد هر دو محور) در نظر بگیرید.

این قطعه کد یک متد کمکی را نشان می‌دهد که حرکت را در امتداد هر محور محاسبه می‌کند. شما می‌توانید این متد کمکی را در متد processJoystickInput() که در مثال زیر توضیح داده شده است، فراخوانی کنید:

کاتلین

private fun getCenteredAxis(
        event: MotionEvent,
        device: InputDevice,
        axis: Int,
        historyPos: Int
): Float {
    val range: InputDevice.MotionRange? = device.getMotionRange(axis, event.source)

    // A joystick at rest does not always report an absolute position of
    // (0,0). Use the getFlat() method to determine the range of values
    // bounding the joystick axis center.
    range?.apply {
        val value: Float = if (historyPos < 0) {
            event.getAxisValue(axis)
        } else {
            event.getHistoricalAxisValue(axis, historyPos)
        }

        // Ignore axis values that are within the 'flat' region of the
        // joystick axis center.
        if (Math.abs(value) > flat) {
            return value
        }
    }
    return 0f
}

جاوا

private static float getCenteredAxis(MotionEvent event,
        InputDevice device, int axis, int historyPos) {
    final InputDevice.MotionRange range =
            device.getMotionRange(axis, event.getSource());

    // A joystick at rest does not always report an absolute position of
    // (0,0). Use the getFlat() method to determine the range of values
    // bounding the joystick axis center.
    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.
        if (Math.abs(value) > flat) {
            return value;
        }
    }
    return 0;
}

با کنار هم قرار دادن همه این موارد، در اینجا نحوه پردازش حرکات جوی‌استیک در بازی شما آورده شده است:

کاتلین

private fun processJoystickInput(event: MotionEvent, historyPos: Int) {

    val inputDevice = event.device

    // Calculate the horizontal distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat axis, or the right control stick.
    var x: Float = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_X, historyPos)
    if (x == 0f) {
        x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_X, historyPos)
    }
    if (x == 0f) {
        x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Z, historyPos)
    }

    // Calculate the vertical distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat switch, or the right control stick.
    var y: Float = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Y, historyPos)
    if (y == 0f) {
        y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_Y, historyPos)
    }
    if (y == 0f) {
        y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_RZ, historyPos)
    }

    // Update the ship object based on the new x and y values
}

جاوا

private void processJoystickInput(MotionEvent event,
        int historyPos) {

    InputDevice inputDevice = event.getDevice();

    // Calculate the horizontal distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat axis, or the right control stick.
    float x = getCenteredAxis(event, inputDevice,
            MotionEvent.AXIS_X, historyPos);
    if (x == 0) {
        x = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_HAT_X, historyPos);
    }
    if (x == 0) {
        x = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_Z, historyPos);
    }

    // Calculate the vertical distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat switch, or the right control stick.
    float y = getCenteredAxis(event, inputDevice,
            MotionEvent.AXIS_Y, historyPos);
    if (y == 0) {
        y = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_HAT_Y, historyPos);
    }
    if (y == 0) {
        y = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_RZ, historyPos);
    }

    // Update the ship object based on the new x and y values
}

برای پشتیبانی از دسته‌های بازی که ویژگی‌های پیچیده‌تری فراتر از یک جوی‌استیک دارند، این بهترین شیوه‌ها را دنبال کنید:

  • کنترل دو دسته بازی. بسیاری از دسته‌های بازی هم دسته چپ و هم دسته راست دارند. برای دسته چپ، اندروید حرکات افقی را به عنوان رویدادهای AXIS_X و حرکات عمودی را به عنوان رویدادهای AXIS_Y گزارش می‌دهد. برای دسته راست، اندروید حرکات افقی را به عنوان رویدادهای AXIS_Z و حرکات عمودی را به عنوان رویدادهای AXIS_RZ گزارش می‌دهد. مطمئن شوید که هر دو دسته بازی را در کد خود کنترل می‌کنید.
  • فشردن ماشه شانه را کنترل کنید (و مطمئن شوید که بازی شما با رویدادهای AXIS_ و KEYCODE_BUTTON_ کار می‌کند). برخی از کنترلرها دارای ماشه‌های شانه چپ و راست هستند. وقتی این ماشه‌ها وجود دارند، یک رویداد AXIS_*TRIGGER یا KEYCODE_BUTTON_*2 یا هر دو را منتشر می‌کنند. برای ماشه چپ، این‌ها AXIS_LTRIGGER و KEYCODE_BUTTON_L2 خواهند بود. برای ماشه راست، این‌ها AXIS_RTRIGGER و KEYCODE_BUTTON_R2 خواهند بود. رویدادهای محور فقط در صورتی رخ می‌دهند که ماشه طیف وسیعی از مقادیر بین 0 و 1 را منتشر کند و برخی از کنترلرها با خروجی آنالوگ علاوه بر رویدادهای محور، رویدادهای دکمه را نیز منتشر می‌کنند. بازی‌ها باید از هر دو رویداد AXIS_ و KEYCODE_BUTTON_ پشتیبانی کنند تا با همه کنترلرهای بازی رایج سازگار بمانند، اما اگر کنترلر هر دو را گزارش می‌دهد، رویدادی را ترجیح دهید که برای گیم‌پلی شما منطقی‌تر باشد. در اندروید ۴.۳ (سطح API ۱۸) و بالاتر، کنترلری که AXIS_LTRIGGER تولید می‌کند، مقدار یکسانی را برای محور AXIS_BRAKE نیز گزارش می‌دهد. همین امر در مورد AXIS_RTRIGGER و AXIS_GAS نیز صادق است. اندروید تمام فشرده شدن ماشه‌های آنالوگ را با مقدار نرمال‌شده از ۰.۰ (رها شده) تا ۱.۰ (کاملاً فشرده شده) گزارش می‌دهد.
  • رفتارها و پشتیبانی‌های خاص ممکن است در محیط‌های شبیه‌سازی‌شده متفاوت باشند . پلتفرم‌های شبیه‌سازی‌شده، مانند Google Play Games ، ممکن است بسته به قابلیت‌های سیستم عامل میزبان، رفتار کمی متفاوتی داشته باشند. برای مثال، برخی از کنترلرهایی که هر دو رویداد AXIS_ و KEYCODE_BUTTON_ را منتشر می‌کنند، فقط رویدادهای AXIS_ را منتشر می‌کنند و پشتیبانی از برخی کنترلرها ممکن است به طور کامل وجود نداشته باشد.

انواع رایج

با توجه به تنوع گسترده پشتیبانی اندروید از کنترلرها، ممکن است نحوه ساخت و آزمایش آن برای اطمینان از عملکرد بدون باگ بازی در بین بازیکنان نامشخص باشد. ما دریافتیم که علیرغم این تنوع ظاهری، تولیدکنندگان کنترلر در سراسر جهان تمایل دارند به طور مداوم به سه سبک مختلف کنترلر پایبند باشند. برخی از آنها سخت‌افزارهایی را برای تغییر بین دو یا چند مورد از این موارد ارائه می‌دهند.

این یعنی شما می‌توانید بازی را با حداقل سه کنترلر در تیم توسعه‌دهنده خود آزمایش کنید و بدون نیاز به مراجعه به لیست‌های مجاز و غیرمجاز، از قابل بازی بودن بازی خود مطمئن باشید.

انواع کنترل‌کننده‌های رایج

رایج‌ترین سبک کنترلرها تمایل به تقلید از طرح‌بندی کنسول‌های بازی محبوب دارند. این هم از نظر زیبایی‌شناسی در برچسب دکمه‌ها و طرح‌بندی و هم از نظر عملکردی با توجه به رویدادهایی که مطرح می‌شوند، اهمیت دارد. کنترلرهایی که دارای دکمه‌های سخت‌افزاری برای تغییر بین انواع مختلف کنسول هستند، رویدادهایی را که ارسال می‌کنند و اغلب حتی طرح‌بندی منطقی دکمه‌هایشان را تغییر می‌دهند.

هنگام آزمایش، توصیه می‌کنیم تأیید کنید که بازی شما با یک کنترلر در هر یک از دسته‌ها کار می‌کند. می‌توانید با کنترلرهای شخص ثالث یا تولیدکنندگان محبوب شخص ثالث آزمایش کنید. به‌طورکلی، ما با نهایت تلاش، محبوب‌ترین کنترلرها را با تعریف بالا تطبیق می‌دهیم.

نوع کنترل کننده تفاوت‌های رفتاری انواع برچسب‌گذاری
دسته‌های بازی به سبک ایکس‌باکس

اینها دسته‌هایی هستند که معمولاً برای پلتفرم‌های مایکروسافت ایکس‌باکس و ویندوز* ساخته می‌شوند.

این کنترل‌کننده‌ها با مجموعه ویژگی‌های مشخص‌شده در ورودی‌های کنترل‌کننده فرآیند مطابقت دارند. دکمه‌های L2/R2 روی این کنترلرها با برچسب LT/RT مشخص شده‌اند.
کنترلرهای سبک سوئیچ

این کنترلرها معمولاً برای خانواده کنسول‌های نینتندو سوییچ* طراحی شده‌اند.

این کنترلرها رویدادهای KeyEvent KEYCODE_BUTTON_R2 KEYCODE_BUTTON_L2 MotionEvent را ارسال می‌کنند. دکمه‌های L2/R2 روی این کنترلرها با ZL/ZR مشخص شده‌اند.

این کنترلرها همچنین جای دکمه‌های A و B و دکمه‌های X و Y را عوض می‌کنند، بنابراین KEYCODE_BUTTON_A دکمه‌ای است که با B برچسب‌گذاری شده است و برعکس.

دسته‌های بازی به سبک پلی‌استیشن

این دسته‌ها معمولاً برای خانواده کنسول‌های سونی پلی‌استیشن* طراحی شده‌اند.

این کنترلرها مانند کنترلرهای مدل ایکس‌باکس، MotionEvent ها را ارسال می‌کنند، اما مانند کنترلرهای مدل سوییچ، وقتی کاملاً فشرده می‌شوند، KeyEvent ها را نیز ارسال می‌کنند. این کنترلرها از مجموعه‌ی متفاوتی از حروف برای دکمه‌های اصلی استفاده می‌کنند.

* مایکروسافت، ایکس‌باکس و ویندوز علائم تجاری ثبت‌شده مایکروسافت هستند؛ نینتندو سوییچ علامت تجاری ثبت‌شده نینتندو آمریکا است؛ پلی‌استیشن علامت تجاری ثبت‌شده سونی اینتراکتیو انترتینمنت است.

دکمه‌های ماشه را از حالت ابهام خارج کنید

برخی از کنترلرها AXIS_LTRIGGER و AXIS_RTRIGGER ، برخی KEYCODE_BUTTON_L2 و KEYCODE_BUTTON_R2 و برخی دیگر همه این رویدادها را بر اساس قابلیت‌های سخت‌افزاری خود ارسال می‌کنند. با پشتیبانی از همه این رویدادها، سازگاری را به حداکثر برسانید.

تمام کنترلرهایی که AXIS_LTRIGGER ارسال می‌کنند، AXIS_BRAKE نیز ارسال می‌کنند، به طور مشابه برای AXIS_RTRIGGER و AXIS_GAS تا به حداکثر رساندن سازگاری بین فرمان‌های مسابقه‌ای و کنترلرهای بازی معمولی کمک کنند. به طور کلی این موضوع مشکلی ایجاد نمی‌کند، اما در مورد ویژگی‌هایی مانند تغییر صفحه کلید مراقب باشید.

ماشه MotionEvent KeyEvent
ماشه چپ AXIS_LTRIGGER
AXIS_BRAKE
KEYCODE_BUTTON_L2
ماشه راست AXIS_RTRIGGER
AXIS_GAS
KEYCODE_BUTTON_R2

باید دقت شود که بازی شما بتواند هم KeyEvent و هم MotionEvent مدیریت کند تا سازگاری با بیشترین تعداد کنترلر ممکن حفظ شود و رویدادها از حالت تکراری خارج شوند.

کنترلرهای پشتیبانی شده

هنگام آزمایش، توصیه می‌کنیم تأیید کنید که بازی شما با یک کنترلر در هر یک از دسته‌ها کار می‌کند.

  • سبک ایکس‌باکس
  • سبک نینتندو سوییچ
  • سبک پلی‌استیشن

شما می‌توانید با کنترلرهای شخص ثالث یا تولیدکنندگان محبوب شخص ثالث آزمایش کنید، و ما معمولاً محبوب‌ترین کنترلرها را تا حد امکان به تعریف ارائه شده نزدیک می‌کنیم.