mirror of
https://github.com/ad044/lainTSX.git
synced 2024-10-22 23:19:06 +00:00
added custom keybinding support
This commit is contained in:
parent
e170489c46
commit
2f8f30d1ba
11 changed files with 200 additions and 89 deletions
12
src/App.tsx
12
src/App.tsx
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
104
src/dom-components/Keybinding.tsx
Normal file
104
src/dom-components/Keybinding.tsx
Normal 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;
|
|
@ -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>
|
||||||
|
|
||||||
|
|
22
src/helpers/keybinding-helpers.ts
Normal file
22
src/helpers/keybinding-helpers.ts
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
20
src/static/css/keybinding.css
Normal file
20
src/static/css/keybinding.css
Normal 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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
23
src/store.ts
23
src/store.ts
|
@ -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: {
|
||||||
|
|
|
@ -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;
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue