[READ-ONLY] Mirror of https://github.com/excaliburjs/create-excalibur. A repo bootstrapping tool to get you started making your first games!
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: Add Spinner and New UI features (#1)

This PR adds new spinner and switches to downloading templates/samples from github!


![image](https://github.com/excaliburjs/create-excalibur/assets/612071/4403d59b-6d80-4b4c-bae1-2f449a050688)


![image](https://github.com/excaliburjs/create-excalibur/assets/612071/4a8d621a-d6e9-498c-845f-5b39ff739657)

authored by

Erik Onarheim and committed by
GitHub
(Feb 16, 2024, 7:42 AM -0600) da6bb400 8345f560

+916 -3647
+16 -50
index.js
··· 1 1 #!/usr/bin/env node 2 - import { input, select, confirm } from '@inquirer/prompts'; 3 - import { dirname } from 'path'; 4 - import { fileURLToPath } from 'url'; 5 - import { actions } from './src/actions.js'; 6 - import { mkdir } from './src/utils.js'; 7 - 8 - const PROJECTS = [ 9 - { 10 - name: 'Typescript', 11 - value: 'typescript', 12 - description: '[ TS starter template ]', 13 - }, 14 - { 15 - name: 'Javascript', 16 - value: 'javascript', 17 - description: '[ JS starter template ]', 18 - }, 19 - ]; 2 + import select from "@inquirer/select"; 3 + import intro from "./src/actions/intro.js"; 4 + import { terminal } from "./src/console.js"; 5 + import { bye } from "./src/utils.js"; 6 + import { FLOW_CHOICES, FLOWS } from "./src/constants.js"; 20 7 21 8 async function main() { 22 - const CURRENT_DIRECTORY = process.cwd(); 23 - const __dirname = dirname(fileURLToPath(import.meta.url)); 24 - actions.intro(); 25 - // 26 - const projectName = await input({ message: 'Name your project:' }); 27 - const selectedTemplate = await select({ 28 - message: 'Select your template', 29 - choices: PROJECTS, 30 - }); 31 - const templatePath = `${__dirname}/src/templates/${selectedTemplate}`; 32 - mkdir(`${CURRENT_DIRECTORY}/${projectName}`); 33 - 34 - actions.createResources(templatePath, projectName); 35 - 36 - // 37 - const installDependencies = await confirm({ 38 - message: 'Install dependencies ?', 39 - }); 40 - 41 - if (installDependencies) { 42 - actions.installDependencies(projectName); 9 + try { 10 + intro(); 11 + const flow = await select({ 12 + message: "Want do you want do?", 13 + choices: FLOW_CHOICES, 14 + }); 15 + await FLOWS[flow](); 16 + } catch (error) { 17 + terminal.line(); 18 + bye(); 43 19 } 44 - 45 - const initRepo = await confirm({ 46 - message: 'Initialize repository ?', 47 - }); 48 - 49 - if (initRepo) { 50 - actions.initRepo(projectName); 51 - } 52 - 53 - actions.outro(projectName); 54 20 } 55 - 21 + // 56 22 main();
+224 -1
package-lock.json
··· 11 11 "dependencies": { 12 12 "@babel/core": "^7.23.7", 13 13 "@inquirer/prompts": "^3.3.0", 14 - "chalk": "^5.3.0" 14 + "chalk": "^5.3.0", 15 + "ora": "^8.0.1", 16 + "simple-git": "^3.22.0" 15 17 }, 16 18 "bin": { 17 19 "create-excalibur": "index.js" ··· 748 750 "@jridgewell/sourcemap-codec": "^1.4.14" 749 751 } 750 752 }, 753 + "node_modules/@kwsites/file-exists": { 754 + "version": "1.1.1", 755 + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", 756 + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", 757 + "dependencies": { 758 + "debug": "^4.1.1" 759 + } 760 + }, 761 + "node_modules/@kwsites/promise-deferred": { 762 + "version": "1.1.1", 763 + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", 764 + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" 765 + }, 751 766 "node_modules/@types/mute-stream": { 752 767 "version": "0.0.4", 753 768 "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", ··· 871 886 "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", 872 887 "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" 873 888 }, 889 + "node_modules/cli-cursor": { 890 + "version": "4.0.0", 891 + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", 892 + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", 893 + "dependencies": { 894 + "restore-cursor": "^4.0.0" 895 + }, 896 + "engines": { 897 + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 898 + }, 899 + "funding": { 900 + "url": "https://github.com/sponsors/sindresorhus" 901 + } 902 + }, 874 903 "node_modules/cli-spinners": { 875 904 "version": "2.9.2", 876 905 "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", ··· 988 1017 "node": ">=6.9.0" 989 1018 } 990 1019 }, 1020 + "node_modules/get-east-asian-width": { 1021 + "version": "1.2.0", 1022 + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", 1023 + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", 1024 + "engines": { 1025 + "node": ">=18" 1026 + }, 1027 + "funding": { 1028 + "url": "https://github.com/sponsors/sindresorhus" 1029 + } 1030 + }, 991 1031 "node_modules/globals": { 992 1032 "version": "11.12.0", 993 1033 "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", ··· 1023 1063 "node": ">=8" 1024 1064 } 1025 1065 }, 1066 + "node_modules/is-interactive": { 1067 + "version": "2.0.0", 1068 + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", 1069 + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", 1070 + "engines": { 1071 + "node": ">=12" 1072 + }, 1073 + "funding": { 1074 + "url": "https://github.com/sponsors/sindresorhus" 1075 + } 1076 + }, 1077 + "node_modules/is-unicode-supported": { 1078 + "version": "2.0.0", 1079 + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", 1080 + "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==", 1081 + "engines": { 1082 + "node": ">=18" 1083 + }, 1084 + "funding": { 1085 + "url": "https://github.com/sponsors/sindresorhus" 1086 + } 1087 + }, 1026 1088 "node_modules/js-tokens": { 1027 1089 "version": "4.0.0", 1028 1090 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", ··· 1050 1112 "node": ">=6" 1051 1113 } 1052 1114 }, 1115 + "node_modules/log-symbols": { 1116 + "version": "6.0.0", 1117 + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", 1118 + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", 1119 + "dependencies": { 1120 + "chalk": "^5.3.0", 1121 + "is-unicode-supported": "^1.3.0" 1122 + }, 1123 + "engines": { 1124 + "node": ">=18" 1125 + }, 1126 + "funding": { 1127 + "url": "https://github.com/sponsors/sindresorhus" 1128 + } 1129 + }, 1130 + "node_modules/log-symbols/node_modules/is-unicode-supported": { 1131 + "version": "1.3.0", 1132 + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", 1133 + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", 1134 + "engines": { 1135 + "node": ">=12" 1136 + }, 1137 + "funding": { 1138 + "url": "https://github.com/sponsors/sindresorhus" 1139 + } 1140 + }, 1053 1141 "node_modules/lru-cache": { 1054 1142 "version": "5.1.1", 1055 1143 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 1056 1144 "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 1057 1145 "dependencies": { 1058 1146 "yallist": "^3.0.2" 1147 + } 1148 + }, 1149 + "node_modules/mimic-fn": { 1150 + "version": "2.1.0", 1151 + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 1152 + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", 1153 + "engines": { 1154 + "node": ">=6" 1059 1155 } 1060 1156 }, 1061 1157 "node_modules/ms": { ··· 1076 1172 "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", 1077 1173 "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" 1078 1174 }, 1175 + "node_modules/onetime": { 1176 + "version": "5.1.2", 1177 + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", 1178 + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", 1179 + "dependencies": { 1180 + "mimic-fn": "^2.1.0" 1181 + }, 1182 + "engines": { 1183 + "node": ">=6" 1184 + }, 1185 + "funding": { 1186 + "url": "https://github.com/sponsors/sindresorhus" 1187 + } 1188 + }, 1189 + "node_modules/ora": { 1190 + "version": "8.0.1", 1191 + "resolved": "https://registry.npmjs.org/ora/-/ora-8.0.1.tgz", 1192 + "integrity": "sha512-ANIvzobt1rls2BDny5fWZ3ZVKyD6nscLvfFRpQgfWsythlcsVUC9kL0zq6j2Z5z9wwp1kd7wpsD/T9qNPVLCaQ==", 1193 + "dependencies": { 1194 + "chalk": "^5.3.0", 1195 + "cli-cursor": "^4.0.0", 1196 + "cli-spinners": "^2.9.2", 1197 + "is-interactive": "^2.0.0", 1198 + "is-unicode-supported": "^2.0.0", 1199 + "log-symbols": "^6.0.0", 1200 + "stdin-discarder": "^0.2.1", 1201 + "string-width": "^7.0.0", 1202 + "strip-ansi": "^7.1.0" 1203 + }, 1204 + "engines": { 1205 + "node": ">=18" 1206 + }, 1207 + "funding": { 1208 + "url": "https://github.com/sponsors/sindresorhus" 1209 + } 1210 + }, 1211 + "node_modules/ora/node_modules/ansi-regex": { 1212 + "version": "6.0.1", 1213 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", 1214 + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", 1215 + "engines": { 1216 + "node": ">=12" 1217 + }, 1218 + "funding": { 1219 + "url": "https://github.com/chalk/ansi-regex?sponsor=1" 1220 + } 1221 + }, 1222 + "node_modules/ora/node_modules/emoji-regex": { 1223 + "version": "10.3.0", 1224 + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", 1225 + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" 1226 + }, 1227 + "node_modules/ora/node_modules/string-width": { 1228 + "version": "7.1.0", 1229 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", 1230 + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", 1231 + "dependencies": { 1232 + "emoji-regex": "^10.3.0", 1233 + "get-east-asian-width": "^1.0.0", 1234 + "strip-ansi": "^7.1.0" 1235 + }, 1236 + "engines": { 1237 + "node": ">=18" 1238 + }, 1239 + "funding": { 1240 + "url": "https://github.com/sponsors/sindresorhus" 1241 + } 1242 + }, 1243 + "node_modules/ora/node_modules/strip-ansi": { 1244 + "version": "7.1.0", 1245 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 1246 + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 1247 + "dependencies": { 1248 + "ansi-regex": "^6.0.1" 1249 + }, 1250 + "engines": { 1251 + "node": ">=12" 1252 + }, 1253 + "funding": { 1254 + "url": "https://github.com/chalk/strip-ansi?sponsor=1" 1255 + } 1256 + }, 1079 1257 "node_modules/os-tmpdir": { 1080 1258 "version": "1.0.2", 1081 1259 "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", ··· 1089 1267 "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 1090 1268 "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" 1091 1269 }, 1270 + "node_modules/restore-cursor": { 1271 + "version": "4.0.0", 1272 + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", 1273 + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", 1274 + "dependencies": { 1275 + "onetime": "^5.1.0", 1276 + "signal-exit": "^3.0.2" 1277 + }, 1278 + "engines": { 1279 + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 1280 + }, 1281 + "funding": { 1282 + "url": "https://github.com/sponsors/sindresorhus" 1283 + } 1284 + }, 1285 + "node_modules/restore-cursor/node_modules/signal-exit": { 1286 + "version": "3.0.7", 1287 + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", 1288 + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" 1289 + }, 1092 1290 "node_modules/run-async": { 1093 1291 "version": "3.0.0", 1094 1292 "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", ··· 1119 1317 }, 1120 1318 "funding": { 1121 1319 "url": "https://github.com/sponsors/isaacs" 1320 + } 1321 + }, 1322 + "node_modules/simple-git": { 1323 + "version": "3.22.0", 1324 + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.22.0.tgz", 1325 + "integrity": "sha512-6JujwSs0ac82jkGjMHiCnTifvf1crOiY/+tfs/Pqih6iow7VrpNKRRNdWm6RtaXpvvv/JGNYhlUtLhGFqHF+Yw==", 1326 + "dependencies": { 1327 + "@kwsites/file-exists": "^1.1.1", 1328 + "@kwsites/promise-deferred": "^1.1.1", 1329 + "debug": "^4.3.4" 1330 + }, 1331 + "funding": { 1332 + "type": "github", 1333 + "url": "https://github.com/steveukx/git-js?sponsor=1" 1334 + } 1335 + }, 1336 + "node_modules/stdin-discarder": { 1337 + "version": "0.2.2", 1338 + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", 1339 + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", 1340 + "engines": { 1341 + "node": ">=18" 1342 + }, 1343 + "funding": { 1344 + "url": "https://github.com/sponsors/sindresorhus" 1122 1345 } 1123 1346 }, 1124 1347 "node_modules/string-width": {
+3 -1
package.json
··· 27 27 "dependencies": { 28 28 "@babel/core": "^7.23.7", 29 29 "@inquirer/prompts": "^3.3.0", 30 - "chalk": "^5.3.0" 30 + "chalk": "^5.3.0", 31 + "ora": "^8.0.1", 32 + "simple-git": "^3.22.0" 31 33 } 32 34 }
-88
src/actions.js
··· 1 - import { log, info, success, warn } from './console.js'; 2 - import { 3 - copy, 4 - getFileExtension, 5 - isMediaFile, 6 - mkdir, 7 - readFile, 8 - runCommand, 9 - writeFile, 10 - } from './utils.js'; 11 - import * as fs from 'fs'; 12 - 13 - const CURRENT_DIRECTORY = process.cwd(); 14 - 15 - function intro() { 16 - log(` 17 - /| ________________ 18 - O|===|* >________________> 19 - \\| 20 - `); 21 - info(' Welcome to Excalibur JS! '); 22 - log('Your friendly TypeScript 2D game engine for the web.'); 23 - log(''); 24 - } 25 - 26 - function createResources(targetPath, projectName) { 27 - const resourcesToCreate = fs.readdirSync(targetPath); 28 - // 29 - resourcesToCreate.forEach((resource) => { 30 - const resourcePath = `${targetPath}/${resource}`; 31 - const resourceStats = fs.statSync(resourcePath); 32 - 33 - if (resourceStats.isDirectory()) { 34 - mkdir(`${CURRENT_DIRECTORY}/${projectName}/${resource}`); 35 - createResources( 36 - `${targetPath}/${resource}`, 37 - `${projectName}/${resource}` 38 - ); 39 - } else if (resourceStats.isFile()) { 40 - const fileExtension = getFileExtension(resourcePath); 41 - 42 - if (isMediaFile(fileExtension)) { 43 - copy(resourcePath, `${CURRENT_DIRECTORY}/${projectName}/${resource}`); 44 - } else { 45 - // 46 - if (resource === 'package.json') { 47 - const packageJSON = JSON.parse( 48 - readFile(`${targetPath}/package.json`) 49 - ); 50 - packageJSON.name = projectName; 51 - writeFile( 52 - `${CURRENT_DIRECTORY}/${projectName}/package.json`, 53 - JSON.stringify(packageJSON, null, 2) 54 - ); 55 - } else { 56 - if (resource === 'gitignore') { 57 - resource = '.gitignore'; 58 - } 59 - const fileContent = readFile(resourcePath); 60 - const writePath = `${CURRENT_DIRECTORY}/${projectName}/${resource}`; 61 - writeFile(writePath, fileContent); 62 - } 63 - } 64 - } 65 - }); 66 - } 67 - function installDependencies(projectName) { 68 - const installed = runCommand(`cd ${projectName} && npm i`); 69 - if (!installed) warn('Unable to install dependencies'); 70 - } 71 - function initRepo(projectName) { 72 - const installed = runCommand(`cd ${projectName} && git init`); 73 - if (!installed) warn('Unable to init repo'); 74 - } 75 - function outro(projectName) { 76 - log(''); 77 - success(' Ready! '); 78 - log(`- cd ${projectName}`); 79 - log(`- npm run dev`); 80 - } 81 - 82 - export const actions = { 83 - intro, 84 - createResources, 85 - initRepo, 86 - installDependencies, 87 - outro, 88 - };
+38
src/actions/clean-template.js
··· 1 + import { 2 + getResourceStats, 3 + readDirectory, 4 + readFile, 5 + removeFolder, 6 + writeFile, 7 + } from "../utils.js"; 8 + 9 + export default function cleanTemplate(templatePath, projectName) { 10 + const resources = readDirectory(templatePath); 11 + resources.forEach((resource) => { 12 + const resourcePath = `${templatePath}/${resource}`; 13 + const resourceStats = getResourceStats(resourcePath); 14 + if (resourceStats.isDirectory()) { 15 + switch (resource) { 16 + case ".github": 17 + case ".git": 18 + removeFolder(resourcePath); 19 + break; 20 + } 21 + } else if (resourceStats.isFile()) { 22 + switch (resource) { 23 + case "package.json": 24 + const packageJSON = JSON.parse(readFile(resourcePath)); 25 + packageJSON.name = projectName; 26 + packageJSON.version = "0.0.0"; 27 + packageJSON.description = ""; 28 + packageJSON.author = ""; 29 + packageJSON.license = ""; 30 + packageJSON.homepage = ""; 31 + packageJSON.repository = {}; 32 + packageJSON.bugs = {}; 33 + writeFile(resourcePath, JSON.stringify(packageJSON, null, 2)); 34 + break; 35 + } 36 + } 37 + }); 38 + }
+14
src/actions/clone-repo.js
··· 1 + import { printLine, terminal, warn } from "../console.js"; 2 + import { runCommand } from "../utils.js"; 3 + 4 + export default async function cloneRepo(repoName, projectName) { 5 + const cloned = await runCommand( 6 + `git clone --depth 1 ${repoName} ${projectName}` 7 + ); 8 + if (!cloned) { 9 + printLine(); 10 + terminal.warning("Unable to clone repo."); 11 + printLine(); 12 + } 13 + return cloned; 14 + }
+24
src/actions/initialize-repository.js
··· 1 + import { confirm } from "@inquirer/prompts"; 2 + import { byeWithActions, transformerConfirm } from "../utils.js"; 3 + import { terminal } from "../console.js"; 4 + 5 + export default async function initRepo(projectName, git, actions) { 6 + const confirmInitRepo = await confirm({ 7 + message: "Initialize a new git repository?", 8 + transformer: transformerConfirm, 9 + }).catch((e) => { 10 + byeWithActions(actions); 11 + }); 12 + 13 + if (confirmInitRepo) { 14 + try { 15 + const spinner = terminal.spinner("Initializing..."); 16 + git.cwd(projectName); 17 + await git.init(); 18 + actions.repoInit = true; 19 + spinner.succeed("Repository initialized."); 20 + } catch (error) { 21 + spinner.fail("Unable to initialize repository."); 22 + } 23 + } 24 + }
+34
src/actions/install-dependencies.js
··· 1 + import { confirm } from "@inquirer/prompts"; 2 + import { byeWithActions, runCommand, transformerConfirm } from "../utils.js"; 3 + import { terminal } from "../console.js"; 4 + 5 + export default async function installDependencies(projectName, actions) { 6 + const confirmInstallDependencies = await confirm({ 7 + message: "Install dependencies?", 8 + transformer: transformerConfirm, 9 + }).catch((e) => { 10 + byeWithActions(actions); 11 + }); 12 + 13 + if (confirmInstallDependencies) { 14 + const spinner = terminal.spinner("Installing dependencies..."); 15 + try { 16 + await runCommand("npm install", projectName); 17 + actions.dependencies = true; 18 + spinner.succeed("Dependencies installed."); 19 + } catch (error) { 20 + spinner.fail("Unable to install dependencies."); 21 + const printLog = await confirm({ 22 + message: "print logs?", 23 + transformer: transformerConfirm, 24 + }).catch((e) => { 25 + byeWithActions(actions); 26 + }); 27 + if (printLog) { 28 + setTimeout(() => { 29 + terminal.print(error); 30 + }, 0.5 * 1000); 31 + } 32 + } 33 + } 34 + }
+21
src/actions/intro.js
··· 1 + import { terminal, textBlue, textGray } from "../console.js"; 2 + 3 + export default function intro() { 4 + const sword = ` 5 + /| ________________ 6 + O|===|${textBlue("*")} >________________> 7 + \\| 8 + `; 9 + // 10 + terminal.line(); 11 + // terminal.blank(); 12 + terminal.print(" Welcome to Excalibur JS! ", textBlue); 13 + terminal.print(sword); 14 + terminal.print( 15 + " Your friendly TypeScript 2D game engine for the web.", 16 + textGray 17 + ); 18 + terminal.blank(); 19 + terminal.blank(); 20 + // terminal.printLine(); 21 + }
+48 -10
src/console.js
··· 1 - import { Chalk } from 'chalk'; 1 + import { Chalk } from "chalk"; 2 + import ora from "ora"; 2 3 3 4 const customChalk = new Chalk({ level: 2 }); 4 5 5 - export const log = console.log; 6 - export function info(message) { 7 - log(customChalk.bgBlue(message)); 6 + export const terminal = { 7 + padding: { 8 + title: " ".repeat(1), 9 + subtitle: " ".repeat(3), 10 + itemList: " ".repeat(4), 11 + }, 12 + spinner: function (text) { 13 + return ora(text).start(); 14 + }, 15 + title: function (text, color) { 16 + console.log(terminal.padding.title, color(text)); 17 + }, 18 + subtitle: function (text, color = textWhite) { 19 + console.log(terminal.padding.subtitle, color(text)); 20 + }, 21 + listItem: function (item) { 22 + const { text, textRelevant, colorRelevant = textBlue } = item; 23 + console.log( 24 + terminal.padding.itemList, 25 + "-", 26 + textGray(text), 27 + colorRelevant(textRelevant) 28 + ); 29 + }, 30 + blank: () => console.log(""), 31 + line: (symbol = "-") => console.log(symbol.repeat(65)), 32 + print: (text, color = textWhite) => console.log(color(text)), 33 + warning: function (text) { 34 + console.log(customChalk.bgYellow.underline.bold(text)); 35 + }, 36 + }; 37 + // 38 + export function success(text) { 39 + return customChalk.bgGreenBright(text); 8 40 } 9 - export function warn(message) { 10 - log(customChalk.yellow(message)); 41 + export function textYellow(text) { 42 + return customChalk.yellow(text); 43 + } 44 + export function textBlue(text) { 45 + return customChalk.blue(text); 11 46 } 12 - export function alert(message) { 13 - log(customChalk.bgRed(message)); 47 + export function textGray(text) { 48 + return customChalk.gray(text); 14 49 } 15 - export function success(message) { 16 - log(customChalk.greenBright(message)); 50 + export function textWhite(text) { 51 + return customChalk.whiteBright(text); 52 + } 53 + export function textMagenta(text) { 54 + return customChalk.magenta(text); 17 55 }
+192
src/constants.js
··· 1 + import { createNewGame } from "./flows/create-new-game.js"; 2 + import { inspectGame } from "./flows/inspect-game.js"; 3 + export const FLOW_CHOICES = [ 4 + { 5 + name: "Create a game", 6 + value: "create", 7 + // description: "[ Start a new project ]", 8 + }, 9 + { 10 + name: "Inspect a game", 11 + value: "inspect", 12 + // description: "[ Check games already build with Excalibur ]", 13 + }, 14 + ]; 15 + export const FLOWS = { 16 + create: createNewGame, 17 + inspect: inspectGame, 18 + }; 19 + export const TEMPLATES = [ 20 + { 21 + name: "Typescript with Browserify", 22 + value: "javascript_browserify", 23 + description: "", 24 + repo: "https://github.com/excaliburjs/template-ts-browserify.git", 25 + startCommand: "npm run all", 26 + }, 27 + { 28 + name: "Typescript with Parcel", 29 + value: "typescript_parcel", 30 + description: "", 31 + repo: "https://github.com/excaliburjs/template-ts-parcel-v2.git", 32 + startCommand: "npm run start", 33 + }, 34 + { 35 + name: "Typescript with Rollup", 36 + value: "typescript_rollup", 37 + description: "", 38 + repo: "https://github.com/excaliburjs/template-ts-rollup.git", 39 + startCommand: "npm run start", 40 + }, 41 + 42 + { 43 + name: "Typescript with Vite", 44 + value: "typescript_vite", 45 + description: "", 46 + repo: "https://github.com/excaliburjs/template-ts-vite.git", 47 + startCommand: "npm run dev", 48 + }, 49 + { 50 + name: "Typescript with Webpack", 51 + value: "typescript_webpack", 52 + description: "", 53 + repo: "https://github.com/excaliburjs/template-ts-webpack.git", 54 + startCommand: "npm run dev", 55 + }, 56 + 57 + { 58 + name: "Javascript with Electron", 59 + value: "javascript_electron", 60 + description: "", 61 + repo: "https://github.com/excaliburjs/template-electron.git", 62 + startCommand: "npm run start", 63 + }, 64 + ]; 65 + export const SHOWCASES = [ 66 + { 67 + title: "Excali-Farm", 68 + value: "Excali-Farm", 69 + 70 + description: 71 + "A little farming game created with the Excalibur Tiled plugin. Come and enjoy the supremely cozy vibes on this lovely farm.", 72 + url: "https://excali-farm.netlify.app/", 73 + source: "https://github.com/mahbarahona/tiles", 74 + }, 75 + { 76 + title: "Retro Ski", 77 + value: "Retro Ski", 78 + 79 + description: 80 + "Put your skis and let's race some randomly generated alpine ski tracks with your friends.", 81 + url: "https://une-entreprise.ch/retroski/", 82 + source: "https://github.com/mathieuher/RetroSki", 83 + }, 84 + { 85 + title: "Night Bike", 86 + value: "Night Bike", 87 + 88 + description: 89 + "Ride your motorcycle through the city at night! Jump over as many vehicles as you can!", 90 + url: "https://nightbike.mattjennin.gs/", 91 + source: "https://github.com/mattjennings/nightbike", 92 + }, 93 + { 94 + title: "Super Metronome Hero", 95 + value: "Super Metronome Hero", 96 + 97 + description: 98 + "Super Metronome Hero is a rhythm game. Rather than trying to make a game I wanted to play out of this rather popular genre I decided to send it up with this nonsense joke game.", 99 + url: "https://super-metronome-hero.vidja.games/", 100 + source: "https://github.com/dcgw/super-metronome-hero", 101 + }, 102 + { 103 + title: "Sword Adventure", 104 + value: "Sword Adventure", 105 + 106 + description: 107 + "This game is an easily customizable template for developing an Excalibur game with Ionic React, the XState library, i18next, Twine integration for dynamic dialogs, and CapacitorJS. It comes with scene transitions, data persistence and tutorials included!", 108 + url: "https://nicastro.in/excalibur-games/sword-adventure", 109 + source: "https://github.com/facondiaGames/sword-adventure", 110 + }, 111 + { 112 + title: "Beach Breach", 113 + value: "Beach Breach", 114 + 115 + description: 116 + "This game was created for Ludum Dare 50. Defend your sandcastle from crabs, turtles, and seagulls in this beach-themed tower defense game!", 117 + url: "http://excaliburjs.com/ludum-50/", 118 + source: "https://github.com/excaliburjs/ludum-50", 119 + }, 120 + { 121 + title: "Meerkattica", 122 + value: "Meerkattica", 123 + 124 + description: 125 + "This game was created for Ludum Dare 48. Dig deeply and quickly to get to your next metal gig, but watch out for the mechanical snake that pursues you!", 126 + url: "http://excaliburjs.com/ludum-48/", 127 + source: "https://github.com/excaliburjs/ludum-48", 128 + }, 129 + { 130 + title: "The Show Must Go On", 131 + value: "The Show Must Go On", 132 + 133 + description: 134 + "This game was created for Ludum Dare 46. Keep the theater production going as the only actor!", 135 + url: "http://excaliburjs.com/ludum-46/", 136 + source: "https://github.com/excaliburjs/ludum-46", 137 + }, 138 + { 139 + title: "Office Daydream", 140 + value: "Office Daydream", 141 + 142 + description: 143 + "This game was created for Ludum Dare 41. Balance the needs of your job with the needs of your dream!", 144 + url: "http://excaliburjs.com/ludum-41/", 145 + source: "https://github.com/excaliburjs/ludum-41", 146 + }, 147 + { 148 + title: "I Just Wanted Groceries", 149 + value: "I Just Wanted Groceries", 150 + 151 + description: 152 + "This game was created for Ludum Dare 38. Avoid talking to people and finish your shopping!", 153 + url: "http://excaliburjs.com/ludum-38/", 154 + source: "https://github.com/excaliburjs/ludum-38", 155 + }, 156 + { 157 + title: "Hexshaper", 158 + value: "Hexshaper", 159 + 160 + description: 161 + "This game was created for Ludum Dare 35. Absorb enemy projectiles and close the portals!", 162 + url: "http://excaliburjs.com/ludum-35/", 163 + source: "https://github.com/excaliburjs/ludum-35", 164 + }, 165 + { 166 + title: "Crypt of the Minotaur", 167 + value: "Crypt of the Minotaur", 168 + 169 + description: 170 + "This game was created for Ludum Dare 33. Play as the Minotaur to defend your treasure!", 171 + url: "http://excaliburjs.com/ludum-33/", 172 + source: "https://github.com/excaliburjs/ludum-33", 173 + }, 174 + { 175 + title: "Sweep Stacks", 176 + value: "Sweep Stacks", 177 + 178 + description: 179 + "This game was created for Ludum Dare 31. Sweep across the screen to clear blocks!", 180 + url: "http://excaliburjs.com/sweep/", 181 + source: "https://github.com/excaliburjs/sweep", 182 + }, 183 + { 184 + title: "Kraken Unchained", 185 + value: "Kraken Unchained", 186 + 187 + description: 188 + "This game was created for Ludum Dare 29. Play as the Kraken and destroy ships!", 189 + url: "http://krakenunchained.azurewebsites.net", 190 + source: "https://github.com/excaliburjs/Ludum-29", 191 + }, 192 + ];
+84
src/flows/create-new-game.js
··· 1 + import { input, select } from "@inquirer/prompts"; 2 + import simpleGit from "simple-git"; 3 + import cleanTemplate from "../actions/clean-template.js"; 4 + import { 5 + printDependencyStatus, 6 + printProjectDirectory, 7 + printRepoStatus, 8 + printSupport, 9 + slugify, 10 + validateProjectName, 11 + } from "../utils.js"; 12 + import { success, terminal } from "../console.js"; 13 + import initRepo from "../actions/initialize-repository.js"; 14 + import installDependencies from "../actions/install-dependencies.js"; 15 + import { TEMPLATES } from "../constants.js"; 16 + 17 + function outro(actions) { 18 + const { projectDirectory, startCommand, dependencies, repoInit } = actions; 19 + terminal.line(); 20 + terminal.title(" Project configured. ", success); 21 + terminal.blank(); 22 + printProjectDirectory(projectDirectory); 23 + 24 + terminal.listItem({ 25 + text: "Run your project:", 26 + textRelevant: startCommand, 27 + }); 28 + printDependencyStatus(dependencies); 29 + printRepoStatus(repoInit); 30 + terminal.blank(); 31 + printSupport(); 32 + terminal.blank(); 33 + terminal.line(); 34 + terminal.blank(); 35 + } 36 + export async function createNewGame() { 37 + const projectName = slugify( 38 + await input({ 39 + message: "Name your project:", 40 + transformer: slugify, 41 + validate: validateProjectName, 42 + }) 43 + ); 44 + const fullPath = `${process.cwd()}/${projectName}`; 45 + const templateValue = await select({ 46 + message: "Choose your stack:", 47 + choices: TEMPLATES, 48 + }); 49 + const template = TEMPLATES.find((item) => item.value === templateValue); 50 + const git = simpleGit(); 51 + const spinner = terminal.spinner("Preparing files..."); 52 + git.clone(template.repo, projectName, async (err) => { 53 + if (err) { 54 + terminal.blank(); 55 + terminal.warning("Error:"); 56 + terminal.print(err.message); 57 + spinner.fail("Failed prepare files."); 58 + process.exit(1); 59 + } 60 + 61 + // clean files 62 + try { 63 + cleanTemplate(fullPath, projectName); 64 + spinner.succeed("Files configured."); 65 + } catch (error) { 66 + spinner.fail("Unable to config files."); 67 + process.exit(1); 68 + } 69 + 70 + let actions = { 71 + projectDirectory: projectName, 72 + startCommand: template.startCommand, 73 + dependencies: false, 74 + repoInit: false, 75 + }; 76 + 77 + await installDependencies(projectName, actions); 78 + await initRepo(projectName, git, actions); 79 + 80 + setTimeout(() => { 81 + outro(actions); 82 + }, 0.6 * 1000); 83 + }); 84 + }
+75
src/flows/inspect-game.js
··· 1 + import { input, select } from "@inquirer/prompts"; 2 + import { success, terminal } from "../console.js"; 3 + import { 4 + printDependencyStatus, 5 + printProjectDirectory, 6 + printSupport, 7 + slugify, 8 + sortByProp, 9 + } from "../utils.js"; 10 + import simpleGit from "simple-git"; 11 + import installDependencies from "../actions/install-dependencies.js"; 12 + import { SHOWCASES } from "../constants.js"; 13 + 14 + function getGames() { 15 + const sorted = SHOWCASES.sort((a, b) => sortByProp(a, b, "title")); 16 + const indexed = sorted.map((game, i) => { 17 + game.name = `${i + 1}. ${game.title} `; 18 + return game; 19 + }); 20 + return indexed; 21 + } 22 + function outro(actions) { 23 + const { projectDirectory, dependencies, gameURL } = actions; 24 + terminal.line(); 25 + terminal.title(" Project downloaded.", success); 26 + terminal.blank(); 27 + printProjectDirectory(projectDirectory); 28 + printDependencyStatus(dependencies); 29 + terminal.listItem({ text: "Game:", textRelevant: gameURL }); 30 + terminal.blank(); 31 + printSupport(); 32 + terminal.blank(); 33 + terminal.line(); 34 + terminal.blank(); 35 + } 36 + export async function inspectGame() { 37 + const GAMES = getGames(); 38 + const gameValue = await select({ 39 + message: `${GAMES.length} games:`, 40 + choices: GAMES, 41 + }); 42 + const game = GAMES.find((game) => game.value === gameValue); 43 + let targetFolder = slugify( 44 + await input({ 45 + message: "Target folder:", 46 + transformer: slugify, 47 + }) 48 + ); 49 + if (!targetFolder) targetFolder = slugify(game.title); 50 + console.log(targetFolder); 51 + 52 + const git = simpleGit(); 53 + const spinner = terminal.spinner("Cloning repository..."); 54 + git.clone(game.source, targetFolder, async (err) => { 55 + if (err) { 56 + terminal.blank(); 57 + terminal.warning("Error:"); 58 + terminal.print(err.message); 59 + spinner.fail("Unable to clone repository"); 60 + process.exit(1); 61 + } 62 + spinner.succeed("Game downloaded."); 63 + let actions = { 64 + projectDirectory: targetFolder, 65 + dependencies: false, 66 + gameURL: game.url, 67 + }; 68 + 69 + await installDependencies(targetFolder, actions); 70 + // 71 + setTimeout(() => { 72 + outro(actions); 73 + }, 0.6 * 1000); 74 + }); 75 + }
-24
src/templates/javascript/gitignore
··· 1 - # Logs 2 - logs 3 - *.log 4 - npm-debug.log* 5 - yarn-debug.log* 6 - yarn-error.log* 7 - pnpm-debug.log* 8 - lerna-debug.log* 9 - 10 - node_modules 11 - dist 12 - dist-ssr 13 - *.local 14 - 15 - # Editor directories and files 16 - .vscode/* 17 - !.vscode/extensions.json 18 - .idea 19 - .DS_Store 20 - *.suo 21 - *.ntvs* 22 - *.njsproj 23 - *.sln 24 - *.sw?
-249
src/templates/javascript/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8" /> 5 - <link rel="shortcut icon" type="icon" href="/favicon.ico" /> 6 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 - <title>ExcaliburJS</title> 8 - <link rel="stylesheet" href="/style.css" /> 9 - </head> 10 - <body> 11 - <!-- --> 12 - <div id="welcome"> 13 - <h1> 14 - <div class="logo"> 15 - <img 16 - src="https://excaliburjs.com/img/ex-logo.png" 17 - width="32" 18 - alt="" 19 - /> 20 - <a 21 - href="https://excaliburjs.com/docs/getting-started" 22 - target="_blank" 23 - > 24 - ExcaliburJS 25 - </a> 26 - </div> 27 - </h1> 28 - 29 - <nav> 30 - <a 31 - class="nav-link" 32 - href="https://excaliburjs.com/docs/category/fundamentals" 33 - target="_blank" 34 - >Documentation 35 - </a> 36 - <a 37 - class="nav-link" 38 - href="https://excaliburjs.com/samples" 39 - target="_blank" 40 - >Samples 41 - </a> 42 - <a 43 - class="nav-link" 44 - href="https://discord.com/invite/W6zUd4tTY3" 45 - target="_blank" 46 - ><svg 47 - xmlns="http://www.w3.org/2000/svg" 48 - width="24" 49 - height="24" 50 - fill="currentColor" 51 - class="bi bi-discord" 52 - viewBox="0 0 16 16" 53 - > 54 - <path 55 - d="M13.545 2.907a13.2 13.2 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.2 12.2 0 0 0-3.658 0 8 8 0 0 0-.412-.833.05.05 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.04.04 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032q.003.022.021.037a13.3 13.3 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019q.463-.63.818-1.329a.05.05 0 0 0-.01-.059l-.018-.011a9 9 0 0 1-1.248-.595.05.05 0 0 1-.02-.066l.015-.019q.127-.095.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.05.05 0 0 1 .053.007q.121.1.248.195a.05.05 0 0 1-.004.085 8 8 0 0 1-1.249.594.05.05 0 0 0-.03.03.05.05 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.2 13.2 0 0 0 4.001-2.02.05.05 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.03.03 0 0 0-.02-.019m-8.198 7.307c-.789 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612" 56 - /> 57 - </svg> 58 - </a> 59 - <a 60 - class="nav-link" 61 - href="https://github.com/excaliburjs/Excalibur" 62 - target="_blank" 63 - ><svg 64 - xmlns="http://www.w3.org/2000/svg" 65 - width="24" 66 - height="24" 67 - fill="currentColor" 68 - class="bi bi-github" 69 - viewBox="0 0 16 16" 70 - > 71 - <path 72 - d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8" 73 - /> 74 - </svg> 75 - </a> 76 - </nav> 77 - </div> 78 - <div id="game" class="LOADING"> 79 - <canvas id="main-canvas" class="gradient-background"></canvas> 80 - <div id="ui"> 81 - <div class="panels"> 82 - <div class="ready-container"> 83 - <button id="btn-play" class="play">Play</button> 84 - </div> 85 - <div class="playing-container"> 86 - <div class="left"> 87 - <p id="label-level"></p> 88 - <p id="label-score"></p> 89 - </div> 90 - 91 - <div class="center"> 92 - <p>HIGHSCORE</p> 93 - <p id="label-high-score"></p> 94 - </div> 95 - 96 - <div class="right"> 97 - <p>Lives</p> 98 - <p id="label-balls"></p> 99 - </div> 100 - </div> 101 - <div class="gameover-container"> 102 - <button id="btn-retry" class="retry">Retry</button> 103 - </div> 104 - <div class="completed-container"> 105 - <button id="btn-next-level" class="next-level">Next level</button> 106 - </div> 107 - <div class="done-container"> 108 - <p>🎉Game completed!🎉</p> 109 - <button id="btn-done" class="restart">Reset the Game</button> 110 - </div> 111 - </div> 112 - </div> 113 - </div> 114 - <div id="credits"> 115 - <details> 116 - <summary>💖 Credits</summary> 117 - <div class="content"> 118 - <div> 119 - <p class="type">Sprites:</p> 120 - <ul> 121 - <li> 122 - "breakout-assets" by 123 - <a 124 - href="https://megacrash.itch.io/breakout-assets" 125 - target="_blank" 126 - > 127 - MegaCrash </a 128 - >CC0 129 - </li> 130 - <!-- --> 131 - </ul> 132 - </div> 133 - <br /> 134 - <div class="row"> 135 - <div> 136 - <p class="type">Music:</p> 137 - <ul> 138 - <li> 139 - "Invaderloop" by 140 - <a 141 - href="https://opengameart.org/content/invaderloop" 142 - target="_blank" 143 - > 144 - symphony</a 145 - > 146 - CC0 147 - </li> 148 - <li> 149 - "Cyberpunk Moonlight Sonata" by 150 - <a 151 - href="https://opengameart.org/content/cyberpunk-moonlight-sonata" 152 - target="_blank" 153 - > 154 - Joth</a 155 - > 156 - CC0 157 - </li> 158 - <li> 159 - "Harmonic Minor" by 160 - <a 161 - href="https://opengameart.org/content/and-the-console-hit-the-floor-electronic" 162 - target="_blank" 163 - > 164 - Alex McCulloch</a 165 - > 166 - CC0 167 - </li> 168 - <li> 169 - "Open Warfare" by 170 - <a 171 - href="https://opengameart.org/content/open-warfare" 172 - target="_blank" 173 - > 174 - Ruskerdax</a 175 - > 176 - CC0 177 - </li> 178 - <li> 179 - "Futuristic-resources" by 180 - <a 181 - href="https://opengameart.org/content/futuristic-resources" 182 - target="_blank" 183 - > 184 - section31</a 185 - > 186 - CC0 187 - </li> 188 - </ul> 189 - </div> 190 - 191 - <div> 192 - <p class="type">Sound Effects:</p> 193 - <ul> 194 - <li> 195 - "win-sound-effect" by 196 - <a 197 - href="https://opengameart.org/content/win-sound-effect" 198 - target="_blank" 199 - > 200 - Listener</a 201 - > 202 - CC0 203 - </li> 204 - <li> 205 - "8bit-death-whirl" by 206 - <a 207 - href="https://opengameart.org/content/8bit-death-whirl" 208 - target="_blank" 209 - > 210 - Fupi</a 211 - > 212 - CC0 213 - </li> 214 - <li> 215 - "player hit (damage)" by 216 - <a 217 - href="https://opengameart.org/content/player-hit-damage" 218 - target="_blank" 219 - > 220 - GreyFrogGames</a 221 - > 222 - CC0 223 - </li> 224 - <li> 225 - "hit sound bitcrush" by 226 - <a 227 - href="https://opengameart.org/content/hit-sound-bitcrush" 228 - target="_blank" 229 - > 230 - DaGameKnower</a 231 - > 232 - CC0 233 - </li> 234 - <li> 235 - "hit" by 236 - <a href="https://erikonarheim.com" target="_blank"> 237 - Erik Onarheim</a 238 - > 239 - CC0 240 - </li> 241 - </ul> 242 - </div> 243 - </div> 244 - </div> 245 - </details> 246 - </div> 247 - <script type="module" src="/src/main.js"></script> 248 - </body> 249 - </html>
-15
src/templates/javascript/package.json
··· 1 - { 2 - "name": "js-starter", 3 - "private": true, 4 - "version": "0.0.0", 5 - "type": "module", 6 - "scripts": { 7 - "dev": "vite", 8 - "build": "vite build", 9 - "preview": "vite preview" 10 - }, 11 - "devDependencies": { 12 - "@excaliburjs/dev-tools": "^0.28.0", 13 - "vite": "^5.0.8" 14 - } 15 - }
src/templates/javascript/public/favicon.ico

This is a binary file and will not be displayed.

src/templates/javascript/public/sample/music/Cyberpunk Moonlight Sonata.mp3

This is a binary file and will not be displayed.

src/templates/javascript/public/sample/music/HarmonicMinor.wav

This is a binary file and will not be displayed.

src/templates/javascript/public/sample/music/S31-Futuristic-Resources.ogg

This is a binary file and will not be displayed.

src/templates/javascript/public/sample/music/cubedcanada+invaderloop.mp3

This is a binary file and will not be displayed.

src/templates/javascript/public/sample/music/ruskerdax_-_open_warfare.mp3

This is a binary file and will not be displayed.

src/templates/javascript/public/sample/sounds/ball_drop.mp3

This is a binary file and will not be displayed.

src/templates/javascript/public/sample/sounds/explosion.wav

This is a binary file and will not be displayed.

src/templates/javascript/public/sample/sounds/hit.wav

This is a binary file and will not be displayed.

src/templates/javascript/public/sample/sounds/hit2.wav

This is a binary file and will not be displayed.

src/templates/javascript/public/sample/sounds/vgdeathsound.wav

This is a binary file and will not be displayed.

src/templates/javascript/public/sample/sounds/win.wav

This is a binary file and will not be displayed.

src/templates/javascript/public/sample/sprites/breakout_sprite.png

This is a binary file and will not be displayed.

-3
src/templates/javascript/src/main.js
··· 1 - import { gameManager } from './sample/managers/game.manager.js'; 2 - 3 - gameManager.init();
-36
src/templates/javascript/src/sample/actors/ball.actor.js
··· 1 - import { Actor, Animation, CollisionType, SpriteSheet, range } from 'excalibur'; 2 - import { assetManager } from '../managers/asset.manager.js'; 3 - 4 - export class Ball extends Actor { 5 - constructor(x, y, radius, vel) { 6 - super({ x, y, radius, vel }); 7 - this.ballSpeed = vel; 8 - this.isColliding = false; 9 - } 10 - 11 - onInitialize(engine) { 12 - // 13 - this.body.collisionType = CollisionType.Passive; 14 - this.setSprite(); 15 - } 16 - 17 - setSprite() { 18 - const sprite = SpriteSheet.fromImageSource({ 19 - image: assetManager.images.spritesheet, 20 - grid: { 21 - rows: 1, 22 - columns: 1, 23 - spriteWidth: 16, 24 - spriteHeight: 16, 25 - }, 26 - spacing: { 27 - originOffset: { x: 32 * 6, y: 256 - 16 }, 28 - }, 29 - }); 30 - const frames = range(0, 0); 31 - const animationMs = 0; 32 - const ballAnim = Animation.fromSpriteSheet(sprite, frames, animationMs); 33 - // 34 - this.graphics.use(ballAnim); 35 - } 36 - }
-100
src/templates/javascript/src/sample/actors/brick.actor.js
··· 1 - import { 2 - Actor, 3 - Animation, 4 - CollisionType, 5 - SpriteSheet, 6 - range, 7 - vec, 8 - } from 'excalibur'; 9 - 10 - import { assetManager } from '../managers/asset.manager.js'; 11 - 12 - export class Brick extends Actor { 13 - constructor(x, y, width, height, type) { 14 - super({ x, y, width, height }); 15 - this.type = type; 16 - this.spriteStatus = 32 * 8; 17 - switch (this.type) { 18 - case '1': 19 - this.life = 1; 20 - this.spriteType = 1; 21 - this.hitScore = 100; 22 - this.destroyScore = this.life * 200; 23 - 24 - break; 25 - case '2': 26 - this.life = 2; 27 - this.spriteType = 2; 28 - this.hitScore = 250; 29 - this.destroyScore = this.life * 200; 30 - 31 - break; 32 - case '3': 33 - this.life = 3; 34 - this.spriteType = 3; 35 - this.hitScore = 250; 36 - this.destroyScore = this.life * 200; 37 - 38 - break; 39 - case '4': 40 - this.life = 4; 41 - this.spriteType = 4; 42 - this.hitScore = 350; 43 - this.destroyScore = this.life * 200; 44 - 45 - break; 46 - case '5': 47 - this.life = 5; 48 - this.spriteType = 5; 49 - this.hitScore = 350; 50 - this.destroyScore = this.life * 200; 51 - 52 - break; 53 - case '6': 54 - this.life = 6; 55 - this.spriteType = 6; 56 - this.hitScore = 350; 57 - this.destroyScore = this.life * 200; 58 - 59 - break; 60 - } 61 - } 62 - onInitialize(engine) { 63 - this.body.collisionType = CollisionType.Passive; 64 - this.setSprite(); 65 - this.scale = vec(2, 2); 66 - } 67 - setSprite() { 68 - const sprite = SpriteSheet.fromImageSource({ 69 - image: assetManager.images.spritesheet, 70 - grid: { 71 - rows: 1, 72 - columns: 1, 73 - spriteWidth: 32, 74 - spriteHeight: 16, 75 - }, 76 - spacing: { 77 - originOffset: { x: this.spriteStatus, y: this.spriteType * 16 }, 78 - }, 79 - }); 80 - const frames = range(0, 0); 81 - const animationMs = 100; 82 - const brickAnim = Animation.fromSpriteSheet(sprite, frames, animationMs); 83 - this.graphics.use(brickAnim); 84 - } 85 - updateSprite() { 86 - switch (this.life) { 87 - case 3: 88 - this.spriteStatus = 32 * 8; 89 - break; 90 - case 2: 91 - this.spriteStatus = 32 * 9; 92 - break; 93 - case 1: 94 - this.spriteStatus = 32 * 10; 95 - break; 96 - } 97 - 98 - this.setSprite(); 99 - } 100 - }
-41
src/templates/javascript/src/sample/actors/paddle.actor.js
··· 1 - import { 2 - Actor, 3 - Animation, 4 - CollisionType, 5 - SpriteSheet, 6 - range, 7 - vec, 8 - } from 'excalibur'; 9 - import { assetManager } from '../managers/asset.manager.js'; 10 - 11 - export class Paddle extends Actor { 12 - constructor(x, y, width, height, speed) { 13 - super({ x, y, width, height }); 14 - this.speed = speed; 15 - this.name = 'paddle'; 16 - } 17 - onInitialize(engine) { 18 - this.body.collisionType = CollisionType.Passive; 19 - this.setSprite(); 20 - this.scale = vec(2, 2); 21 - } 22 - setSprite() { 23 - const sprite = SpriteSheet.fromImageSource({ 24 - image: assetManager.images.spritesheet, 25 - grid: { 26 - rows: 4, 27 - columns: 14, 28 - spriteWidth: 32, 29 - spriteHeight: 16, 30 - }, 31 - spacing: { 32 - originOffset: { x: 48, y: 256 - 16 }, 33 - }, 34 - }); 35 - const frames = range(0, 0); 36 - const animationMs = 0; 37 - const paddleAnim = Animation.fromSpriteSheet(sprite, frames, animationMs); 38 - // 39 - this.graphics.use(paddleAnim); 40 - } 41 - }
-237
src/templates/javascript/src/sample/levels/levels.js
··· 1 - import { Color, vec } from 'excalibur'; 2 - import { LEVEL_MUSIC } from '../models.js'; 3 - 4 - const BALL_SPEED = { 5 - NORMAL: vec(250, -250), 6 - MEDIUM: vec(280, -280), 7 - HARD: vec(300, -300), 8 - }; 9 - const LEVEL_BG = { 10 - BLUE: { 11 - LIGHT: '#3f45f881', 12 - DARK: '#15188581', 13 - }, 14 - GREEN: { 15 - LIGHT: '#43e517ae', 16 - DARK: '#2a880fae', 17 - }, 18 - PINK: { 19 - LIGHT: '#dd0b9391', 20 - DARK: '#44223891', 21 - }, 22 - PURPLE: { 23 - LIGHT: '#9617f05e', 24 - DARK: '#250c6f5e', 25 - }, 26 - YELLOW: { 27 - LIGHT: '#f5f903ae', 28 - DARK: '#86880eae', 29 - }, 30 - ORANGE: { 31 - LIGHT: '#f57f0991', 32 - DARK: '#7f470f91', 33 - }, 34 - RED: { 35 - LIGHT: '#f41313ad', 36 - DARK: '#7b141e91', 37 - }, 38 - }; 39 - const EMPTY_ROW = ['', '', '', '', '', '', '', '']; 40 - const ROW_1 = ['1', '1', '1', '1', '1', '1', '1', '1']; 41 - const ROW_2 = ['2', '2', '2', '2', '2', '2', '2', '2']; 42 - const ROW_3 = ['3', '3', '3', '3', '3', '3', '3', '3']; 43 - const ROW_4 = ['4', '4', '4', '4', '4', '4', '4', '4']; 44 - const ROW_5 = ['5', '5', '5', '5', '5', '5', '5', '5']; 45 - const ROW_6 = ['6', '6', '6', '6', '6', '6', '6', '6']; 46 - // LEVELS 47 - const level001 = { 48 - name: 'level001', 49 - balls: 3, 50 - music: LEVEL_MUSIC.LEVEL_2, 51 - ballSpeed: BALL_SPEED.NORMAL, 52 - bgColor: Color.Transparent, 53 - bricksSetup: [EMPTY_ROW, ROW_1, ROW_2, ROW_1, EMPTY_ROW], 54 - }; 55 - const level002 = { 56 - name: 'level002', 57 - balls: 3, 58 - music: LEVEL_MUSIC.LEVEL_1, 59 - ballSpeed: BALL_SPEED.NORMAL, 60 - bgColor: Color.fromHex(LEVEL_BG.BLUE.LIGHT), 61 - bricksSetup: [ 62 - ['3', '', '3', '', '', '3', '', '3'], 63 - ['3', '', '3', '', '', '3', '', '3'], 64 - ['3', '', '3', '', '', '3', '', '3'], 65 - ['1', '1', '1', '1', '1', '1', '1', '1'], 66 - ], 67 - }; 68 - const level003 = { 69 - name: 'level003', 70 - balls: 3, 71 - music: LEVEL_MUSIC.LEVEL_3, 72 - ballSpeed: BALL_SPEED.NORMAL, 73 - bgColor: Color.fromHex(LEVEL_BG.BLUE.DARK), 74 - bricksSetup: [ 75 - ['3', '', '3', '', '3', '', '3', ''], 76 - ['', '3', '', '3', '', '3', '', '3'], 77 - ['', '2', '', '2', '', '2', '', '2'], 78 - ['', '2', '', '2', '', '2', '', '2'], 79 - ['', '1', '', '1', '', '1', '', '1'], 80 - ], 81 - }; 82 - const level004 = { 83 - name: 'level004', 84 - balls: 3, 85 - music: LEVEL_MUSIC.LEVEL_4, 86 - ballSpeed: BALL_SPEED.MEDIUM, 87 - bgColor: Color.fromHex(LEVEL_BG.GREEN.DARK), 88 - bricksSetup: [ 89 - ['6', '', '', '', '', '', '', '6'], 90 - ROW_3, 91 - ROW_3, 92 - ['6', '', '', '', '', '', '', '6'], 93 - ], 94 - }; 95 - const level005 = { 96 - name: 'level005', 97 - balls: 3, 98 - music: LEVEL_MUSIC.LEVEL_5, 99 - ballSpeed: BALL_SPEED.MEDIUM, 100 - bgColor: Color.fromHex(LEVEL_BG.GREEN.LIGHT), 101 - bricksSetup: [ 102 - ['1', '', '2', '3', '3', '2', '', '1'], 103 - ['1', '', '2', '3', '3', '2', '', '1'], 104 - ['1', '', '2', '3', '3', '2', '', '1'], 105 - EMPTY_ROW, 106 - ], 107 - }; 108 - const level006 = { 109 - name: 'level006', 110 - balls: 3, 111 - music: LEVEL_MUSIC.LEVEL_1, 112 - ballSpeed: BALL_SPEED.MEDIUM, 113 - bgColor: Color.fromHex(LEVEL_BG.PINK.DARK), 114 - 115 - bricksSetup: [ 116 - ['', '3', '3', '3', '3', '3', '3', ''], 117 - ['2', '', '', '4', '4', '', '', '2'], 118 - ['3', '', '', '4', '4', '', '', '3'], 119 - ['', '1', '1', '1', '1', '1', '1', ''], 120 - ], 121 - }; 122 - const level007 = { 123 - name: 'level007', 124 - balls: 3, 125 - music: LEVEL_MUSIC.LEVEL_5, 126 - ballSpeed: BALL_SPEED.MEDIUM, 127 - bgColor: Color.fromHex(LEVEL_BG.PURPLE.LIGHT), 128 - bricksSetup: [ 129 - ROW_5, 130 - ['5', '', '', '4', '4', '', '', '5'], 131 - ['5', '', '4', '4', '4', '4', '', '5'], 132 - ['5', '4', '4', '4', '4', '4', '4', '5'], 133 - ], 134 - }; 135 - const level008 = { 136 - name: 'level008', 137 - balls: 3, 138 - music: LEVEL_MUSIC.LEVEL_1, 139 - ballSpeed: BALL_SPEED.HARD, 140 - bgColor: Color.fromHex(LEVEL_BG.PURPLE.DARK), 141 - bricksSetup: [ 142 - ['', '1', '6', '6', '6', '1', '', ''], 143 - ['', '', '1', '6', '1', '', '', ''], 144 - ['3', '3', '', '1', '', '', '3', '3'], 145 - ['', '', '1', '', '1', '', '', ''], 146 - ], 147 - }; 148 - const level009 = { 149 - name: 'level009', 150 - balls: 3, 151 - music: LEVEL_MUSIC.LEVEL_2, 152 - ballSpeed: BALL_SPEED.HARD, 153 - bgColor: Color.fromHex(LEVEL_BG.YELLOW.DARK), 154 - bricksSetup: [ 155 - ['', '', '', '5', '', '5', '', '5'], 156 - ['', '', '5', '', '5', '', '5', ''], 157 - ['', '5', '', '5', '', '5', '', ''], 158 - ['5', '', '5', '', '5', '', '', ''], 159 - ], 160 - }; 161 - const level010 = { 162 - name: 'level010', 163 - balls: 3, 164 - music: LEVEL_MUSIC.LEVEL_2, 165 - ballSpeed: BALL_SPEED.HARD, 166 - bgColor: Color.fromHex(LEVEL_BG.ORANGE.DARK), 167 - 168 - bricksSetup: [ 169 - ['3', '3', '3', '5', '', '', '', '3'], 170 - ['3', '3', '5', '', '', '', '5', '3'], 171 - ['3', '5', '', '', '', '5', '3', '3'], 172 - ['5', '', '', '', '5', '3', '3', '3'], 173 - ], 174 - }; 175 - const level011 = { 176 - name: 'level011', 177 - balls: 3, 178 - music: LEVEL_MUSIC.LEVEL_3, 179 - ballSpeed: BALL_SPEED.HARD, 180 - bgColor: Color.fromHex(LEVEL_BG.ORANGE.LIGHT), 181 - bricksSetup: [ 182 - ['5', '', '6', '6', '6', '', '4', '4'], 183 - ['5', '', '1', '', '3', '', '4', ''], 184 - ['5', '', '1', '', '3', '', '3', ''], 185 - ['5', '5', '1', '', '3', '5', '4', ''], 186 - ], 187 - }; 188 - const level012 = { 189 - name: 'level012', 190 - balls: 3, 191 - music: LEVEL_MUSIC.LEVEL_4, 192 - ballSpeed: BALL_SPEED.HARD, 193 - bgColor: Color.fromHex(LEVEL_BG.RED.DARK), 194 - bricksSetup: [ 195 - ['5', '5', '', '', '', '', '5', '5'], 196 - ['', '', '6', '6', '6', '6', '', ''], 197 - ['5', '5', '', '', '', '', '5', '5'], 198 - ['', '1', '1', '1', '1', '1', '1', ''], 199 - ], 200 - }; 201 - const level013 = { 202 - name: 'level013', 203 - balls: 3, 204 - music: LEVEL_MUSIC.LEVEL_5, 205 - ballSpeed: BALL_SPEED.HARD, 206 - bgColor: Color.fromHex(LEVEL_BG.RED.LIGHT), 207 - bricksSetup: [ 208 - ['5', '5', '5', '5', '6', '6', '6', '6'], 209 - ['5', '6', '6', '5', '6', '5', '5', '6'], 210 - ['5', '6', '6', '5', '6', '5', '5', '6'], 211 - ['5', '5', '5', '5', '6', '6', '6', '6'], 212 - ], 213 - }; 214 - const level014 = { 215 - name: 'level014', 216 - balls: 3, 217 - music: LEVEL_MUSIC.LEVEL_3, 218 - ballSpeed: BALL_SPEED.HARD, 219 - bgColor: Color.fromHex(LEVEL_BG.RED.LIGHT), 220 - bricksSetup: [[ROW_4], [ROW_6], [ROW_1], [ROW_4]], 221 - }; 222 - export const levelsSeed = [ 223 - level001, 224 - level002, 225 - level003, 226 - level004, 227 - level005, 228 - level006, 229 - level007, 230 - level008, 231 - level009, 232 - level010, 233 - level011, 234 - level012, 235 - level013, 236 - level014, 237 - ];
-41
src/templates/javascript/src/sample/managers/asset.manager.js
··· 1 - import { ImageSource, Loader, Sound } from 'excalibur'; 2 - 3 - class AssetManager { 4 - constructor() {} 5 - init() { 6 - this.loader = new Loader(); 7 - this.loader.suppressPlayButton = true; 8 - this.loader.backgroundColor = 'black'; 9 - // 10 - this.images = { 11 - spritesheet: new ImageSource('/sample/sprites/breakout_sprite.png'), 12 - }; 13 - 14 - this.sounds = { 15 - levelMusicDefault: new Sound('/sample/music/HarmonicMinor.wav'), 16 - levelMusic1: new Sound('/sample/music/HarmonicMinor.wav'), 17 - levelMusic2: new Sound('/sample/music/Cyberpunk Moonlight Sonata.mp3'), 18 - levelMusic3: new Sound('/sample/music/cubedcanada+invaderloop.mp3'), 19 - levelMusic4: new Sound('/sample/music/S31-Futuristic-Resources.ogg'), 20 - levelMusic5: new Sound('/sample/music/ruskerdax_-_open_warfare.mp3'), 21 - // 22 - levelCompleted: new Sound('/sample/sounds/win.wav'), 23 - levelGameover: new Sound('/sample/sounds/vgdeathsound.wav'), 24 - brickDestroy: new Sound('/sample/sounds/explosion.wav'), 25 - brickHit: new Sound('/sample/sounds/hit2.wav'), 26 - ballDrop: new Sound('/sample/sounds/ball_drop.mp3'), 27 - paddleHit: new Sound('/sample/sounds/hit.wav'), 28 - }; 29 - // 30 - for (const key in this.images) { 31 - this.loader.addResource(this.images[key]); 32 - } 33 - for (const key in this.sounds) { 34 - const sound = this.sounds[key]; 35 - this.loader.addResource(sound); 36 - sound.volume = 0.2; 37 - } 38 - } 39 - } 40 - 41 - export const assetManager = new AssetManager();
-7
src/templates/javascript/src/sample/managers/audio.manager.js
··· 1 - class AudioManager { 2 - init() { 3 - console.warn('AudioManager not implemented'); 4 - } 5 - } 6 - 7 - export const audioManager = new AudioManager();
-170
src/templates/javascript/src/sample/managers/game.manager.js
··· 1 - import { DevTool } from '@excaliburjs/dev-tools'; 2 - import { Color, Engine } from 'excalibur'; 3 - 4 - import { GAME_STATES, SCENE_EVENTS, SCENE_STATE } from '../models.js'; 5 - import { uiManager } from './ui.manager.js'; 6 - import { levelManager } from './level.manager.js'; 7 - import { assetManager } from './asset.manager.js'; 8 - import { audioManager } from './audio.manager.js'; 9 - import { Subject } from '../utils.js'; 10 - 11 - class GameManager { 12 - constructor(engine) { 13 - this.game = engine; 14 - this.scoreGlobal = 0; 15 - this.scoreLevel = 0; 16 - this.highscore = 0; 17 - 18 - this.gameState = new Subject(); 19 - this.sceneState = new Subject(); 20 - } 21 - 22 - init() { 23 - this.setupListeners(); 24 - // start engine 25 - this.gameState.next(GAME_STATES.LOADING); 26 - } 27 - setupListeners() { 28 - eventBus.on(SCENE_EVENTS.UPDATE_BALL, (balls) => { 29 - uiManager.printBalls(balls); 30 - }); 31 - eventBus.on(SCENE_EVENTS.UPDATE_SCORE, (score) => { 32 - this.scoreLevel += score; 33 - uiManager.printScore(this.scoreLevel); 34 - }); 35 - 36 - this.gameState.onChange((newGameState) => { 37 - console.log(`[${newGameState}]`); 38 - switch (newGameState) { 39 - case GAME_STATES.LOADING: 40 - assetManager.init(); 41 - levelManager.init(); 42 - audioManager.init(); 43 - uiManager.init(); 44 - this.loadLevels(); 45 - this.activateDebugMode(); 46 - this.game.start(assetManager.loader).then(() => { 47 - this.gameState.next(GAME_STATES.READY); 48 - eventBus.emit(SCENE_STATE.READY); 49 - }); 50 - break; 51 - case GAME_STATES.READY: 52 - uiManager.updateState(SCENE_STATE.READY); 53 - break; 54 - case GAME_STATES.PLAYING: 55 - break; 56 - case GAME_STATES.COMPLETED: 57 - uiManager.updateState('DONE'); 58 - break; 59 - case GAME_STATES.ERROR: 60 - break; 61 - } 62 - }); 63 - this.sceneState.onChange((newSceneState) => { 64 - console.log( 65 - `GAME_STATE [${this.gameState.current()}]/[${newSceneState}]` 66 - ); 67 - switch (newSceneState) { 68 - case SCENE_STATE.LOADING: 69 - break; 70 - case SCENE_STATE.READY: 71 - this.scoreLevel = this.scoreGlobal; 72 - break; 73 - case SCENE_STATE.PLAYING: 74 - break; 75 - case SCENE_STATE.PAUSED: 76 - break; 77 - case SCENE_STATE.COMPLETED: 78 - this.scoreGlobal = this.scoreLevel; 79 - const newHighscore = this.scoreGlobal > this.highscore; 80 - if (newHighscore) this.highscore = this.scoreGlobal; 81 - 82 - if (levelManager.levelsCompleted()) { 83 - this.gameState.next(GAME_STATES.COMPLETED); 84 - return; 85 - } 86 - 87 - break; 88 - case SCENE_STATE.GAMEOVER: 89 - this.scoreLevel = this.scoreGlobal; 90 - break; 91 - case SCENE_STATE.ERROR: 92 - break; 93 - } 94 - 95 - uiManager.updateState(newSceneState); 96 - }); 97 - } 98 - loadLevels() { 99 - levelManager.levels.forEach((lvl) => this.game.add(lvl.name, lvl)); 100 - } 101 - activateDebugMode() { 102 - new DevTool(this.game); 103 - } 104 - 105 - // actions 106 - loadLevel(level) { 107 - this.sceneState.next(SCENE_STATE.LOADING); 108 - 109 - this.game.goToScene(level.name); 110 - this.game.currentScene.onInitialize(this.game); 111 - uiManager.printUI( 112 - level.name, 113 - level.balls, 114 - this.scoreGlobal, 115 - this.highscore 116 - ); 117 - } 118 - startGame() { 119 - const initialLevel = levelManager.initial(); 120 - this.loadLevel(initialLevel); 121 - } 122 - retryLevel() { 123 - const sameLevel = levelManager.current(); 124 - this.loadLevel(sameLevel); 125 - } 126 - nextLevel() { 127 - const nextLevel = levelManager.next(); 128 - if (!nextLevel) { 129 - console.warn('No more levels'); 130 - return; 131 - } 132 - 133 - this.loadLevel(nextLevel); 134 - } 135 - resetGame() { 136 - this.scoreGlobal = 0; 137 - this.startGame(); 138 - } 139 - } 140 - 141 - class EventBus { 142 - constructor() { 143 - this.events = {}; 144 - } 145 - on(event, callback) { 146 - this.events[event] = this.events[event] || []; 147 - this.events[event].push(callback); 148 - } 149 - emit(event, data = {}) { 150 - if (this.events[event]) { 151 - this.events[event].forEach((callback) => { 152 - callback(data); 153 - }); 154 - } 155 - } 156 - } 157 - 158 - // 159 - const game = new Engine({ 160 - width: 600, 161 - height: 400, 162 - canvasElementId: 'main-canvas', 163 - backgroundColor: Color.Black, 164 - antialiasing: false, 165 - fixedUpdateFps: 30, 166 - }); 167 - const gameManager = new GameManager(game); 168 - const eventBus = new EventBus(); 169 - 170 - export { eventBus, gameManager };
-33
src/templates/javascript/src/sample/managers/level.manager.js
··· 1 - import { levelsSeed } from '../levels/levels.js'; 2 - import { LevelScene } from '../scenes/level.scene.js'; 3 - 4 - class LevelManager { 5 - constructor(levelSetup) { 6 - this.currentLevelIndex = 0; 7 - this.levelsSetup = levelSetup; 8 - this.levels = []; 9 - } 10 - 11 - init() { 12 - this.levelsSetup.forEach((lvl) => this.levels.push(new LevelScene(lvl))); 13 - } 14 - initial() { 15 - this.currentLevelIndex = 0; 16 - return this.levels[this.currentLevelIndex]; 17 - } 18 - next() { 19 - const levelExist = this.levels[this.currentLevelIndex + 1]; 20 - if (!levelExist) return false; 21 - this.currentLevelIndex++; 22 - return levelExist; 23 - } 24 - current() { 25 - return this.levels[this.currentLevelIndex]; 26 - } 27 - 28 - levelsCompleted() { 29 - return this.currentLevelIndex >= this.levels.length - 1; 30 - } 31 - } 32 - 33 - export const levelManager = new LevelManager(levelsSeed);
-48
src/templates/javascript/src/sample/managers/ui.manager.js
··· 1 - import { gameManager } from './game.manager.js'; 2 - 3 - class UIManager { 4 - constructor() { 5 - this.gameContainer = document.getElementById('game'); 6 - this.btnPlay = document.getElementById('btn-play'); 7 - this.btnRetry = document.getElementById('btn-retry'); 8 - this.btnNextLevel = document.getElementById('btn-next-level'); 9 - this.btnDone = document.getElementById('btn-done'); 10 - this.labelLevel = document.getElementById('label-level'); 11 - this.labelScore = document.getElementById('label-score'); 12 - this.labelBalls = document.getElementById('label-balls'); 13 - this.labelHighscore = document.getElementById('label-high-score'); 14 - } 15 - 16 - init() { 17 - this.btnPlay.onclick = () => gameManager.startGame(); 18 - this.btnRetry.onclick = () => gameManager.retryLevel(); 19 - this.btnNextLevel.onclick = () => gameManager.nextLevel(); 20 - this.btnDone.onclick = () => gameManager.resetGame(); 21 - } 22 - printUI(level, balls, score, highscore) { 23 - this.printLevel(level); 24 - this.printBalls(balls); 25 - this.printScore(score); 26 - this.printHighscore(highscore); 27 - } 28 - updateState(state) { 29 - this.gameContainer.className = state; 30 - } 31 - 32 - // 33 - printBalls(amount) { 34 - this.labelBalls.innerText = '🪩'.repeat(amount); 35 - } 36 - 37 - printScore(score) { 38 - this.labelScore.innerText = `${score}`; 39 - } 40 - printHighscore(score) { 41 - this.labelHighscore.innerText = `${score}`; 42 - } 43 - printLevel(lvl) { 44 - this.labelLevel.innerText = `${lvl}`; 45 - } 46 - } 47 - 48 - export const uiManager = new UIManager();
-41
src/templates/javascript/src/sample/models.js
··· 1 - export const GAME_STATES = { 2 - LOADING: 'LOADING', 3 - READY: 'READY', 4 - PLAYING: 'PLAYING', 5 - COMPLETED: 'COMPLETED', 6 - ERROR: 'ERROR', 7 - }; 8 - 9 - export const BRICK_TYPE = { 10 - LEVEL_1: 'LEVEL_1', 11 - LEVEL_2: 'LEVEL_2', 12 - LEVEL_3: 'LEVEL_3', 13 - LEVEL_4: 'LEVEL_4', 14 - LEVEL_5: 'LEVEL_5', 15 - LEVEL_6: 'LEVEL_6', 16 - }; 17 - export const LEVEL_MUSIC = { 18 - LEVEL_1: 'level_music_1', 19 - LEVEL_2: 'level_music_2', 20 - LEVEL_3: 'level_music_3', 21 - LEVEL_4: 'level_music_4', 22 - LEVEL_5: 'level_music_5', 23 - }; 24 - export const SCENE_EVENTS = { 25 - UPDATE_BALL: 'GAME_EVENTS__UPDATE_BALL', 26 - UPDATE_SCORE: 'GAME_EVENTS__UPDATE_SCORE', 27 - }; 28 - export const SCENE_STATE = { 29 - LOADING: 'SCENE_STATE__LOADING', 30 - READY: 'SCENE_STATE__READY', 31 - PLAYING: 'SCENE_STATE__PLAYING', 32 - PAUSED: 'SCENE_STATE__PAUSED', 33 - COMPLETED: 'SCENE_STATE__COMPLETED', 34 - GAMEOVER: 'SCENE_STATE__GAMEOVER', 35 - ERROR: 'SCENE_STATE__ERROR', 36 - }; 37 - export const PLAYER_CONTROLS = { 38 - KEYBOARD: 'KEYBOARD', 39 - MOUSE: 'MOUSE', 40 - BOTH: 'BOTH', 41 - };
-33
src/templates/javascript/src/sample/particles/galaxy.particles.js
··· 1 - import { Color, EmitterType, ParticleEmitter, Vector } from 'excalibur'; 2 - 3 - // https://excaliburjs.com/docs/particles 4 - // https://excaliburjs.com/particle-tester/ 5 - export function createGalaxy(setup) { 6 - const { width, height, x, y } = setup; 7 - const emitter = new ParticleEmitter({ 8 - width, 9 - height, 10 - x, 11 - y, 12 - }); 13 - emitter.emitterType = EmitterType.Circle; 14 - emitter.radius = 375; 15 - emitter.minVel = 400; 16 - emitter.maxVel = 415; 17 - emitter.minAngle = 0; 18 - emitter.maxAngle = 5.2; 19 - emitter.isEmitting = true; 20 - emitter.emitRate = 300; 21 - emitter.opacity = 0.8; 22 - emitter.fadeFlag = true; 23 - emitter.particleLife = 3000; 24 - emitter.maxSize = 1; 25 - emitter.minSize = 1; 26 - emitter.startSize = 1; 27 - emitter.endSize = 1; 28 - emitter.acceleration = new Vector(514, 2000); 29 - emitter.beginColor = Color.Transparent; 30 - emitter.endColor = Color.Transparent; 31 - 32 - return emitter; 33 - }
-261
src/templates/javascript/src/sample/scenes/level.scene.js
··· 1 - import { Input, Scene } from 'excalibur'; 2 - import { Brick } from '../actors/brick.actor.js'; 3 - import { Paddle } from '../actors/paddle.actor.js'; 4 - import { Ball } from '../actors/ball.actor.js'; 5 - import { SCENE_EVENTS, SCENE_STATE, PLAYER_CONTROLS } from '../models.js'; 6 - import { eventBus, gameManager } from '../managers/game.manager.js'; 7 - import { assetManager } from '../managers/asset.manager.js'; 8 - import { createGalaxy } from '../particles/galaxy.particles.js'; 9 - 10 - export class LevelScene extends Scene { 11 - constructor(setup) { 12 - super(); 13 - const { balls, name, ballSpeed, bgColor, bricksSetup, music } = setup; 14 - this.name = name; 15 - this.balls = this.levelBalls; 16 - this.ballSpeed = ballSpeed; 17 - this.bgColor = bgColor; 18 - this.bricksSetup = bricksSetup; 19 - this.levelMusic = 20 - assetManager.sounds[music] || assetManager.sounds.levelMusicDefault; 21 - this.levelMusic.volume = 0.1; 22 - this.levelBalls = balls; 23 - this.levelBricks = []; 24 - 25 - this.state = null; 26 - this.PLAYER_CONTROLS = null; 27 - 28 - this.playerControls = PLAYER_CONTROLS.BOTH; 29 - } 30 - onInitialize(engine) { 31 - this.init(engine); 32 - } 33 - 34 - init(engine) { 35 - this.reset(); 36 - this.backgroundColor = this.bgColor; 37 - //create game objects 38 - const galaxyParticle = this.createGalaxyParticles(engine); 39 - this.levelBricks = this.createBricks(); 40 - 41 - const paddle = this.createPaddle(engine); 42 - paddle.on('postupdate', () => { 43 - const offsetX = 0; 44 - const hitLimits = { 45 - screenLeft: paddle.pos.x - paddle.width / 2 < 0 + offsetX, 46 - screenRight: paddle.pos.x + paddle.width / 2 > engine.drawWidth, 47 - }; 48 - 49 - if (hitLimits.screenLeft) { 50 - paddle.pos.x = 0 + paddle.width / 2 + offsetX; 51 - } 52 - if (hitLimits.screenRight) { 53 - paddle.pos.x = engine.drawWidth - paddle.width / 2 - offsetX; 54 - } 55 - }); 56 - 57 - const ball = this.createBall(engine); 58 - ball.on('postupdate', () => { 59 - const hitLimits = { 60 - screenTop: ball.pos.y - ball.height / 2 < 0, 61 - screenLeft: ball.pos.x - ball.width / 2 < 0, 62 - screenRight: ball.pos.x + ball.width / 2 > engine.drawWidth, 63 - }; 64 - 65 - if (hitLimits.screenTop) { 66 - // send to bottom 67 - ball.vel.y = ball.vel.y = ball.ballSpeed.y * -1 + Math.random(); 68 - } else if (hitLimits.screenLeft) { 69 - // send to the right 70 - ball.vel.x = ball.ballSpeed.x + Math.random(); 71 - } else if (hitLimits.screenRight) { 72 - // send to the left 73 - ball.vel.x = ball.ballSpeed.x * -1 + Math.random(); 74 - } 75 - }); 76 - // destroy bricks 77 - ball.on('collisionstart', (e) => { 78 - const collideWithBrick = this.levelBricks.indexOf(e.other) > -1; 79 - const collideWithPaddle = e.other.name === 'paddle'; 80 - if (collideWithBrick) { 81 - let brick = e.other; 82 - brick.life--; 83 - 84 - eventBus.emit(SCENE_EVENTS.UPDATE_SCORE, brick.hitScore); 85 - 86 - if (brick.life <= 0) { 87 - assetManager.sounds.brickDestroy.play(0.1); 88 - brick.kill(); 89 - eventBus.emit(SCENE_EVENTS.UPDATE_SCORE, brick.destroyScore); 90 - } else { 91 - assetManager.sounds.brickHit.play(0.1); 92 - brick.updateSprite(); 93 - } 94 - 95 - if (this.levelCompleted()) { 96 - this.levelMusic.stop(); 97 - assetManager.sounds.levelCompleted.play(); 98 - 99 - ball.kill(); 100 - this.setState(SCENE_STATE.COMPLETED); 101 - return; 102 - } 103 - } 104 - if (collideWithPaddle) { 105 - assetManager.sounds.paddleHit.play(); 106 - } 107 - 108 - if (!ball.isColliding) { 109 - ball.isColliding = true; 110 - const intersection = e.contact.mtv.normalize(); 111 - const hitX = Math.abs(intersection.x) > Math.abs(intersection.y); 112 - 113 - if (hitX) { 114 - // reverse x 115 - ball.vel.x *= -1; 116 - } else { 117 - // reverse y 118 - ball.vel.y *= -1; 119 - } 120 - } 121 - }); 122 - ball.on('collisionend', () => { 123 - ball.isColliding = false; 124 - }); 125 - // lose ball 126 - ball.on('exitviewport', () => { 127 - this.balls--; 128 - assetManager.sounds.ballDrop.play(); 129 - eventBus.emit(SCENE_EVENTS.UPDATE_BALL, this.balls); 130 - 131 - if (this.gameover()) { 132 - this.levelMusic.stop(); 133 - assetManager.sounds.levelGameover.play(); 134 - ball.kill(); 135 - this.setState(SCENE_STATE.GAMEOVER); 136 - } else { 137 - ball.pos.x = Math.random() * engine.drawWidth; 138 - ball.pos.y = -10; 139 - } 140 - }); 141 - 142 - // setup player controls 143 - 144 - if ( 145 - this.playerControls === PLAYER_CONTROLS.MOUSE || 146 - this.playerControls === PLAYER_CONTROLS.BOTH 147 - ) { 148 - // MOUSE CONTROLS 149 - engine.input.pointers.primary.on('move', (e) => { 150 - const canMove = this.state === SCENE_STATE.PLAYING; 151 - if (canMove) { 152 - paddle.pos.x = e.worldPos.x; 153 - } 154 - }); 155 - } 156 - if ( 157 - this.playerControls === PLAYER_CONTROLS.KEYBOARD || 158 - this.playerControls === PLAYER_CONTROLS.BOTH 159 - ) { 160 - // KEYBOARD CONTROLS 161 - paddle.on('preupdate', () => { 162 - paddle.vel.x = 0; 163 - const KEYBOARD_LEFT_PRESSED = 164 - engine.input.keyboard.isHeld(Input.Keys.Left) || 165 - engine.input.keyboard.isHeld(Input.Keys.A); 166 - const KEYBOARD_RIGHT_PRESSED = 167 - engine.input.keyboard.isHeld(Input.Keys.Right) || 168 - engine.input.keyboard.isHeld(Input.Keys.D); 169 - 170 - if (KEYBOARD_LEFT_PRESSED) { 171 - paddle.vel.x = -1; 172 - } 173 - if (KEYBOARD_RIGHT_PRESSED) { 174 - paddle.vel.x = 1; 175 - } 176 - if (paddle.vel.x !== 0) { 177 - paddle.vel.x = paddle.vel.x * paddle.speed; 178 - } 179 - }); 180 - } 181 - 182 - // add game objects to the scene 183 - this.add(galaxyParticle); 184 - this.add(paddle); 185 - this.add(ball); 186 - this.levelBricks.forEach((brick) => this.add(brick)); 187 - // 188 - this.levelMusic.stop(); 189 - this.levelMusic.play(); 190 - this.setState(SCENE_STATE.PLAYING); 191 - } 192 - createGalaxyParticles(engine) { 193 - return createGalaxy({ 194 - width: engine.drawWidth, 195 - height: engine.drawHeight, 196 - x: engine.drawWidth / 2, 197 - y: -50, 198 - }); 199 - } 200 - reset() { 201 - this.clear(); 202 - this.balls = this.levelBalls; 203 - } 204 - 205 - setState(newState) { 206 - if (this.state === newState) return; 207 - this.state = newState; 208 - gameManager.sceneState.next(newState); 209 - } 210 - // game objects 211 - createBricks() { 212 - const padding = 32 + 24 / 6; 213 - const offsetX = 32 * 2; 214 - const offsetY = 32 + 48; 215 - const brickWidth = 32; 216 - const brickHeight = 16; 217 - const bricks = []; 218 - 219 - this.bricksSetup.forEach((row, rowIndex) => { 220 - for (let pos = 0; pos < row.length; pos++) { 221 - if (row[pos] === '') { 222 - continue; 223 - } 224 - 225 - const x = offsetX + pos * (brickWidth + padding); 226 - const y = offsetY + rowIndex * (brickHeight + padding); 227 - const brick = new Brick(x, y, brickWidth, brickHeight, row[pos]); 228 - brick.z = 1; 229 - bricks.push(brick); 230 - } 231 - }); 232 - 233 - return bricks; 234 - } 235 - createBall(engine) { 236 - const x = engine.drawWidth / 2; 237 - const y = -10; 238 - const radius = 8; 239 - const vel = this.ballSpeed; 240 - const ball = new Ball(x, y, radius, vel); 241 - ball.z = 1; 242 - return ball; 243 - } 244 - createPaddle(engine) { 245 - const x = 100; 246 - const y = engine.drawHeight - 64; 247 - const width = 32; 248 - const height = 4; 249 - const speed = 500; 250 - const paddle = new Paddle(x, y, width, height, speed); 251 - paddle.z = 1; 252 - return paddle; 253 - } 254 - // state 255 - levelCompleted() { 256 - return this.levelBricks.every((b) => b.isKilled()); 257 - } 258 - gameover() { 259 - return this.balls <= 0; 260 - } 261 - }
-46
src/templates/javascript/src/sample/utils.js
··· 1 - export class Subject { 2 - constructor() { 3 - this.value = null; 4 - this.subscribers = new Set(); 5 - } 6 - current() { 7 - return this.value; 8 - } 9 - next(newValue) { 10 - this.value = newValue; 11 - this.subscribers.forEach((cb) => cb(this.value)); 12 - } 13 - onChange(callback) { 14 - this.subscribers.add(callback); 15 - 16 - const unsubscribe = () => { 17 - this.subscribers.delete(callback); 18 - }; 19 - 20 - return { unsubscribe }; 21 - } 22 - } 23 - export class BehaviourSubject { 24 - constructor(initialValue) { 25 - this.value = initialValue; 26 - this.subscribers = new Set(); 27 - } 28 - 29 - current() { 30 - return this.value; 31 - } 32 - next(newValue) { 33 - const prev = this.value; 34 - this.value = newValue; 35 - this.subscribers.forEach((cb) => cb(this.value, prev)); 36 - } 37 - onChange(callback) { 38 - this.subscribers.add(callback); 39 - callback(this.value); 40 - const unsubscribe = () => { 41 - this.subscribers.delete(callback); 42 - }; 43 - 44 - return { unsubscribe }; 45 - } 46 - }
-284
src/templates/javascript/style.css
··· 1 - * { 2 - padding: 0; 3 - margin: 0; 4 - } 5 - :root { 6 - --color-primary: #2085d1; 7 - --bg-color: #edf2f7; 8 - --text-color: rgb(42, 41, 41); 9 - --border-radius: 16px/44px; 10 - } 11 - html, 12 - body { 13 - height: 100%; 14 - padding: 1rem; 15 - } 16 - 17 - body { 18 - color: var(--text-color); 19 - padding: 1rem 1rem; 20 - 21 - font-family: monospace; 22 - 23 - display: flex; 24 - flex-direction: column; 25 - gap: 1rem; 26 - 27 - background-color: var(--bg-color); 28 - opacity: 0.8; 29 - background-image: radial-gradient( 30 - var(--color-primary) 0.5px, 31 - var(--bg-color) 0.5px 32 - ); 33 - background-size: 10px 10px; 34 - } 35 - 36 - h1 { 37 - display: flex; 38 - align-items: center; 39 - justify-content: center; 40 - 41 - gap: 0.3rem; 42 - 43 - .logo { 44 - display: flex; 45 - align-items: center; 46 - } 47 - } 48 - 49 - nav { 50 - font-family: sans-serif; 51 - display: flex; 52 - align-items: center; 53 - justify-content: center; 54 - gap: 1rem; 55 - padding: 1rem; 56 - } 57 - 58 - .nav-link { 59 - display: flex; 60 - justify-content: center; 61 - align-items: center; 62 - gap: 0.3rem; 63 - 64 - background-color: var(--color-primary); 65 - color: whitesmoke; 66 - padding: 1rem 1rem; 67 - text-decoration: none; 68 - 69 - height: 16px; 70 - border-radius: var(--border-radius); 71 - transition: filter 0.3s ease-in-out; 72 - border: 2px solid black; 73 - 74 - &:active { 75 - transform: scale(0.98); 76 - } 77 - 78 - &:hover { 79 - filter: saturate(2); 80 - } 81 - } 82 - #credits { 83 - display: flex; 84 - align-items: center; 85 - justify-content: center; 86 - 87 - details { 88 - width: 630px; 89 - 90 - summary { 91 - cursor: pointer; 92 - font-size: 1.2rem; 93 - text-align: center; 94 - } 95 - 96 - .type { 97 - font-weight: bold; 98 - } 99 - } 100 - 101 - .row { 102 - display: flex; 103 - gap: 2rem; 104 - } 105 - } 106 - #game { 107 - display: flex; 108 - align-items: center; 109 - justify-content: center; 110 - 111 - position: relative; 112 - 113 - #main-canvas { 114 - width: 600px; 115 - height: 400px; 116 - border-radius: var(--border-radius); 117 - background: linear-gradient(167deg, #22043b, #6916bc, #db1481); 118 - background-size: 360% 360%; 119 - animation: gradient-animation 6s ease infinite; 120 - } 121 - 122 - #ui { 123 - position: absolute; 124 - background-color: rgba(42, 41, 41, 0.3); 125 - width: 100%; 126 - height: 100%; 127 - width: 600px; 128 - border-radius: var(--border-radius); 129 - 130 - display: flex; 131 - align-items: center; 132 - justify-content: center; 133 - 134 - button { 135 - width: 200px; 136 - height: 56px; 137 - border: 3px solid black; 138 - cursor: pointer; 139 - border-radius: var(--border-radius); 140 - transition: all 0.3s ease-in-out; 141 - font-weight: bold; 142 - 143 - &:active { 144 - transform: scale(0.98); 145 - } 146 - 147 - &.play { 148 - background-color: #2085d1; 149 - } 150 - &.retry { 151 - background-color: #6f7378; 152 - } 153 - &.next-level { 154 - background-color: #20d199; 155 - } 156 - &.restart { 157 - background-color: #8420d1; 158 - } 159 - } 160 - .ready-container { 161 - display: none; 162 - } 163 - .playing-container { 164 - display: none; 165 - } 166 - .gameover-container { 167 - display: none; 168 - } 169 - .completed-container { 170 - display: none; 171 - } 172 - .done-container { 173 - display: none; 174 - } 175 - } 176 - 177 - /* */ 178 - &.SCENE_STATE__LOADING { 179 - #ui { 180 - display: none; 181 - } 182 - } 183 - &.SCENE_STATE__READY { 184 - #ui { 185 - .ready-container { 186 - display: flex; 187 - align-items: center; 188 - justify-content: center; 189 - height: 100%; 190 - transition: all 0.3s ease-in-out; 191 - } 192 - } 193 - } 194 - &.SCENE_STATE__PLAYING { 195 - #ui { 196 - display: flex; 197 - pointer-events: none; 198 - background-color: transparent; 199 - .playing-container { 200 - position: absolute; 201 - color: whitesmoke; 202 - /* background-color: whitesmoke; */ 203 - top: 1rem; 204 - left: 2rem; 205 - width: 90%; 206 - 207 - display: flex; 208 - align-items: center; 209 - justify-content: space-between; 210 - 211 - gap: 1rem; 212 - 213 - .center { 214 - text-align: center; 215 - } 216 - .right { 217 - text-align: right; 218 - } 219 - } 220 - } 221 - 222 - #main-canvas { 223 - cursor: e-resize; 224 - } 225 - } 226 - &.SCENE_STATE__COMPLETED { 227 - #ui { 228 - display: flex; 229 - 230 - .completed-container { 231 - display: flex; 232 - align-items: center; 233 - justify-content: center; 234 - height: 100%; 235 - 236 - transition: all 0.3s ease-in-out; 237 - } 238 - } 239 - } 240 - &.SCENE_STATE__GAMEOVER { 241 - #ui { 242 - display: flex; 243 - .gameover-container { 244 - display: flex; 245 - align-items: center; 246 - justify-content: center; 247 - height: 100%; 248 - transition: all 0.3s ease-in-out; 249 - } 250 - } 251 - } 252 - &.DONE { 253 - #ui { 254 - display: flex; 255 - 256 - width: 90%; 257 - background-color: whitesmoke; 258 - 259 - .done-container { 260 - display: flex; 261 - align-items: center; 262 - justify-content: center; 263 - flex-direction: column; 264 - gap: 1rem; 265 - 266 - button { 267 - height: 56px; 268 - } 269 - } 270 - } 271 - } 272 - } 273 - 274 - @keyframes gradient-animation { 275 - 0% { 276 - background-position: 0% 50%; 277 - } 278 - 50% { 279 - background-position: 100% 50%; 280 - } 281 - 100% { 282 - background-position: 0% 50%; 283 - } 284 - }
-24
src/templates/typescript/gitignore
··· 1 - # Logs 2 - logs 3 - *.log 4 - npm-debug.log* 5 - yarn-debug.log* 6 - yarn-error.log* 7 - pnpm-debug.log* 8 - lerna-debug.log* 9 - 10 - node_modules 11 - dist 12 - dist-ssr 13 - *.local 14 - 15 - # Editor directories and files 16 - .vscode/* 17 - !.vscode/extensions.json 18 - .idea 19 - .DS_Store 20 - *.suo 21 - *.ntvs* 22 - *.njsproj 23 - *.sln 24 - *.sw?
-249
src/templates/typescript/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8" /> 5 - <link rel="shortcut icon" type="icon" href="/favicon.ico" /> 6 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 - <title>ExcaliburJS</title> 8 - <link rel="stylesheet" href="/style.css" /> 9 - </head> 10 - <body> 11 - <!-- --> 12 - <div id="welcome"> 13 - <h1> 14 - <div class="logo"> 15 - <img 16 - src="https://excaliburjs.com/img/ex-logo.png" 17 - width="32" 18 - alt="" 19 - /> 20 - <a 21 - href="https://excaliburjs.com/docs/getting-started" 22 - target="_blank" 23 - > 24 - ExcaliburJS 25 - </a> 26 - </div> 27 - </h1> 28 - 29 - <nav> 30 - <a 31 - class="nav-link" 32 - href="https://excaliburjs.com/docs/category/fundamentals" 33 - target="_blank" 34 - >Documentation 35 - </a> 36 - <a 37 - class="nav-link" 38 - href="https://excaliburjs.com/samples" 39 - target="_blank" 40 - >Samples 41 - </a> 42 - <a 43 - class="nav-link" 44 - href="https://discord.com/invite/W6zUd4tTY3" 45 - target="_blank" 46 - ><svg 47 - xmlns="http://www.w3.org/2000/svg" 48 - width="24" 49 - height="24" 50 - fill="currentColor" 51 - class="bi bi-discord" 52 - viewBox="0 0 16 16" 53 - > 54 - <path 55 - d="M13.545 2.907a13.2 13.2 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.2 12.2 0 0 0-3.658 0 8 8 0 0 0-.412-.833.05.05 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.04.04 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032q.003.022.021.037a13.3 13.3 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019q.463-.63.818-1.329a.05.05 0 0 0-.01-.059l-.018-.011a9 9 0 0 1-1.248-.595.05.05 0 0 1-.02-.066l.015-.019q.127-.095.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.05.05 0 0 1 .053.007q.121.1.248.195a.05.05 0 0 1-.004.085 8 8 0 0 1-1.249.594.05.05 0 0 0-.03.03.05.05 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.2 13.2 0 0 0 4.001-2.02.05.05 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.03.03 0 0 0-.02-.019m-8.198 7.307c-.789 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612" 56 - /> 57 - </svg> 58 - </a> 59 - <a 60 - class="nav-link" 61 - href="https://github.com/excaliburjs/Excalibur" 62 - target="_blank" 63 - ><svg 64 - xmlns="http://www.w3.org/2000/svg" 65 - width="24" 66 - height="24" 67 - fill="currentColor" 68 - class="bi bi-github" 69 - viewBox="0 0 16 16" 70 - > 71 - <path 72 - d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8" 73 - /> 74 - </svg> 75 - </a> 76 - </nav> 77 - </div> 78 - <div id="game" class="LOADING"> 79 - <canvas id="main-canvas" class="gradient-background"></canvas> 80 - <div id="ui"> 81 - <div class="panels"> 82 - <div class="ready-container"> 83 - <button id="btn-play" class="play">Play</button> 84 - </div> 85 - <div class="playing-container"> 86 - <div class="left"> 87 - <p id="label-level"></p> 88 - <p id="label-score"></p> 89 - </div> 90 - 91 - <div class="center"> 92 - <p>HIGHSCORE</p> 93 - <p id="label-high-score"></p> 94 - </div> 95 - 96 - <div class="right"> 97 - <p>Lives</p> 98 - <p id="label-balls"></p> 99 - </div> 100 - </div> 101 - <div class="gameover-container"> 102 - <button id="btn-retry" class="retry">Retry</button> 103 - </div> 104 - <div class="completed-container"> 105 - <button id="btn-next-level" class="next-level">Next level</button> 106 - </div> 107 - <div class="done-container"> 108 - <p>🎉Game completed!🎉</p> 109 - <button id="btn-done" class="restart">Reset the Game</button> 110 - </div> 111 - </div> 112 - </div> 113 - </div> 114 - <div id="credits"> 115 - <details> 116 - <summary>💖 Credits</summary> 117 - <div class="content"> 118 - <div> 119 - <p class="type">Sprites:</p> 120 - <ul> 121 - <li> 122 - "breakout-assets" by 123 - <a 124 - href="https://megacrash.itch.io/breakout-assets" 125 - target="_blank" 126 - > 127 - MegaCrash </a 128 - >CC0 129 - </li> 130 - <!-- --> 131 - </ul> 132 - </div> 133 - <br /> 134 - <div class="row"> 135 - <div> 136 - <p class="type">Music:</p> 137 - <ul> 138 - <li> 139 - "Invaderloop" by 140 - <a 141 - href="https://opengameart.org/content/invaderloop" 142 - target="_blank" 143 - > 144 - symphony</a 145 - > 146 - CC0 147 - </li> 148 - <li> 149 - "Cyberpunk Moonlight Sonata" by 150 - <a 151 - href="https://opengameart.org/content/cyberpunk-moonlight-sonata" 152 - target="_blank" 153 - > 154 - Joth</a 155 - > 156 - CC0 157 - </li> 158 - <li> 159 - "Harmonic Minor" by 160 - <a 161 - href="https://opengameart.org/content/and-the-console-hit-the-floor-electronic" 162 - target="_blank" 163 - > 164 - Alex McCulloch</a 165 - > 166 - CC0 167 - </li> 168 - <li> 169 - "Open Warfare" by 170 - <a 171 - href="https://opengameart.org/content/open-warfare" 172 - target="_blank" 173 - > 174 - Ruskerdax</a 175 - > 176 - CC0 177 - </li> 178 - <li> 179 - "Futuristic-resources" by 180 - <a 181 - href="https://opengameart.org/content/futuristic-resources" 182 - target="_blank" 183 - > 184 - section31</a 185 - > 186 - CC0 187 - </li> 188 - </ul> 189 - </div> 190 - 191 - <div> 192 - <p class="type">Sound Effects:</p> 193 - <ul> 194 - <li> 195 - "win-sound-effect" by 196 - <a 197 - href="https://opengameart.org/content/win-sound-effect" 198 - target="_blank" 199 - > 200 - Listener</a 201 - > 202 - CC0 203 - </li> 204 - <li> 205 - "8bit-death-whirl" by 206 - <a 207 - href="https://opengameart.org/content/8bit-death-whirl" 208 - target="_blank" 209 - > 210 - Fupi</a 211 - > 212 - CC0 213 - </li> 214 - <li> 215 - "player hit (damage)" by 216 - <a 217 - href="https://opengameart.org/content/player-hit-damage" 218 - target="_blank" 219 - > 220 - GreyFrogGames</a 221 - > 222 - CC0 223 - </li> 224 - <li> 225 - "hit sound bitcrush" by 226 - <a 227 - href="https://opengameart.org/content/hit-sound-bitcrush" 228 - target="_blank" 229 - > 230 - DaGameKnower</a 231 - > 232 - CC0 233 - </li> 234 - <li> 235 - "hit" by 236 - <a href="https://erikonarheim.com" target="_blank"> 237 - Erik Onarheim</a 238 - > 239 - CC0 240 - </li> 241 - </ul> 242 - </div> 243 - </div> 244 - </div> 245 - </details> 246 - </div> 247 - <script type="module" src="/src/main.ts"></script> 248 - </body> 249 - </html>
-19
src/templates/typescript/package.json
··· 1 - { 2 - "name": "ts-starter", 3 - "private": true, 4 - "version": "0.0.0", 5 - "type": "module", 6 - "scripts": { 7 - "dev": "vite", 8 - "build": "tsc && vite build", 9 - "preview": "vite preview" 10 - }, 11 - "devDependencies": { 12 - "@excaliburjs/dev-tools": "^0.28.0", 13 - "typescript": "^5.2.2", 14 - "vite": "^5.0.8" 15 - }, 16 - "dependencies": { 17 - "excalibur": "^0.28.6" 18 - } 19 - }
src/templates/typescript/public/favicon.ico

This is a binary file and will not be displayed.

src/templates/typescript/public/sample/music/Cyberpunk Moonlight Sonata.mp3

This is a binary file and will not be displayed.

src/templates/typescript/public/sample/music/HarmonicMinor.wav

This is a binary file and will not be displayed.

src/templates/typescript/public/sample/music/S31-Futuristic-Resources.ogg

This is a binary file and will not be displayed.

src/templates/typescript/public/sample/music/cubedcanada+invaderloop.mp3

This is a binary file and will not be displayed.

src/templates/typescript/public/sample/music/ruskerdax_-_open_warfare.mp3

This is a binary file and will not be displayed.

src/templates/typescript/public/sample/sounds/ball_drop.mp3

This is a binary file and will not be displayed.

src/templates/typescript/public/sample/sounds/explosion.wav

This is a binary file and will not be displayed.

src/templates/typescript/public/sample/sounds/hit.wav

This is a binary file and will not be displayed.

src/templates/typescript/public/sample/sounds/hit2.wav

This is a binary file and will not be displayed.

src/templates/typescript/public/sample/sounds/vgdeathsound.wav

This is a binary file and will not be displayed.

src/templates/typescript/public/sample/sounds/win.wav

This is a binary file and will not be displayed.

src/templates/typescript/public/sample/sprites/breakout_sprite.png

This is a binary file and will not be displayed.

-3
src/templates/typescript/src/main.ts
··· 1 - import { gameManager } from './sample/managers/game.manager'; 2 - 3 - gameManager.init();
-44
src/templates/typescript/src/sample/actors/ball.actor.ts
··· 1 - import { 2 - Actor, 3 - Animation, 4 - CollisionType, 5 - SpriteSheet, 6 - Vector, 7 - range, 8 - } from 'excalibur'; 9 - import { assetManager } from '../managers/asset.manager'; 10 - 11 - export class Ball extends Actor { 12 - ballSpeed!: Vector; 13 - isColliding = false; 14 - 15 - constructor(x: number, y: number, radius: number, vel: Vector) { 16 - super({ x, y, radius, vel }); 17 - this.ballSpeed = vel; 18 - } 19 - 20 - onInitialize(): void { 21 - this.body.collisionType = CollisionType.Passive; 22 - this.setSprite(); 23 - } 24 - 25 - setSprite() { 26 - const sprite = SpriteSheet.fromImageSource({ 27 - image: assetManager.images.spritesheet, 28 - grid: { 29 - rows: 1, 30 - columns: 1, 31 - spriteWidth: 16, 32 - spriteHeight: 16, 33 - }, 34 - spacing: { 35 - originOffset: { x: 32 * 6, y: 256 - 16 }, 36 - }, 37 - }); 38 - const frames = range(0, 0); 39 - const animationMs = 0; 40 - const ballAnim = Animation.fromSpriteSheet(sprite, frames, animationMs); 41 - // 42 - this.graphics.use(ballAnim); 43 - } 44 - }
-111
src/templates/typescript/src/sample/actors/brick.actor.ts
··· 1 - import { 2 - Actor, 3 - Animation, 4 - CollisionType, 5 - SpriteSheet, 6 - range, 7 - vec, 8 - } from 'excalibur'; 9 - import { assetManager } from '../managers/asset.manager'; 10 - 11 - export class Brick extends Actor { 12 - type!: string; 13 - spriteType!: number; 14 - spriteStatus!: number; 15 - life!: number; 16 - hitScore = 0; 17 - destroyScore = 0; 18 - constructor( 19 - x: number, 20 - y: number, 21 - width: number, 22 - height: number, 23 - type: string 24 - ) { 25 - super({ x, y, width, height }); 26 - this.type = type; 27 - this.spriteStatus = 32 * 8; 28 - switch (this.type) { 29 - case '1': 30 - this.life = 1; 31 - this.spriteType = 1; 32 - this.hitScore = 100; 33 - this.destroyScore = this.life * 200; 34 - 35 - break; 36 - case '2': 37 - this.life = 2; 38 - this.spriteType = 2; 39 - this.hitScore = 250; 40 - this.destroyScore = this.life * 200; 41 - 42 - break; 43 - case '3': 44 - this.life = 3; 45 - this.spriteType = 3; 46 - this.hitScore = 250; 47 - this.destroyScore = this.life * 200; 48 - 49 - break; 50 - case '4': 51 - this.life = 4; 52 - this.spriteType = 4; 53 - this.hitScore = 350; 54 - this.destroyScore = this.life * 200; 55 - 56 - break; 57 - case '5': 58 - this.life = 5; 59 - this.spriteType = 5; 60 - this.hitScore = 350; 61 - this.destroyScore = this.life * 200; 62 - 63 - break; 64 - case '6': 65 - this.life = 6; 66 - this.spriteType = 6; 67 - this.hitScore = 350; 68 - this.destroyScore = this.life * 200; 69 - 70 - break; 71 - } 72 - } 73 - onInitialize(): void { 74 - this.body.collisionType = CollisionType.Passive; 75 - this.setSprite(); 76 - this.scale = vec(2, 2); 77 - } 78 - setSprite() { 79 - const sprite = SpriteSheet.fromImageSource({ 80 - image: assetManager.images.spritesheet, 81 - grid: { 82 - rows: 1, 83 - columns: 1, 84 - spriteWidth: 32, 85 - spriteHeight: 16, 86 - }, 87 - spacing: { 88 - originOffset: { x: this.spriteStatus, y: this.spriteType * 16 }, 89 - }, 90 - }); 91 - const frames = range(0, 0); 92 - const animationMs = 100; 93 - const brickAnim = Animation.fromSpriteSheet(sprite, frames, animationMs); 94 - this.graphics.use(brickAnim); 95 - } 96 - updateSprite() { 97 - switch (this.life) { 98 - case 3: 99 - this.spriteStatus = 32 * 8; 100 - break; 101 - case 2: 102 - this.spriteStatus = 32 * 9; 103 - break; 104 - case 1: 105 - this.spriteStatus = 32 * 10; 106 - break; 107 - } 108 - 109 - this.setSprite(); 110 - } 111 - }
-49
src/templates/typescript/src/sample/actors/paddle.actor.ts
··· 1 - import { 2 - Actor, 3 - Animation, 4 - CollisionType, 5 - SpriteSheet, 6 - range, 7 - vec, 8 - } from 'excalibur'; 9 - import { assetManager } from '../managers/asset.manager'; 10 - 11 - export class Paddle extends Actor { 12 - speed!: number; 13 - constructor( 14 - x: number, 15 - y: number, 16 - width: number, 17 - height: number, 18 - speed: number 19 - ) { 20 - super({ x, y, width, height }); 21 - this.speed = speed; 22 - this.name = 'paddle'; 23 - } 24 - onInitialize(): void { 25 - this.body.collisionType = CollisionType.Passive; 26 - this.setSprite(); 27 - this.scale = vec(2, 2); 28 - } 29 - 30 - setSprite() { 31 - const sprite = SpriteSheet.fromImageSource({ 32 - image: assetManager.images.spritesheet, 33 - grid: { 34 - rows: 4, 35 - columns: 14, 36 - spriteWidth: 32, 37 - spriteHeight: 16, 38 - }, 39 - spacing: { 40 - originOffset: { x: 48, y: 256 - 16 }, 41 - }, 42 - }); 43 - const frames = range(0, 0); 44 - const animationMs = 0; 45 - const paddleAnim = Animation.fromSpriteSheet(sprite, frames, animationMs); 46 - // 47 - this.graphics.use(paddleAnim); 48 - } 49 - }
-238
src/templates/typescript/src/sample/levels/levels.ts
··· 1 - import { Color, vec } from 'excalibur'; 2 - import { LEVEL_MUSIC, LevelSetup } from '../models'; 3 - 4 - const BALL_SPEED = { 5 - NORMAL: vec(250, -250), 6 - MEDIUM: vec(280, -280), 7 - HARD: vec(300, -300), 8 - }; 9 - const LEVEL_BG = { 10 - BLUE: { 11 - LIGHT: '#3f45f881', 12 - DARK: '#15188581', 13 - }, 14 - GREEN: { 15 - LIGHT: '#43e517ae', 16 - DARK: '#2a880fae', 17 - }, 18 - PINK: { 19 - LIGHT: '#dd0b9391', 20 - DARK: '#44223891', 21 - }, 22 - PURPLE: { 23 - LIGHT: '#9617f05e', 24 - DARK: '#250c6f5e', 25 - }, 26 - YELLOW: { 27 - LIGHT: '#f5f903ae', 28 - DARK: '#86880eae', 29 - }, 30 - ORANGE: { 31 - LIGHT: '#f57f0991', 32 - DARK: '#7f470f91', 33 - }, 34 - RED: { 35 - LIGHT: '#f41313ad', 36 - DARK: '#7b141e91', 37 - }, 38 - }; 39 - const EMPTY_ROW = ['', '', '', '', '', '', '', '']; 40 - const ROW_1 = ['1', '1', '1', '1', '1', '1', '1', '1']; 41 - const ROW_2 = ['2', '2', '2', '2', '2', '2', '2', '2']; 42 - const ROW_3 = ['3', '3', '3', '3', '3', '3', '3', '3']; 43 - const ROW_4 = ['4', '4', '4', '4', '4', '4', '4', '4']; 44 - const ROW_5 = ['5', '5', '5', '5', '5', '5', '5', '5']; 45 - const ROW_6 = ['6', '6', '6', '6', '6', '6', '6', '6']; 46 - // LEVELS 47 - const level001: LevelSetup = { 48 - name: 'level001', 49 - balls: 3, 50 - music: LEVEL_MUSIC.LEVEL_2, 51 - ballSpeed: BALL_SPEED.NORMAL, 52 - bgColor: Color.Transparent, 53 - bricksSetup: [EMPTY_ROW, ROW_1, ROW_2, ROW_1, EMPTY_ROW], 54 - }; 55 - const level002: LevelSetup = { 56 - name: 'level002', 57 - balls: 3, 58 - music: LEVEL_MUSIC.LEVEL_1, 59 - ballSpeed: BALL_SPEED.NORMAL, 60 - bgColor: Color.fromHex(LEVEL_BG.BLUE.LIGHT), 61 - bricksSetup: [ 62 - ['3', '', '3', '', '', '3', '', '3'], 63 - ['3', '', '3', '', '', '3', '', '3'], 64 - ['3', '', '3', '', '', '3', '', '3'], 65 - ['1', '1', '1', '1', '1', '1', '1', '1'], 66 - ], 67 - }; 68 - const level003: LevelSetup = { 69 - name: 'level003', 70 - balls: 3, 71 - music: LEVEL_MUSIC.LEVEL_3, 72 - ballSpeed: BALL_SPEED.NORMAL, 73 - bgColor: Color.fromHex(LEVEL_BG.BLUE.DARK), 74 - bricksSetup: [ 75 - ['3', '', '3', '', '3', '', '3', ''], 76 - ['', '3', '', '3', '', '3', '', '3'], 77 - ['', '2', '', '2', '', '2', '', '2'], 78 - ['', '2', '', '2', '', '2', '', '2'], 79 - ['', '1', '', '1', '', '1', '', '1'], 80 - ], 81 - }; 82 - const level004: LevelSetup = { 83 - name: 'level004', 84 - balls: 3, 85 - music: LEVEL_MUSIC.LEVEL_4, 86 - ballSpeed: BALL_SPEED.MEDIUM, 87 - bgColor: Color.fromHex(LEVEL_BG.GREEN.DARK), 88 - bricksSetup: [ 89 - ['6', '', '', '', '', '', '', '6'], 90 - ROW_3, 91 - ROW_3, 92 - ['6', '', '', '', '', '', '', '6'], 93 - ], 94 - }; 95 - const level005: LevelSetup = { 96 - name: 'level005', 97 - balls: 3, 98 - music: LEVEL_MUSIC.LEVEL_5, 99 - ballSpeed: BALL_SPEED.MEDIUM, 100 - bgColor: Color.fromHex(LEVEL_BG.GREEN.LIGHT), 101 - bricksSetup: [ 102 - ['1', '', '2', '3', '3', '2', '', '1'], 103 - ['1', '', '2', '3', '3', '2', '', '1'], 104 - ['1', '', '2', '3', '3', '2', '', '1'], 105 - EMPTY_ROW, 106 - ], 107 - }; 108 - const level006: LevelSetup = { 109 - name: 'level006', 110 - balls: 3, 111 - music: LEVEL_MUSIC.LEVEL_1, 112 - ballSpeed: BALL_SPEED.MEDIUM, 113 - bgColor: Color.fromHex(LEVEL_BG.PINK.DARK), 114 - 115 - bricksSetup: [ 116 - ['', '3', '3', '3', '3', '3', '3', ''], 117 - ['2', '', '', '4', '4', '', '', '2'], 118 - ['3', '', '', '4', '4', '', '', '3'], 119 - ['', '1', '1', '1', '1', '1', '1', ''], 120 - ], 121 - }; 122 - const level007: LevelSetup = { 123 - name: 'level007', 124 - balls: 3, 125 - music: LEVEL_MUSIC.LEVEL_5, 126 - ballSpeed: BALL_SPEED.MEDIUM, 127 - bgColor: Color.fromHex(LEVEL_BG.PURPLE.LIGHT), 128 - bricksSetup: [ 129 - ROW_5, 130 - ['5', '', '', '4', '4', '', '', '5'], 131 - ['5', '', '4', '4', '4', '4', '', '5'], 132 - ['5', '4', '4', '4', '4', '4', '4', '5'], 133 - ], 134 - }; 135 - const level008: LevelSetup = { 136 - name: 'level008', 137 - balls: 3, 138 - music: LEVEL_MUSIC.LEVEL_1, 139 - ballSpeed: BALL_SPEED.HARD, 140 - bgColor: Color.fromHex(LEVEL_BG.PURPLE.DARK), 141 - bricksSetup: [ 142 - ['', '1', '6', '6', '6', '1', '', ''], 143 - ['', '', '1', '6', '1', '', '', ''], 144 - ['3', '3', '', '1', '', '', '3', '3'], 145 - ['', '', '1', '', '1', '', '', ''], 146 - ], 147 - }; 148 - const level009: LevelSetup = { 149 - name: 'level009', 150 - balls: 3, 151 - music: LEVEL_MUSIC.LEVEL_2, 152 - ballSpeed: BALL_SPEED.HARD, 153 - bgColor: Color.fromHex(LEVEL_BG.YELLOW.DARK), 154 - bricksSetup: [ 155 - ['', '', '', '5', '', '5', '', '5'], 156 - ['', '', '5', '', '5', '', '5', ''], 157 - ['', '5', '', '5', '', '5', '', ''], 158 - ['5', '', '5', '', '5', '', '', ''], 159 - ], 160 - }; 161 - const level010: LevelSetup = { 162 - name: 'level010', 163 - balls: 3, 164 - music: LEVEL_MUSIC.LEVEL_2, 165 - ballSpeed: BALL_SPEED.HARD, 166 - bgColor: Color.fromHex(LEVEL_BG.ORANGE.DARK), 167 - 168 - bricksSetup: [ 169 - ['3', '3', '3', '5', '', '', '', '3'], 170 - ['3', '3', '5', '', '', '', '5', '3'], 171 - ['3', '5', '', '', '', '5', '3', '3'], 172 - ['5', '', '', '', '5', '3', '3', '3'], 173 - ], 174 - }; 175 - const level011: LevelSetup = { 176 - name: 'level011', 177 - balls: 3, 178 - music: LEVEL_MUSIC.LEVEL_3, 179 - ballSpeed: BALL_SPEED.HARD, 180 - bgColor: Color.fromHex(LEVEL_BG.ORANGE.LIGHT), 181 - bricksSetup: [ 182 - ['5', '', '6', '6', '6', '', '4', '4'], 183 - ['5', '', '1', '', '3', '', '4', ''], 184 - ['5', '', '1', '', '3', '', '3', ''], 185 - ['5', '5', '1', '', '3', '5', '4', ''], 186 - ], 187 - }; 188 - const level012: LevelSetup = { 189 - name: 'level012', 190 - balls: 3, 191 - music: LEVEL_MUSIC.LEVEL_4, 192 - ballSpeed: BALL_SPEED.HARD, 193 - bgColor: Color.fromHex(LEVEL_BG.RED.DARK), 194 - bricksSetup: [ 195 - ['5', '5', '', '', '', '', '5', '5'], 196 - ['', '', '6', '6', '6', '6', '', ''], 197 - ['5', '5', '', '', '', '', '5', '5'], 198 - ['', '1', '1', '1', '1', '1', '1', ''], 199 - ], 200 - }; 201 - 202 - const level013: LevelSetup = { 203 - name: 'level013', 204 - balls: 3, 205 - music: LEVEL_MUSIC.LEVEL_5, 206 - ballSpeed: BALL_SPEED.HARD, 207 - bgColor: Color.fromHex(LEVEL_BG.RED.LIGHT), 208 - bricksSetup: [ 209 - ['5', '5', '5', '5', '6', '6', '6', '6'], 210 - ['5', '6', '6', '5', '6', '5', '5', '6'], 211 - ['5', '6', '6', '5', '6', '5', '5', '6'], 212 - ['5', '5', '5', '5', '6', '6', '6', '6'], 213 - ], 214 - }; 215 - const level014: LevelSetup = { 216 - name: 'level014', 217 - balls: 3, 218 - music: LEVEL_MUSIC.LEVEL_3, 219 - ballSpeed: BALL_SPEED.HARD, 220 - bgColor: Color.fromHex(LEVEL_BG.RED.LIGHT), 221 - bricksSetup: [[ROW_4], [ROW_6], [ROW_1], [ROW_4]], 222 - }; 223 - export const levelsSeed: LevelSetup[] = [ 224 - level001, 225 - level002, 226 - level003, 227 - level004, 228 - level005, 229 - level006, 230 - level007, 231 - level008, 232 - level009, 233 - level010, 234 - level011, 235 - level012, 236 - level013, 237 - level014, 238 - ];
-46
src/templates/typescript/src/sample/managers/asset.manager.ts
··· 1 - import { ImageSource, Loader, Sound } from 'excalibur'; 2 - import { ImageResource, SoundResource } from '../models'; 3 - 4 - class AssetManager { 5 - loader!: Loader; 6 - images!: ImageResource; 7 - sounds!: SoundResource; 8 - 9 - constructor() {} 10 - init() { 11 - this.loader = new Loader(); 12 - this.loader.suppressPlayButton = true; 13 - this.loader.backgroundColor = 'black'; 14 - // 15 - this.images = { 16 - spritesheet: new ImageSource('/sample/sprites/breakout_sprite.png'), 17 - }; 18 - 19 - this.sounds = { 20 - levelMusicDefault: new Sound('/sample/music/HarmonicMinor.wav'), 21 - levelMusic1: new Sound('/sample/music/HarmonicMinor.wav'), 22 - levelMusic2: new Sound('/sample/music/Cyberpunk Moonlight Sonata.mp3'), 23 - levelMusic3: new Sound('/sample/music/cubedcanada+invaderloop.mp3'), 24 - levelMusic4: new Sound('/sample/music/S31-Futuristic-Resources.ogg'), 25 - levelMusic5: new Sound('/sample/music/ruskerdax_-_open_warfare.mp3'), 26 - // 27 - levelCompleted: new Sound('/sample/sounds/win.wav'), 28 - levelGameover: new Sound('/sample/sounds/vgdeathsound.wav'), 29 - brickDestroy: new Sound('/sample/sounds/explosion.wav'), 30 - brickHit: new Sound('/sample/sounds/hit2.wav'), 31 - ballDrop: new Sound('/sample/sounds/ball_drop.mp3'), 32 - paddleHit: new Sound('/sample/sounds/hit.wav'), 33 - }; 34 - // 35 - for (const key in this.images) { 36 - this.loader.addResource(this.images[key]); 37 - } 38 - for (const key in this.sounds) { 39 - const sound = this.sounds[key]; 40 - this.loader.addResource(sound); 41 - sound.volume = 0.2; 42 - } 43 - } 44 - } 45 - 46 - export const assetManager = new AssetManager();
-7
src/templates/typescript/src/sample/managers/audio.manager.ts
··· 1 - class AudioManager { 2 - init() { 3 - console.warn('AudioManager not implemented'); 4 - } 5 - } 6 - 7 - export const audioManager = new AudioManager();
-173
src/templates/typescript/src/sample/managers/game.manager.ts
··· 1 - import { DevTool } from '@excaliburjs/dev-tools'; 2 - import { Color, Engine } from 'excalibur'; 3 - 4 - import { GAME_STATES, SCENE_EVENTS, SCENE_STATE } from '../models'; 5 - import { uiManager } from './ui.manager'; 6 - import { levelManager } from './level.manager'; 7 - import { assetManager } from './asset.manager'; 8 - import { audioManager } from './audio.manager'; 9 - import { LevelScene } from '../scenes/level.scene'; 10 - import { Subject } from '../utils'; 11 - 12 - class GameManager { 13 - game!: Engine; 14 - scoreGlobal = 0; 15 - scoreLevel = 0; 16 - highscore = 0; 17 - // 18 - 19 - gameState = new Subject(); 20 - sceneState = new Subject(); 21 - 22 - constructor(engine: Engine) { 23 - this.game = engine; 24 - } 25 - 26 - init() { 27 - this.setupListeners(); 28 - // start engine 29 - this.gameState.next(GAME_STATES.LOADING); 30 - } 31 - private setupListeners() { 32 - eventBus.on(SCENE_EVENTS.UPDATE_BALL, (balls: number) => { 33 - uiManager.printBalls(balls); 34 - }); 35 - eventBus.on(SCENE_EVENTS.UPDATE_SCORE, (score: number) => { 36 - this.scoreLevel += score; 37 - uiManager.printScore(this.scoreLevel); 38 - }); 39 - 40 - this.gameState.onChange((newGameState: GAME_STATES) => { 41 - console.log(`[${newGameState}]`); 42 - switch (newGameState) { 43 - case GAME_STATES.LOADING: 44 - assetManager.init(); 45 - levelManager.init(); 46 - audioManager.init(); 47 - uiManager.init(); 48 - this.loadLevels(); 49 - this.activateDebugMode(); 50 - this.game.start(assetManager.loader).then(() => { 51 - this.gameState.next(GAME_STATES.READY); 52 - eventBus.emit(SCENE_STATE.READY); 53 - }); 54 - break; 55 - case GAME_STATES.READY: 56 - uiManager.updateState(SCENE_STATE.READY); 57 - break; 58 - case GAME_STATES.PLAYING: 59 - break; 60 - case GAME_STATES.COMPLETED: 61 - uiManager.updateState('DONE'); 62 - break; 63 - case GAME_STATES.ERROR: 64 - break; 65 - } 66 - }); 67 - this.sceneState.onChange((newSceneState: SCENE_STATE) => { 68 - console.log(`[${this.gameState.current()}]/[${newSceneState}]`); 69 - switch (newSceneState) { 70 - case SCENE_STATE.LOADING: 71 - break; 72 - case SCENE_STATE.READY: 73 - this.scoreLevel = this.scoreGlobal; 74 - break; 75 - case SCENE_STATE.PLAYING: 76 - break; 77 - case SCENE_STATE.PAUSED: 78 - break; 79 - case SCENE_STATE.COMPLETED: 80 - this.scoreGlobal = this.scoreLevel; 81 - const newHighscore = this.scoreGlobal > this.highscore; 82 - if (newHighscore) this.highscore = this.scoreGlobal; 83 - 84 - if (levelManager.levelsCompleted()) { 85 - this.gameState.next(GAME_STATES.COMPLETED); 86 - return; 87 - } 88 - 89 - break; 90 - case SCENE_STATE.GAMEOVER: 91 - this.scoreLevel = this.scoreGlobal; 92 - break; 93 - case SCENE_STATE.ERROR: 94 - break; 95 - } 96 - 97 - uiManager.updateState(newSceneState); 98 - }); 99 - } 100 - 101 - private loadLevels() { 102 - levelManager.levels.forEach((lvl) => this.game.add(lvl.name, lvl)); 103 - } 104 - private activateDebugMode() { 105 - new DevTool(this.game); 106 - } 107 - 108 - // actions 109 - private loadLevel(level: LevelScene) { 110 - this.sceneState.next(SCENE_STATE.LOADING); 111 - 112 - this.game.goToScene(level.name); 113 - this.game.currentScene.onInitialize(this.game); 114 - uiManager.printUI( 115 - level.name, 116 - level.balls, 117 - this.scoreGlobal, 118 - this.highscore 119 - ); 120 - } 121 - startGame() { 122 - const initialLevel = levelManager.initial(); 123 - this.loadLevel(initialLevel); 124 - } 125 - retryLevel() { 126 - const sameLevel = levelManager.current(); 127 - this.loadLevel(sameLevel); 128 - } 129 - nextLevel() { 130 - const nextLevel: any = levelManager.next(); 131 - if (!nextLevel) { 132 - console.warn('No more levels'); 133 - return; 134 - } 135 - 136 - this.loadLevel(nextLevel); 137 - } 138 - resetGame() { 139 - this.scoreGlobal = 0; 140 - this.startGame(); 141 - } 142 - } 143 - 144 - class EventBus { 145 - events: Record<string, any> = {}; 146 - 147 - constructor() {} 148 - on(event: string, callback: any) { 149 - this.events[event] = this.events[event] || []; 150 - this.events[event].push(callback); 151 - } 152 - emit(event: string, data: any = {}) { 153 - if (this.events[event]) { 154 - this.events[event].forEach((callback: any) => { 155 - callback(data); 156 - }); 157 - } 158 - } 159 - } 160 - 161 - // 162 - const game = new Engine({ 163 - width: 600, 164 - height: 400, 165 - canvasElementId: 'main-canvas', 166 - backgroundColor: Color.Black, 167 - antialiasing: false, 168 - fixedUpdateFps: 30, 169 - }); 170 - const gameManager = new GameManager(game); 171 - const eventBus = new EventBus(); 172 - 173 - export { eventBus, gameManager };
-36
src/templates/typescript/src/sample/managers/level.manager.ts
··· 1 - import { LevelScene } from '../scenes/level.scene'; 2 - import { LevelSetup } from '../models'; 3 - import { levelsSeed } from '../levels/levels'; 4 - 5 - class LevelManager { 6 - currentLevelIndex = 0; 7 - levels: LevelScene[] = []; 8 - levelsSetup!: LevelSetup[]; 9 - 10 - constructor(seeds: LevelSetup[]) { 11 - this.levelsSetup = seeds; 12 - } 13 - 14 - init() { 15 - this.levelsSetup.forEach((lvl) => this.levels.push(new LevelScene(lvl))); 16 - } 17 - initial() { 18 - this.currentLevelIndex = 0; 19 - return this.levels[this.currentLevelIndex]; 20 - } 21 - next() { 22 - const levelExist = this.levels[this.currentLevelIndex + 1]; 23 - if (!levelExist) return false; 24 - this.currentLevelIndex++; 25 - return levelExist; 26 - } 27 - current() { 28 - return this.levels[this.currentLevelIndex]; 29 - } 30 - 31 - levelsCompleted() { 32 - return this.currentLevelIndex >= this.levels.length - 1; 33 - } 34 - } 35 - 36 - export const levelManager = new LevelManager(levelsSeed);
-59
src/templates/typescript/src/sample/managers/ui.manager.ts
··· 1 - import { SCENE_STATE } from '../models'; 2 - import { gameManager } from './game.manager'; 3 - 4 - class UIManager { 5 - gameContainer: any; 6 - btnPlay: any; 7 - btnRetry: any; 8 - btnNextLevel: any; 9 - btnDone: any; 10 - labelLevel: any; 11 - labelScore: any; 12 - labelBalls: any; 13 - labelHighscore: any; 14 - 15 - constructor() { 16 - this.gameContainer = document.getElementById('game'); 17 - this.btnPlay = document.getElementById('btn-play'); 18 - this.btnRetry = document.getElementById('btn-retry'); 19 - this.btnNextLevel = document.getElementById('btn-next-level'); 20 - this.btnDone = document.getElementById('btn-done'); 21 - this.labelLevel = document.getElementById('label-level'); 22 - this.labelScore = document.getElementById('label-score'); 23 - this.labelBalls = document.getElementById('label-balls'); 24 - this.labelHighscore = document.getElementById('label-high-score'); 25 - } 26 - 27 - init() { 28 - this.btnPlay.onclick = () => gameManager.startGame(); 29 - this.btnRetry.onclick = () => gameManager.retryLevel(); 30 - this.btnNextLevel.onclick = () => gameManager.nextLevel(); 31 - this.btnDone.onclick = () => gameManager.resetGame(); 32 - } 33 - printUI(level: string, balls: number, score: number, highscore: number) { 34 - this.printLevel(level); 35 - this.printBalls(balls); 36 - this.printScore(score); 37 - this.printHighscore(highscore); 38 - } 39 - updateState(state: SCENE_STATE | 'DONE') { 40 - this.gameContainer.className = state; 41 - } 42 - 43 - // 44 - printBalls(amount: number) { 45 - this.labelBalls.innerText = '🪩'.repeat(amount); 46 - } 47 - 48 - printScore(score: number) { 49 - this.labelScore.innerText = `${score}`; 50 - } 51 - printHighscore(score: number) { 52 - this.labelHighscore.innerText = `${score}`; 53 - } 54 - printLevel(lvl: string) { 55 - this.labelLevel.innerText = `${lvl}`; 56 - } 57 - } 58 - 59 - export const uiManager = new UIManager();
-67
src/templates/typescript/src/sample/models.ts
··· 1 - import { Color, ImageSource, Sound, Vector } from 'excalibur'; 2 - 3 - export enum BRICK_TYPE { 4 - LEVEL_1 = 'LEVEL_1', 5 - LEVEL_2 = 'LEVEL_2', 6 - LEVEL_3 = 'LEVEL_3', 7 - LEVEL_4 = 'LEVEL_4', 8 - LEVEL_5 = 'LEVEL_5', 9 - LEVEL_6 = 'LEVEL_6', 10 - } 11 - export enum GAME_STATES { 12 - LOADING = 'LOADING', 13 - READY = 'READY', 14 - PLAYING = 'PLAYING', 15 - COMPLETED = 'COMPLETED', 16 - ERROR = 'ERROR', 17 - } 18 - export enum SCENE_STATE { 19 - LOADING = 'SCENE_STATE__LOADING', 20 - READY = 'SCENE_STATE__READY', 21 - PLAYING = 'SCENE_STATE__PLAYING', 22 - PAUSED = 'SCENE_STATE__PAUSED', 23 - COMPLETED = 'SCENE_STATE__COMPLETED', 24 - GAMEOVER = 'SCENE_STATE__GAMEOVER', 25 - ERROR = 'SCENE_STATE__ERROR', 26 - } 27 - export enum SCENE_EVENTS { 28 - UPDATE_BALL = 'GAME_EVENTS__UPDATE_BALL', 29 - UPDATE_SCORE = 'GAME_EVENTS__UPDATE_SCORE', 30 - } 31 - export enum LEVEL_MUSIC { 32 - LEVEL_1 = 'level_music_1', 33 - LEVEL_2 = 'level_music_2', 34 - LEVEL_3 = 'level_music_3', 35 - LEVEL_4 = 'level_music_4', 36 - LEVEL_5 = 'level_music_5', 37 - } 38 - export enum PLAYER_CONTROLS { 39 - KEYBOARD = 'KEYBOARD', 40 - MOUSE = 'MOUSE', 41 - BOTH = 'BOTH', 42 - } 43 - 44 - export interface BrickSetup { 45 - type: BRICK_TYPE; 46 - life: number; 47 - cols: number; 48 - width: number; 49 - height: number; 50 - } 51 - export interface LevelSetup { 52 - balls: number; 53 - name: string; 54 - ballSpeed: Vector; 55 - bgColor: Color; 56 - bricksSetup: any[]; 57 - music: LEVEL_MUSIC; 58 - } 59 - export interface ParticleSetup { 60 - width: number; 61 - height: number; 62 - x: number; 63 - y: number; 64 - } 65 - 66 - export type ImageResource = Record<string, ImageSource>; 67 - export type SoundResource = Record<string, Sound>;
-34
src/templates/typescript/src/sample/particles/galaxy.particles.ts
··· 1 - import { Color, EmitterType, ParticleEmitter, Vector } from 'excalibur'; 2 - import { ParticleSetup } from '../models'; 3 - 4 - // https://excaliburjs.com/docs/particles 5 - // https://excaliburjs.com/particle-tester/ 6 - export function createGalaxy(setup: ParticleSetup) { 7 - const { width, height, x, y } = setup; 8 - const emitter = new ParticleEmitter({ 9 - width, 10 - height, 11 - x, 12 - y, 13 - }); 14 - emitter.emitterType = EmitterType.Circle; 15 - emitter.radius = 375; 16 - emitter.minVel = 400; 17 - emitter.maxVel = 415; 18 - emitter.minAngle = 0; 19 - emitter.maxAngle = 5.2; 20 - emitter.isEmitting = true; 21 - emitter.emitRate = 300; 22 - emitter.opacity = 0.8; 23 - emitter.fadeFlag = true; 24 - emitter.particleLife = 3000; 25 - emitter.maxSize = 1; 26 - emitter.minSize = 1; 27 - emitter.startSize = 1; 28 - emitter.endSize = 1; 29 - emitter.acceleration = new Vector(514, 2000); 30 - emitter.beginColor = Color.Transparent; 31 - emitter.endColor = Color.Transparent; 32 - 33 - return emitter; 34 - }
-284
src/templates/typescript/src/sample/scenes/level.scene.ts
··· 1 - import { 2 - Actor, 3 - CollisionStartEvent, 4 - Color, 5 - Engine, 6 - Input, 7 - Scene, 8 - Sound, 9 - Vector, 10 - } from 'excalibur'; 11 - import { Brick } from '../actors/brick.actor'; 12 - import { Paddle } from '../actors/paddle.actor'; 13 - import { Ball } from '../actors/ball.actor'; 14 - import { 15 - SCENE_EVENTS, 16 - LevelSetup, 17 - SCENE_STATE, 18 - PLAYER_CONTROLS, 19 - } from '../models'; 20 - import { eventBus, gameManager } from '../managers/game.manager'; 21 - import { assetManager } from '../managers/asset.manager'; 22 - import { createGalaxy } from '../particles/galaxy.particles'; 23 - 24 - export class LevelScene extends Scene { 25 - name: string; 26 - balls: number; 27 - ballSpeed: Vector; 28 - bgColor: Color; 29 - bricksSetup!: any[]; 30 - // 31 - levelMusic!: Sound; 32 - levelBalls!: number; 33 - levelBricks: Actor[] = []; 34 - 35 - state!: SCENE_STATE; 36 - playerControls!: PLAYER_CONTROLS; 37 - 38 - constructor(setup: LevelSetup) { 39 - super(); 40 - const { balls, name, ballSpeed, bgColor, bricksSetup, music } = setup; 41 - this.name = name; 42 - this.levelBalls = balls; 43 - this.balls = this.levelBalls; 44 - this.ballSpeed = ballSpeed; 45 - this.bgColor = bgColor; 46 - this.bricksSetup = bricksSetup; 47 - this.levelMusic = 48 - assetManager.sounds[music] || assetManager.sounds.levelMusicDefault; 49 - this.levelMusic.volume = 0.1; 50 - 51 - this.playerControls = PLAYER_CONTROLS.BOTH; 52 - } 53 - onInitialize(engine: Engine): void { 54 - this.init(engine); 55 - } 56 - 57 - init(engine: Engine) { 58 - this.reset(); 59 - this.backgroundColor = this.bgColor; 60 - //create game objects 61 - const galaxyParticle = this.createGalaxyParticles(engine); 62 - this.levelBricks = this.createBricks(); 63 - 64 - const paddle = this.createPaddle(engine); 65 - paddle.on('postupdate', () => { 66 - const hitLimits = { 67 - screenLeft: paddle.pos.x - paddle.width / 2 < 0, 68 - screenRight: paddle.pos.x + paddle.width / 2 > engine.drawWidth, 69 - }; 70 - 71 - if (hitLimits.screenLeft) { 72 - paddle.pos.x = 0 + paddle.width / 2; 73 - } 74 - if (hitLimits.screenRight) { 75 - paddle.pos.x = engine.drawWidth - paddle.width / 2; 76 - } 77 - }); 78 - 79 - const ball = this.createBall(engine); 80 - ball.on('postupdate', () => { 81 - const hitLimits = { 82 - screenTop: ball.pos.y - ball.height / 2 < 0, 83 - screenLeft: ball.pos.x - ball.width / 2 < 0, 84 - screenRight: ball.pos.x + ball.width / 2 > engine.drawWidth, 85 - }; 86 - 87 - if (hitLimits.screenTop) { 88 - // send to bottom 89 - ball.vel.y = ball.vel.y = ball.ballSpeed.y * -1 + Math.random(); 90 - } else if (hitLimits.screenLeft) { 91 - // send to the right 92 - ball.vel.x = ball.ballSpeed.x + Math.random(); 93 - } else if (hitLimits.screenRight) { 94 - // send to the left 95 - ball.vel.x = ball.ballSpeed.x * -1 + Math.random(); 96 - } 97 - }); 98 - // destroy bricks 99 - ball.on('collisionstart', (e: CollisionStartEvent) => { 100 - const collideWithBrick = this.levelBricks.indexOf(e.other) > -1; 101 - const collideWithPaddle = e.other.name === 'paddle'; 102 - if (collideWithBrick) { 103 - let brick: any = e.other; 104 - brick.life--; 105 - 106 - eventBus.emit(SCENE_EVENTS.UPDATE_SCORE, brick.hitScore); 107 - 108 - if (brick.life <= 0) { 109 - assetManager.sounds.brickDestroy.play(0.1); 110 - brick.kill(); 111 - eventBus.emit(SCENE_EVENTS.UPDATE_SCORE, brick.destroyScore); 112 - } else { 113 - assetManager.sounds.brickHit.play(0.1); 114 - brick.updateSprite(); 115 - } 116 - 117 - if (this.levelCompleted()) { 118 - this.levelMusic.stop(); 119 - assetManager.sounds.levelCompleted.play(); 120 - 121 - ball.kill(); 122 - this.setState(SCENE_STATE.COMPLETED); 123 - return; 124 - } 125 - } 126 - if (collideWithPaddle) { 127 - assetManager.sounds.paddleHit.play(); 128 - } 129 - 130 - if (!ball.isColliding) { 131 - ball.isColliding = true; 132 - const intersection = e.contact.mtv.normalize(); 133 - const hitX = Math.abs(intersection.x) > Math.abs(intersection.y); 134 - 135 - if (hitX) { 136 - // reverse x 137 - ball.vel.x *= -1; 138 - } else { 139 - // reverse y 140 - ball.vel.y *= -1; 141 - } 142 - } 143 - }); 144 - ball.on('collisionend', () => { 145 - ball.isColliding = false; 146 - }); 147 - // lose ball 148 - ball.on('exitviewport', () => { 149 - this.balls--; 150 - assetManager.sounds.ballDrop.play(); 151 - eventBus.emit(SCENE_EVENTS.UPDATE_BALL, this.balls); 152 - 153 - if (this.gameover()) { 154 - this.levelMusic.stop(); 155 - assetManager.sounds.levelGameover.play(); 156 - ball.kill(); 157 - this.setState(SCENE_STATE.GAMEOVER); 158 - } else { 159 - ball.pos.x = Math.random() * engine.drawWidth; 160 - ball.pos.y = -10; 161 - } 162 - }); 163 - 164 - // setup player controls 165 - 166 - if ( 167 - this.playerControls === PLAYER_CONTROLS.MOUSE || 168 - this.playerControls === PLAYER_CONTROLS.BOTH 169 - ) { 170 - // MOUSE CONTROLS 171 - engine.input.pointers.primary.on('move', (e) => { 172 - const canMove = this.state === SCENE_STATE.PLAYING; 173 - if (canMove) { 174 - paddle.pos.x = e.worldPos.x; 175 - } 176 - }); 177 - } 178 - if ( 179 - this.playerControls === PLAYER_CONTROLS.KEYBOARD || 180 - this.playerControls === PLAYER_CONTROLS.BOTH 181 - ) { 182 - // KEYBOARD CONTROLS 183 - paddle.on('preupdate', () => { 184 - paddle.vel.x = 0; 185 - const KEYBOARD_LEFT_PRESSED = 186 - engine.input.keyboard.isHeld(Input.Keys.Left) || 187 - engine.input.keyboard.isHeld(Input.Keys.A); 188 - const KEYBOARD_RIGHT_PRESSED = 189 - engine.input.keyboard.isHeld(Input.Keys.Right) || 190 - engine.input.keyboard.isHeld(Input.Keys.D); 191 - 192 - if (KEYBOARD_LEFT_PRESSED) { 193 - paddle.vel.x = -1; 194 - } 195 - if (KEYBOARD_RIGHT_PRESSED) { 196 - paddle.vel.x = 1; 197 - } 198 - if (paddle.vel.x !== 0) { 199 - paddle.vel.x = paddle.vel.x * paddle.speed; 200 - } 201 - }); 202 - } 203 - 204 - // add game objects to the scene 205 - this.add(paddle); 206 - this.add(ball); 207 - this.add(galaxyParticle); 208 - this.levelBricks.forEach((brick) => this.add(brick)); 209 - // 210 - this.levelMusic.stop(); 211 - this.levelMusic.play(); 212 - this.setState(SCENE_STATE.PLAYING); 213 - } 214 - createGalaxyParticles(engine: Engine) { 215 - return createGalaxy({ 216 - width: engine.drawWidth, 217 - height: engine.drawHeight, 218 - x: engine.drawWidth / 2, 219 - y: -50, 220 - }); 221 - } 222 - reset() { 223 - this.clear(); 224 - this.balls = this.levelBalls; 225 - } 226 - 227 - setState(newState: SCENE_STATE) { 228 - if (this.state === newState) return; 229 - this.state = newState; 230 - gameManager.sceneState.next(newState); 231 - } 232 - // game objects 233 - createBricks() { 234 - const padding = 32 + 24 / 6; 235 - const offsetX = 32 * 2; 236 - const offsetY = 32 + 48; 237 - const brickWidth = 32; 238 - const brickHeight = 16; 239 - const bricks: Actor[] = []; 240 - 241 - this.bricksSetup.forEach((row: any[], rowIndex: number) => { 242 - for (let pos = 0; pos < row.length; pos++) { 243 - if (row[pos] === '') { 244 - continue; 245 - } 246 - 247 - const x = offsetX + pos * (brickWidth + padding); 248 - const y = offsetY + rowIndex * (brickHeight + padding); 249 - const brick = new Brick(x, y, brickWidth, brickHeight, row[pos]); 250 - brick.z = 1; 251 - bricks.push(brick); 252 - } 253 - }); 254 - 255 - return bricks; 256 - } 257 - 258 - createBall(engine: Engine) { 259 - const x = engine.drawWidth / 2; 260 - const y = -10; 261 - const radius = 8; 262 - const vel = this.ballSpeed; 263 - const ball = new Ball(x, y, radius, vel); 264 - ball.z = 1; 265 - return ball; 266 - } 267 - createPaddle(engine: Engine) { 268 - const x = 100; 269 - const y = engine.drawHeight - 64; 270 - const width = 32; 271 - const height = 4; 272 - const speed = 500; 273 - const paddle = new Paddle(x, y, width, height, speed); 274 - paddle.z = 1; 275 - return paddle; 276 - } 277 - // state 278 - levelCompleted() { 279 - return this.levelBricks.every((b) => b.isKilled()); 280 - } 281 - gameover() { 282 - return this.balls <= 0; 283 - } 284 - }
-47
src/templates/typescript/src/sample/utils.ts
··· 1 - export class Subject { 2 - value: any; 3 - subscribers = new Set(); 4 - 5 - current() { 6 - return this.value; 7 - } 8 - next(newValue: any) { 9 - this.value = newValue; 10 - this.subscribers.forEach((cb: any) => cb(this.value)); 11 - } 12 - onChange(callback: any) { 13 - this.subscribers.add(callback); 14 - 15 - const unsubscribe = () => { 16 - this.subscribers.delete(callback); 17 - }; 18 - 19 - return { unsubscribe }; 20 - } 21 - } 22 - export class BehaviourSubject { 23 - value: any; 24 - subscribers = new Set(); 25 - 26 - constructor(initialValue: any) { 27 - this.value = initialValue; 28 - } 29 - 30 - current() { 31 - return this.value; 32 - } 33 - next(newValue: any) { 34 - const prev = this.value; 35 - this.value = newValue; 36 - this.subscribers.forEach((cb: any) => cb(this.value, prev)); 37 - } 38 - onChange(callback: any) { 39 - this.subscribers.add(callback); 40 - callback(this.value); 41 - const unsubscribe = () => { 42 - this.subscribers.delete(callback); 43 - }; 44 - 45 - return { unsubscribe }; 46 - } 47 - }
-1
src/templates/typescript/src/vite-env.d.ts
··· 1 - /// <reference types="vite/client" />
-286
src/templates/typescript/style.css
··· 1 - * { 2 - padding: 0; 3 - margin: 0; 4 - } 5 - :root { 6 - --color-primary: #2085d1; 7 - --bg-color: #edf2f7; 8 - --text-color: rgb(42, 41, 41); 9 - --border-radius: 16px/44px; 10 - } 11 - html, 12 - body { 13 - height: 100%; 14 - padding: 1rem; 15 - } 16 - 17 - body { 18 - color: var(--text-color); 19 - padding: 1rem 1rem; 20 - 21 - font-family: monospace; 22 - 23 - display: flex; 24 - flex-direction: column; 25 - gap: 1rem; 26 - 27 - background-color: var(--bg-color); 28 - opacity: 0.8; 29 - background-image: radial-gradient( 30 - var(--color-primary) 0.5px, 31 - var(--bg-color) 0.5px 32 - ); 33 - background-size: 10px 10px; 34 - } 35 - 36 - h1 { 37 - display: flex; 38 - align-items: center; 39 - justify-content: center; 40 - 41 - gap: 0.3rem; 42 - 43 - .logo { 44 - display: flex; 45 - align-items: center; 46 - } 47 - } 48 - 49 - nav { 50 - font-family: sans-serif; 51 - display: flex; 52 - align-items: center; 53 - justify-content: center; 54 - gap: 1rem; 55 - padding: 1rem; 56 - } 57 - 58 - .nav-link { 59 - display: flex; 60 - justify-content: center; 61 - align-items: center; 62 - gap: 0.3rem; 63 - 64 - background-color: var(--color-primary); 65 - color: whitesmoke; 66 - padding: 1rem 1rem; 67 - text-decoration: none; 68 - 69 - height: 16px; 70 - border-radius: var(--border-radius); 71 - transition: filter 0.3s ease-in-out; 72 - border: 2px solid black; 73 - 74 - &:active { 75 - transform: scale(0.98); 76 - } 77 - 78 - &:hover { 79 - filter: saturate(2); 80 - } 81 - } 82 - 83 - #credits { 84 - display: flex; 85 - align-items: center; 86 - justify-content: center; 87 - 88 - details { 89 - width: 630px; 90 - 91 - summary { 92 - cursor: pointer; 93 - font-size: 1.2rem; 94 - text-align: center; 95 - } 96 - 97 - .type { 98 - font-weight: bold; 99 - } 100 - } 101 - 102 - .row { 103 - display: flex; 104 - gap: 2rem; 105 - } 106 - } 107 - 108 - #game { 109 - display: flex; 110 - align-items: center; 111 - justify-content: center; 112 - 113 - position: relative; 114 - 115 - #main-canvas { 116 - width: 600px; 117 - height: 400px; 118 - border-radius: var(--border-radius); 119 - background: linear-gradient(167deg, #22043b, #6916bc, #db1481); 120 - background-size: 360% 360%; 121 - animation: gradient-animation 6s ease infinite; 122 - } 123 - 124 - #ui { 125 - position: absolute; 126 - background-color: rgba(42, 41, 41, 0.3); 127 - width: 100%; 128 - height: 100%; 129 - width: 600px; 130 - border-radius: var(--border-radius); 131 - 132 - display: flex; 133 - align-items: center; 134 - justify-content: center; 135 - 136 - button { 137 - width: 200px; 138 - height: 56px; 139 - border: 3px solid black; 140 - cursor: pointer; 141 - border-radius: var(--border-radius); 142 - transition: all 0.3s ease-in-out; 143 - font-weight: bold; 144 - 145 - &:active { 146 - transform: scale(0.98); 147 - } 148 - 149 - &.play { 150 - background-color: #2085d1; 151 - } 152 - &.retry { 153 - background-color: #6f7378; 154 - } 155 - &.next-level { 156 - background-color: #20d199; 157 - } 158 - &.restart { 159 - background-color: #8420d1; 160 - } 161 - } 162 - .ready-container { 163 - display: none; 164 - } 165 - .playing-container { 166 - display: none; 167 - } 168 - .gameover-container { 169 - display: none; 170 - } 171 - .completed-container { 172 - display: none; 173 - } 174 - .done-container { 175 - display: none; 176 - } 177 - } 178 - 179 - /* */ 180 - &.SCENE_STATE__LOADING { 181 - #ui { 182 - display: none; 183 - } 184 - } 185 - &.SCENE_STATE__READY { 186 - #ui { 187 - .ready-container { 188 - display: flex; 189 - align-items: center; 190 - justify-content: center; 191 - height: 100%; 192 - transition: all 0.3s ease-in-out; 193 - } 194 - } 195 - } 196 - &.SCENE_STATE__PLAYING { 197 - #ui { 198 - display: flex; 199 - pointer-events: none; 200 - background-color: transparent; 201 - .playing-container { 202 - position: absolute; 203 - color: whitesmoke; 204 - /* background-color: whitesmoke; */ 205 - top: 1rem; 206 - left: 2rem; 207 - width: 90%; 208 - 209 - display: flex; 210 - align-items: center; 211 - justify-content: space-between; 212 - 213 - gap: 1rem; 214 - 215 - .center { 216 - text-align: center; 217 - } 218 - .right { 219 - text-align: right; 220 - } 221 - } 222 - } 223 - 224 - #main-canvas { 225 - cursor: e-resize; 226 - } 227 - } 228 - &.SCENE_STATE__COMPLETED { 229 - #ui { 230 - display: flex; 231 - 232 - .completed-container { 233 - display: flex; 234 - align-items: center; 235 - justify-content: center; 236 - height: 100%; 237 - 238 - transition: all 0.3s ease-in-out; 239 - } 240 - } 241 - } 242 - &.SCENE_STATE__GAMEOVER { 243 - #ui { 244 - display: flex; 245 - .gameover-container { 246 - display: flex; 247 - align-items: center; 248 - justify-content: center; 249 - height: 100%; 250 - transition: all 0.3s ease-in-out; 251 - } 252 - } 253 - } 254 - &.DONE { 255 - #ui { 256 - display: flex; 257 - 258 - width: 90%; 259 - background-color: whitesmoke; 260 - 261 - .done-container { 262 - display: flex; 263 - align-items: center; 264 - justify-content: center; 265 - flex-direction: column; 266 - gap: 1rem; 267 - 268 - button { 269 - height: 56px; 270 - } 271 - } 272 - } 273 - } 274 - } 275 - 276 - @keyframes gradient-animation { 277 - 0% { 278 - background-position: 0% 50%; 279 - } 280 - 50% { 281 - background-position: 100% 50%; 282 - } 283 - 100% { 284 - background-position: 0% 50%; 285 - } 286 - }
-23
src/templates/typescript/tsconfig.json
··· 1 - { 2 - "compilerOptions": { 3 - "target": "ES2020", 4 - "useDefineForClassFields": true, 5 - "module": "ESNext", 6 - "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 - "skipLibCheck": true, 8 - 9 - /* Bundler mode */ 10 - "moduleResolution": "bundler", 11 - "allowImportingTsExtensions": true, 12 - "resolveJsonModule": true, 13 - "isolatedModules": true, 14 - "noEmit": true, 15 - 16 - /* Linting */ 17 - "strict": true, 18 - "noUnusedLocals": true, 19 - "noUnusedParameters": true, 20 - "noFallthroughCasesInSwitch": true 21 - }, 22 - "include": ["src"] 23 - }
+143 -28
src/utils.js
··· 1 - import { alert, log } from './console.js'; 2 - import { execSync } from 'child_process'; 3 - import * as fs from 'fs'; 4 - import * as path from 'path'; 1 + import * as child from "child_process"; 2 + import * as fs from "fs"; 3 + import * as os from "os"; 4 + import { textMagenta, textYellow, terminal } from "./console.js"; 5 5 6 - export function runCommand(command) { 7 - try { 8 - execSync(`${command}`, { stdio: 'inherit' }); 9 - } catch (e) { 10 - alert(`Failed to execute ${command}.`); 11 - log(e); 12 - return false; 6 + // prompts transformers 7 + export function transformerConfirm(value) { 8 + return value ? "Yes" : "No"; 9 + } 10 + // prompts validators 11 + export function validateProjectName(name) { 12 + if (name === "") return false; 13 + // if dir exists =>false 14 + return true; 15 + } 16 + // prints 17 + export function printDocs() { 18 + terminal.listItem({ 19 + text: "Explore our Docs:", 20 + textRelevant: "https://excaliburjs.com/docs/", 21 + }); 22 + } 23 + export function printProjectDirectory(projectDirectory) { 24 + terminal.listItem({ 25 + text: "Enter your directory:", 26 + textRelevant: `cd ./${projectDirectory}`, 27 + }); 28 + } 29 + export function printDependencyStatus(status) { 30 + const text = "Dependencies:"; 31 + if (status) { 32 + terminal.listItem({ text, textRelevant: "Installed" }); 33 + } else { 34 + terminal.listItem({ 35 + text, 36 + textRelevant: "Not installed", 37 + colorRelevant: textYellow, 38 + }); 39 + } 40 + } 41 + export function printRepoStatus(status) { 42 + const text = "Git Repository:"; 43 + if (status) { 44 + terminal.listItem({ 45 + text, 46 + textRelevant: "Initialized", 47 + }); 48 + } else { 49 + terminal.listItem({ 50 + text, 51 + textRelevant: "Not initialized", 52 + colorRelevant: textYellow, 53 + }); 54 + } 55 + } 56 + // 57 + export function printDiscord() { 58 + terminal.listItem({ 59 + text: "Join our Discord:", 60 + textRelevant: "https://discord.com/invite/W6zUd4tTY3", 61 + colorRelevant: textMagenta, 62 + }); 63 + } 64 + export function printSupport() { 65 + terminal.subtitle("If you find yourself stuck:"); 66 + printDiscord(); 67 + printDocs(); 68 + } 69 + export function printActions(actions) { 70 + const { projectDirectory, dependencies } = actions; 71 + terminal.line(); 72 + terminal.warning(" Remember: "); 73 + terminal.blank(); 74 + terminal.listItem({ 75 + text: "You can find your project in:", 76 + textRelevant: `./${projectDirectory}`, 77 + }); 78 + if (dependencies) { 79 + terminal.listItem({ text: "Dependencies:", textRelevant: "Installed" }); 80 + } else { 81 + terminal.listItem({ 82 + text: "Dependencies:", 83 + textRelevant: "pending", 84 + colorRelevant: textYellow, 85 + }); 13 86 } 14 - return true; 87 + terminal.blank(); 15 88 } 16 - export function getFileExtension(filePath) { 17 - return path.extname(filePath).slice(1); 89 + export function bye() { 90 + terminal.print("👋 See u soon."); 91 + terminal.line(); 92 + process.exit(1); 93 + } 94 + export function byeWithActions(actions) { 95 + printActions(actions); 96 + bye(); 18 97 } 19 - export function isMediaFile(fileExtension) { 20 - const mediaExtensions = ['png', 'mp3', 'wav', 'ogg', 'ico']; 21 - const result = mediaExtensions.some( 22 - (extension) => extension === fileExtension.toLowerCase() 23 - ); 24 - return result; 98 + // Filesystem 99 + export function isWindows() { 100 + return os.platform() === "win32"; 25 101 } 26 - export function mkdir(path) { 27 - fs.mkdirSync(path); 102 + export function runCommand(command, directory) { 103 + return new Promise((resolve, reject) => { 104 + child.exec(command, { cwd: directory }, (error, stdout, stderr) => { 105 + if (error) { 106 + reject(error); 107 + } 108 + resolve(); 109 + }); 110 + }); 28 111 } 29 - export function readFile(path, encoding = 'utf-8') { 112 + export function readFile(path, encoding = "utf-8") { 30 113 return fs.readFileSync(path, encoding); 31 114 } 32 - export function writeFile(path, content, encoding = 'utf-8') { 115 + export function readDirectory(path) { 116 + return fs.readdirSync(path); 117 + } 118 + export function getResourceStats(path) { 119 + return fs.statSync(path); 120 + } 121 + export function writeFile(path, content, encoding = "utf-8") { 33 122 fs.writeFileSync(path, content, encoding); 34 123 } 35 - export function copy(from, to) { 36 - const sourceStream = fs.createReadStream(from); 37 - const destinationStream = fs.createWriteStream(to); 124 + export function removeFile(path) { 125 + try { 126 + fs.unlinkSync(path); 127 + } catch (err) { 128 + console.error(err.message); 129 + } 130 + } 131 + export function removeFolder(path) { 132 + try { 133 + fs.rmSync(path, { recursive: true, force: true }); 134 + } catch (err) { 135 + console.error(err.message); 136 + } 137 + } 38 138 39 - sourceStream.pipe(destinationStream); 139 + // formatter 140 + export function slugify(str) { 141 + str = str.replace(/^\s+|\s+$/g, ""); 142 + str = str.toLowerCase(); 143 + str = str 144 + .replace(/[^a-z0-9 -]/g, "") 145 + .replace(/\s+/g, "-") 146 + .replace(/-+/g, "-"); 147 + return str; 148 + } 149 + 150 + // arrays 151 + export function sortByProp(a, b, prop) { 152 + if (a[prop] < b[prop]) return -1; 153 + if (a[prop] > b[prop]) return 1; 154 + return 0; 40 155 }