make games crisp
This commit is contained in:
parent
ebb1afebdb
commit
64e76502e8
19
app/src/css/game.css
Normal file
19
app/src/css/game.css
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game {
|
||||||
|
background-color: white;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game.active {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
@ -71,10 +71,6 @@
|
||||||
color: var(--c64-dark-blue);
|
color: var(--c64-dark-blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--cyan);
|
color: var(--cyan);
|
||||||
}
|
}
|
||||||
|
|
@ -88,9 +84,10 @@ body {
|
||||||
font-family: var(--font-family);
|
font-family: var(--font-family);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
/* black bars */
|
|
||||||
background: var(--c64-light-blue);
|
background: var(--c64-light-blue);
|
||||||
color: var(--c64-light-blue);
|
color: var(--c64-light-blue);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -147,16 +147,4 @@
|
||||||
|
|
||||||
#scrollback .output {
|
#scrollback .output {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
|
||||||
|
|
||||||
/* games */
|
|
||||||
|
|
||||||
.game {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.game.active {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
}
|
||||||
|
|
@ -9,12 +9,10 @@ export const Layout: FC = async ({ children, title }) => (
|
||||||
|
|
||||||
<link href="/css/reset.css" rel="stylesheet" />
|
<link href="/css/reset.css" rel="stylesheet" />
|
||||||
<link href="/css/main.css" rel="stylesheet" />
|
<link href="/css/main.css" rel="stylesheet" />
|
||||||
|
<link href="/css/game.css" rel="stylesheet" />
|
||||||
|
|
||||||
<script type="importmap" dangerouslySetInnerHTML={{
|
<script type="importmap" dangerouslySetInnerHTML={{ __html: `{ "imports": { "@/": "/" } }` }} />
|
||||||
__html: `{ "imports": { "@/": "/" } }`
|
|
||||||
}} />
|
|
||||||
<script src="/js/main.js" type="module" async></script>
|
<script src="/js/main.js" type="module" async></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body data-mode="tall">
|
<body data-mode="tall">
|
||||||
<main>
|
<main>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
import type { Message, InputState } from "../shared/types.js"
|
import type { Message, InputState } from "../shared/types.js"
|
||||||
import { GameContext } from "../shared/game.js"
|
import { GameContext } from "../shared/game.js"
|
||||||
import { focusInput } from "./focus.js"
|
import { focusInput } from "./focus.js"
|
||||||
import { $ } from "./dom.js"
|
import { $$, scrollback } from "./dom.js"
|
||||||
import { randomId } from "../shared/utils.js"
|
import { randomId } from "../shared/utils.js"
|
||||||
import { setStatus, addOutput } from "./scrollback.js"
|
import { setStatus, addOutput } from "./scrollback.js"
|
||||||
import { browserCommands } from "./commands.js"
|
import { browserCommands } from "./commands.js"
|
||||||
|
|
||||||
const FPS = 30
|
const FPS = 30
|
||||||
|
const HEIGHT = 540
|
||||||
|
const WIDTH = 980
|
||||||
|
|
||||||
|
type Game = { init?: () => void, update?: (delta: number, input: InputState) => void, draw?: (ctx: GameContext) => void }
|
||||||
|
|
||||||
let oldMode = "cinema"
|
let oldMode = "cinema"
|
||||||
let running = false
|
let running = false
|
||||||
let canvas: HTMLCanvasElement
|
let canvas: HTMLCanvasElement
|
||||||
type Game = { init?: () => void, update?: (delta: number, input: InputState) => void, draw?: (ctx: GameContext) => void }
|
|
||||||
|
|
||||||
const pressedStack = new Set<string>()
|
const pressedStack = new Set<string>()
|
||||||
|
|
||||||
let pressed: InputState = {
|
let pressed: InputState = {
|
||||||
|
|
@ -35,34 +38,41 @@ export async function handleGameStart(msg: Message) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvasId = randomId()
|
|
||||||
addOutput(msgId, { html: `<canvas id="${canvasId}" class="game active" height="540" width="960" tabindex="0"></canvas>` })
|
|
||||||
|
|
||||||
if (document.body.dataset.mode === "tall") {
|
if (document.body.dataset.mode === "tall") {
|
||||||
browserCommands.mode?.()
|
browserCommands.mode?.()
|
||||||
oldMode = "tall"
|
oldMode = "tall"
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas = $(canvasId) as HTMLCanvasElement
|
canvas = createCanvas()
|
||||||
canvas.focus()
|
canvas.focus()
|
||||||
setStatus(msgId, "ok")
|
setStatus(msgId, "ok")
|
||||||
canvas.addEventListener("keydown", handleKeydown)
|
canvas.addEventListener("keydown", handleKeydown)
|
||||||
canvas.addEventListener("keyup", handleKeyup)
|
canvas.addEventListener("keyup", handleKeyup)
|
||||||
|
window.addEventListener("resize", resizeCanvas)
|
||||||
|
resizeCanvas()
|
||||||
gameLoop(new GameContext(canvas.getContext("2d")!), game)
|
gameLoop(new GameContext(canvas.getContext("2d")!), game)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createCanvas(): HTMLCanvasElement {
|
||||||
|
const canvas = $$("canvas.game.active") as HTMLCanvasElement
|
||||||
|
canvas.id = randomId()
|
||||||
|
canvas.height = HEIGHT
|
||||||
|
canvas.width = WIDTH
|
||||||
|
canvas.tabIndex = 0
|
||||||
|
|
||||||
|
const main = document.querySelector("main")
|
||||||
|
main?.parentNode?.insertBefore(canvas, main)
|
||||||
|
|
||||||
|
return canvas
|
||||||
|
}
|
||||||
|
|
||||||
function handleKeydown(e: KeyboardEvent) {
|
function handleKeydown(e: KeyboardEvent) {
|
||||||
pressedStack.add(e.key)
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
if (e.key === "Escape" || (e.ctrlKey && e.key === "c")) {
|
if (e.key === "Escape" || (e.ctrlKey && e.key === "c")) {
|
||||||
running = false
|
endGame()
|
||||||
if (oldMode === "tall") browserCommands.mode?.()
|
|
||||||
canvas.classList.remove("active")
|
|
||||||
canvas.style.height = canvas.height / 2 + "px"
|
|
||||||
canvas.style.width = canvas.width / 2 + "px"
|
|
||||||
focusInput()
|
|
||||||
} else {
|
} else {
|
||||||
|
pressedStack.add(e.key)
|
||||||
pressed.key = e.key
|
pressed.key = e.key
|
||||||
pressed.ctrl = e.ctrlKey
|
pressed.ctrl = e.ctrlKey
|
||||||
pressed.shift = e.shiftKey
|
pressed.shift = e.shiftKey
|
||||||
|
|
@ -80,6 +90,19 @@ function handleKeyup(e: KeyboardEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resizeCanvas() {
|
||||||
|
const scale = Math.min(
|
||||||
|
window.innerWidth / 960,
|
||||||
|
window.innerHeight / 540
|
||||||
|
)
|
||||||
|
|
||||||
|
canvas.width = 960 * scale
|
||||||
|
canvas.height = 540 * scale
|
||||||
|
|
||||||
|
const ctx = canvas.getContext("2d")!
|
||||||
|
ctx.setTransform(scale, 0, 0, scale, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
function gameLoop(ctx: GameContext, game: Game) {
|
function gameLoop(ctx: GameContext, game: Game) {
|
||||||
running = true
|
running = true
|
||||||
let last = 0
|
let last = 0
|
||||||
|
|
@ -99,4 +122,20 @@ function gameLoop(ctx: GameContext, game: Game) {
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(loop)
|
requestAnimationFrame(loop)
|
||||||
|
}
|
||||||
|
|
||||||
|
function endGame() {
|
||||||
|
running = false
|
||||||
|
|
||||||
|
if (oldMode === "tall") browserCommands.mode?.()
|
||||||
|
|
||||||
|
canvas.classList.remove("active")
|
||||||
|
canvas.style.height = HEIGHT / 2 + "px"
|
||||||
|
canvas.style.width = WIDTH / 2 + "px"
|
||||||
|
|
||||||
|
const output = $$("li.output")
|
||||||
|
output.append(canvas)
|
||||||
|
scrollback.append(output)
|
||||||
|
|
||||||
|
focusInput()
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user