feat: virtual gamepad
This commit is contained in:
parent
30ad575bfd
commit
8c11487892
|
@ -37,7 +37,9 @@
|
||||||
"@flecks/redux": "^1.4.1",
|
"@flecks/redux": "^1.4.1",
|
||||||
"@flecks/socket": "^1.4.1",
|
"@flecks/socket": "^1.4.1",
|
||||||
"@flecks/user": "^1.4.1",
|
"@flecks/user": "^1.4.1",
|
||||||
"@humus/inventory": "^2.0.0"
|
"@humus/inventory": "^2.0.0",
|
||||||
|
"fscreen": "^1.2.0",
|
||||||
|
"react-joystick-component": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@flecks/fleck": "^1.4.1"
|
"@flecks/fleck": "^1.4.1"
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import {PropTypes, React} from '@flecks/react';
|
||||||
|
|
||||||
|
import styles from './button.module.scss';
|
||||||
|
|
||||||
|
function Button({down, up}) {
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/control-has-associated-label
|
||||||
|
<button
|
||||||
|
className={styles.button}
|
||||||
|
onPointerDown={down}
|
||||||
|
onPointerUp={up}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.defaultProps = {
|
||||||
|
down: () => {},
|
||||||
|
up: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
Button.displayName = 'Button';
|
||||||
|
|
||||||
|
Button.propTypes = {
|
||||||
|
down: PropTypes.func,
|
||||||
|
up: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Button;
|
|
@ -0,0 +1,12 @@
|
||||||
|
.button {
|
||||||
|
background: radial-gradient(rgb(92, 113, 175) 30%, rgb(61, 89, 171)) !important;
|
||||||
|
border: 0.25rem solid rgb(0, 0, 51);
|
||||||
|
border-radius: 50px;
|
||||||
|
height: 5rem;
|
||||||
|
margin: 0 1.5rem;
|
||||||
|
opacity: 0.5;
|
||||||
|
width: 5rem;
|
||||||
|
&:hover {
|
||||||
|
background: radial-gradient(rgb(113, 129, 175) 30%, rgb(61, 89, 171)) !important;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import {PropTypes, React} from '@flecks/react';
|
||||||
|
|
||||||
|
import Button from './button';
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
function Buttons({inputNormalizer}) {
|
||||||
|
return (
|
||||||
|
<div className={styles.buttons}>
|
||||||
|
<Button
|
||||||
|
down={() => {
|
||||||
|
inputNormalizer.onButtonPress({index: 0});
|
||||||
|
}}
|
||||||
|
up={() => {
|
||||||
|
inputNormalizer.onButtonRelease({index: 0});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
down={() => {
|
||||||
|
inputNormalizer.onButtonPress({index: 1});
|
||||||
|
}}
|
||||||
|
up={() => {
|
||||||
|
inputNormalizer.onButtonRelease({index: 1});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Buttons.defaultProps = {};
|
||||||
|
|
||||||
|
Buttons.displayName = 'Buttons';
|
||||||
|
|
||||||
|
Buttons.propTypes = {
|
||||||
|
// eslint-disable-next-line react/forbid-prop-types
|
||||||
|
inputNormalizer: PropTypes.any.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Buttons;
|
|
@ -0,0 +1,3 @@
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
}
|
25
packages/app/src/components/app/play/controller/index.jsx
Normal file
25
packages/app/src/components/app/play/controller/index.jsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import {PropTypes, React} from '@flecks/react';
|
||||||
|
|
||||||
|
import Buttons from './buttons';
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
import Joystick from './joystick';
|
||||||
|
|
||||||
|
function Controller({inputNormalizer}) {
|
||||||
|
return (
|
||||||
|
<div className={styles.controller}>
|
||||||
|
<Joystick inputNormalizer={inputNormalizer} />
|
||||||
|
<Buttons inputNormalizer={inputNormalizer} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller.defaultProps = {};
|
||||||
|
|
||||||
|
Controller.displayName = 'Controller';
|
||||||
|
|
||||||
|
Controller.propTypes = {
|
||||||
|
// eslint-disable-next-line react/forbid-prop-types
|
||||||
|
inputNormalizer: PropTypes.any.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Controller;
|
|
@ -0,0 +1,9 @@
|
||||||
|
.controller {
|
||||||
|
align-items: center;
|
||||||
|
bottom: 20%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
left: 10%;
|
||||||
|
position: absolute;
|
||||||
|
width: 80%;
|
||||||
|
}
|
33
packages/app/src/components/app/play/controller/joystick.jsx
Normal file
33
packages/app/src/components/app/play/controller/joystick.jsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import {PropTypes, React} from '@flecks/react';
|
||||||
|
import {Joystick as JoystickComponent} from 'react-joystick-component';
|
||||||
|
|
||||||
|
import styles from './joystick.module.scss';
|
||||||
|
|
||||||
|
function Joystick({inputNormalizer}) {
|
||||||
|
return (
|
||||||
|
<div className={styles.joystick}>
|
||||||
|
<JoystickComponent
|
||||||
|
move={(event) => {
|
||||||
|
inputNormalizer.onAxisChange({index: 0, value: event.x / 50});
|
||||||
|
inputNormalizer.onAxisChange({index: 1, value: event.y / -50});
|
||||||
|
}}
|
||||||
|
size={100}
|
||||||
|
stop={() => {
|
||||||
|
inputNormalizer.onAxisChange({index: 0, value: 0});
|
||||||
|
inputNormalizer.onAxisChange({index: 1, value: 0});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Joystick.defaultProps = {};
|
||||||
|
|
||||||
|
Joystick.displayName = 'Joystick';
|
||||||
|
|
||||||
|
Joystick.propTypes = {
|
||||||
|
// eslint-disable-next-line react/forbid-prop-types
|
||||||
|
inputNormalizer: PropTypes.any.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Joystick;
|
|
@ -0,0 +1,13 @@
|
||||||
|
.joystick {
|
||||||
|
opacity: 0.5;
|
||||||
|
transform: scale(calc(1 / var(--scale)));
|
||||||
|
> div {
|
||||||
|
background: radial-gradient(rgb(0, 0, 21) 20%, rgb(0, 0, 51)) !important;
|
||||||
|
> button {
|
||||||
|
background: radial-gradient(rgb(92, 113, 175) 30%, rgb(61, 89, 171)) !important;
|
||||||
|
&:hover {
|
||||||
|
background: radial-gradient(rgb(113, 129, 175) 30%, rgb(61, 89, 171)) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,23 +2,34 @@ import {InputNormalizer} from '@avocado/input/client';
|
||||||
import {
|
import {
|
||||||
React,
|
React,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useState,
|
||||||
} from '@flecks/react';
|
} from '@flecks/react';
|
||||||
import {push} from '@flecks/react/router';
|
import {push} from '@flecks/react/router';
|
||||||
import {useDispatch} from '@flecks/redux';
|
import {useDispatch} from '@flecks/redux';
|
||||||
import {useSocket} from '@flecks/socket';
|
import {useSocket} from '@flecks/socket';
|
||||||
import {setSelfEntity} from '@humus/app/state';
|
import {setSelfEntity} from '@humus/app/state';
|
||||||
|
import fscreen from 'fscreen';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useSelfEntity,
|
useSelfEntity,
|
||||||
} from '../../../hooks';
|
} from '../../../hooks';
|
||||||
|
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
import Controller from './controller';
|
||||||
import Renderable from './renderable';
|
import Renderable from './renderable';
|
||||||
import Ui from './ui';
|
import Ui from './ui';
|
||||||
|
|
||||||
|
const focus = (element) => {
|
||||||
|
element.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputNormalizer = new InputNormalizer();
|
||||||
|
|
||||||
function Play() {
|
function Play() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const selfEntity = useSelfEntity();
|
const selfEntity = useSelfEntity();
|
||||||
const socket = useSocket();
|
const socket = useSocket();
|
||||||
|
const [controllerVisible, setControllerVisible] = useState(false);
|
||||||
// Join.
|
// Join.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const join = async () => {
|
const join = async () => {
|
||||||
|
@ -42,7 +53,6 @@ function Play() {
|
||||||
if (!selfEntity) {
|
if (!selfEntity) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const inputNormalizer = new InputNormalizer();
|
|
||||||
inputNormalizer.listen(window.document.body);
|
inputNormalizer.listen(window.document.body);
|
||||||
selfEntity.listenForInput(inputNormalizer);
|
selfEntity.listenForInput(inputNormalizer);
|
||||||
selfEntity.actionRegistry.setTransformerFor('UseItem', (type, value) => {
|
selfEntity.actionRegistry.setTransformerFor('UseItem', (type, value) => {
|
||||||
|
@ -73,10 +83,21 @@ function Play() {
|
||||||
}
|
}
|
||||||
}, [selfEntity]);
|
}, [selfEntity]);
|
||||||
return (
|
return (
|
||||||
<>
|
<div
|
||||||
|
className={styles.play}
|
||||||
|
onTouchStart={async () => {
|
||||||
|
await fscreen.requestFullscreen(window.document.body);
|
||||||
|
setControllerVisible(true);
|
||||||
|
}}
|
||||||
|
ref={focus}
|
||||||
|
role="presentation"
|
||||||
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
||||||
|
tabIndex="0"
|
||||||
|
>
|
||||||
<Renderable />
|
<Renderable />
|
||||||
<Ui />
|
<Ui />
|
||||||
</>
|
{controllerVisible && <Controller inputNormalizer={inputNormalizer} />}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
4
packages/app/src/components/app/play/index.module.scss
Normal file
4
packages/app/src/components/app/play/index.module.scss
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.play {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
|
@ -11,18 +11,14 @@ import {
|
||||||
useSelfEntity,
|
useSelfEntity,
|
||||||
} from '../../../../hooks';
|
} from '../../../../hooks';
|
||||||
import Hotbar from './hotbar';
|
import Hotbar from './hotbar';
|
||||||
|
import styles from './index.module.scss';
|
||||||
const focus = (element) => {
|
|
||||||
element.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
const PlayUi = () => {
|
const PlayUi = () => {
|
||||||
const {config: {'@humus/app': {resolution}}} = useFlecks();
|
const {config: {'@humus/app': {resolution}}} = useFlecks();
|
||||||
const room = useRoom();
|
const room = useRoom();
|
||||||
const selfEntity = useSelfEntity();
|
const selfEntity = useSelfEntity();
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
<div className={styles.ui}>
|
||||||
<div ref={focus} tabIndex="0">
|
|
||||||
<RoomUi
|
<RoomUi
|
||||||
domScale={Vector.div(resolution, usePropertyChange(selfEntity?.camera, 'viewSize', [0, 0]))}
|
domScale={Vector.div(resolution, usePropertyChange(selfEntity?.camera, 'viewSize', [0, 0]))}
|
||||||
position={Vector.scale(usePropertyChange(selfEntity?.camera, 'realOffset', [0, 0]), -1)}
|
position={Vector.scale(usePropertyChange(selfEntity?.camera, 'realOffset', [0, 0]), -1)}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
.ui {
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user