finish adding mobile controls

This commit is contained in:
Corwin 2024-04-05 19:47:04 +01:00
parent 552f8f878d
commit 48373faa17
No known key found for this signature in database
9 changed files with 345 additions and 118 deletions

View file

@ -1,4 +1,4 @@
import { useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Mgba, MgbaHandle } from "./mgba"; import { Mgba, MgbaHandle } from "./mgba";
import { BindingsControl, DefaultBindingsSet, Bindings } from "./bindings"; import { BindingsControl, DefaultBindingsSet, Bindings } from "./bindings";
import { styled } from "styled-components"; import { styled } from "styled-components";
@ -48,7 +48,7 @@ const StartButtonWrapper = styled.button`
function App() { function App() {
const [{ volume, bindings }, setState] = useLocalStorage( const [{ volume, bindings }, setState] = useLocalStorage(
{ volume: 1.0, bindings: DefaultBindingsSet() }, { volume: 1.0, bindings: DefaultBindingsSet() },
"agbrswebplayer" "agbrswebplayer",
); );
const setVolume = (newVolume: number) => const setVolume = (newVolume: number) =>
@ -66,6 +66,30 @@ function App() {
setShowBindings(!showBindings); setShowBindings(!showBindings);
}); });
useEffect(() => {
const buttonPress = (event: MessageEvent) => {
const data = event.data;
const { isPressed, button, reset } = data;
if (isPressed === true) {
mgbaRef.current?.buttonPress(button);
} else if (isPressed === false) {
mgbaRef.current?.buttonRelease(button);
}
if (reset) {
mgbaRef.current?.restart();
}
};
window.addEventListener("message", buttonPress);
return () => {
window.removeEventListener("message", buttonPress);
};
});
useAvoidItchIoScrolling(); useAvoidItchIoScrolling();
const gameUrl = window.location.hash.slice(1); const gameUrl = window.location.hash.slice(1);

View file

@ -72,7 +72,6 @@ export const Mgba = forwardRef<MgbaHandle, MgbaProps>(
if (state !== MgbaState.Uninitialised) return; if (state !== MgbaState.Uninitialised) return;
setState(MgbaState.Initialising); setState(MgbaState.Initialising);
mgbaModule.current = { mgbaModule.current = {
canvas: canvas.current, canvas: canvas.current,
}; };
@ -89,7 +88,7 @@ export const Mgba = forwardRef<MgbaHandle, MgbaProps>(
try { try {
mgbaModule.current.quitGame(); mgbaModule.current.quitGame();
mgbaModule.current.quitMgba(); mgbaModule.current.quitMgba();
} catch { } } catch {}
}; };
}, [state]); }, [state]);
@ -127,10 +126,10 @@ export const Mgba = forwardRef<MgbaHandle, MgbaProps>(
return { return {
restart: () => mgbaModule.current.quickReload(), restart: () => mgbaModule.current.quickReload(),
buttonPress: (key: GbaKey) => mgbaModule.current.buttonPress(key), buttonPress: (key: GbaKey) => mgbaModule.current.buttonPress(key),
buttonRelease: (key: GbaKey) => mgbaModule.current.buttonRelease(key), buttonRelease: (key: GbaKey) => mgbaModule.current.buttonUnpress(key),
}; };
}); });
return <MgbaCanvas ref={canvas} />; return <MgbaCanvas ref={canvas} />;
} },
); );

BIN
website/site/assets/L.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

BIN
website/site/assets/R.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

BIN
website/site/assets/ab.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

View file

@ -1,132 +1,336 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>agb - a rust framework for making Game Boy Advance games</title>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
<head> body {
<meta name="viewport" content="width=device-width, initial-scale=1.0"> font: 1.2em/1.62 sans-serif;
<title>agb - a rust framework for making Game Boy Advance games</title> background-color: white;
<style> font-size: 1.5rem;
*, line-height: 1.6;
*::before, min-height: 100vh;
*::after { display: flex;
box-sizing: border-box; flex-direction: column;
} margin: 0;
}
body { h1,
font: 1.2em/1.62 sans-serif; h2,
background-color: white; h3 {
font-size: 1.5rem; line-height: 1.2;
line-height: 1.6; }
min-height: 100vh;
display: flex;
flex-direction: column;
margin: 0;
}
h1, .gameDisplay > div {
h2, display: flex;
h3 { justify-content: center;
line-height: 1.2; height: 100%;
} }
.gameDisplay>div { .gameDisplay {
display: flex; height: clamp(480px, 40vh, calc(100vw / 3));
justify-content: center; max-width: 100vw;
height: 100%; margin-top: 20px;
} overflow: hidden;
}
.gameDisplay { .gameDisplay .imageWrapper {
height: clamp(480px, 40vh, calc(100vw / 3)); aspect-ratio: 15 / 31;
max-width: 100vw; height: 100%;
margin-top: 20px; }
overflow: hidden;
}
.gameDisplay .imageWrapper { .gameDisplay .imageWrapper > img {
aspect-ratio: 15 / 31; height: 100%;
height: 100%; image-rendering: pixelated;
} }
.gameDisplay .imageWrapper>img { .gameDisplay iframe {
height: 100%; border: 0;
image-rendering: pixelated; height: 100%;
} max-width: 100vw;
aspect-ratio: 240 / 160;
}
.gameDisplay iframe { header,
border: 0; .desktopHelp {
height: 100%; max-width: 60%;
max-width: 100vw; margin-left: auto;
aspect-ratio: 240 / 160; margin-right: auto;
} margin-block-end: 40px;
}
header, .red {
.desktopHelp { background-color: #f5755e;
max-width: 60%; flex-grow: 1;
margin-left: auto; }
margin-right: auto;
margin-block-end: 40px;
}
.red { .links {
background-color: #f5755e; margin-left: auto;
flex-grow: 1; margin-right: auto;
} display: flex;
justify-content: space-around;
margin-top: 40px;
margin-bottom: 40px;
max-width: 40rem;
}
.links { .links > a {
margin-left: auto; text-decoration: none;
margin-right: auto; color: black;
display: flex; background-color: #fad288;
justify-content: space-around; border: solid #fad288 2px;
margin-top: 40px; border-radius: 5px;
margin-bottom: 40px; padding: 5px 10px;
max-width: 40rem; }
}
.links>a { .links > a:hover {
text-decoration: none; border: solid black 2px;
color: black; }
background-color: #fad288;
border: solid #fad288 2px;
border-radius: 5px;
padding: 5px 10px;
}
.links>a:hover { .mobileControls {
border: solid black 2px; display: flex;
} gap: 10px;
</style> justify-content: center;
</head> align-items: center;
flex-direction: column;
margin-bottom: 40px;
}
<body> .mobileControls img {
<header> image-rendering: pixelated;
<h1>agb - a rust framework for making Game Boy Advance games</h1> height: 100%;
</header> }
<section>
<div class="gameDisplay"> .mobileControlsRow {
<div> display: flex;
<div class="imageWrapper"><img src="assets/left.png" /></div> align-items: center;
<iframe id="gameFrame" onload="this.contentWindow.focus()" src="mgba/index.html#/assets/combo.gba"></iframe> justify-content: center;
<div class="imageWrapper"><img src="assets/right.png" /></div> gap: 40px;
}
@media (max-width: 800px) {
.desktopHelp {
display: none;
}
}
@media (min-width: 800px) {
.mobileControls {
display: none;
}
}
.mobileControlsBig {
height: calc(32px * 6);
}
.mobileControlsSmall {
height: calc(32px * 3);
}
</style>
</head>
<body>
<header>
<h1>agb - a rust framework for making Game Boy Advance games</h1>
</header>
<section>
<div class="gameDisplay">
<div>
<div class="imageWrapper"><img src="assets/left.png" /></div>
<iframe
id="gameFrame"
onload="this.contentWindow.focus()"
src="mgba/index.html#/assets/combo.gba"
></iframe>
<div class="imageWrapper"><img src="assets/right.png" /></div>
</div>
</div> </div>
</div> <div id="mobileControls" class="mobileControls">
<div class="mobileControls"> <div class="mobileControlsRow mobileControlsSmall">
<img id="mobileL" src="assets/L.png" />
<img id="mobileR" src="assets/R.png" />
</div>
<div class="mobileControlsRow mobileControlsBig">
<img id="mobileDpad" src="assets/dpad.png" />
<img id="mobileAb" src="assets/ab.png" />
</div>
<div class="mobileControlsRow mobileControlsSmall">
<img id="mobileStart" src="assets/START.png" />
<img id="mobileSelect" src="assets/SELECT.png" />
</div>
<div class="mobileControlsRow">
<button id="mobileRestart">Restart</button>
</div>
</div>
<div class="desktopHelp">
<p>
Press escape to open the menu where you can view or change controls
and restart the game. The game provided is a combination of multiple
Game Boy Advance games made using agb, you can press left or right on
the main menu to switch game.
</p>
</div>
</section>
</div> <section class="red">
<div class="desktopHelp"> <div class="links">
<p> <a href="https://github.com/agbrs/agb">GitHub</a>
Press escape to open the menu where you can view or change controls <a href="book/">Book</a>
and restart the game. The game provided is a combination of multiple </div>
Game Boy Advance games made using agb, you can press left or right on </section>
the main menu to switch game.
</p>
</div>
</section>
<section class="red"> <script>
<div class="links"> const addSimpleButton = (ele, key) => {
<a href="https://github.com/agbrs/agb">GitHub</a> ele.addEventListener("touchstart", (evt) => pressButton(key));
<a href="book/">Book</a> ele.addEventListener("touchend", (evt) => releaseButton(key));
</div> };
</section>
</body>
</html> mobileRestart.addEventListener("click", () => {
gameFrame.contentWindow.postMessage({ reset: true });
});
const pressButton = (key) => {
gameFrame.contentWindow.postMessage({ isPressed: true, button: key });
};
const releaseButton = (key) => {
gameFrame.contentWindow.postMessage({ isPressed: false, button: key });
};
mobileControls.addEventListener("touchmove", (evt) =>
evt.preventDefault()
);
mobileControls.addEventListener("contextmenu", (evt) =>
evt.preventDefault()
);
addSimpleButton(mobileL, "L");
addSimpleButton(mobileR, "L");
addSimpleButton(mobileStart, "Start");
addSimpleButton(mobileSelect, "Select");
let previouslyPressedButtons = [];
const dpadMovement = (touch) => {
const target = touch.target.getBoundingClientRect();
const touchPoint = { x: touch.clientX, y: touch.clientY };
const targetArea = {
x: target.left,
y: target.top,
width: target.width,
height: target.height,
};
const relativePosition = {
x: touchPoint.x - targetArea.x,
y: touchPoint.y - targetArea.y,
};
const touchedBox = {
x: Math.floor(relativePosition.x / (targetArea.width / 3)),
y: Math.floor(relativePosition.y / (targetArea.height / 3)),
};
const buttonBoxMapping = [
[["Up", "Left"], ["Up"], ["Up", "Right"]],
[["Left"], [], ["Right"]],
[["Down", "Left"], ["Down"], ["Down", "Right"]],
];
const buttonsToPress =
(buttonBoxMapping[touchedBox.y] ?? [])[touchedBox.x] ?? [];
for (let oldButton of previouslyPressedButtons) {
if (!buttonsToPress.includes(oldButton)) {
releaseButton(oldButton);
}
}
for (let newButton of buttonsToPress) {
if (!previouslyPressedButtons.includes(newButton)) {
pressButton(newButton);
}
}
previouslyPressedButtons = buttonsToPress;
};
mobileDpad.addEventListener("touchstart", (evt) =>
dpadMovement(evt.touches[0])
);
mobileDpad.addEventListener("touchmove", (evt) =>
dpadMovement(evt.touches[0])
);
mobileDpad.addEventListener("touchend", (evt) => {
for (let oldButton of previouslyPressedButtons) {
releaseButton(oldButton);
}
previouslyPressedButtons = [];
});
let mobileAbAPress = undefined;
const mobileAbMovement = (touch) => {
const target = touch.target.getBoundingClientRect();
const touchPoint = { x: touch.clientX, y: touch.clientY };
const targetArea = {
x: target.left,
y: target.top,
width: target.width,
height: target.height,
};
const relativePosition = {
x: touchPoint.x - targetArea.x,
y: touchPoint.y - targetArea.y,
};
const aPress = relativePosition.x > relativePosition.y;
if (aPress !== mobileAbAPress) {
if (mobileAbAPress === true) {
releaseButton("A");
} else if (mobileAbAPress === false) {
releaseButton("B");
}
}
if (aPress) {
pressButton("A");
} else {
pressButton("B");
}
mobileAbAPress = aPress;
};
mobileAb.addEventListener("touchstart", (evt) =>
mobileAbMovement(evt.touches[0])
);
mobileAb.addEventListener("touchmove", (evt) =>
mobileAbMovement(evt.touches[0])
);
mobileAb.addEventListener("touchend", (evt) => {
if (mobileAbAPress === true) {
releaseButton("A");
} else if (mobileAbAPress === false) {
releaseButton("B");
}
mobileAbAPress = undefined;
});
</script>
</body>
</html>