UNPKG

prendy

Version:

Make games with prerendered backdrops using babylonjs and repond

196 lines (195 loc) 8.46 kB
import { getSpeedAndAngleFromVector, getVectorFromSpeedAndAngle, getVectorSpeed, } from "chootils/dist/speedAngleDistance2d"; import React, { useCallback, useEffect, useRef } from "react"; import { animated, useSpring } from "react-spring"; import { getRefs, getState, setState } from "repond"; const SIZES = { leftThumbContainer: 110, }; export function VirtualStick(_) { const globalRefs = getRefs().global.main; const { current: local } = useRef({ springAllowed: true, cameraJustChanged: false, // circleRef: null as null | Ellipse xAddPos: 0, yAddPos: 0, xAddRot: 0, yAddRot: 0, leftJoystickOffset: 10, topJoystickOffset: 10, // isDown: false, floatLeft: 0, floatTop: 0, // pointerDownTime: 0, // timestamp // pointerId: 0, }); const leftPuck = useRef(null); const leftThumbContainer = useRef(null); const refs = { leftPuck, leftThumbContainer, }; const [spring, springApi] = useSpring(() => ({ position: [0, 0], config: { tension: 300 }, })); const [outerPositionSpring, outerPositionSpringApi] = useSpring(() => ({ position: [0, 0], config: { tension: 300 }, })); const [opacitySpring, opacitySpringApi] = useSpring(() => ({ circleOpacity: 1, outerOpacity: 0, config: { tension: 300, bounce: 0 }, })); useEffect(() => { const pointerMoveEvent = (event) => { // if the pointerId doesnt match, return if (event.pointerId !== local.pointerId) return; const { leftPuck, leftThumbContainer } = refs; if (!leftPuck || !leftThumbContainer) return; if (!local.isDown) return; const coordinates = { x: event.clientX, y: event.clientY, }; local.xAddPos = coordinates.x - SIZES.leftThumbContainer * 0.5 - local.leftJoystickOffset; local.yAddPos = coordinates.y - SIZES.leftThumbContainer * 0.5 - local.topJoystickOffset; const limitedOffset = 35; const { angle, speed } = getSpeedAndAngleFromVector({ x: local.xAddPos, y: local.yAddPos, }); let editedSpeed = speed > limitedOffset ? limitedOffset : speed; const clampedPosition = getVectorFromSpeedAndAngle(editedSpeed, angle); const normalizedPosition = getVectorFromSpeedAndAngle(editedSpeed / limitedOffset, angle); local.floatLeft = clampedPosition.x; local.floatTop = clampedPosition.y; springApi.start({ position: [local.floatLeft, local.floatTop], immediate: true, }); setState({ players: { main: { inputVelocity: normalizedPosition }, }, }); }; const pointerUpEvent = (event) => { requestAnimationFrame(() => { // if the pointerId doesnt match, return if (event.pointerId !== local.pointerId) return; local.isDown = false; const { inputVelocity } = getState().players.main; opacitySpringApi.start({ circleOpacity: 0.5, outerOpacity: 0 }); springApi.start({ position: [0, 0], immediate: false }); // check if its been a short time since pressing down, // and if the stick hasnt moved much? const timeSincePointerDown = Date.now() - local.pointerDownTime; const wasAShortTime = timeSincePointerDown < 250; const didntMoveJoystickFar = getVectorSpeed(inputVelocity) < 0.1; if (wasAShortTime && didntMoveJoystickFar) { setState({ players: { main: { interactButtonPressTime: Date.now() } } }); } else { // set the input velocity to 0 if the virtual stick wasn't pressed setState({ players: { main: { inputVelocity: { x: 0, y: 0 } } } }); } setState({ players: { main: { virtualControlsReleaseTime: Date.now() } } }); }); }; window.addEventListener("pointerup", pointerUpEvent); window.addEventListener("pointercancel", pointerUpEvent); // window.addEventListener("pointerdown", pointerDownEvent); window.addEventListener("pointermove", pointerMoveEvent); return () => { window.removeEventListener("pointerup", pointerUpEvent); window.removeEventListener("pointercancel", pointerUpEvent); // window.removeEventListener("pointerdown", pointerDownEvent); window.removeEventListener("pointermove", pointerMoveEvent); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const pointerDownEvent = useCallback((event) => { requestAnimationFrame(() => { // if the stick is already being held down, don't do anything if (local.isDown) return; local.pointerId = event.pointerId; // const { virtualControlsPressTime, virtualControlsReleaseTime } = getState().players.main; // const virtualStickIsHeld = virtualControlsPressTime > virtualControlsReleaseTime; // if (virtualStickIsHeld) return; local.pointerDownTime = Date.now(); const { leftPuck, leftThumbContainer } = refs; if (!leftPuck || !leftThumbContainer) return; const coordinates = { x: event.clientX, y: event.clientY, }; // leftPuck.isVisible = true; local.leftJoystickOffset = coordinates.x - SIZES.leftThumbContainer * 0.5; local.topJoystickOffset = coordinates.y - SIZES.leftThumbContainer * 0.5; local.isDown = true; outerPositionSpringApi.start({ position: [local.leftJoystickOffset, local.topJoystickOffset], immediate: true, }); opacitySpringApi.start({ circleOpacity: 1.0, outerOpacity: 0.9, }); setState({ players: { main: { virtualControlsPressTime: Date.now() } }, }); }); }, []); return (React.createElement("div", { id: "virtual-stick", onPointerDown: pointerDownEvent, style: { pointerEvents: "auto", // pointerEvents: "none" as const, position: "absolute", top: 0, left: 0, width: "100vw", height: "100vh", zIndex: 100, overflow: "hidden", opacity: 0.1, // opacity: 0.0, // background: "green", } }, React.createElement(animated.div, { ref: refs.leftThumbContainer, style: { position: "relative", backgroundColor: "rgba(232, 232, 232, 0.5)", padding: 0, height: SIZES.leftThumbContainer + "px", width: SIZES.leftThumbContainer + "px", borderRadius: "500px", transform: outerPositionSpring.position.to((x, y) => `translate(${x}px , ${y}px )`), opacity: opacitySpring.outerOpacity, display: "flex", alignItems: "center", justifyContent: "center", direction: "row", willChange: "transform, opacity", }, id: "leftThumb" }, React.createElement(animated.div, { ref: refs.leftPuck, style: { position: "relative", backgroundColor: "rgba(232, 232, 232, 0.75)", padding: 0, height: "60px", width: "60px", borderRadius: "500px", // transform: `translate(${local.leftJoystickOffset}px , ${local.topJoystickOffset}px )`, transform: spring.position.to((x, y) => `translate(${x}px , ${y}px )`), opacity: opacitySpring.circleOpacity, willChange: "transform, opacity", }, id: "leftPuck" })))); }