minecraft-clone / src /components /ui /MobileControls.tsx
TomatitoToho's picture
Upload src/components/ui/MobileControls.tsx with huggingface_hub
78691c3 verified
Raw
History Blame Contribute Delete
6.11 kB
'use client'
import { useCallback, useRef, useEffect, useState } from 'react'
import { usePlayerStore } from '@/stores/playerStore'
export function MobileControls() {
const [moveActive, setMoveActive] = useState(false)
const [lookActive, setLookActive] = useState(false)
const moveRef = useRef<{ x: number; z: number }>({ x: 0, z: 0 })
const lookRef = useRef<{ lastX: number; lastY: number }>({ lastX: 0, lastY: 0 })
const moveTouchId = useRef<number | null>(null)
const lookTouchId = useRef<number | null>(null)
// Move joystick
const handleMoveStart = useCallback((e: React.TouchEvent) => {
e.preventDefault()
const touch = e.changedTouches[0]
moveTouchId.current = touch.identifier
lookRef.current.lastX = touch.clientX
lookRef.current.lastY = touch.clientY
setMoveActive(true)
}, [])
const handleMoveMove = useCallback((e: React.TouchEvent) => {
e.preventDefault()
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i]
if (touch.identifier === moveTouchId.current) {
const dx = (touch.clientX - lookRef.current.lastX) / 40
const dz = (touch.clientY - lookRef.current.lastY) / 40
moveRef.current = {
x: Math.max(-1, Math.min(1, dx)),
z: Math.max(-1, Math.min(1, dz)),
}
;(window as any).__MC_MOBILE_MOVE = moveRef.current
}
}
}, [])
const handleMoveEnd = useCallback(() => {
moveTouchId.current = null
moveRef.current = { x: 0, z: 0 }
;(window as any).__MC_MOBILE_MOVE = null
setMoveActive(false)
}, [])
// Look joystick
const handleLookStart = useCallback((e: React.TouchEvent) => {
e.preventDefault()
const touch = e.changedTouches[0]
lookTouchId.current = touch.identifier
lookRef.current = { lastX: touch.clientX, lastY: touch.clientY }
setLookActive(true)
}, [])
const handleLookMove = useCallback((e: React.TouchEvent) => {
e.preventDefault()
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i]
if (touch.identifier === lookTouchId.current) {
const dx = touch.clientX - lookRef.current.lastX
const dy = touch.clientY - lookRef.current.lastY
lookRef.current = { lastX: touch.clientX, lastY: touch.clientY }
const player = usePlayerStore.getState().player
if (player) {
const sensitivity = 0.004
const yaw = player.rotation[0] - dx * sensitivity
const pitch = Math.max(-Math.PI / 2 + 0.01,
Math.min(Math.PI / 2 - 0.01, player.rotation[1] - dy * sensitivity))
usePlayerStore.getState().updateRotation([yaw, pitch])
}
}
}
}, [])
const handleLookEnd = useCallback(() => {
lookTouchId.current = null
setLookActive(false)
}, [])
const handleJump = useCallback(() => {
;(window as any).__MC_MOBILE_JUMP = true
setTimeout(() => { (window as any).__MC_MOBILE_JUMP = false }, 100)
}, [])
const handleBreak = useCallback(() => {
;(window as any).__MC_MOBILE_BREAK = true
}, [])
const handlePlace = useCallback(() => {
;(window as any).__MC_MOBILE_PLACE = true
}, [])
return (
<div className="absolute inset-0 z-20 pointer-events-none">
{/* Move joystick - left side */}
<div
className="absolute bottom-16 left-4 w-28 h-28 rounded-full border-2 border-white/30 bg-white/10 pointer-events-auto touch-none"
onTouchStart={handleMoveStart}
onTouchMove={handleMoveMove}
onTouchEnd={handleMoveEnd}
>
<div className={`absolute inset-0 flex items-center justify-center text-white/50 text-xs
${moveActive ? 'text-green-400/80' : ''}`}>
{moveActive ? '●' : 'Move'}
</div>
</div>
{/* Look area - right side */}
<div
className="absolute bottom-16 right-4 w-28 h-28 rounded-full border-2 border-white/30 bg-white/10 pointer-events-auto touch-none"
onTouchStart={handleLookStart}
onTouchMove={handleLookMove}
onTouchEnd={handleLookEnd}
>
<div className={`absolute inset-0 flex items-center justify-center text-white/50 text-xs
${lookActive ? 'text-blue-400/80' : ''}`}>
{lookActive ? '●' : 'Look'}
</div>
</div>
{/* Action buttons */}
<div className="absolute bottom-16 left-1/2 -translate-x-1/2 flex gap-3 pointer-events-auto">
{/* Jump */}
<button
className="w-12 h-12 rounded-full bg-white/15 border border-white/30 flex items-center justify-center text-white/70 active:bg-white/30"
onTouchStart={handleJump}
>
</button>
</div>
{/* Break/Place buttons */}
<div className="absolute bottom-32 right-6 flex flex-col gap-2 pointer-events-auto">
<button
className="w-14 h-14 rounded-xl bg-red-500/30 border border-red-400/50 flex items-center justify-center text-white text-xs active:bg-red-500/50"
onTouchStart={handleBreak}
>
</button>
<button
className="w-14 h-14 rounded-xl bg-blue-500/30 border border-blue-400/50 flex items-center justify-center text-white text-xs active:bg-blue-500/50"
onTouchStart={handlePlace}
>
🧱
</button>
</div>
{/* Fly toggle */}
<button
className="absolute top-4 right-4 px-3 py-1.5 bg-white/15 border border-white/30 rounded text-white/70 text-xs pointer-events-auto active:bg-white/30"
onClick={() => {
const { player } = usePlayerStore.getState()
if (player) usePlayerStore.getState().setFlying(!player.flying)
}}
>
✈ Fly
</button>
{/* Inventory */}
<button
className="absolute top-4 right-20 px-3 py-1.5 bg-white/15 border border-white/30 rounded text-white/70 text-xs pointer-events-auto active:bg-white/30"
onClick={() => usePlayerStore.getState().setGamemode('creative')}
>
📦 Inv
</button>
</div>
)
}