নিয়ন্ত্রক কর্ম হ্যান্ডেল

কন্ট্রোলারদের দুই ধরনের কাজ রয়েছে:

  • 'অন' এবং 'অফ' এর বাইনারি অবস্থা আছে এমন যেকোনো বাটনের জন্য ব্যবহৃত KeyEvent
  • MotionEvent এমন যেকোনো অ্যাক্সিসের জন্য ব্যবহৃত হয় যা একটি নির্দিষ্ট মানের পরিসর (রেঞ্জ) প্রদান করে। যেমন, অ্যানালগ স্টিকের জন্য -১ থেকে ১ অথবা অ্যানালগ ট্রিগারের জন্য ০ থেকে ১।

আপনি focus থাকা View থেকে এই ইনপুটগুলো পড়তে পারেন।

কোটলিন

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 )। উল্লেখ্য যে, এই সোর্স টাইপটি স্পষ্টভাবে নির্দেশ করে না যে গেম কন্ট্রোলারে ডি-প্যাড বাটন আছে কি না, যদিও বেশিরভাগ কন্ট্রোলারে সাধারণত দিকনির্দেশক নিয়ন্ত্রণ থাকে।
  • SOURCE_DPAD সোর্স টাইপটি নির্দেশ করে যে ইনপুট ডিভাইসটিতে ডি-প্যাড বাটন রয়েছে (উদাহরণস্বরূপ, 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++ ডেভেলপারদের গেম কন্ট্রোলার লাইব্রেরি ব্যবহার করা উচিত। এটি সমস্ত কন্ট্রোলারকে সবচেয়ে সাধারণ বৈশিষ্ট্যগুলোর একটি উপসেটে একীভূত করে এবং তাদের মধ্যে একটি সামঞ্জস্যপূর্ণ ইন্টারফেস প্রদান করে, যার মধ্যে বাটন লেআউট শনাক্ত করার ক্ষমতাও অন্তর্ভুক্ত।

এই চিত্রে দেখানো হয়েছে যে, একজন অ্যান্ড্রয়েড গেম ডেভেলপার অ্যান্ড্রয়েডে একটি সাধারণ কন্ট্রোলার দেখতে কেমন হবে বলে আশা করতে পারেন।

ডি-প্যাড, অ্যানালগ স্টিক এবং বাটন সহ চিহ্নিত ইনপুটযুক্ত একটি সাধারণ গেম কন্ট্রোলার।
চিত্র ১. একটি সাধারণ গেম কন্ট্রোলারের প্রোফাইল।

সারণিতে গেম কন্ট্রোলারের জন্য স্ট্যান্ডার্ড ইভেন্টের নাম এবং প্রকার তালিকাভুক্ত করা হয়েছে। ইভেন্টের সম্পূর্ণ তালিকার জন্য, ‘সাধারণ প্রকারভেদ’ দেখুন। সিস্টেমটি onGenericMotionEvent মাধ্যমে MotionEvent ইভেন্ট এবং onKeyDownonKeyUp মাধ্যমে KeyEvent ইভেন্ট প্রেরণ করে।

কন্ট্রোলার ইনপুট মূল ঘটনা মোশনইভেন্ট
১. ডি-প্যাড
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
৭. বি বাটন 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);
      }
    }
    

প্রক্রিয়া দিকনির্দেশক প্যাড ইনপুট

৪-মুখী ডিরেকশনাল প্যাড বা ডি-প্যাড হলো অনেক গেম কন্ট্রোলারে ব্যবহৃত একটি সাধারণ ফিজিক্যাল কন্ট্রোল। অ্যান্ড্রয়েড ডি-প্যাডের উপর (UP) এবং নিচের (DOWN) চাপকে AXIS_HAT_Y ইভেন্ট হিসেবে রিপোর্ট করে, যেখানে -1.0 উপরে (up) এবং 1.0 নিচে (down) নির্দেশ করে। এটি ডি-প্যাডের বাম (LEFT) বা ডান (RIGHT) চাপকে AXIS_HAT_X ইভেন্ট হিসেবে রিপোর্ট করে, যেখানে -1.0 বামে (left) এবং 1.0 ডানে (right) নির্দেশ করে।

কিছু কন্ট্রোলার এর পরিবর্তে একটি কী কোডের মাধ্যমে ডি-প্যাড প্রেস রিপোর্ট করে। যদি আপনার গেমে ডি-প্যাড প্রেসের গুরুত্ব থাকে, তবে টেবিল ২-এ সুপারিশ অনুযায়ী হ্যাট অ্যাক্সিস ইভেন্ট এবং ডি-প্যাড কী কোডগুলোকে একই ইনপুট ইভেন্ট হিসেবে গণ্য করা উচিত।

সারণি ২. ডি-প্যাড কী কোড এবং হ্যাট অ্যাক্সিস মানের জন্য প্রস্তাবিত ডিফল্ট গেম অ্যাকশনসমূহ।

গেম অ্যাকশন ডি-প্যাড কী কোড টুপি অক্ষ কোড
উপরে উঠুন KEYCODE_DPAD_UP AXIS_HAT_Y (মান ০ থেকে -১.০ এর জন্য)
নিচে নামুন KEYCODE_DPAD_DOWN AXIS_HAT_Y (মান ০ থেকে ১.০ এর জন্য)
বাম দিকে যান KEYCODE_DPAD_LEFT AXIS_HAT_X (মান ০ থেকে -১.০ এর জন্য)
ডানদিকে সরান KEYCODE_DPAD_RIGHT AXIS_HAT_X (মান ০ থেকে ১.০ এর জন্য)

নিম্নলিখিত কোড স্নিপেটটি একটি হেল্পার ক্লাস দেখায় যা আপনাকে একটি ইনপুট ইভেন্ট থেকে হ্যাট অ্যাক্সিস এবং কী কোড মান পরীক্ষা করে ডি-প্যাডের দিক নির্ধারণ করতে দেয়।

কোটলিন

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);
     }
}

আপনার গেমে যেখানেই ডি-প্যাড ইনপুট প্রসেস করতে চান, সেখানেই এই হেল্পার ক্লাসটি ব্যবহার করতে পারেন (উদাহরণস্বরূপ, 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 । অ্যাক্সিস ইভেন্টগুলো কেবল তখনই ঘটে যখন ট্রিগারটি ০ থেকে ১-এর মধ্যে কোনো মানের পরিসর নির্গত করে, এবং অ্যানালগ আউটপুটসহ কিছু কন্ট্রোলার অ্যাক্সিস ইভেন্টের পাশাপাশি বাটন ইভেন্টও নির্গত করে। সমস্ত সাধারণ গেম কন্ট্রোলারের সাথে সামঞ্জস্যপূর্ণ থাকার জন্য গেমগুলোকে অবশ্যই AXIS_ এবং KEYCODE_BUTTON_ উভয় ইভেন্ট সমর্থন করতে হবে, তবে যদি কোনো কন্ট্রোলার উভয়ই রিপোর্ট করে, তবে আপনার গেমপ্লের জন্য যে ইভেন্টটি সবচেয়ে বেশি অর্থবহ, সেটিই বেছে নিন। অ্যান্ড্রয়েড ৪.৩ (এপিআই লেভেল ১৮) এবং এর পরবর্তী সংস্করণগুলোতে, যে কন্ট্রোলার AXIS_LTRIGGER ট্রিগার তৈরি করে, সেটি AXIS_BRAKE অ্যাক্সিসের জন্যও একটি অভিন্ন মান রিপোর্ট করে। AXIS_RTRIGGER এবং AXIS_GAS ক্ষেত্রেও একই কথা প্রযোজ্য। অ্যান্ড্রয়েড সমস্ত অ্যানালগ ট্রিগার প্রেসকে ০.০ (ছেড়ে দেওয়া) থেকে ১.০ (সম্পূর্ণ চাপা) পর্যন্ত একটি নর্মালাইজড মান দিয়ে রিপোর্ট করে।
  • এমুলেটেড পরিবেশে নির্দিষ্ট আচরণ এবং সমর্থন ভিন্ন হতে পারেগুগল প্লে গেমসের মতো এমুলেটেড প্ল্যাটফর্মগুলোর আচরণ হোস্ট অপারেটিং সিস্টেমের সক্ষমতার ওপর ভিত্তি করে কিছুটা ভিন্ন হতে পারে। উদাহরণস্বরূপ, কিছু কন্ট্রোলার যা AXIS_ এবং KEYCODE_BUTTON_ উভয় ইভেন্টই নির্গত করে, সেগুলো কেবল AXIS_ ইভেন্টই নির্গত করে, এবং কিছু কন্ট্রোলারের জন্য সমর্থন পুরোপুরি অনুপস্থিত থাকতে পারে।

সাধারণ রূপগুলি

অ্যান্ড্রয়েডে কন্ট্রোলারের জন্য বিভিন্ন ধরনের সাপোর্ট থাকায়, আপনার গেমটি প্লেয়ারদের মধ্যে ত্রুটিমুক্তভাবে কাজ করছে কিনা তা যাচাই করার জন্য কীভাবে বিল্ড ও টেস্ট করবেন, তা স্পষ্ট নাও হতে পারে। আমরা দেখেছি যে এই আপাত বৈচিত্র্য থাকা সত্ত্বেও, বিশ্বজুড়ে কন্ট্রোলার নির্মাতারা সাধারণত ধারাবাহিকভাবে তিন ধরনের কন্ট্রোলার ব্যবহার করে থাকেন। কেউ কেউ এগুলোর মধ্যে দুই বা ততোধিক কন্ট্রোলারের মধ্যে পরিবর্তন করার জন্য হার্ডওয়্যার টগল সুবিধা দিয়ে থাকেন।

এর মানে হলো, আপনি আপনার ডেভ টিমের মধ্যে মাত্র তিনটি কন্ট্রোলার দিয়েই পরীক্ষা করতে পারেন এবং allow ও deny লিস্টের সাহায্য না নিয়েই নিশ্চিত থাকতে পারেন যে আপনার গেমটি খেলার যোগ্য।

সাধারণ কন্ট্রোলারের প্রকারভেদ

সবচেয়ে প্রচলিত কন্ট্রোলারগুলো জনপ্রিয় গেম কনসোলগুলোর লেআউট অনুকরণ করে তৈরি করা হয়। বাটনের লেবেল ও লেআউটের দিক থেকে এটি নান্দনিক, এবং এর মাধ্যমে কী ধরনের ইভেন্ট সক্রিয় হয়, সেই দিক থেকে এটি কার্যকরী। যেসব কন্ট্রোলারে বিভিন্ন ধরনের কনসোলের মধ্যে হার্ডওয়্যার টগল করার সুবিধা থাকে, সেগুলো তাদের পাঠানো ইভেন্টগুলো পরিবর্তন করে এবং প্রায়শই তাদের লজিক্যাল বাটন লেআউটও বদলে ফেলে।

পরীক্ষা করার সময় আমরা আপনাকে প্রতিটি বিভাগের একটি করে কন্ট্রোলার দিয়ে আপনার গেমটি কাজ করছে কিনা তা যাচাই করার পরামর্শ দিই। আপনি ফার্স্ট-পার্টি কন্ট্রোলার বা জনপ্রিয় থার্ড-পার্টি নির্মাতাদের কন্ট্রোলার দিয়ে পরীক্ষা করতে পারেন। সাধারণত আমরা আমাদের সর্বোচ্চ চেষ্টার মাধ্যমে সবচেয়ে জনপ্রিয় কন্ট্রোলারগুলোকে উপরের সংজ্ঞার সাথে ম্যাপ করে দেব।

কন্ট্রোলার টাইপ আচরণগত পার্থক্য লেবেলিং বৈচিত্র্য
এক্সবক্স স্টাইলের কন্ট্রোলার

এগুলো সাধারণত মাইক্রোসফট এক্সবক্স এবং উইন্ডোজ* প্ল্যাটফর্মের জন্য তৈরি কন্ট্রোলার।

এই কন্ট্রোলারগুলো প্রসেস কন্ট্রোলার ইনপুট- এ বর্ণিত বৈশিষ্ট্যগুলোর সাথে মেলে। এই কন্ট্রোলারগুলোর L2/R2 বাটনগুলোতে LT/RT লেখা আছে।
সুইচ স্টাইল কন্ট্রোলার

এই কন্ট্রোলারগুলো সাধারণত নিন্টেন্ডো সুইচ* সিরিজের কনসোলগুলোর জন্য ডিজাইন করা হয়।

এই কন্ট্রোলারগুলো KeyEvent KEYCODE_BUTTON_R2 KEYCODE_BUTTON_L2 MotionEvent পাঠায়। এই কন্ট্রোলারগুলোর L2/R2 বাটনগুলোতে ZL/ZR লেবেল করা আছে।

এই কন্ট্রোলারগুলোতে AB বাটন এবং XY বাটনও অদলবদল করা থাকে, তাই KEYCODE_BUTTON_A হলো B লেবেলযুক্ত বাটন এবং এর বিপরীতটিও সত্য।

প্লেস্টেশন স্টাইলের কন্ট্রোলার

এই কন্ট্রোলারগুলো সাধারণত সনি প্লেস্টেশন* সিরিজের কনসোলগুলোর জন্য ডিজাইন করা হয়।

এই কন্ট্রোলারগুলো এক্সবক্স স্টাইল কন্ট্রোলারগুলোর মতো MotionEvent পাঠায়, কিন্তু পুরোপুরি চাপ দিলে সুইচ স্টাইল কন্ট্রোলারগুলোর মতো KeyEvent ও পাঠায়। এই কন্ট্রোলারগুলোতে ফেস বাটনগুলোর জন্য ভিন্ন ধরনের গ্লিফ ব্যবহার করা হয়।

* মাইক্রোসফট, এক্সবক্স, এবং উইন্ডোজ হলো মাইক্রোসফটের নিবন্ধিত ট্রেডমার্ক; নিন্টেন্ডো সুইচ হলো নিন্টেন্ডো অফ আমেরিকা ইনকর্পোরেটেডের নিবন্ধিত ট্রেডমার্ক; প্লেস্টেশন হলো সনি ইন্টারঅ্যাকটিভ এন্টারটেইনমেন্ট ইনকর্পোরেটেডের নিবন্ধিত ট্রেডমার্ক।

ট্রিগার বোতামগুলির দ্ব্যর্থতা দূর করুন

কিছু কন্ট্রোলার AXIS_LTRIGGERAXIS_RTRIGGER পাঠায়, কিছু পাঠায় KEYCODE_BUTTON_L2KEYCODE_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

যত বেশি সম্ভব কন্ট্রোলারের সাথে সামঞ্জস্য বজায় রাখতে এবং ইভেন্টগুলো থেকে ডুপ্লিকেট বাদ দেওয়া নিশ্চিত করতে, আপনার গেমটি যেন KeyEventMotionEvent উভয়ই পরিচালনা করতে পারে, সেদিকে খেয়াল রাখা উচিত।

সমর্থিত কন্ট্রোলার

পরীক্ষা করার সময় আমরা আপনাকে পরামর্শ দিই যে, আপনার গেমটি প্রতিটি ক্যাটাগরির একটি করে কন্ট্রোলার দিয়ে কাজ করছে কিনা তা যাচাই করে নিন।

  • এক্সবক্স স্টাইল
  • নিন্টেন্ডো সুইচ স্টাইল
  • প্লেস্টেশন স্টাইল

আপনি ফার্স্ট-পার্টি কন্ট্রোলার বা জনপ্রিয় থার্ড-পার্টি নির্মাতাদের কন্ট্রোলার দিয়ে পরীক্ষা করতে পারেন, এবং আমরা সাধারণত সবচেয়ে জনপ্রিয় কন্ট্রোলারগুলোকে সংজ্ঞার সাথে যথাসম্ভব নিখুঁতভাবে মিলিয়ে নিই।