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

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

هنگامی که بازیکنان به صورت فیزیکی یک کنترلر بازی را به دستگاه های مجهز به اندروید خود متصل می کنند یا به صورت بی سیم جفت می کنند، سیستم به طور خودکار کنترلر را به عنوان دستگاه ورودی شناسایی می کند و شروع به گزارش رویدادهای ورودی آن می کند. بازی شما می‌تواند این رویدادهای ورودی را با پیاده‌سازی روش‌های پاسخ به تماس زیر در Activity فعال یا View متمرکز خود دریافت کند (شما باید پاسخ‌های تماس را برای Activity یا View اجرا کنید، اما نه هر دو):

رویکرد توصیه شده این است که رویدادها را از یک شی View خاص که کاربر با آن تعامل دارد، ضبط کنید. برای دریافت اطلاعات در مورد نوع رویداد ورودی دریافتی، اشیای زیر را که توسط callback ها ارائه می شود، بررسی کنید:

KeyEvent
شیئی که رویدادهای پد جهت (D-pad) و دکمه گیم پد را توصیف می کند. رویدادهای کلیدی با یک کد کلیدی همراه می‌شوند که نشان می‌دهد دکمه خاصی فعال شده است، مانند DPAD_DOWN یا BUTTON_A . می‌توانید کد کلید را با فراخوانی getKeyCode() یا از تماس‌های رویداد کلیدی مانند onKeyDown() دریافت کنید.
MotionEvent
جسمی که ورودی جوی استیک و حرکات ماشه شانه را توصیف می کند. رویدادهای حرکت با یک کد اقدام و مجموعه ای از مقادیر محور همراه هستند. کد عمل تغییر حالتی را که مانند حرکت جوی استیک رخ داده است را مشخص می کند. مقادیر محور موقعیت و سایر ویژگی‌های حرکتی را برای یک کنترل فیزیکی خاص، مانند AXIS_X یا AXIS_RTRIGGER توصیف می‌کنند. می‌توانید کد عمل را با فراخوانی getAction() و مقدار محور را با فراخوانی getAxisValue() دریافت کنید.

این درس بر این تمرکز دارد که چگونه می‌توانید ورودی رایج‌ترین کنترل‌های فیزیکی (دکمه‌های گیم‌پد، پدهای جهت‌گیری، و جوی استیک‌ها) را در صفحه بازی با اجرای روش‌های برگشت به تماس View بالا و پردازش اشیاء KeyEvent و MotionEvent مدیریت کنید.

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

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

برای تأیید اینکه یک دستگاه ورودی متصل یک کنترل‌کننده بازی است، getSources() را فراخوانی کنید تا یک فیلد بیت ترکیبی از انواع منبع ورودی که در آن دستگاه پشتیبانی می‌شوند به دست آورید. سپس می توانید تست کنید تا ببینید آیا فیلدهای زیر تنظیم شده اند یا خیر:

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

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

کاتلین

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 (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
                    || sources and InputDevice.SOURCE_JOYSTICK == InputDevice.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);
        int sources = dev.getSources();

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

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

برای تشخیص اینکه آیا یک کد کلید یا کد محور خاص توسط یک کنترلر بازی متصل پشتیبانی می شود، از این تکنیک ها استفاده کنید:

  • در اندروید 4.4 (سطح API 19) یا بالاتر، می‌توانید با فراخوانی hasKeys(int...) تعیین کنید که آیا یک کد کلید روی یک کنترلر بازی متصل پشتیبانی می‌شود یا خیر.
  • در Android 3.1 (سطح API 12) یا بالاتر، می‌توانید با فراخوانی getMotionRanges() تمام محورهای موجود را که روی یک کنترلر بازی متصل پشتیبانی می‌شوند، پیدا کنید. سپس، بر روی هر شیء InputDevice.MotionRange بازگشتی، getAxis() را فراخوانی کنید تا شناسه محور آن را دریافت کنید.

پردازش دکمه گیم پد

شکل 1 نشان می دهد که چگونه اندروید کدهای کلید و مقادیر محور را به کنترل های فیزیکی در اکثر کنترلرهای بازی نگاشت می کند.

شکل 1. نمایه یک کنترلر بازی عمومی.

فراخوان های موجود در شکل به موارد زیر اشاره دارند:

کدهای کلیدی رایج که با فشار دادن دکمه های صفحه بازی ایجاد می شوند عبارتند از BUTTON_A ، BUTTON_B ، BUTTON_SELECT و BUTTON_START . برخی از کنترل‌کننده‌های بازی نیز با فشار دادن مرکز نوار متقاطع D-pad، کد کلید DPAD_CENTER را فعال می‌کنند. بازی شما می‌تواند کد کلید را با فراخوانی getKeyCode() یا از فراخوان‌های رویداد کلیدی مانند onKeyDown() بازرسی کند، و اگر رویدادی را نشان می‌دهد که مربوط به بازی شما است، آن را به عنوان یک اکشن بازی پردازش کنید. جدول 1 اقدامات بازی توصیه شده برای رایج ترین دکمه های گیم پد را فهرست می کند.

جدول 1. اقدامات بازی توصیه شده برای دکمه های صفحه بازی.

بازی اکشن کد کلید دکمه
شروع بازی در منوی اصلی، یا مکث/لغو مکث در طول بازی BUTTON_START *
نمایش منو BUTTON_SELECT * و KEYCODE_MENU *
مانند رفتار ناوبری Android Back که در راهنمای طراحی ناوبری توضیح داده شده است. KEYCODE_BACK
به یک مورد قبلی در یک منو برگردید BUTTON_B
انتخاب را تأیید کنید یا اقدام اصلی بازی را انجام دهید BUTTON_A و DPAD_CENTER

* بازی شما نباید به وجود دکمه های Start، Select یا Menu متکی باشد.

نکته: یک صفحه پیکربندی را در بازی خود در نظر بگیرید تا به کاربران امکان دهد نگاشت های کنترلر بازی خود را برای اقدامات بازی شخصی سازی کنند.

قطعه زیر نشان می‌دهد که چگونه می‌توانید onKeyDown() لغو کنید تا فشارهای دکمه BUTTON_A و DPAD_CENTER را با یک عمل بازی مرتبط کنید.

کاتلین

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

    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        var handled = false
        if (event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD) {
            if (event.repeatCount == 0) {
                when (keyCode) {
                    // Handle gamepad and D-pad button presses to navigate the ship
                    ...

                    else -> {
                        keyCode.takeIf { isFireKey(it) }?.run {
                            // Update the ship object to fire lasers
                            ...
                            handled = true
                        }
                    }
                }
            }
            if (handled) {
                return true
            }
        }
        return super.onKeyDown(keyCode, event)
    }

    // Here we treat Button_A and DPAD_CENTER as the primary action
    // keys for the game.
    private fun isFireKey(keyCode: Int): Boolean =
            keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_BUTTON_A
}

جاوا

public class GameView extends View {
    ...

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean handled = false;
        if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
                == InputDevice.SOURCE_GAMEPAD) {
            if (event.getRepeatCount() == 0) {
                switch (keyCode) {
                    // Handle gamepad and D-pad button presses to
                    // navigate the ship
                    ...

                    default:
                         if (isFireKey(keyCode)) {
                             // Update the ship object to fire lasers
                             ...
                             handled = true;
                         }
                     break;
                }
            }
            if (handled) {
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    private static boolean isFireKey(int keyCode) {
        // Here we treat Button_A and DPAD_CENTER as the primary action
        // keys for the game.
        return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                || keyCode == KeyEvent.KEYCODE_BUTTON_A;
    }
}

توجه: در اندروید 4.2 (سطح API 17) و پایین‌تر، سیستم به‌طور پیش‌فرض با BUTTON_A به‌عنوان کلید Android Back رفتار می‌کند. اگر برنامه شما از این نسخه‌های Android پشتیبانی می‌کند، مطمئن شوید که BUTTON_A به عنوان اکشن اصلی بازی در نظر بگیرید. برای تعیین نسخه Android SDK فعلی در دستگاه، به مقدار Build.VERSION.SDK_INT مراجعه کنید.

ورودی پد جهت را پردازش کنید

پد جهت 4 جهته (D-pad) یک کنترل فیزیکی رایج در بسیاری از کنترلرهای بازی است. Android فشارهای D-pad UP و DOWN را به‌عنوان رویدادهای AXIS_HAT_Y با گستره 1.0 (بالا) تا 1.0 (پایین) گزارش می‌کند و D-pad LEFT یا RIGHT به‌عنوان رویدادهای AXIS_HAT_X با دامنه از -1.0 (چپ) تا 1.0 ( درست).

برخی از کنترلرها در عوض فشارهای D-pad را با یک کد کلید گزارش می دهند. اگر بازی شما به فشارهای D-pad اهمیت می دهد، باید رویدادهای محور کلاه و کدهای کلید D-pad را همانطور که در جدول 2 توصیه می شود، به عنوان رویدادهای ورودی یکسان در نظر بگیرید.

جدول 2. اقدامات پیش فرض بازی برای کدهای کلید D-pad و مقادیر محور کلاه توصیه شده است.

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

قطعه کد زیر یک کلاس کمکی را نشان می دهد که به شما امکان می دهد محور کلاه و مقادیر کد کلید را از یک رویداد ورودی بررسی کنید تا جهت 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.
            event.source and InputDevice.SOURCE_DPAD != 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.
        if ((event.getSource() & InputDevice.SOURCE_DPAD)
             != InputDevice.SOURCE_DPAD) {
             return true;
         } else {
             return false;
         }
     }
}

شما می توانید از این کلاس کمکی در بازی خود در هر جایی که می خواهید ورودی 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.
    ...
}

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

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

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

می‌توانید از اطلاعات تاریخی برای نمایش دقیق‌تر حرکت یک شی بازی بر اساس ورودی جوی استیک استفاده کنید. برای بازیابی مقادیر فعلی و تاریخی، getAxisValue() یا getHistoricalAxisValue() را فراخوانی کنید. همچنین می توانید با فراخوانی getHistorySize() تعداد نقاط تاریخی موجود در رویداد جوی استیک را بیابید.

قطعه زیر نشان می دهد که چگونه می توانید برای پردازش ورودی جوی استیک، فراخوانی 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) که محور در مرکز آن در نظر گرفته می شود. اگر مقدار محور گزارش شده توسط Android در ناحیه مسطح قرار گیرد، باید کنترل کننده را در حالت استراحت قرار دهید (یعنی بدون حرکت در امتداد هر دو محور).

قطعه زیر یک روش کمکی را نشان می دهد که حرکت در امتداد هر محور را محاسبه می کند. شما این کمک کننده را در متد 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
}

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

  • دسته های کنترلر دوتایی. بسیاری از کنترلرهای بازی دارای جوی استیک چپ و راست هستند. برای چوب سمت چپ، Android حرکات افقی را به عنوان رویدادهای AXIS_X و حرکات عمودی را به عنوان رویدادهای AXIS_Y گزارش می‌کند. برای چوب سمت راست، Android حرکات افقی را به عنوان رویدادهای 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_ پشتیبانی کنند تا با همه کنترل‌کننده‌های بازی رایج سازگار باشند، اما اگر کنترل‌کننده هر دو را گزارش کند، رویدادی را ترجیح می‌دهند که بیشترین حس را برای گیم‌پلی شما داشته باشد. در Android 4.3 (سطح API 18) و بالاتر، کنترل‌کننده‌ای که AXIS_LTRIGGER تولید می‌کند نیز مقدار یکسانی را برای محور AXIS_BRAKE گزارش می‌کند. همین امر برای AXIS_RTRIGGER و AXIS_GAS نیز صادق است. اندروید تمام فشارهای ماشه آنالوگ را با مقدار نرمال شده از 0.0 (آزاد شده) تا 1.0 (فشار کامل) گزارش می دهد.
  • رفتارها و پشتیبانی خاص ممکن است در محیط های شبیه سازی شده متفاوت باشد . پلتفرم‌های شبیه‌سازی‌شده، مانند بازی‌های Google Play ، ممکن است بر اساس قابلیت‌های سیستم عامل میزبان، از نظر رفتار کمی متفاوت باشند. برای مثال، برخی از کنترل‌کننده‌هایی که رویدادهای AXIS_ و KEYCODE_BUTTON_ را منتشر می‌کنند، فقط رویدادهای AXIS_ را منتشر می‌کنند، و پشتیبانی از برخی کنترل‌کننده‌ها ممکن است به طور کامل وجود نداشته باشد.