RadialGamePad
RadialGamePad copied to clipboard
A customizable Android library for creating virtual gamepads
RadialGamePad
RadialGamePad is an Android library for creating gamepads overlays. It has been designed with customization in mind and it's currently powering all the layouts you see in Lemuroid.
Screen 1 | Screen 2 | Screen 3 |
---|---|---|
![]() |
![]() |
![]() |
Getting started
RadialGamePad is distributed through jitpack, modify your build.gradle
file to include this:
repositories {
maven { url "https://jitpack.io" }
}
dependencies {
implementation 'com.github.swordfish90:radialgamepad:$LAST_RELEASE'
// To handle the Flow of events from RadialGamePad
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
}
Usage
As the name suggests, RadialGamePad is built around the idea circular dials. There is a primary dial in the center which contains a primary control such as Cross
, Stick
or PrimaryButtons
, surrounded by optional secondary dials.
To define the layout a RadialGamePadConfig
object is passed to the constructor, and the library will take care of sizing and positioning controls to optimize the available space.
Events are returned as a Kotlin Flow
and are composed of three different types:
- Direction: fired from Sticks and Crosses and indicates a direction through the two components xAxis and yAxis
-
Button: indicates when a control has been pressed or released (
KeyEvent.ACTION_DOWN
orKeyEvent.ACTION_UP
) - Gesture: a higher level event such as tap, double tap or triple tap
Here's a simple example which creates a remote control pad and handles its events:
class MainActivity : Activity() {
private lateinit var pad: RadialGamePad
private val padConfig = RadialGamePadConfig(
sockets = 6,
primaryDial = PrimaryDialConfig.Cross(
CrossConfig(
id = 0,
useDiagonals = false
)
),
secondaryDials = listOf(
SecondaryDialConfig.SingleButton(
index = 1,
scale = 1f,
distance = 0f,
ButtonConfig(
id = KeyEvent.KEYCODE_BUTTON_SELECT,
iconId = R.drawable.ic_play
)
),
SecondaryDialConfig.SingleButton(
index = 2,
scale = 1f,
distance = 0f,
ButtonConfig(
id = KeyEvent.KEYCODE_BUTTON_L1,
iconId = R.drawable.ic_stop
)
),
SecondaryDialConfig.SingleButton(
index = 4,
scale = 1f,
distance = 0f,
ButtonConfig(
id = KeyEvent.KEYCODE_BUTTON_MODE,
iconId = R.drawable.ic_volume_down
)
),
SecondaryDialConfig.SingleButton(
index = 5,
scale = 1f,
distance = 0f,
ButtonConfig(
id = KeyEvent.KEYCODE_BUTTON_MODE,
iconId = R.drawable.ic_volume_up
)
)
)
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
// Create a pad with default theme and 8dp of margins
pad = RadialGamePad(padConfig, 8f, requireContext())
findViewById<FrameLayout>(R.id.container).addView(pad)
// Collect the Flow of events to a handler
lifecycleScope.launch {
pad.events()
.flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED)
.collect {
handleEvent(it)
}
}
}
private fun handleEvent(event: Event) {
// Do something with the event.
when (event) {
is Event.Button -> {
Log.d("Event", "Button event from control ${event.id}")
}
is Event.Gesture -> {
Log.d("Event", "Gesture event from control ${event.id}")
}
is Event.Direction -> {
Log.d("Event", "Direction event from control ${event.id}")
}
}
}
}
Advanced usage
Check the included app sample. If you want to see even more layouts check Lemuroid.
Random tips
- Do not put margins around RadialGamePad. If you need them use the margin variable, so that touch events will be still forwarded to the view
- If your gamepad is split into multiple views (very likely for games), consider using SecondaryDialConfig.Empty to enforce symmetry.