A lightweight JavaScript game engine
git clone git://soophie.de/game-engine
1
*.css
1
MINIFY=true2
AUTO_COMPILE=true3
SOURCE_FILE=less/style.less4
TARGET_FILE=css/style.css
Binary file
1
<!DOCTYPE html>2
<html lang="en" dir="ltr">3
<head>4
<meta charset="utf-8">5
<meta name="viewport" content="width=device-width, initial-scale=1.0">6
<meta name="author" content="typable">7
<meta name="keywords" content="">8
<meta name="description" content="">9
<meta name="robots" content="noindex, nofollow">10
<meta name="google" content="notranslate">11
<title>git.typable</title>12
<link href="/asset/favicon.png" rel="shortcut icon" type="image/png">13
<link href="/css/style.css" rel="stylesheet">14
<script src="/js/index.js" type="module"></script>15
</head>16
<body>17
<header class="container-fluid">18
<div class="row">19
<div class="container">20
<h1>git.typable</h1>21
<p>A place for commonly used code snippets.</p>22
</div>23
</div>24
</header>25
<main class="container-fluid">26
<div class="row">27
<div class="container">28
<article>29
<label>Game</label>30
<pre>Game(width, height, { fps })</pre>31
<p>By extending the Game class you have access to a newly created canvas with game loop, which calls the update and render methods.</p>32
<code><pre>import { Game } from 'https://git.typable.dev/std/js/game.js';</pre></code>33
<code><pre>class DinoGame extends Game {34
constructor() {35
super(900, 600); // (width, height)36
this.point = { x: 100, y: 100 };37
}38
update() {39
this.point.x += 1;40
}41
render() {42
super.render(); // screen clear43
let { x, y } = this.point;44
this.g.fillRect(x, y, 50, 50);45
}46
}48
const game = new DinoGame();49
document.body.append(game.canvas);51
game.start();</pre></code>52
<!--53
<h3>update</h3>54
<p>Method for update related purposes.</p>55
<h3>render</h3>56
<p>Method for render related purposes.</p>57
<h3>start</h3>58
<p>Starts the game loop.</p>59
-->60
</article>61
<article>62
<label>scaleCanvas</label>63
<pre>scaleCanvas(canvas, width, height)</pre>64
<p>Scales a canvas to the device related pixel ratio.</p>65
<code><pre>import { scaleCanvas } from 'https://git.typable.dev/std/js/canvas.js';</pre></code>66
<code><pre>const canvas = document.createElement('canvas');67
scaleCanvas(canvas, 900, 600); // (width, height)68
document.body.append(canvas);</pre></code>69
</div>70
</div>71
</main>72
<footer class="container-fluid">73
<div class="row">74
<div class="container">75
<span>© typable 2021</span>76
<a href="https://typable.dev/imprint">Imprint</a>77
<a href="https://typable.dev/terms">Terms</a>78
</div>79
</div>80
</footer>81
</body>82
</html>
1
window.onload = function() {2
append_clipboard_button();3
};5
function append_clipboard_button() {6
for(const code of document.querySelectorAll('code')) {7
const clipboard = document.createElement('div');8
clipboard.classList.add('clipboard');9
clipboard.classList.add('ico');10
clipboard.textContent = 'link';11
clipboard.title = 'Copy';12
code.append(clipboard);14
clipboard.onclick = function() {15
navigator.clipboard.writeText(code.querySelector('pre').textContent);16
}17
}18
}
1
export function html(func) {2
return {3
build: function(args) {4
let template = document.createElement('template');5
template.innerHTML = func(args);6
let fragment = template.content.cloneNode(true);7
return {8
element: fragment.firstElementChild,9
render: function(parent) {10
parent.appendChild(fragment);11
}12
};13
}14
};15
}
1
* {2
margin: 0;3
padding: 0;4
font-family: 'Roboto', sans-serif;5
box-sizing: border-box;7
&:hover,8
&:focus {9
outline: none;10
}11
}13
html {14
position: relative;15
overflow-x: hidden;16
min-height: 100vh;17
}19
body {20
width: 100%;21
font-size: 14px;22
min-height: 100vh;23
}25
img {26
pointer-events: none;27
user-select: none;28
}30
input {31
-webkit-appearance: none;32
}34
button {35
user-select: none;36
cursor: pointer;37
}39
.col-auto {40
padding-left: calc(@grid-gutter-width / 2);41
padding-right: calc(@grid-gutter-width / 2);42
}44
.hidden {45
display: none !important;46
}
1
@import 'variable';2
@import 'normalize';4
@import url('https://fonts.googleapis.com/icon?family=Material+Icons');5
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700');6
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500');8
* {9
color: #212121;10
font-family: 'Inter';11
}13
.container-fluid {14
.col-auto;15
}17
.container {18
max-width: @width;19
margin: 0 auto;20
.col-auto;21
}23
.row {24
margin-left: calc(-@grid-gutter-width / 2);25
margin-right: calc(-@grid-gutter-width / 2);26
}28
.ico {29
font-family: 'Material Icons';30
font-weight: normal;31
font-style: normal;32
font-size: 24px;33
display: inline-block;34
line-height: 1;35
text-transform: none;36
letter-spacing: normal;37
word-wrap: normal;38
white-space: nowrap;39
direction: ltr;40
-webkit-font-smoothing: antialiased;41
text-rendering: optimizedLegibility;42
-moz-osx-font-smoothing: grayscale;43
font-feature-settings: 'liga';44
user-select: none;45
}47
header {48
padding: 50px 0;49
margin-bottom: 20px;51
h1 {52
font-size: 28px;53
margin-bottom: 10px;54
}56
p {57
font-size: 16px;58
color: #666666;59
}60
}62
main {63
margin-bottom: 100px;65
article {66
border: 1px solid #CCC;67
border-radius: 4px;68
padding: 20px;69
margin-bottom: 20px;70
overflow-x: hidden;72
label {73
display: block;74
font-size: 20px;75
font-weight: 600;76
margin-bottom: 10px;77
}79
& > pre {80
display: inline-block;81
font-size: 13px;82
font-family: 'Roboto Mono';83
line-height: 19px;84
padding: 6px 6px;85
border: 1px solid #EEE;86
border-radius: 2px;87
margin-bottom: 10px;88
overflow-x: auto;89
max-width: 100%;91
&::-webkit-scrollbar {92
height: 8px;93
}95
&::-webkit-scrollbar-track {96
background: #EEE;97
}99
&::-webkit-scrollbar-thumb {100
background: #AAA;102
&:hover {103
background: #888;104
}105
}106
}108
p {109
font-size: 14px;110
font-weight: 400;111
color: #666666;112
line-height: 20px;113
margin-bottom: 15px;114
}116
code {117
display: block;118
position: relative;119
background: #F5F5F5;120
border-radius: 2px;121
margin-bottom: 15px;122
overflow-x: hidden;124
pre {125
font-size: 13px;126
font-family: 'Roboto Mono';127
line-height: 19px;128
tab-size: 25px;129
overflow-x: auto;130
padding: 10px 10px;131
padding-right: 40px;133
&::-webkit-scrollbar {134
height: 8px;135
}137
&::-webkit-scrollbar-track {138
background: #EEE;139
}141
&::-webkit-scrollbar-thumb {142
background: #AAA;144
&:hover {145
background: #888;146
}147
}148
}150
.clipboard {151
position: absolute;152
top: 4px;153
right: 4px;154
width: 30px;155
height: 30px;156
border-radius: 50%;157
color: #888888;158
display: grid;159
place-content: center;160
cursor: pointer;162
&:hover {163
color: #424242;164
}165
}166
}168
h3 {169
margin-bottom: 5px;170
}171
}172
}174
footer {175
margin-bottom: 50px;177
.container {178
display: flex;179
justify-content: flex-end;180
}182
span {183
flex: 1;184
align-self: flex-start;185
color: #666666;186
}188
a {189
margin-left: 15px;190
color: #666666;192
&:hover {193
color: #424242;194
}195
}196
}
1
@grid-gutter-width: 40px;2
@width: 700px;
1
const DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1;3
export function scaleCanvas(canvas, width, height) {4
const g = canvas.getContext('2d');5
const backingStoreRatio = g.webkitBackingStorePixelRatio || 1;6
const ratio = DEVICE_PIXEL_RATIO / backingStoreRatio;7
if(DEVICE_PIXEL_RATIO != backingStoreRatio) {8
canvas.width = width * ratio;9
canvas.height = height * ratio;10
canvas.style.width = width + 'px';11
canvas.style.height = height + 'px';12
}13
else {14
canvas.width = width;15
canvas.height = height;16
canvas.style.width = '';17
canvas.style.height = '';18
}19
g.scale(ratio, ratio);20
}
1
import { scaleCanvas } from './canvas.js';3
export class Game {4
constructor(width, height, { fps } = {}) {5
this.width = width || 900;6
this.height = height || 600;7
this.fps = 1000 / (fps || 60);8
this.then = Date.now();9
this.canvas = document.createElement('canvas');10
scaleCanvas(this.canvas, this.width, this.height);11
this.g = this.canvas.getContext('2d');12
}13
start() {14
requestAnimationFrame(this.loop.bind(this));15
this.update();16
this.g.save();17
this.render(),18
this.g.restore();19
}20
loop() {21
requestAnimationFrame(this.loop.bind(this));22
this.now = Date.now();23
this.elapsed = this.now - this.then;24
if(this.elapsed > this.fps) {25
this.then = this.now - (this.elapsed % this.fps);26
this.update();27
this.g.save();28
this.render(),29
this.g.restore();30
}31
}32
update() {33
// empty34
}35
render() {36
this.g.clearRect(0, 0, this.width, this.height);37
}38
}