added custom keybinding support

This commit is contained in:
ad044 2021-04-05 17:51:54 +04:00
parent e170489c46
commit 2f8f30d1ba
11 changed files with 200 additions and 89 deletions

View file

@ -1,12 +1,21 @@
import React from "react"; import React, { useEffect } from "react";
import "./static/css/page.css"; import "./static/css/page.css";
import Game from "./dom-components/Game"; import Game from "./dom-components/Game";
import { HashRouter, Route, Switch } from "react-router-dom"; import { HashRouter, Route, Switch } from "react-router-dom";
import Notes from "./dom-components/Notes"; import Notes from "./dom-components/Notes";
import MainPage from "./dom-components/MainPage"; import MainPage from "./dom-components/MainPage";
import Guide from "./dom-components/Guide"; import Guide from "./dom-components/Guide";
import Keybinding from "./dom-components/Keybinding";
import { useStore } from "./store";
const App = () => { const App = () => {
const setKeybindings = useStore((state) => state.setKeybindings);
useEffect(() => {
const keybindingSettings = localStorage.getItem("lainKeybindings");
if (keybindingSettings) setKeybindings(JSON.parse(keybindingSettings));
}, [setKeybindings]);
return ( return (
<HashRouter> <HashRouter>
<Switch> <Switch>
@ -14,6 +23,7 @@ const App = () => {
<Route path={"/notes"} exact component={Notes} /> <Route path={"/notes"} exact component={Notes} />
<Route path={"/game"} exact component={Game} /> <Route path={"/game"} exact component={Game} />
<Route path={"/guide"} exact component={Guide} /> <Route path={"/guide"} exact component={Guide} />
<Route path={"/keybinding"} exact component={Keybinding} />
</Switch> </Switch>
</HashRouter> </HashRouter>
); );

View file

@ -120,9 +120,6 @@ const InputHandler = memo(() => {
return () => { return () => {
window.removeEventListener("keydown", handleKeyBoardEvent); window.removeEventListener("keydown", handleKeyBoardEvent);
window.removeEventListener("keyup", () => {
firedRef.current = false;
});
}; };
}, [handleKeyBoardEvent]); }, [handleKeyBoardEvent]);

View file

@ -10,6 +10,7 @@ const Header = () => {
<Link to="/game">start</Link> <Link to="/game">start</Link>
<Link to="/guide">guide</Link> <Link to="/guide">guide</Link>
<a href="https://discord.com/invite/W22Ga2R">discord</a> <a href="https://discord.com/invite/W22Ga2R">discord</a>
<Link to="/keybinding">keybinding</Link>
</div> </div>
); );
}; };

View file

@ -0,0 +1,104 @@
import React, { useCallback, useEffect } from "react";
import { formatKey } from "../helpers/keybinding-helpers";
import "../static/css/keybinding.css";
import { useStore } from "../store";
import Header from "./Header";
const Keybinding = () => {
const setKeybindings = useStore((state) => state.setKeybindings);
const bindings = useStore((state) => state.keybindings);
useEffect(() => {
document.title = "< keybinding >";
}, []);
const handleRemap = useCallback(
(keyToRemap: string, to: string) => {
if (to.length === 1) to = to.toLowerCase();
const exists = Object.values(bindings).includes(to);
if (!exists) {
const newBindings = { ...bindings, [keyToRemap]: to };
setKeybindings(newBindings);
localStorage.setItem("lainKeybindings", JSON.stringify(newBindings));
} else {
const takenKeybind = Object.keys(bindings).find(
(k) => bindings[k] === to
);
if (takenKeybind) {
const newBindings = {
...bindings,
[takenKeybind]: "",
[keyToRemap]: to,
};
setKeybindings(newBindings);
localStorage.setItem("lainKeybindings", JSON.stringify(newBindings));
}
}
},
[bindings, setKeybindings]
);
const startKeybindListener = useCallback(
(keyToRemap: string) => {
window.addEventListener(
"keydown",
(event) => handleRemap(keyToRemap, event.key),
{ once: true }
);
},
[handleRemap]
);
const resetToDefault = useCallback(() => {
setKeybindings({
DOWN: "ArrowDown",
LEFT: "ArrowLeft",
UP: "ArrowUp",
RIGHT: "ArrowRight",
CIRCLE: "x",
CROSS: "z",
TRIANGLE: "d",
SQUARE: "s",
R2: "t",
L2: "e",
L1: "w",
R1: "r",
START: "v",
SELECT: "c",
});
localStorage.removeItem("lainKeybindings");
}, [setKeybindings]);
return (
<>
<Header />
<br />
<p className="keybinding-note">
This is the keybindings page. To change a keybinding, just click on it
and press the button you wish to bind it to after. In order for this to
take effect, you must refresh the game page.
</p>
<br />
<div className="keybinding">
<table className="control-table">
<tbody>
{Object.entries(bindings).map((pair, idx) => (
<tr onClick={() => startKeybindListener(pair[0])} key={idx}>
<td>{formatKey(pair[0])}</td>
<td>{pair[1]}</td>
</tr>
))}
</tbody>
</table>
</div>
<br />
<button className="reset-to-default-button" onClick={resetToDefault}>
Reset to default bindings
</button>
</>
);
};
export default Keybinding;

View file

@ -60,10 +60,7 @@ const Notes = () => {
<p> <p>
Your setup must support WebGL2 in order to play this game. You Your setup must support WebGL2 in order to play this game. You
can check this directly by going to{" "} can check this directly by going to{" "}
<a <a href={"https://get.webgl.org/webgl2/"} className="notes-a">
href={"https://get.webgl.org/webgl2/"}
className="webgl-anchor"
>
this website this website
</a> </a>
. If it's not supported, this is most likely due to your . If it's not supported, this is most likely due to your
@ -174,6 +171,14 @@ const Notes = () => {
</tr> </tr>
</tbody> </tbody>
</table> </table>
<br />
<span className="text-center">
If you'd like to change the keybindings, go{" "}
<Link to={"/keybinding"} className="notes-a">
here
</Link>
.
</span>
</td> </td>
</tr> </tr>

View file

@ -0,0 +1,22 @@
export const formatKey = (key: string) => {
switch (key) {
case "DOWN":
return "↓";
case "LEFT":
return "←";
case "UP":
return "↑";
case "RIGHT":
return "→";
case "CIRCLE":
return "◯";
case "CROSS":
return "✖";
case "SQUARE":
return "◼";
case "TRIANGLE":
return "▲";
default:
return key;
}
};

View file

@ -0,0 +1,20 @@
.keybinding {
color: white;
display: block;
margin: 0 auto;
text-align: center;
font-size: 1.5rem;
}
.keybinding-note {
text-align: center;
font-size: 1.7rem;
color: white;
padding-left: 5em;
padding-right: 5em;
}
.reset-to-default-button {
display: block;
margin: 0 auto;
}

View file

@ -56,7 +56,7 @@
vertical-align: middle; vertical-align: middle;
} }
.webgl-anchor { .notes-a {
text-align: unset; text-align: unset;
font-size: unset; font-size: unset;
font-weight: unset; font-weight: unset;

View file

@ -105,6 +105,8 @@ type State = {
siteSaveState: SiteSaveState; siteSaveState: SiteSaveState;
keybindings: Record<string, string>;
inputCooldown: number; inputCooldown: number;
}; };
@ -237,6 +239,24 @@ export const useStore = create(
}, },
}, },
// keybindings
keybindings: {
DOWN: "ArrowDown",
LEFT: "ArrowLeft",
UP: "ArrowUp",
RIGHT: "ArrowRight",
CIRCLE: "x",
CROSS: "z",
TRIANGLE: "d",
SQUARE: "s",
R2: "t",
L2: "e",
L1: "w",
R1: "r",
START: "v",
SELECT: "c",
},
inputCooldown: -1, inputCooldown: -1,
} as State, } as State,
(set) => ({ (set) => ({
@ -340,6 +360,9 @@ export const useStore = create(
playerName: userState.playerName, playerName: userState.playerName,
})), })),
setKeybindings: (to: Record<string, string>) =>
set(() => ({ keybindings: to })),
restartGameState: () => restartGameState: () =>
set(() => ({ set(() => ({
siteSaveState: { siteSaveState: {

View file

@ -1,59 +0,0 @@
import {
Loader,
RGBAFormat,
RGBFormat,
ImageLoader,
Texture,
LinearFilter,
NearestFilter,
ClampToEdgeWrapping,
} from "three";
/*
custom implementation of TextureLoader that automatically sets minFilter to NearestFilter for proper WebGL1 support.
this is still experimental
*/
export class CustomTextureLoader extends Loader {
load = (
url: string,
onLoad?: (texture: Texture) => void,
onProgress?: (event: ProgressEvent) => void,
onError?: (event: ErrorEvent) => void
): Texture => {
const texture = new Texture();
texture.generateMipmaps = false;
texture.wrapS = texture.wrapT = ClampToEdgeWrapping;
texture.minFilter = LinearFilter;
const loader = new ImageLoader(this.manager);
loader.setCrossOrigin(this.crossOrigin);
loader.setPath(this.path);
loader.load(
url,
function (image: HTMLImageElement) {
texture.image = image;
const isJPEG =
url.search(/\.jpe?g($|\?)/i) > 0 ||
url.search(/^data:image\/jpeg/) === 0;
texture.format = isJPEG ? RGBFormat : RGBAFormat;
texture.needsUpdate = true;
if (onLoad !== undefined) {
onLoad(texture);
}
},
onProgress,
onError
);
return texture;
};
}
export default CustomTextureLoader;

View file

@ -1,25 +1,13 @@
const getKeyPress = (key: string) => { import { useStore } from "../store";
// make the keybinds work with caps lock on aswell
if (["X", "Z", "D", "E", "V", "T", "W", "R", "S", "C"].includes(key))
key = key.toLowerCase();
const keyCodeAssocs = { const getKeyPress = (key: string) => {
ArrowDown: "DOWN", const keybindings = useStore.getState().keybindings;
ArrowLeft: "LEFT",
ArrowUp: "UP", console.log(keybindings);
ArrowRight: "RIGHT", // make the keybinds work with caps lock on aswell
x: "CIRCLE", if (key.length === 1) key = key.toLowerCase();
z: "CROSS",
d: "TRIANGLE", return Object.keys(keybindings).find((k) => keybindings[k] === key);
s: "SQUARE",
t: "R2",
e: "L2",
w: "L1",
r: "R1",
v: "START",
c: "SELECT",
};
return keyCodeAssocs[key as keyof typeof keyCodeAssocs];
}; };
export default getKeyPress; export default getKeyPress;