Add games, and polish a system for loading stuff without having to provide all of the unity build assets for each instance.

This commit is contained in:
wearrrrr 2024-07-19 00:27:28 -05:00
parent c3e17ea81a
commit 021b314e6b
13 changed files with 5545 additions and 18 deletions

@ -1 +1 @@
Subproject commit 3feceedb2e0ff4266df7a593c2e24945c1c46d65
Subproject commit 731e408a4b6802a99ff4916cc310199c8420ca45

4924
public/unity/UnityLoader.js Normal file

File diff suppressed because one or more lines are too long

423
public/unity/index.css Normal file
View file

@ -0,0 +1,423 @@
*,:before,:after {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: #e5e7eb
}
:before,:after {
--tw-content: ""
}
html {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
font-family: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";
font-feature-settings: normal;
font-variation-settings: normal
}
body {
margin: 0;
line-height: inherit
}
hr {
height: 0;
color: inherit;
border-top-width: 1px
}
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted
}
h1,h2,h3,h4,h5,h6 {
font-size: inherit;
font-weight: inherit
}
a {
color: inherit;
text-decoration: inherit
}
b,strong {
font-weight: bolder
}
code,kbd,samp,pre {
font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
font-size: 1em
}
small {
font-size: 80%
}
sub,sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sub {
bottom: -.25em
}
sup {
top: -.5em
}
table {
text-indent: 0;
border-color: inherit;
border-collapse: collapse
}
button,input,optgroup,select,textarea {
font-family: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
font-size: 100%;
font-weight: inherit;
line-height: inherit;
color: inherit;
margin: 0;
padding: 0
}
button,select {
text-transform: none
}
button,[type=button],[type=reset],[type=submit] {
-webkit-appearance: button;
background-color: transparent;
background-image: none
}
:-moz-focusring {
outline: auto
}
:-moz-ui-invalid {
box-shadow: none
}
progress {
vertical-align: baseline
}
::-webkit-inner-spin-button,::-webkit-outer-spin-button {
height: auto
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px
}
::-webkit-search-decoration {
-webkit-appearance: none
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit
}
summary {
display: list-item
}
blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre {
margin: 0
}
fieldset {
margin: 0;
padding: 0
}
legend {
padding: 0
}
ol,ul,menu {
list-style: none;
margin: 0;
padding: 0
}
dialog {
padding: 0
}
textarea {
resize: vertical
}
input::-moz-placeholder,textarea::-moz-placeholder {
opacity: 1;
color: #9ca3af
}
input::placeholder,textarea::placeholder {
opacity: 1;
color: #9ca3af
}
button,[role=button] {
cursor: pointer
}
:disabled {
cursor: default
}
img,svg,video,canvas,audio,iframe,embed,object {
display: block;
vertical-align: middle
}
img,video {
max-width: 100%;
height: auto
}
[hidden] {
display: none
}
*,:before,:after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / .5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia:
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / .5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia:
}
.container {
width: 100%
}
@media (min-width: 640px) {
.container {
max-width:640px
}
}
@media (min-width: 768px) {
.container {
max-width:768px
}
}
@media (min-width: 1024px) {
.container {
max-width:1024px
}
}
@media (min-width: 1280px) {
.container {
max-width:1280px
}
}
@media (min-width: 1536px) {
.container {
max-width:1536px
}
}
.flex {
display: flex
}
.hidden {
display: none
}
.h-5 {
height: 1.25rem
}
.h-full {
height: 100%
}
.w-10 {
width: 2.5rem
}
.w-5 {
width: 1.25rem
}
.w-full {
width: 100%
}
@keyframes spin {
to {
transform: rotate(360deg)
}
}
.animate-spin {
animation: spin 1s linear infinite
}
.cursor-pointer {
cursor: pointer
}
.flex-col {
flex-direction: column
}
.items-center {
align-items: center
}
.justify-center {
justify-content: center
}
.gap-5 {
gap: 1.25rem
}
.border {
border-width: 1px
}
.bg-slate-800 {
--tw-bg-opacity: 1;
background-color: rgb(30 41 59 / var(--tw-bg-opacity))
}
.bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity))
}
.text-slate-200 {
--tw-text-opacity: 1;
color: rgb(226 232 240 / var(--tw-text-opacity))
}
.underline {
text-decoration-line: underline
}
.opacity-25 {
opacity: .25
}
.opacity-75 {
opacity: .75
}
#gameContainer {
overflow: hidden
}

View file

@ -0,0 +1,35 @@
const a = window.location.pathname.split("/").pop();
if (a && UnityLoader) {
let t = function () {
setTimeout(() => {
n.Module.canvas.style.height = "100%"
}, 1000)
requestAnimationFrame(t)
};
// UnityLoader.Error.handler = e => {
// throw document.querySelector("#loader").classList.add("hidden"),
// document.querySelector("#error").classList.remove("hidden"),
// e
// };
const n = UnityLoader.instantiate("gameContainer", `/games/${a}/data.json`, {
onProgress: (e, r) => { },
Module: {
onRuntimeInitialized: () => {
document.querySelector("#loader").classList.add("hidden"),
document.querySelector("#gameContainer").classList.remove("hidden")
}
,
wasmRequest: function (e, r) {
e(this.wasmBinary).then(function (o) {
r(o.instance)
})
},
print: () => { },
printErr: () => { }
}
});
requestAnimationFrame(t)
} else
document.querySelector("#loader").classList.add("hidden"),
document.querySelector("#error").classList.remove("hidden");

32
public/unity/preload.js Normal file
View file

@ -0,0 +1,32 @@
(function() {
const t = document.createElement("link").relList;
if (t && t.supports && t.supports("modulepreload"))
return;
for (const e of document.querySelectorAll('link[rel="modulepreload"]'))
i(e);
new MutationObserver(e=>{
for (const r of e)
if (r.type === "childList")
for (const o of r.addedNodes)
o.tagName === "LINK" && o.rel === "modulepreload" && i(o)
}
).observe(document, {
childList: !0,
subtree: !0
});
function s(e) {
const r = {};
return e.integrity && (r.integrity = e.integrity),
e.referrerPolicy && (r.referrerPolicy = e.referrerPolicy),
e.crossOrigin === "use-credentials" ? r.credentials = "include" : e.crossOrigin === "anonymous" ? r.credentials = "omit" : r.credentials = "same-origin",
r
}
function i(e) {
if (e.ep)
return;
e.ep = !0;
const r = s(e);
fetch(e.href, r)
}
}
)();

View file

@ -1,12 +1,12 @@
---
const { name, image, slugName, lang } = Astro.props;
const { game } = Astro.props;
---
<div class="game" data-name={name}>
<a href=`/${lang}/game/${slugName || name}`>
<img class="game-img" src={image} alt="" />
<div class="game" data-name={game.name}>
<a href=`/game/${game.slug}`>
<img class="game-img" src={game.image} alt="" />
</a>
<p class="game-title">{name}</p>
<p class="game-title">{game.name}</p>
</div>
<style>

View file

@ -2,7 +2,7 @@ import { ui, defaultLang } from "./ui";
export const STATIC_PATHS = [{ params: { lang: "en" } }, { params: { lang: "jp" } }];
export function getLangFromUrl(url: URL) {
function getLangFromUrl(url: URL) {
// comma lol
const [, lang] = url.pathname.split("/");
if (lang in ui) return lang as keyof typeof ui;

View file

@ -5,6 +5,13 @@
"image": "/games/2048/logo.png",
"slug": "2048"
},
"adofai": {
"name": "A Dance of Fire and Ice",
"description": "A Dance of Fire and Ice is a strict rhythm game. Keep your focus as you guide two orbiting planets along a winding path without breaking their perfect equilibrium. Press on every beat of the music to move in a line. Every pattern has its own rhythm to it. It can get difficult. This game is purely based on rhythm, so use your ears more than your sight.",
"image": "/games/adofai/logo.webp",
"slug": "adofai",
"unity": true
},
"backrooms": {
"name": "The Backrooms",
"image": "/games/backrooms/logo.png",
@ -80,6 +87,27 @@
"image": "/games/draw-the-hill/logo.png",
"slug": "draw-the-hill"
},
"ducklife": {
"name": "Duck Life",
"description": "Duck Life is the first game in the Duck Life series. A tornado has struck your farm and destroyed everything. All that remains is a single duck egg. Train this duckling to peak athletic form so you can earn money to rebuild the farm.",
"image": "/games/ducklife/logo.png",
"slug": "ducklife",
"unity": true
},
"ducklife3": {
"name": "Duck Life 3",
"description": "Duck Life is the first game in the Duck Life series. A tornado has struck your farm and destroyed everything. All that remains is a single duck egg. Train this duckling to peak athletic form so you can earn money to rebuild the farm.",
"image": "/games/ducklife3/logo.webp",
"slug": "ducklife3",
"unity": true
},
"ducklife4": {
"name": "Duck Life 4",
"description": "Duck Life is the first game in the Duck Life series. A tornado has struck your farm and destroyed everything. All that remains is a single duck egg. Train this duckling to peak athletic form so you can earn money to rebuild the farm.",
"image": "/games/ducklife/logo.png",
"slug": "ducklife4",
"unity": true
},
"evil-glitch": {
"name": "Evil Glitch",
"image": "/games/evil-glitch/logo.png",
@ -185,6 +213,11 @@
"image": "/games/retro-bowl/logo.png",
"slug": "retro-bowl"
},
"sans-fight": {
"name": "Sans Fight",
"image": "/games/sans-fight/icon-256.png",
"slug": "sans-fight"
},
"slope": {
"name": "Slope",
"image": "/games/slope/logo.png",

View file

@ -3,10 +3,9 @@ import Layout from "../../layouts/Layout.astro";
import games from "../../json/games.json";
import GameItem from "@components/GameItem.astro";
import { STATIC_PATHS, i18n, getLangFromUrl } from "@i18n/utils";
import { STATIC_PATHS, i18n } from "@i18n/utils";
import Input from "@components/UI/Input.astro";
const t = i18n.inferLangUseTranslations(Astro.url);
const lang = getLangFromUrl(Astro.url);
export function getStaticPaths() {
return STATIC_PATHS;
@ -23,7 +22,7 @@ export function getStaticPaths() {
{
Object.keys(games).map((key) => {
const game = (games as any)[key];
return <GameItem lang={lang} name={game.name} image={game.image} slugName={game.slug} />;
return <GameItem game={game} />;
})
}
</main>

View file

@ -1,7 +1,7 @@
---
export const prerender = false;
import Layout from "src/layouts/Layout.astro";
import games from "../../../json/games.json";
import Layout from "../../layouts/Layout.astro";
import games from "../../json/games.json";
let gamesList = games as GameList;
@ -26,28 +26,52 @@ if (!gameData) {
<Layout title="Game">
<div id="main-content">
<h1 class="title-text">{gameData.name}</h1>
<p class="title-desc">{gameData?.description || "Play the game!"}</p>
<div class="game-container">
<iframe scrolling="no" src=`/games/${gameData.slug}` title={gameData.name} id="game-frame"/>
{gameData.unity ?
<iframe scrolling="no" src=`/unity/${gameData.slug}` title={gameData.name} id="game-frame"/> :
<iframe scrolling="no" src=`/games/${gameData.slug}` title={gameData.name} id="game-frame"/>
}
<div class="game-info">
<p class="game-title">{gameData.name}</p>
<p class="game-desc">{gameData.description}</p>
</div>
</div>
</div>
</Layout>
<style>
#main-content {
margin-top: 2rem;
}
.game-container {
display: flex;
align-items: center;
flex-direction: column;
overflow: hidden;
margin-top: 4rem;
margin-bottom: 4rem;
}
#game-frame {
width: 80%;
aspect-ratio: 16 / 8;
height: 80vh;
width: 1350px;
border: 0;
}
.game-info {
display: flex;
flex-direction: column;
gap: 1rem;
background-color: var(--background-highlight);
width: 1350px;
padding: 1rem;
}
.game-title {
font-weight: bold;
font-size: 20px;
margin-top: 0.2rem;
}
.game-desc {
margin: 0;
}
</style>
<script>

View file

@ -0,0 +1,55 @@
---
export const prerender = false;
---
<!DOCTYPE html>
<html lang="en" class="w-full h-full">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Unity Content</title>
<script is:inline src="/unity/UnityLoader.js"></script>
<script is:inline src="/unity/instantiateUnity.js"></script>
<link rel="stylesheet" href="/unity/index.css">
</head>
<body class="w-full h-full bg-slate-800 text-slate-200">
<div
id="loader"
class="w-full h-full flex items-center justify-center flex-col gap-5"
>
<img src="/favicon.svg" class="w-10" />
<svg
class="animate-spin h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
</div>
<div class="h-full w-full hidden" id="error">
<div
class="w-full h-full flex items-center justify-center flex-col gap-5"
>
<img src="/favicon.svg" class="w-10" />
<p>An error occurred.</p>
<p class="underline cursor-pointer" onclick="location.reload()">
Refresh
</p>
</div>
</div>
<div id="gameContainer" class="w-full h-full hidden bg-white"></div>
</body>
</html>

1
src/types.d.ts vendored
View file

@ -43,6 +43,7 @@ type GameMetadata = {
description?: string,
image: string,
slug: string,
unity?: boolean
}
type GameList = {

View file

@ -5,7 +5,8 @@
"baseUrl": ".",
"paths": {
"@components/*": ["src/components/*"],
"@i18n/*": ["src/i18n/*"]
"@i18n/*": ["src/i18n/*"],
"@json/*": ["src/json/*"],
}
}
}