Skip to content

Commit

Permalink
docs(vanilla): add joystick
Browse files Browse the repository at this point in the history
  • Loading branch information
verekia committed Apr 4, 2024
1 parent d3f6edd commit 2d41431
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 12 deletions.
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,23 +353,25 @@ useAnimationFrame(

## Virtual joysticks

⚠️ React-only for now ⚠️
⚠️ React and vanilla-only for now ⚠️

Mana Potion includes **🗿 non-reactive** and **headless** virtual joysticks for mobile controls. Each virtual joystick is associated with a single `<JoystickArea />`. You can create your own Joystick objects with `createJoystick()` or use one of the two default ones that are already available on the joysticks store. The default ones are called `movement` and `rotation` joysticks.

You can choose between 2 modes, follow or origin, by setting `maxFollowDistance` or `maxOriginDistance`:
You can choose between 2 modes, `follow` or `origin`, and can adjust the `maxFollowDistance` or `maxOriginDistance`. Use the `onStart`, `onMove`, and `onEnd` callbacks to update your game state and optionally show a joystick on the screen.

```jsx
import { JoystickArea, getJoysticks } from '@manapotion/react'

const MobileUI = () => {
return (
<JoystickArea
joystick={getJoysticks().movement}
maxFollowDistance={50} // or maxOriginDistance={50}
/>
)
}
const MobileUI = () => (
<JoystickArea
joystick={getJoysticks().movement}
mode="follow" // Default
maxFollowDistance={50} // Default
onStart={handleStart}
onMove={handleMove}
onEnd={handleEnd}
/>
)
```

In follow mode, the joystick will follow the user's finger, which is good for player movement.
Expand Down
2 changes: 1 addition & 1 deletion examples/react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import DiscordIcon from './components/DiscordIcon'
import GithubIcon from './components/GithubIcon'
import Item from './components/Item'
import Label from './components/Label'
import MobileJoystick from './components/MobileJoystick'
import {
LeftMouseButtonDownLabel,
MiddleMouseButtonDownLabel,
Expand All @@ -34,7 +35,6 @@ import {
RightMouseButtonDownLabel,
} from './components/mouse-labels'
import TwitterIcon from './components/TwitterIcon'
import MobileJoystick from './MobileJoystick'

interface EventNotificationActions {
setMessage: (message: string) => void
Expand Down
File renamed without changes.
File renamed without changes.
97 changes: 96 additions & 1 deletion examples/vanilla/src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import {
exitFullscreen,
FullscreenChangePayload,
getBrowser,
getJoysticks,
getKeyboard,
getMouse,
Joystick,
LeftMouseButtonDownPayload,
LeftMouseButtonUpPayload,
listeners,
Expand All @@ -14,6 +16,7 @@ import {
lockPointer,
MiddleMouseButtonDownPayload,
MiddleMouseButtonUpPayload,
mountJoystickArea,
MouseMovePayload,
MouseScrollPayload,
PageFocusChangePayload,
Expand Down Expand Up @@ -75,6 +78,44 @@ const updateKeyboard = () => {
elMeta.className = getLabelClass(meta)
}

let joystickMode: 'follow' | 'origin' = 'follow'
let unsubJoystickArea: () => void

const updateJoystickButton = () => {
const btn = document.getElementById('joystick-mode-btn')!
btn.textContent = joystickMode === 'follow' ? 'Follow' : 'Origin'
}

const handleJoystickStart = (joystick: Joystick) => {
const joystickCurrentEl = document.getElementById('joystick-current')!
const joystickOriginEl = document.getElementById('joystick-origin')!
const joystickFollowEl = document.getElementById('joystick-follow')!
joystickCurrentEl.style.transform = `translate(${joystick.current.x}px, ${-joystick.current.y!}px)`
joystickOriginEl.style.transform = `translate(${joystick.origin.x}px, ${-joystick.origin.y!}px)`
joystickFollowEl.style.transform = `translate(${joystick.follow.x}px, ${-joystick.follow.y!}px)`
joystickCurrentEl.style.opacity = '1'
joystickMode === 'follow' && (joystickFollowEl.style.opacity = '1')
joystickOriginEl.style.opacity = '1'
}

const handleJoystickEnd = () => {
const joystickCurrentEl = document.getElementById('joystick-current')!
const joystickOriginEl = document.getElementById('joystick-origin')!
const joystickFollowEl = document.getElementById('joystick-follow')!
joystickCurrentEl.style.opacity = '0'
joystickOriginEl.style.opacity = '0'
joystickFollowEl.style.opacity = '0'
}

const handleJoystickMove = (joystick: Joystick) => {
const joystickCurrentEl = document.getElementById('joystick-current')!
const joystickOriginEl = document.getElementById('joystick-origin')!
const joystickFollowEl = document.getElementById('joystick-follow')!
joystickCurrentEl.style.transform = `translate(${joystick.current.x}px, ${-joystick.current.y!}px)`
joystickOriginEl.style.transform = `translate(${joystick.origin.x}px, ${-joystick.origin.y!}px)`
joystickFollowEl.style.transform = `translate(${joystick.follow.x}px, ${-joystick.follow.y!}px)`
}

document.addEventListener('DOMContentLoaded', () => {
updateKeyboard()

Expand Down Expand Up @@ -182,6 +223,17 @@ document.addEventListener('DOMContentLoaded', () => {
onKeyUp: updateKeyboard,
})

unsubJoystickArea = mountJoystickArea({
element: document.getElementById('joystick-area')!,
mode: joystickMode,
joystick: getJoysticks().movement,
onMove: handleJoystickMove,
onStart: handleJoystickStart,
onEnd: handleJoystickEnd,
})

updateJoystickButton()

startAnimationFrame(({ elapsed }) => {
const el = document.getElementById('animationFrame')!
el.textContent = String(elapsed)
Expand Down Expand Up @@ -209,6 +261,20 @@ window.unlockOrientation = unlockOrientation
window.lockKeys = lockKeys
// @ts-expect-error should define this function in the global scope
window.unlockKeys = unlockKeys
// @ts-expect-error should define this function in the global scope
window.toggleJoystickMode = () => {
joystickMode = joystickMode === 'follow' ? 'origin' : 'follow'
unsubJoystickArea()
unsubJoystickArea = mountJoystickArea({
element: document.getElementById('joystick-area')!,
mode: joystickMode,
joystick: getJoysticks().movement,
onMove: handleJoystickMove,
onStart: handleJoystickStart,
onEnd: handleJoystickEnd,
})
updateJoystickButton()
}

export const App = html`
<main class="mx-auto max-w-7xl px-5 pb-16 pt-5" oncontextmenu="event.preventDefault()">
Expand Down Expand Up @@ -384,7 +450,36 @@ export const App = html`
</section>
<section>
<h2 class="section-heading">🕹️ Virtual joysticks</h2>
<div>Vanilla support coming soon.</div>
<div class="relative w-max">
<div class="absolute left-[58px] top-[75px] max-w-36 text-center mobile:hidden">
Switch to 👆 mobile mode in devtools
</div>
<div
id="joystick-area"
class="relative z-10 h-48 w-64 rounded-md border border-slate-500"
>
<div
id="joystick-current"
class="pointer-events-none absolute -bottom-6 -left-6 size-12 rounded-full bg-red-500 opacity-0 transition-opacity"
></div>
<div
id="joystick-origin"
class="pointer-events-none absolute -bottom-6 -left-6 size-12 rounded-full bg-blue-500 opacity-0 transition-opacity"
></div>
<div
id="joystick-follow"
class="pointer-events-none absolute -bottom-6 -left-6 size-12 rounded-full bg-green-500 opacity-0 transition-opacity"
></div>
</div>
<div class="mt-3 flex items-center justify-center gap-3">
Mode
<button
class="btn capitalize"
onclick="window.toggleJoystickMode()"
id="joystick-mode-btn"
></button>
</div>
</div>
</section>
<section>
<h2 class="section-heading">🔄 Animation loops</h2>
Expand Down

0 comments on commit 2d41431

Please sign in to comment.