├── .gitignore ├── packages ├── lib │ ├── .prettierignore │ ├── .eslintignore │ ├── src │ │ ├── text │ │ │ ├── index.ts │ │ │ └── Text.ts │ │ ├── effects │ │ │ ├── sfx │ │ │ │ └── index.ts │ │ │ ├── particles │ │ │ │ ├── index.ts │ │ │ │ └── Emitter.ts │ │ │ ├── tweens │ │ │ │ ├── index.ts │ │ │ │ ├── Interpolation.ts │ │ │ │ ├── Easing.ts │ │ │ │ └── Tween.ts │ │ │ ├── index.ts │ │ │ └── utils │ │ │ │ └── index.ts │ │ ├── group │ │ │ ├── index.ts │ │ │ ├── Grid.ts │ │ │ └── Group.ts │ │ ├── inputs │ │ │ ├── index.ts │ │ │ ├── Keyboard.ts │ │ │ └── Keys.ts │ │ ├── graphics │ │ │ ├── index.ts │ │ │ ├── Spritemap.ts │ │ │ └── Sprite.ts │ │ ├── index.ts │ │ ├── extras │ │ │ ├── index.ts │ │ │ ├── TransitionScreen.ts │ │ │ ├── Backdrop.ts │ │ │ └── SplashScreen.ts │ │ ├── geom │ │ │ ├── index.ts │ │ │ ├── Line.ts │ │ │ ├── Circle.ts │ │ │ ├── Rectangle.ts │ │ │ └── Triangle.ts │ │ ├── EngineConstants.ts │ │ ├── typedocs.ts │ │ ├── math │ │ │ ├── index.ts │ │ │ └── Point.ts │ │ ├── Stage.ts │ │ ├── Scene.ts │ │ └── tiles │ │ │ ├── index.ts │ │ │ └── MapCamera.ts │ ├── scripts │ │ ├── copyReadmeToDocs.js │ │ ├── removeReadme.js │ │ ├── copyReadme.js │ │ ├── autocomplete.js │ │ └── create_release.js │ ├── .gitignore │ ├── typedoc.json │ ├── .prettierrc │ ├── tsup.config.ts │ ├── .eslintrc │ ├── CHANGELOG.md │ ├── tsconfig.json │ └── package.json ├── create-inks2d │ ├── .prettierignore │ ├── .eslintignore │ ├── platform-mobile │ │ ├── resources │ │ │ └── .gitkeep │ │ ├── src │ │ │ ├── vite-env.d.ts │ │ │ ├── app.ts │ │ │ └── main.ts │ │ ├── public │ │ │ ├── logo.png │ │ │ └── mw_inks2d.png │ │ ├── .readme │ │ │ ├── android_workflow_001.png │ │ │ ├── android_workflow_002.png │ │ │ └── android_workflow_003.png │ │ ├── capacitor.config.json │ │ ├── _gitignore │ │ ├── vite.config.js │ │ ├── .prettierrc │ │ ├── tsconfig.json │ │ ├── .eslintrc │ │ ├── package.json │ │ ├── index.html │ │ ├── _github │ │ │ └── workflows │ │ │ │ └── build-android.yml │ │ └── README.md │ ├── index.js │ ├── platform-web │ │ ├── src │ │ │ ├── vite-env.d.ts │ │ │ └── main.ts │ │ ├── public │ │ │ ├── logo.png │ │ │ └── mw_inks2d.png │ │ ├── vite.config.js │ │ ├── _gitignore │ │ ├── .prettierrc │ │ ├── tsconfig.json │ │ ├── .eslintrc │ │ ├── package.json │ │ └── index.html │ ├── tsup.config.ts │ ├── CHANGELOG.md │ ├── .gitignore │ ├── src │ │ ├── types.ts │ │ └── platforms.ts │ ├── .prettierrc │ ├── tsconfig.json │ ├── .eslintrc │ ├── README.md │ ├── package.json │ └── scripts │ │ └── create_release.js └── examples │ ├── games │ ├── flappy-beans │ │ ├── assets │ │ │ ├── credits.txt │ │ │ ├── images │ │ │ │ ├── bg.png │ │ │ │ ├── bean.png │ │ │ │ ├── hand.png │ │ │ │ ├── menu.png │ │ │ │ ├── play.png │ │ │ │ ├── b_pipe.png │ │ │ │ ├── badges.png │ │ │ │ ├── floor.png │ │ │ │ ├── ranking.png │ │ │ │ ├── ready.png │ │ │ │ ├── restart.png │ │ │ │ ├── t_pipe.png │ │ │ │ ├── title.png │ │ │ │ ├── btn_pause.png │ │ │ │ ├── btn_play.png │ │ │ │ ├── result_bg.png │ │ │ │ ├── tap_left.png │ │ │ │ ├── tap_right.png │ │ │ │ └── splash-logo.png │ │ │ ├── mw_inks2d.png │ │ │ ├── fonts │ │ │ │ ├── prstartk.ttf │ │ │ │ └── ubuntu.ttf │ │ │ └── sounds │ │ │ │ └── bounce.wav │ │ ├── src │ │ │ ├── gameConfig.ts │ │ │ ├── main.ts │ │ │ ├── scenes │ │ │ │ ├── StartScreen.ts │ │ │ │ └── SplashScreen.ts │ │ │ └── objects │ │ │ │ └── Wnd_GameOver.ts │ │ └── index.html │ ├── avoider │ │ ├── bomb1.png │ │ ├── bomb2.png │ │ ├── bomb3.png │ │ └── credits.txt │ ├── breakout │ │ ├── assets │ │ │ ├── mw_inks2d.png │ │ │ ├── images │ │ │ │ ├── button.png │ │ │ │ ├── paddle.png │ │ │ │ ├── star.png │ │ │ │ ├── tiles.png │ │ │ │ └── startscreen-bg.png │ │ │ └── credits.txt │ │ ├── src │ │ │ ├── gameConfig.ts │ │ │ └── main.ts │ │ └── index.html │ └── memory-game │ │ ├── assets │ │ ├── images │ │ │ ├── credits.txt │ │ │ └── cards.png │ │ └── mw_inks2d.png │ │ ├── src │ │ ├── gameConfig.ts │ │ ├── main.ts │ │ └── scenes │ │ │ └── GameScreen.ts │ │ └── index.html │ ├── playground │ ├── particles │ │ ├── assets │ │ │ ├── flames.png │ │ │ ├── smokes.png │ │ │ └── spark.png │ │ ├── fire.html │ │ ├── smoke.html │ │ ├── fireworks.html │ │ └── src │ │ │ ├── fireworks.ts │ │ │ ├── smoke.ts │ │ │ └── fire.ts │ ├── spritemap │ │ ├── assets │ │ │ └── warrior_1.png │ │ ├── index.html │ │ └── src │ │ │ └── spritemap.ts │ ├── _basic │ │ ├── src │ │ │ └── main.ts │ │ └── index.html │ ├── alpha │ │ ├── index.html │ │ └── src │ │ │ └── alpha.ts │ └── collision │ │ ├── hitTestCircle.html │ │ ├── hitTestRectangle.html │ │ ├── hitTestCircleTriangle.html │ │ ├── hitTestCircleRectangle.html │ │ ├── hitTestRectangleTriangle.html │ │ └── src │ │ ├── hitTestCircleRectangle.ts │ │ ├── hitTestRectangle.ts │ │ ├── hitTestCircle.ts │ │ ├── hitTestCircleTriangle.ts │ │ └── hitTestRectangleTriangle.ts │ ├── .gitignore │ ├── package.json │ ├── vite.config.js │ ├── index.html │ └── tsconfig.json ├── .readme ├── header.png └── showcase │ ├── two-dots.png │ ├── lolly-balls.png │ ├── shinobi-way.png │ ├── pimi-jumpers.png │ ├── noah-crush-mania.png │ └── get-the-black-dots.png ├── .husky ├── pre-commit └── commit-msg ├── commitlinterrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── question.md │ ├── feature_request.md │ └── bug_report.md ├── workflows │ ├── publish-docs.yml │ ├── publish-lib.yml │ └── publish-cig.yml └── CODE_OF_CONDUCT.md ├── turbo.json ├── LICENSE.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .turbo -------------------------------------------------------------------------------- /packages/lib/.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* -------------------------------------------------------------------------------- /packages/create-inks2d/.prettierignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /packages/create-inks2d/.eslintignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | -------------------------------------------------------------------------------- /packages/lib/.eslintignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | /scripts/* -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/resources/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/lib/src/text/index.ts: -------------------------------------------------------------------------------- 1 | export { Text } from "./Text"; 2 | -------------------------------------------------------------------------------- /packages/lib/src/effects/sfx/index.ts: -------------------------------------------------------------------------------- 1 | export { Sound } from "./Sound"; 2 | -------------------------------------------------------------------------------- /.readme/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/.readme/header.png -------------------------------------------------------------------------------- /packages/create-inks2d/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import "./dist/index.cjs"; 4 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-web/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/lib/src/group/index.ts: -------------------------------------------------------------------------------- 1 | export { Group } from "./Group"; 2 | export { Grid } from "./Grid"; 3 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | # npm run validate 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | .git/hooks/commit-msg $1 5 | -------------------------------------------------------------------------------- /.readme/showcase/two-dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/.readme/showcase/two-dots.png -------------------------------------------------------------------------------- /packages/lib/src/inputs/index.ts: -------------------------------------------------------------------------------- 1 | export { Keyboard } from "./Keyboard"; 2 | export { Keys } from "./Keys"; 3 | -------------------------------------------------------------------------------- /.readme/showcase/lolly-balls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/.readme/showcase/lolly-balls.png -------------------------------------------------------------------------------- /.readme/showcase/shinobi-way.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/.readme/showcase/shinobi-way.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/credits.txt: -------------------------------------------------------------------------------- 1 | Art from: 2 | 3 | https://opengameart.org/content/flappy-beans -------------------------------------------------------------------------------- /packages/lib/src/graphics/index.ts: -------------------------------------------------------------------------------- 1 | export { Sprite } from "./Sprite"; 2 | export { Spritemap } from "./Spritemap"; 3 | -------------------------------------------------------------------------------- /.readme/showcase/pimi-jumpers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/.readme/showcase/pimi-jumpers.png -------------------------------------------------------------------------------- /.readme/showcase/noah-crush-mania.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/.readme/showcase/noah-crush-mania.png -------------------------------------------------------------------------------- /.readme/showcase/get-the-black-dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/.readme/showcase/get-the-black-dots.png -------------------------------------------------------------------------------- /packages/examples/games/avoider/bomb1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/avoider/bomb1.png -------------------------------------------------------------------------------- /packages/examples/games/avoider/bomb2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/avoider/bomb2.png -------------------------------------------------------------------------------- /packages/examples/games/avoider/bomb3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/avoider/bomb3.png -------------------------------------------------------------------------------- /packages/lib/src/effects/particles/index.ts: -------------------------------------------------------------------------------- 1 | export { ParticleSystem } from "./ParticleSystem"; 2 | export { Emitter } from "./Emitter"; 3 | -------------------------------------------------------------------------------- /packages/lib/scripts/copyReadmeToDocs.js: -------------------------------------------------------------------------------- 1 | import fse from "fs-extra"; 2 | 3 | fse.copySync("../../.readme", "docs/.readme", { overwrite: true }); 4 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-web/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/create-inks2d/platform-web/public/logo.png -------------------------------------------------------------------------------- /packages/examples/games/avoider/credits.txt: -------------------------------------------------------------------------------- 1 | https://opengameart.org/content/bombman-2d-resources 2 | https://opengameart.org/content/puzzle-pack-2-795-assets -------------------------------------------------------------------------------- /packages/lib/src/index.ts: -------------------------------------------------------------------------------- 1 | export { Engine } from "./Engine"; 2 | export { Scene } from "./Scene"; 3 | export { DisplayObject } from "./DisplayObject"; 4 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/create-inks2d/platform-mobile/public/logo.png -------------------------------------------------------------------------------- /packages/examples/games/breakout/assets/mw_inks2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/breakout/assets/mw_inks2d.png -------------------------------------------------------------------------------- /packages/examples/games/memory-game/assets/images/credits.txt: -------------------------------------------------------------------------------- 1 | https://opengameart.org/content/playing-cards-pack 2 | https://opengameart.org/content/boardgame-pack -------------------------------------------------------------------------------- /packages/create-inks2d/platform-web/public/mw_inks2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/create-inks2d/platform-web/public/mw_inks2d.png -------------------------------------------------------------------------------- /packages/examples/games/breakout/assets/images/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/breakout/assets/images/button.png -------------------------------------------------------------------------------- /packages/examples/games/breakout/assets/images/paddle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/breakout/assets/images/paddle.png -------------------------------------------------------------------------------- /packages/examples/games/breakout/assets/images/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/breakout/assets/images/star.png -------------------------------------------------------------------------------- /packages/examples/games/breakout/assets/images/tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/breakout/assets/images/tiles.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/bg.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/mw_inks2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/mw_inks2d.png -------------------------------------------------------------------------------- /packages/examples/games/memory-game/assets/mw_inks2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/memory-game/assets/mw_inks2d.png -------------------------------------------------------------------------------- /packages/examples/playground/particles/assets/flames.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/playground/particles/assets/flames.png -------------------------------------------------------------------------------- /packages/examples/playground/particles/assets/smokes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/playground/particles/assets/smokes.png -------------------------------------------------------------------------------- /packages/examples/playground/particles/assets/spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/playground/particles/assets/spark.png -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/public/mw_inks2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/create-inks2d/platform-mobile/public/mw_inks2d.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/bean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/bean.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/hand.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/menu.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/play.png -------------------------------------------------------------------------------- /packages/examples/games/memory-game/assets/images/cards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/memory-game/assets/images/cards.png -------------------------------------------------------------------------------- /packages/examples/playground/spritemap/assets/warrior_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/playground/spritemap/assets/warrior_1.png -------------------------------------------------------------------------------- /packages/lib/src/effects/tweens/index.ts: -------------------------------------------------------------------------------- 1 | export { Easing } from "./Easing"; 2 | export { Interpolation } from "./Interpolation"; 3 | export { Tween } from "./Tween"; 4 | -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/fonts/prstartk.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/fonts/prstartk.ttf -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/fonts/ubuntu.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/fonts/ubuntu.ttf -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/b_pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/b_pipe.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/badges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/badges.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/floor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/floor.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/ranking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/ranking.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/ready.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/restart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/restart.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/t_pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/t_pipe.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/title.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/sounds/bounce.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/sounds/bounce.wav -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/btn_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/btn_pause.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/btn_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/btn_play.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/result_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/result_bg.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/tap_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/tap_left.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/tap_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/tap_right.png -------------------------------------------------------------------------------- /packages/examples/games/breakout/assets/images/startscreen-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/breakout/assets/images/startscreen-bg.png -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/assets/images/splash-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/examples/games/flappy-beans/assets/images/splash-logo.png -------------------------------------------------------------------------------- /packages/examples/games/memory-game/src/gameConfig.ts: -------------------------------------------------------------------------------- 1 | export const _gameConfig = { 2 | game: { 3 | numberOfCards: 20, 4 | cardsPerLine: 5, 5 | best: -1, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/lib/scripts/removeReadme.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | fs.rmSync(".readme", { recursive: true, force: true }); 4 | fs.rmSync("README.md", { recursive: true, force: true }); 5 | -------------------------------------------------------------------------------- /packages/lib/src/extras/index.ts: -------------------------------------------------------------------------------- 1 | export { Backdrop } from "./Backdrop"; 2 | export { SplashScreen } from "./SplashScreen"; 3 | export { TransitionScreen } from "./TransitionScreen"; 4 | -------------------------------------------------------------------------------- /packages/lib/src/geom/index.ts: -------------------------------------------------------------------------------- 1 | export { Circle } from "./Circle"; 2 | export { Line } from "./Line"; 3 | export { Rectangle } from "./Rectangle"; 4 | export { Triangle } from "./Triangle"; 5 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/.readme/android_workflow_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/create-inks2d/platform-mobile/.readme/android_workflow_001.png -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/.readme/android_workflow_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/create-inks2d/platform-mobile/.readme/android_workflow_002.png -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/.readme/android_workflow_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicklessOne/inks2d/HEAD/packages/create-inks2d/platform-mobile/.readme/android_workflow_003.png -------------------------------------------------------------------------------- /packages/create-inks2d/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts"], 5 | format: ["cjs"], 6 | minify: true, 7 | clean: true, 8 | }); 9 | -------------------------------------------------------------------------------- /commitlinterrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": { 3 | "example": "Use this for the examples/playground folder", 4 | "game": "Use this for the examples/games folder", 5 | "bump": "Use this for bump versioning" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "com.inks2d_mobile_starter.app", 3 | "appName": "inks2d-mobile-starter", 4 | "webDir": "dist", 5 | "server": { 6 | "androidScheme": "https" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/examples/games/breakout/assets/credits.txt: -------------------------------------------------------------------------------- 1 | https://opengameart.org/content/puzzle-pack-2-795-assets 2 | https://opengameart.org/content/puzzle-game-art 3 | https://fonts.google.com/share?selection.family=Gemunu%20Libre:wght@600 -------------------------------------------------------------------------------- /packages/create-inks2d/platform-web/vite.config.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { defineConfig } from "vite"; 3 | 4 | export default defineConfig({ 5 | base: "./", 6 | 7 | server: { 8 | port: 3000, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about inks2d 4 | labels: question 5 | --- 6 | 7 | ## Search terms 8 | 9 | 10 | 11 | ## Question -------------------------------------------------------------------------------- /packages/lib/scripts/copyReadme.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import fse from "fs-extra"; 3 | 4 | fse.copySync("../../.readme", ".readme", { overwrite: true }); 5 | fs.copyFile("../../README.md", "README.md", (err) => { 6 | if (err) throw err; 7 | console.log("Root README.md copied!"); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/src/app.ts: -------------------------------------------------------------------------------- 1 | import { KeepAwake } from "@capacitor-community/keep-awake"; 2 | import { App } from "@capacitor/app"; 3 | 4 | KeepAwake.keepAwake(); 5 | window.screen.orientation.lock("portrait"); 6 | 7 | App.addListener("backButton", () => { 8 | App.exitApp(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/create-inks2d/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | # v1.1.0 (2023-08-12) 4 | 5 | - feat(create-inks2d): add mobile platform 6 | 7 | # v1.0.2 (2023-06-11) 8 | 9 | - chore(create-inks2d): add platform-* folders in build 10 | 11 | # v1.0.1 (2023-06-11) 12 | 13 | - fix: typo 14 | 15 | # v1.0.0 (2023-06-11) 16 | 17 | - initial version -------------------------------------------------------------------------------- /packages/lib/src/effects/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | *```ts 3 | * import { Emitter, ParticleSystem } from "inks2d/effects/particles"; 4 | *``` 5 | * @category Namespaces 6 | */ 7 | export * as particles from "./particles"; 8 | 9 | /** 10 | * ```ts 11 | * import { Sound } from "inks2d/effects/sfx"; 12 | * ``` 13 | * @category Namespaces 14 | */ 15 | export * as sfx from "./sfx"; 16 | -------------------------------------------------------------------------------- /packages/examples/.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? 25 | -------------------------------------------------------------------------------- /packages/create-inks2d/.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? 25 | -------------------------------------------------------------------------------- /packages/lib/.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 | docs 13 | dist-ssr 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-web/_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? 25 | -------------------------------------------------------------------------------- /packages/examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vite", 7 | "dev:examples": "vite" 8 | }, 9 | "dependencies": { 10 | "inks2d": "*" 11 | }, 12 | "devDependencies": { 13 | "typescript": "^4.9.3", 14 | "vite": "^4.2.0", 15 | "vite-plugin-list-directory-contents": "^1.4.5" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/create-inks2d/src/types.ts: -------------------------------------------------------------------------------- 1 | type ColorFunc = (str: string | number) => string; 2 | 3 | type PlatformVariant = { 4 | name: string; 5 | display: string; 6 | color: ColorFunc; 7 | customCommand?: string; 8 | }; 9 | 10 | type Platform = { 11 | name: string; 12 | display: string; 13 | color: ColorFunc; 14 | variants?: PlatformVariant[]; 15 | }; 16 | 17 | export type { ColorFunc, Platform, PlatformVariant }; 18 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/_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 | inks2d-game.keystore 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /packages/lib/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://typedoc.org/schema.json", 3 | "name": "inks2d", 4 | "out": "docs", 5 | "media": "../../.readme", 6 | "entryPoints": ["./src/typedocs.ts"], 7 | "sort": ["source-order"], 8 | "categorizeByGroup": false, 9 | "visibilityFilters": {}, 10 | "categoryOrder": ["inks2d", "Namespaces", "*"], 11 | "excludePrivate": true, 12 | "includeVersion": true, 13 | "excludeNotDocumented": true 14 | } 15 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/vite.config.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { defineConfig } from "vite"; 4 | 5 | const _dirname = 6 | typeof __dirname !== "undefined" 7 | ? __dirname 8 | : path.dirname(fileURLToPath(import.meta.url)); 9 | 10 | export default defineConfig({ 11 | server: { 12 | port: 3000, 13 | }, 14 | 15 | build: { 16 | outDir: "dist/", 17 | emptyOutDir: true, 18 | }, 19 | 20 | plugins: [], 21 | }); 22 | -------------------------------------------------------------------------------- /packages/examples/games/breakout/src/gameConfig.ts: -------------------------------------------------------------------------------- 1 | export const _gameConfig = { 2 | game: { 3 | best: 0, 4 | maxLives: 3, 5 | lastScore: -1, 6 | }, 7 | tiles: { 8 | numberOfRows: 8, 9 | tilesPerRow: 8, 10 | chanceToSpawn: 0.55, 11 | scoresPerRow: [100, 90, 80, 70, 60, 50, 40, 30], 12 | }, 13 | ball: { 14 | initialSpeed: { 15 | x: 4, 16 | y: -4, 17 | }, 18 | maxSpeed: { 19 | x: 8, 20 | y: -8, 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest improvements or new features 4 | labels: enhancement 5 | --- 6 | 7 | ## Search Terms 8 | 9 | 10 | 11 | ## Problem 12 | 13 | 14 | 15 | ## Suggested Solution 16 | 17 | -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/src/gameConfig.ts: -------------------------------------------------------------------------------- 1 | export const _gameConfig: Record = { 2 | game: { 3 | score: 0, 4 | best: 0, 5 | }, 6 | config: { 7 | gameSpeed: 1.5, 8 | jumpForce: -5, 9 | beanRotation: 15, 10 | distanceBetweenPipes: 120, 11 | distanceUntilFirstPipe: 500, 12 | pipeGap: 120, 13 | numPipes: 2, 14 | lastHeight: undefined, 15 | maxDistanceBetweenGaps: -260, 16 | silverBadgeIn: 20, 17 | goldBadgeIn: 40, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/lib/src/EngineConstants.ts: -------------------------------------------------------------------------------- 1 | import { type DisplayObject } from "./DisplayObject"; 2 | import { type Emitter } from "./effects/particles/Emitter"; 3 | import { Tween } from "./effects/tweens"; 4 | 5 | export const EC_DRAGGABLE_SPRITES: DisplayObject[] = []; 6 | export const EC_BUTTONS: DisplayObject[] = []; 7 | export const EC_PARTICLES: DisplayObject[] = []; 8 | export const EC_SHAKING_SPRITES: DisplayObject[] = []; 9 | export const EC_EMITTERS: Emitter[] = []; 10 | export const EC_TWEENS: Tween[] = []; 11 | -------------------------------------------------------------------------------- /packages/examples/vite.config.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { defineConfig } from "vite"; 4 | import { directoryPlugin } from "vite-plugin-list-directory-contents"; 5 | 6 | const _dirname = 7 | typeof __dirname !== "undefined" 8 | ? __dirname 9 | : path.dirname(fileURLToPath(import.meta.url)); 10 | 11 | export default defineConfig({ 12 | base: "./", 13 | 14 | server: { 15 | port: 3000, 16 | }, 17 | 18 | plugins: [directoryPlugin({ baseDir: __dirname })], 19 | }); 20 | -------------------------------------------------------------------------------- /packages/lib/scripts/autocomplete.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | const libImports = ` 4 | import "./collision"; 5 | import "./effects/particles"; 6 | import "./effects/sfx"; 7 | import "./effects/tweens"; 8 | import "./effects/utils"; 9 | import "./extras"; 10 | import "./geom"; 11 | import "./graphics"; 12 | import "./group"; 13 | import "./inputs"; 14 | import "./math"; 15 | import "./text"; 16 | import "./tiles/"; 17 | import "./utils"; 18 | `; 19 | 20 | fs.appendFileSync("./dist/index.d.ts", libImports); 21 | console.log("Done!"); 22 | -------------------------------------------------------------------------------- /packages/examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | inks2d Examples 6 | 7 | 8 | 9 | 15 | {%DIRECTORY%} 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/lib/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": false, 4 | "bracketSpacing": true, 5 | "embeddedLanguageFormatting": "auto", 6 | "htmlWhitespaceSensitivity": "css", 7 | "insertPragma": false, 8 | "jsxSingleQuote": false, 9 | "printWidth": 80, 10 | "proseWrap": "always", 11 | "quoteProps": "as-needed", 12 | "requirePragma": false, 13 | "semi": true, 14 | "singleQuote": false, 15 | "tabWidth": 2, 16 | "trailingComma": "all", 17 | "useTabs": true, 18 | "vueIndentScriptAndStyle": false 19 | } 20 | -------------------------------------------------------------------------------- /packages/create-inks2d/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": false, 4 | "bracketSpacing": true, 5 | "embeddedLanguageFormatting": "auto", 6 | "htmlWhitespaceSensitivity": "css", 7 | "insertPragma": false, 8 | "jsxSingleQuote": false, 9 | "printWidth": 80, 10 | "proseWrap": "always", 11 | "quoteProps": "as-needed", 12 | "requirePragma": false, 13 | "semi": true, 14 | "singleQuote": false, 15 | "tabWidth": 2, 16 | "trailingComma": "all", 17 | "useTabs": true, 18 | "vueIndentScriptAndStyle": false 19 | } 20 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": false, 4 | "bracketSpacing": true, 5 | "embeddedLanguageFormatting": "auto", 6 | "htmlWhitespaceSensitivity": "css", 7 | "insertPragma": false, 8 | "jsxSingleQuote": false, 9 | "printWidth": 80, 10 | "proseWrap": "always", 11 | "quoteProps": "as-needed", 12 | "requirePragma": false, 13 | "semi": true, 14 | "singleQuote": false, 15 | "tabWidth": 2, 16 | "trailingComma": "all", 17 | "useTabs": true, 18 | "vueIndentScriptAndStyle": false 19 | } 20 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": false, 4 | "bracketSpacing": true, 5 | "embeddedLanguageFormatting": "auto", 6 | "htmlWhitespaceSensitivity": "css", 7 | "insertPragma": false, 8 | "jsxSingleQuote": false, 9 | "printWidth": 80, 10 | "proseWrap": "always", 11 | "quoteProps": "as-needed", 12 | "requirePragma": false, 13 | "semi": true, 14 | "singleQuote": false, 15 | "tabWidth": 2, 16 | "trailingComma": "all", 17 | "useTabs": true, 18 | "vueIndentScriptAndStyle": false 19 | } 20 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "pipeline": { 4 | "dev": { 5 | "cache": false, 6 | "dependsOn": ["^dev"] 7 | }, 8 | "dev:examples": { 9 | "cache": false 10 | }, 11 | "build": { 12 | "outputs": ["dist/**"], 13 | "dependsOn": ["^build"] 14 | }, 15 | "build:docs": { 16 | "outputs": ["docs/**"], 17 | "dependsOn": ["^build"] 18 | }, 19 | "release": { 20 | "cache": false 21 | }, 22 | "validate": { 23 | "cache": false 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "noEmit": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noImplicitOverride": true, 17 | "skipLibCheck": true 18 | }, 19 | "include": ["playground"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/examples/playground/_basic/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { Rectangle } from "inks2d/geom"; 3 | 4 | const g = new Engine(512, 512); 5 | 6 | class Main extends Scene { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | override start(engine: Engine) { 12 | super.start(engine); 13 | 14 | const rect = new Rectangle(50, 50, "blue"); 15 | rect.draggable = true; 16 | rect.position.x = g.stage.width / 2; 17 | rect.position.y = g.stage.height / 2; 18 | g.stage.addChild(rect); 19 | } 20 | } 21 | 22 | g.scene = new Main(); 23 | g.centerscreen = true; 24 | g.start(); 25 | -------------------------------------------------------------------------------- /packages/lib/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | entry: [ 5 | "src/index.ts", 6 | "src/collision/index.ts", 7 | "src/effects/particles/index.ts", 8 | "src/effects/sfx/index.ts", 9 | "src/effects/tweens/index.ts", 10 | "src/effects/utils/index.ts", 11 | "src/extras/index.ts", 12 | "src/geom/index.ts", 13 | "src/graphics/index.ts", 14 | "src/group/index.ts", 15 | "src/inputs/index.ts", 16 | "src/math/index.ts", 17 | "src/text/index.ts", 18 | "src/tiles/index.ts", 19 | "src/utils/index.ts", 20 | ], 21 | format: ["esm"], 22 | dts: true, 23 | minify: true, 24 | sourcemap: true, 25 | treeshake: true, 26 | clean: true, 27 | }); 28 | -------------------------------------------------------------------------------- /packages/create-inks2d/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "lib": ["ESNext", "DOM"], 8 | "strict": true, 9 | "sourceMap": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "noImplicitOverride": true, 16 | "skipLibCheck": true, 17 | "outDir": "./dist", 18 | "rootDir": "./src", 19 | "baseUrl": "./src", 20 | "declaration": true, 21 | "declarationDir": "./dist/", 22 | "emitDeclarationOnly": true, 23 | "allowJs": false 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "lib": ["ESNext", "DOM"], 8 | "strict": true, 9 | "sourceMap": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "noImplicitOverride": true, 16 | "skipLibCheck": true, 17 | "outDir": "./dist", 18 | "rootDir": "./src", 19 | "baseUrl": "./src", 20 | "declaration": true, 21 | "declarationDir": "./dist/", 22 | "emitDeclarationOnly": true, 23 | "allowJs": false 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "lib": ["ESNext", "DOM"], 8 | "strict": true, 9 | "sourceMap": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "noImplicitOverride": true, 16 | "skipLibCheck": true, 17 | "outDir": "./dist", 18 | "rootDir": "./src", 19 | "baseUrl": "./src", 20 | "declaration": true, 21 | "declarationDir": "./dist/", 22 | "emitDeclarationOnly": true, 23 | "allowJs": false 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /packages/examples/games/memory-game/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { SplashScreen } from "inks2d/extras"; 3 | import { GameScreen } from "./scenes/GameScreen"; 4 | 5 | const g = new Engine(512, 700, 60, false, "none", "#475c8d"); 6 | 7 | class Main extends Scene { 8 | constructor() { 9 | super(); 10 | } 11 | 12 | async start(engine: Engine) { 13 | super.start(engine); 14 | 15 | const gamescreen = new GameScreen(g); 16 | g.stage.addChild(gamescreen); 17 | } 18 | } 19 | 20 | g.scene = new SplashScreen( 21 | ["assets/images/cards.png"], 22 | () => { 23 | g.scene = new Main(); 24 | }, 25 | 0, 26 | "assets/mw_inks2d.png" 27 | ); 28 | 29 | g.centerscreen = true; 30 | g.start(); 31 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-web/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { SplashScreen } from "inks2d/extras"; 3 | import { Sprite } from "inks2d/graphics"; 4 | 5 | const g = new Engine(640, 480); 6 | 7 | class Main extends Scene { 8 | constructor() { 9 | super(); 10 | } 11 | 12 | override start(e: Engine) { 13 | super.start(e); 14 | 15 | const logo = new Sprite(g.loader.store["./logo.png"]); 16 | logo.position.x = g.stage.width / 2; 17 | logo.position.y = g.stage.height / 2; 18 | g.stage.addChild(logo); 19 | } 20 | } 21 | 22 | g.scene = new SplashScreen( 23 | ["./logo.png"], 24 | () => { 25 | g.scene = new Main(); 26 | }, 27 | 0, 28 | "./mw_inks2d.png", 29 | ); 30 | 31 | g.centerscreen = true; 32 | g.start(); 33 | -------------------------------------------------------------------------------- /packages/lib/src/typedocs.ts: -------------------------------------------------------------------------------- 1 | export { Engine } from "./Engine"; 2 | export { Loader } from "./Loader"; 3 | export { Scene } from "./Scene"; 4 | export { Stage } from "./Stage"; 5 | export { DisplayObject } from "./DisplayObject"; 6 | 7 | /** 8 | * ```ts 9 | * import { 10 | * hitTestPoint, 11 | * hitTestLine, 12 | * hitTestLinePoint, 13 | * hitTestLineCircle, 14 | * hitTestLineRectangle, 15 | * hitTestCircle, 16 | * hitTestRectangle, 17 | * hitTestCircleRectangle, 18 | * hitTestCircleTriangle, 19 | * hitTestRectangleTriangle, 20 | *} from "inks2d/collision"; 21 | *``` 22 | * @category Namespaces 23 | */ 24 | export * as collision from "./collision"; 25 | 26 | /** 27 | * @category Namespaces 28 | */ 29 | export * as effects from "./effects"; 30 | -------------------------------------------------------------------------------- /packages/lib/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@typescript-eslint/eslint-plugin"], 3 | "parser": "@typescript-eslint/parser", 4 | "overrides": [ 5 | { 6 | "files": [".ts"], 7 | "extends": ["standard-with-typescript", "eslint-config-prettier"], 8 | "parserOptions": { 9 | "project": "./tsconfig.json", 10 | "tsconfigRootDir": "./packages/lib", 11 | "ecmaVersion": "latest", 12 | "sourceType": "module" 13 | } 14 | } 15 | ], 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": "error", 19 | "@typescript-eslint/strict-boolean-expressions": "off", 20 | "@typescript-eslint/no-this-alias": "warn", 21 | "@typescript-eslint/no-floating-promises": "off" 22 | }, 23 | "env": { 24 | "browser": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/examples/playground/alpha/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | inks2d 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/examples/playground/particles/fire.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | inks2d 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/examples/playground/particles/smoke.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | inks2d 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | Flappy Beans 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/examples/playground/spritemap/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | inks2d 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/examples/playground/particles/fireworks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | inks2d 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@typescript-eslint/eslint-plugin"], 3 | "parser": "@typescript-eslint/parser", 4 | "overrides": [ 5 | { 6 | "files": [".ts"], 7 | "extends": ["standard-with-typescript", "eslint-config-prettier"], 8 | "parserOptions": { 9 | "project": "./tsconfig.json", 10 | "tsconfigRootDir": "./packages/lib", 11 | "ecmaVersion": "latest", 12 | "sourceType": "module" 13 | } 14 | } 15 | ], 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": "error", 19 | "@typescript-eslint/strict-boolean-expressions": "off", 20 | "@typescript-eslint/no-this-alias": "warn", 21 | "@typescript-eslint/no-floating-promises": "off" 22 | }, 23 | "env": { 24 | "browser": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-web/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@typescript-eslint/eslint-plugin"], 3 | "parser": "@typescript-eslint/parser", 4 | "overrides": [ 5 | { 6 | "files": [".ts"], 7 | "extends": ["standard-with-typescript", "eslint-config-prettier"], 8 | "parserOptions": { 9 | "project": "./tsconfig.json", 10 | "tsconfigRootDir": "./packages/lib", 11 | "ecmaVersion": "latest", 12 | "sourceType": "module" 13 | } 14 | } 15 | ], 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": "error", 19 | "@typescript-eslint/strict-boolean-expressions": "off", 20 | "@typescript-eslint/no-this-alias": "warn", 21 | "@typescript-eslint/no-floating-promises": "off" 22 | }, 23 | "env": { 24 | "browser": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inks2d-web-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 | "typescript": "^5.0.2", 13 | "vite": "^4.3.0", 14 | "@typescript-eslint/eslint-plugin": "^5.59.7", 15 | "@typescript-eslint/parser": "^5.59.7", 16 | "eslint": "^8.41.0", 17 | "eslint-config-prettier": "^8.8.0", 18 | "eslint-config-standard-with-typescript": "^34.0.1", 19 | "eslint-plugin-import": "^2.27.5", 20 | "eslint-plugin-node": "^11.1.0", 21 | "eslint-plugin-promise": "^6.1.1", 22 | "prettier": "^2.8.8" 23 | }, 24 | "dependencies": { 25 | "inks2d": "^0.1.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/examples/playground/collision/hitTestCircle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | inks2d 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/examples/playground/collision/hitTestRectangle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | inks2d 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/publish-docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docs to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish-docs: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Set up Node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: "16" 21 | 22 | - name: Install 23 | run: npm ci 24 | 25 | - name: Publish 26 | run: npm run build:docs 27 | 28 | - name: Publish documentation to GitHub Pages 29 | uses: peaceiris/actions-gh-pages@v3 30 | with: 31 | github_token: ${{ secrets.GITHUB_TOKEN }} 32 | publish_dir: ./packages/lib/docs 33 | -------------------------------------------------------------------------------- /packages/examples/playground/collision/hitTestCircleTriangle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | inks2d 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/examples/playground/collision/hitTestCircleRectangle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | inks2d 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/examples/playground/collision/hitTestRectangleTriangle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | inks2d 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/create-inks2d/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@typescript-eslint/eslint-plugin"], 3 | "parser": "@typescript-eslint/parser", 4 | "overrides": [ 5 | { 6 | "files": [".ts"], 7 | "extends": ["standard-with-typescript", "eslint-config-prettier"], 8 | "parserOptions": { 9 | "project": "./tsconfig.json", 10 | "tsconfigRootDir": "./packages/lib", 11 | "ecmaVersion": "latest", 12 | "sourceType": "module" 13 | } 14 | } 15 | ], 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": "error", 19 | "@typescript-eslint/strict-boolean-expressions": "off", 20 | "@typescript-eslint/no-this-alias": "warn", 21 | "@typescript-eslint/no-floating-promises": "off" 22 | }, 23 | "env": { 24 | "browser": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/create-inks2d/src/platforms.ts: -------------------------------------------------------------------------------- 1 | import { Platform } from "types"; 2 | import { yellow, green } from "kolorist"; 3 | 4 | const PLATFORMS: Platform[] = [ 5 | { 6 | name: "web", 7 | display: "Web", 8 | color: yellow, 9 | }, 10 | { 11 | name: "mobile", 12 | display: "Mobile", 13 | color: green, 14 | }, 15 | 16 | /*, 17 | { 18 | name: "mobile", 19 | display: "Mobile", 20 | color: green, 21 | variants: [ 22 | { 23 | name: "mobile-android", 24 | display: "Android", 25 | color: green, 26 | }, 27 | ], 28 | }, 29 | { 30 | name: "pc", 31 | display: "PC", 32 | color: cyan, 33 | },*/ 34 | ]; 35 | 36 | const PLATFORMS_NAMES = PLATFORMS.map( 37 | (f) => (f.variants && f.variants.map((v) => v.name)) || [f.name], 38 | ).reduce((a, b) => a.concat(b), []); 39 | 40 | export { PLATFORMS, PLATFORMS_NAMES }; 41 | -------------------------------------------------------------------------------- /packages/examples/games/breakout/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { SplashScreen } from "inks2d/extras"; 3 | import { StartScreen } from "./scenes/StartScreen"; 4 | 5 | const g = new Engine(512, 700, 60, false, "none", "#475c8d"); 6 | 7 | class Main extends Scene { 8 | constructor() { 9 | super(); 10 | } 11 | 12 | async start(engine: Engine) { 13 | super.start(engine); 14 | 15 | const startscreen = new StartScreen(g); 16 | g.stage.addChild(startscreen); 17 | } 18 | } 19 | 20 | g.scene = new SplashScreen( 21 | [ 22 | "assets/images/button.png", 23 | "assets/images/paddle.png", 24 | "assets/images/star.png", 25 | "assets/images/startscreen-bg.png", 26 | "assets/images/tiles.png", 27 | ], 28 | () => { 29 | g.scene = new Main(); 30 | }, 31 | 0, 32 | "assets/mw_inks2d.png", 33 | true 34 | ); 35 | 36 | g.centerscreen = true; 37 | g.start(); 38 | -------------------------------------------------------------------------------- /packages/lib/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | - feat: add removeTween helper function [#35](https://github.com/inkasadev/inks2d/issues/35) 4 | 5 | ## v0.1.3 (2023-08-23) 6 | 7 | ### Features 8 | 9 | - feat: screen auto-resize when fullscreen mode 10 | 11 | # v0.1.1 (2023-06-12) 12 | 13 | ### Docs 14 | 15 | - docs: update readme with create-inks2d info 16 | 17 | # v0.1.0 (2023-06-10) 18 | 19 | ### Features 20 | 21 | - feat: SplashScreen class image support [#12](https://github.com/inkasadev/inks2d/issues/12) 22 | 23 | ### Docs 24 | 25 | - docs: update Loader class documentation [#4](https://github.com/inkasadev/inks2d/issues/4) 26 | 27 | ### Thanks 28 | 29 | - @Alef-gabriel 30 | 31 | ## v0.0.7 (2023-04-22) 32 | 33 | ### Bugs 34 | 35 | - fix minor bugs 36 | 37 | ## v0.0.6 (2023-04-21) 38 | 39 | ### Bugs 40 | - fix EngineConstants tween import 41 | 42 | ## 0.0.4 (2023-04-19) 43 | 44 | - initial project 45 | 46 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { SplashScreen } from "inks2d/extras"; 3 | import { Sprite } from "inks2d/graphics"; 4 | import { Detect } from "inks2d/utils"; 5 | import "./app"; 6 | 7 | const border = Detect.Android() ? "none" : "1px dashed #000"; 8 | const g = new Engine(1080, 1920, 60, true, border); 9 | 10 | class Main extends Scene { 11 | constructor() { 12 | super(); 13 | } 14 | 15 | override start(e: Engine) { 16 | super.start(e); 17 | 18 | const logo = new Sprite(g.loader.store["./logo.png"]); 19 | logo.position.x = g.stage.width / 2; 20 | logo.position.y = g.stage.height / 2; 21 | g.stage.addChild(logo); 22 | } 23 | } 24 | 25 | g.scene = new SplashScreen( 26 | ["./logo.png"], 27 | () => { 28 | g.scene = new Main(); 29 | }, 30 | 0, 31 | "./mw_inks2d.png", 32 | ); 33 | 34 | if (Detect.Android()) g.fullscreen = true; 35 | else g.centerscreen = true; 36 | g.start(); 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2023 inks2d 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "inks2d", 4 | "type": "module", 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "scripts": { 9 | "dev": "turbo run dev --parallel", 10 | "dev:lib": "turbo run dev --filter=inks2d", 11 | "dev:cig": "turbo run dev --filter=create-inks2d", 12 | "dev:examples": "npm run build:lib && turbo run dev:examples --filter=examples", 13 | "build:docs": "turbo run build:docs --filter=inks2d", 14 | "build:lib": "turbo run build --filter=inks2d", 15 | "build:cig": "turbo run build --filter=create-inks2d", 16 | "release:lib": "npm run build:lib && turbo run release --filter=inks2d", 17 | "release:cig": "npm run build:cig && turbo run release --filter=create-inks2d", 18 | "validate": "turbo run validate --filter=inks2d", 19 | "prepare": "husky install" 20 | }, 21 | "devDependencies": { 22 | "git-commit-msg-linter": "^4.9.4", 23 | "husky": "^8.0.3", 24 | "tsup": "^6.7.0", 25 | "turbo": "^1.8.5", 26 | "typescript": "^5.0.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/examples/playground/alpha/src/alpha.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { Rectangle } from "inks2d/geom"; 3 | 4 | const g = new Engine(512, 512); 5 | 6 | class Main extends Scene { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | async start(engine: Engine) { 12 | super.start(engine); 13 | 14 | const rect1 = new Rectangle(50, 50, "blue"); 15 | rect1.position.x = g.stage.width / 2; 16 | rect1.position.y = g.stage.height / 2; 17 | g.stage.addChild(rect1); 18 | 19 | const rect2 = new Rectangle(50, 50, "green"); 20 | rect2.position.x = g.stage.width / 2 + 25; 21 | rect2.position.y = g.stage.height / 2 + 25; 22 | rect2.alpha = 0.5; 23 | g.stage.addChild(rect2); 24 | 25 | const rect3 = new Rectangle(50, 50, "red"); 26 | rect3.position.x = g.stage.width / 2 - 25; 27 | rect3.position.y = g.stage.height / 2 - 25; 28 | rect3.alpha = 0.5; 29 | g.stage.addChild(rect3); 30 | } 31 | } 32 | 33 | g.scene = new Main(); 34 | g.centerscreen = true; 35 | g.start(); 36 | -------------------------------------------------------------------------------- /packages/create-inks2d/README.md: -------------------------------------------------------------------------------- 1 | # create-inks2d 2 | 3 | ## Scaffolding Your First inks2d Project 4 | 5 | With NPM: 6 | 7 | ```bash 8 | $ npm create inks2d@latest 9 | ``` 10 | 11 | With Yarn: 12 | 13 | ```bash 14 | $ yarn create inks2d 15 | ``` 16 | 17 | With PNPM: 18 | 19 | ```bash 20 | $ pnpm create inks2d 21 | ``` 22 | 23 | Then follow the prompts! 24 | 25 | You can also directly specify the project name and the platform you want to use via additional command line options. For example, to scaffold a inks2d + Web project, run: 26 | 27 | ```bash 28 | # npm 6.x 29 | npm create inks2d@latest my-inks2d-game --platform web 30 | 31 | # npm 7+, extra double-dash is needed: 32 | npm create inks2d@latest my-inks2d-game -- --platform web 33 | 34 | # yarn 35 | yarn create inks2d my-inks2d-game --platform web 36 | 37 | # pnpm 38 | pnpm create inks2d my-inks2d-game --platform web 39 | ``` 40 | 41 | Currently supported platforms presets include: 42 | 43 | - `web` 44 | - `mobile` 45 | 46 | You can use `.` for the project name to scaffold in the current directory. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug in inks2d 4 | labels: bug 5 | --- 6 | 7 | ## Search terms 8 | 9 | 10 | 11 | ## Expected Behavior 12 | 13 | 14 | 15 | ## Actual Behavior 16 | 17 | 18 | 19 | ## Steps to reproduce the bug 20 | 21 | 32 | 33 | ## Environment 34 | 35 | - inks2d version: 36 | - TypeScript version: 37 | - Node.js version: 38 | - OS: -------------------------------------------------------------------------------- /packages/lib/src/group/Grid.ts: -------------------------------------------------------------------------------- 1 | import { type DisplayObject } from "DisplayObject"; 2 | import { Group } from "./Group"; 3 | 4 | export class Grid extends Group { 5 | constructor( 6 | columns: number = 0, 7 | rows: number = 0, 8 | cellWidth: number = 32, 9 | cellHeight: number = 32, 10 | centerCell: boolean = false, 11 | xOffset: number = 0, 12 | yOffset: number = 0, 13 | makeSprite: () => DisplayObject, 14 | callback?: (sprite: DisplayObject) => void, 15 | ) { 16 | super(); 17 | 18 | const length = columns * rows; 19 | 20 | for (let i = 0; i < length; i++) { 21 | const x = (i % columns) * cellWidth; 22 | const y = Math.floor(i / columns) * cellHeight; 23 | 24 | const sprite = makeSprite(); 25 | this.addChild(sprite); 26 | 27 | if (!centerCell) { 28 | sprite.position.x = x + xOffset; 29 | sprite.position.y = y + yOffset; 30 | } else { 31 | sprite.position.x = x + cellWidth / 2 + xOffset; 32 | sprite.position.y = y + cellHeight / 2 + yOffset; 33 | } 34 | 35 | if (callback != null) callback(sprite); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/examples/playground/collision/src/hitTestCircleRectangle.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { hitTestCircleRectangle } from "inks2d/collision"; 3 | import { Circle, Rectangle } from "inks2d/geom"; 4 | import { Point } from "inks2d/math"; 5 | 6 | const g = new Engine(512, 512); 7 | 8 | let _r1: Rectangle, _c1: Circle; 9 | 10 | class Main extends Scene { 11 | constructor() { 12 | super(); 13 | } 14 | 15 | async start(engine: Engine) { 16 | super.start(engine); 17 | 18 | _r1 = new Rectangle(50, 50, "gray"); 19 | _r1.position.x = g.stage.width / 2; 20 | _r1.position.y = g.stage.height / 2; 21 | _r1.pivot.x = _r1.pivot.y = 0.5; 22 | _r1.alpha = 0.7; 23 | g.stage.addChild(_r1); 24 | 25 | _c1 = new Circle(50, 50); 26 | _c1.pivot.x = _c1.pivot.y = 0.5; 27 | g.stage.addChild(_c1); 28 | } 29 | 30 | update() { 31 | _c1.position = new Point(g.pointer.x, g.pointer.y); 32 | 33 | if (hitTestCircleRectangle(_c1, _r1, false, true, true).hasContact) 34 | _c1.fillStyle = "red"; 35 | else _c1.fillStyle = "gray"; 36 | } 37 | } 38 | 39 | g.scene = new Main(); 40 | g.centerscreen = true; 41 | g.start(); 42 | -------------------------------------------------------------------------------- /packages/examples/games/breakout/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 20 | 24 | Breakout 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /packages/examples/games/memory-game/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 20 | 24 | Memory Game 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /packages/lib/src/extras/TransitionScreen.ts: -------------------------------------------------------------------------------- 1 | import { Engine } from "Engine"; 2 | import { Tween } from "inks2d/effects/tweens"; 3 | import { Rectangle } from "inks2d/geom"; 4 | import { Point } from "inks2d/math"; 5 | 6 | export class TransitionScreen extends Rectangle { 7 | private _delay: number; 8 | public onBetween: (() => void) | undefined; 9 | 10 | constructor(delay: number, g: Engine, color: string = "black") { 11 | super(g.stage.width, g.stage.height, color); 12 | 13 | this.pivot = new Point(); 14 | this.alpha = 0; 15 | this._delay = delay; 16 | } 17 | 18 | start(removeAfterBlink: boolean = true): void { 19 | const tween1 = new Tween(); 20 | tween1.onComplete = () => { 21 | if (this.onBetween) this.onBetween(); 22 | }; 23 | tween1.onUpdate = ({ alpha }) => { 24 | this.alpha = alpha; 25 | }; 26 | tween1.from({ alpha: 0 }).to({ alpha: 1 }).duration(this._delay); 27 | 28 | const tween2 = new Tween(); 29 | tween2.onComplete = () => { 30 | if (removeAfterBlink) this.parent?.removeChild(this); 31 | }; 32 | tween2.onUpdate = ({ alpha }) => { 33 | this.alpha = alpha; 34 | }; 35 | tween2.from({ alpha: 1 }).to({ alpha: 0 }).duration(this._delay); 36 | 37 | tween1.chain(tween2); 38 | tween1.start(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inks2d-mobile-starter", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "setup:android": "npm i @capacitor/android && npx cap add android", 10 | "assets:android": "npx capacitor-assets generate --android", 11 | "preview:web": "npm run build && vite preview", 12 | "preview:android": "npm run build && npx cap run android" 13 | }, 14 | "devDependencies": { 15 | "@typescript-eslint/eslint-plugin": "^5.59.7", 16 | "@typescript-eslint/parser": "^5.59.7", 17 | "eslint": "^8.41.0", 18 | "eslint-config-prettier": "^8.8.0", 19 | "eslint-config-standard-with-typescript": "^34.0.1", 20 | "eslint-plugin-import": "^2.27.5", 21 | "eslint-plugin-node": "^11.1.0", 22 | "eslint-plugin-promise": "^6.1.1", 23 | "prettier": "^2.8.8", 24 | "@capacitor/assets": "^2.0.4", 25 | "@capacitor/cli": "^5.0.5", 26 | "typescript": "^5.0.2", 27 | "vite": "^4.3.9" 28 | }, 29 | "dependencies": { 30 | "@capacitor-community/keep-awake": "^4.0.0", 31 | "@capacitor/app": "^5.0.3", 32 | "@capacitor/core": "^5.0.5", 33 | "cordova-plugin-screen-orientation": "^3.0.3", 34 | "inks2d": "^0.1.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/lib/src/geom/Line.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "DisplayObject"; 2 | import { Point } from "inks2d/math"; 3 | 4 | export class Line extends DisplayObject { 5 | public a: Point; 6 | public b: Point; 7 | public lineJoin: "round" | "bevel" | "miter" = "round"; 8 | public lineCap: "butt" | "round" | "square" = "butt"; 9 | 10 | constructor( 11 | a: Point = new Point(0, 0), 12 | b: Point = new Point(32, 32), 13 | strokeStyle: string = "gray", 14 | lineWidth: number = 0, 15 | ) { 16 | super(); 17 | 18 | this.strokeStyle = strokeStyle; 19 | this.lineWidth = lineWidth; 20 | this.a = a; 21 | this.b = b; 22 | } 23 | 24 | get slope(): number { 25 | const yDelta = this.b.y - this.a.y; 26 | const xDelta = this.b.x - this.a.x || 1; 27 | return yDelta / xDelta; 28 | } 29 | 30 | get yIntercepts(): number { 31 | return this.slope * this.b.x * -1 + this.b.y; 32 | } 33 | 34 | render(ctx: CanvasRenderingContext2D): void { 35 | ctx.strokeStyle = this.strokeStyle; 36 | ctx.lineWidth = this.lineWidth; 37 | ctx.lineJoin = this.lineJoin; 38 | ctx.lineCap = this.lineCap; 39 | ctx.beginPath(); 40 | ctx.moveTo(this.a.x, this.a.y); 41 | ctx.lineTo(this.b.x, this.b.y); 42 | if (this.strokeStyle !== "none") ctx.stroke(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/lib/src/inputs/Keyboard.ts: -------------------------------------------------------------------------------- 1 | export class Keyboard { 2 | private readonly _keys: number[] = []; 3 | 4 | public isDown: boolean = false; 5 | public isUp: boolean = true; 6 | public press?: (e: KeyboardEvent) => void; 7 | public release?: (e: KeyboardEvent) => void; 8 | 9 | constructor(...keys: number[]) { 10 | this._keys = [...keys]; 11 | 12 | window.addEventListener("keydown", this.downHandler.bind(this), false); 13 | window.addEventListener("keyup", this.upHandler.bind(this), false); 14 | } 15 | 16 | removeListeners(): void { 17 | window.removeEventListener("keydown", this.downHandler.bind(this), false); 18 | window.removeEventListener("keyup", this.upHandler.bind(this), false); 19 | } 20 | 21 | private downHandler(e: KeyboardEvent): void { 22 | if (this._keys.includes(e.keyCode) || this._keys.length === 0) { 23 | if (this.isUp && this.press != null) { 24 | this.press(e); 25 | } 26 | 27 | this.isDown = true; 28 | this.isUp = false; 29 | } 30 | 31 | e.preventDefault(); 32 | } 33 | 34 | private upHandler(e: KeyboardEvent): void { 35 | if (this._keys.includes(e.keyCode) || this._keys.length === 0) { 36 | if (this.release != null) { 37 | this.release(e); 38 | } 39 | 40 | this.isDown = false; 41 | this.isUp = true; 42 | } 43 | 44 | e.preventDefault(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/lib/src/group/Group.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "DisplayObject"; 2 | 3 | export class Group extends DisplayObject { 4 | private _resize: boolean = false; 5 | 6 | constructor(...spritesToGroup: DisplayObject[]) { 7 | super(); 8 | this.add(...spritesToGroup); 9 | } 10 | 11 | get dynamicSize(): boolean { 12 | return this._resize; 13 | } 14 | 15 | set dynamicSize(value: boolean) { 16 | this._resize = value; 17 | if (value) this.calculateSize(); 18 | } 19 | 20 | private calculateSize(): void { 21 | if (this.children.length > 0) { 22 | let _newWidth = 0; 23 | let _newHeight = 0; 24 | 25 | this.children.forEach((child) => { 26 | if (child.position.x + child.width > _newWidth) { 27 | _newWidth = child.position.x + child.width; 28 | } 29 | 30 | if (child.position.y + child.height > _newHeight) { 31 | _newHeight = child.position.y + child.height; 32 | } 33 | }); 34 | 35 | this.width = _newWidth; 36 | this.height = _newHeight; 37 | } 38 | } 39 | 40 | public override addChild(sprite: DisplayObject): void { 41 | super.addChild(sprite); 42 | this.calculateSize(); 43 | } 44 | 45 | public override removeChild(sprite: DisplayObject): boolean { 46 | const isRemoved = super.removeChild(sprite); 47 | 48 | if (isRemoved) this.calculateSize(); 49 | 50 | return isRemoved; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/create-inks2d/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-inks2d", 3 | "version": "1.1.0", 4 | "type": "module", 5 | "license": "MIT", 6 | "author": "Phillipe Martins", 7 | "bin": { 8 | "create-inks2d": "index.js", 9 | "cig": "index.js" 10 | }, 11 | "files": [ 12 | "index.js", 13 | "platform-*", 14 | "dist" 15 | ], 16 | "scripts": { 17 | "dev": "tsup --watch", 18 | "build": "tsup", 19 | "release": "npm publish" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/inkasadev/inks2d.git", 24 | "directory": "packages/create-inks2d" 25 | }, 26 | "bugs": "https://github.com/inkasadev/inks2d/issues", 27 | "homepage": "https://github.com/inkasadev/inks2d/tree/main/packages/create-inks2d#readme", 28 | "devDependencies": { 29 | "@types/cross-spawn": "^6.0.2", 30 | "@types/minimist": "^1.2.2", 31 | "@types/prompts": "^2.4.4", 32 | "@typescript-eslint/eslint-plugin": "^5.59.7", 33 | "@typescript-eslint/parser": "^5.59.7", 34 | "cross-spawn": "^7.0.3", 35 | "eslint": "^8.41.0", 36 | "eslint-config-prettier": "^8.8.0", 37 | "eslint-config-standard-with-typescript": "^34.0.1", 38 | "eslint-plugin-import": "^2.27.5", 39 | "eslint-plugin-node": "^11.1.0", 40 | "eslint-plugin-promise": "^6.1.1", 41 | "kolorist": "^1.8.0", 42 | "minimist": "^1.2.8", 43 | "prettier": "^2.8.8", 44 | "prompts": "^2.4.2", 45 | "tsup": "^6.7.0", 46 | "typescript": "^5.0.4" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/examples/playground/_basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | inks2d 15 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /packages/examples/playground/collision/src/hitTestRectangle.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { hitTestRectangle } from "inks2d/collision"; 3 | import { Rectangle } from "inks2d/geom"; 4 | import { Point } from "inks2d/math"; 5 | import { contain } from "inks2d/utils"; 6 | 7 | const g = new Engine(512, 512); 8 | 9 | class Main extends Scene { 10 | private _r1: Rectangle = new Rectangle(40, 40, "red"); 11 | private _r2: Rectangle = new Rectangle(50, 50, "green"); 12 | 13 | constructor() { 14 | super(); 15 | } 16 | 17 | async start(engine: Engine) { 18 | super.start(engine); 19 | 20 | this._r1.position = new Point(200, 250); 21 | this._r1.velocity = new Point(2, 2); 22 | this._r1.friction = new Point(1, 1); 23 | this._r1.alpha = 0.5; 24 | g.stage.addChild(this._r1); 25 | 26 | this._r2.position = new Point(g.stage.width / 2, g.stage.height / 2); 27 | this._r2.velocity = new Point(2, 2); 28 | this._r2.friction = new Point(1, 1); 29 | this._r2.alpha = 0.5; 30 | g.stage.addChild(this._r2); 31 | } 32 | 33 | update() { 34 | this._r1.update(); 35 | contain( 36 | this._r1, 37 | { 38 | x: this._r1.width / 2, 39 | y: this._r1.height / 2, 40 | width: g.stage.width - this._r1.width / 2, 41 | height: g.stage.height - this._r1.height / 2, 42 | }, 43 | true 44 | ); 45 | hitTestRectangle(this._r1, this._r2, true, true, true); 46 | } 47 | } 48 | 49 | g.scene = new Main(); 50 | g.centerscreen = true; 51 | g.start(); 52 | -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { SplashScreen } from "inks2d/extras"; 3 | import { StartScreen } from "./scenes/StartScreen"; 4 | 5 | const g = new Engine(290, 515, 60, false, "none"); 6 | 7 | class Main extends Scene { 8 | constructor() { 9 | super(); 10 | } 11 | 12 | async start(engine: Engine) { 13 | super.start(engine); 14 | 15 | g.loader.store["assets/sounds/bounce.wav"].volume = 0.2; 16 | 17 | const startscreen = new StartScreen(g); 18 | g.stage.addChild(startscreen); 19 | } 20 | } 21 | 22 | g.scene = new SplashScreen( 23 | [ 24 | "assets/images/bg.png", 25 | "assets/images/hand.png", 26 | "assets/images/play.png", 27 | "assets/images/ranking.png", 28 | "assets/images/tap_right.png", 29 | "assets/images/tap_left.png", 30 | "assets/images/title.png", 31 | "assets/images/bean.png", 32 | "assets/images/t_pipe.png", 33 | "assets/images/b_pipe.png", 34 | "assets/images/floor.png", 35 | "assets/images/ready.png", 36 | "assets/images/hand.png", 37 | "assets/images/btn_play.png", 38 | "assets/images/btn_pause.png", 39 | "assets/images/result_bg.png", 40 | "assets/images/badges.png", 41 | "assets/images/menu.png", 42 | "assets/images/restart.png", 43 | "assets/fonts/prstartk.ttf", 44 | "assets/sounds/bounce.wav", 45 | ], 46 | () => { 47 | g.scene = new Main(); 48 | g.pause(); 49 | }, 50 | 0, 51 | "assets/mw_inks2d.png", 52 | true 53 | ); 54 | g.centerscreen = true; 55 | g.start(); 56 | -------------------------------------------------------------------------------- /packages/examples/playground/particles/src/fireworks.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { Emitter, ParticleSystem } from "inks2d/effects/particles"; 3 | import { Sprite } from "inks2d/graphics"; 4 | import { Point } from "inks2d/math"; 5 | 6 | const g = new Engine(512, 512, 60, false, "none", "black"); 7 | 8 | class Main extends Scene { 9 | constructor() { 10 | super(); 11 | } 12 | 13 | async start(engine: Engine) { 14 | super.start(engine); 15 | 16 | const emitter = new Emitter(); 17 | const fireworks = new ParticleSystem( 18 | () => { 19 | const sprite = new Sprite(g.loader.store["assets/spark.png"]); 20 | sprite.blendMode = "lighter"; 21 | g.stage.addChild(sprite); 22 | return sprite; 23 | }, 24 | new Point(), 25 | new Point(0, 0.01), 26 | 100, // numParticles 27 | true, // randomSpacing 28 | 0, 29 | 360, // min/max Angle 30 | 10, 31 | 30, // min/max Size 32 | 0.1, 33 | 1, // min/max Speed 34 | 1, 35 | 1, // min/max Scale Speed 36 | 0.001, 37 | 0.005, // min/max Alpha Speed 38 | 0, 39 | 0, // min/max Rotation Speed, 40 | 250 41 | ); 42 | 43 | fireworks.setPosition(new Point(g.stage.halfWidth, g.stage.halfHeight)); 44 | emitter.addParticle("fireworks", fireworks, 5); 45 | emitter.play("fireworks"); 46 | } 47 | } 48 | 49 | g.loader.onComplete = () => { 50 | g.scene = new Main(); 51 | g.centerscreen = true; 52 | g.start(); 53 | }; 54 | 55 | g.loader.load(["assets/spark.png"]); 56 | -------------------------------------------------------------------------------- /.github/workflows/publish-lib.yml: -------------------------------------------------------------------------------- 1 | name: Publish inks2d 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | npm-publish: 10 | name: npm-publish 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | token: ${{secrets.PAT}} 18 | - id: check 19 | uses: EndBug/version-check@v2.1.0 20 | with: 21 | diff-search: true 22 | file-name: ./packages/lib/package.json 23 | 24 | - name: Set up Node 25 | if: steps.check.outputs.changed == 'true' 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: "16" 29 | 30 | - name: Install 31 | if: steps.check.outputs.changed == 'true' 32 | run: npm ci 33 | 34 | - name: Setup publish token 35 | if: steps.check.outputs.changed == 'true' 36 | run: echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" > .npmrc 37 | env: 38 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 39 | 40 | - name: Publish 41 | if: steps.check.outputs.changed == 'true' 42 | run: npm run release:lib 43 | 44 | - name: Generate Release 45 | if: steps.check.outputs.changed == 'true' 46 | run: | 47 | git config user.email "hi@inkasadev.com" 48 | git config user.name "inks2d Bot" 49 | git config advice.ignoredHook false 50 | node packages/lib/scripts/create_release.js 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 20 | inks2d Game 21 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 20 | inks2d Game 21 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /.github/workflows/publish-cig.yml: -------------------------------------------------------------------------------- 1 | name: Publish create-inks2d 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | npm-publish: 10 | name: npm-publish 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | token: ${{secrets.PAT}} 18 | - id: check 19 | uses: EndBug/version-check@v2.1.0 20 | with: 21 | diff-search: true 22 | file-name: ./packages/create-inks2d/package.json 23 | 24 | - name: Set up Node 25 | if: steps.check.outputs.changed == 'true' 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: "16" 29 | 30 | - name: Install 31 | if: steps.check.outputs.changed == 'true' 32 | run: npm ci 33 | 34 | - name: Setup publish token 35 | if: steps.check.outputs.changed == 'true' 36 | run: echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" > .npmrc 37 | env: 38 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 39 | 40 | - name: Publish 41 | if: steps.check.outputs.changed == 'true' 42 | run: npm run release:cig 43 | 44 | - name: Generate Release 45 | if: steps.check.outputs.changed == 'true' 46 | run: | 47 | git config user.email "hi@inkasadev.com" 48 | git config user.name "inks2d Bot" 49 | git config advice.ignoredHook false 50 | node packages/create-inks2d/scripts/create_release.js 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | -------------------------------------------------------------------------------- /packages/examples/playground/particles/src/smoke.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { Emitter, ParticleSystem } from "inks2d/effects/particles"; 3 | import { Spritemap } from "inks2d/graphics"; 4 | import { Point } from "inks2d/math"; 5 | import { randomInt } from "inks2d/math"; 6 | 7 | const g = new Engine(512, 512, 60, false, "none", "black"); 8 | 9 | class Main extends Scene { 10 | constructor() { 11 | super(); 12 | } 13 | 14 | async start(engine: Engine) { 15 | super.start(engine); 16 | 17 | const emitter = new Emitter(); 18 | const smoke = new ParticleSystem( 19 | () => { 20 | const sprite = new Spritemap( 21 | g.loader.store["assets/smokes.png"], 22 | 64, 23 | 64 24 | ); 25 | sprite.blendMode = "lighter"; 26 | sprite.frame = randomInt(0, 3); 27 | g.stage.addChild(sprite); 28 | return sprite; 29 | }, 30 | new Point(g.stage.halfWidth, g.stage.halfHeight + 150), 31 | new Point(0, -0.1), 32 | 10, // numParticles 33 | true, // randomSpacing 34 | -150, 35 | 0, // min/max Angle 36 | 20, 37 | 100, // min/max Size 38 | -0.1, 39 | 1, // min/max Speed 40 | 0.01, 41 | 100, // min/max Scale Speed 42 | 0.005, 43 | 0.01, // min/max Alpha Speed 44 | 0, 45 | 0 // min/max Rotation Speed 46 | ); 47 | 48 | emitter.addParticle("smoke", smoke, 0.05); 49 | emitter.play("smoke"); 50 | } 51 | } 52 | 53 | g.loader.onComplete = () => { 54 | g.scene = new Main(); 55 | g.centerscreen = true; 56 | g.start(); 57 | }; 58 | 59 | g.loader.load(["assets/smokes.png"]); 60 | -------------------------------------------------------------------------------- /packages/lib/src/inputs/Keys.ts: -------------------------------------------------------------------------------- 1 | export const Keys: Record = { 2 | BACKSPACE: 8, 3 | TAB: 9, 4 | ENTER: 13, 5 | SHIFT: 16, 6 | CTRL: 17, 7 | ALT: 18, 8 | PAUSE_BREAK: 19, 9 | CAPS_LOCK: 20, 10 | SPACE: 32, 11 | PAGE_UP: 33, 12 | PAGE_DOWN: 34, 13 | END: 35, 14 | HOME: 36, 15 | LEFT_ARROW: 37, 16 | UP_ARROW: 38, 17 | RIGHT_ARROW: 39, 18 | DOWN_ARROW: 40, 19 | INSERT: 45, 20 | DELETE: 46, 21 | NUM_0: 48, 22 | NUM_1: 49, 23 | NUM_2: 50, 24 | NUM_3: 51, 25 | NUM_4: 52, 26 | NUM_5: 53, 27 | NUM_6: 54, 28 | NUM_7: 55, 29 | NUM_8: 56, 30 | NUM_9: 57, 31 | A: 65, 32 | B: 66, 33 | C: 67, 34 | D: 68, 35 | E: 69, 36 | F: 70, 37 | G: 71, 38 | H: 72, 39 | I: 73, 40 | J: 74, 41 | K: 75, 42 | L: 76, 43 | M: 77, 44 | N: 78, 45 | O: 79, 46 | P: 80, 47 | Q: 81, 48 | R: 82, 49 | S: 83, 50 | T: 84, 51 | U: 85, 52 | V: 86, 53 | W: 87, 54 | X: 88, 55 | Y: 89, 56 | Z: 90, 57 | NUMPAD_0: 96, 58 | NUMPAD_1: 97, 59 | NUMPAD_2: 98, 60 | NUMPAD_3: 99, 61 | NUMPAD_4: 100, 62 | NUMPAD_5: 101, 63 | NUMPAD_6: 102, 64 | NUMPAD_7: 103, 65 | NUMPAD_8: 104, 66 | NUMPAD_9: 105, 67 | MULTIPLY: 106, 68 | ADD: 107, 69 | SUBTRACT: 109, 70 | DECIMAL_POINT: 110, 71 | DIVIDE: 111, 72 | F1: 112, 73 | F2: 113, 74 | F3: 114, 75 | F4: 115, 76 | F5: 116, 77 | F6: 117, 78 | F7: 118, 79 | F8: 119, 80 | F9: 120, 81 | F10: 121, 82 | F11: 122, 83 | F12: 123, 84 | NUM_LOCK: 144, 85 | SCROLL_LOCK: 145, 86 | SEMI_COLON: 186, 87 | EQUAL_SIGN: 187, 88 | COMMA: 188, 89 | DASH: 189, 90 | PERIOD: 190, 91 | FORWARD_SLASH: 191, 92 | GRAVE_ACCENT: 192, 93 | OPEN_BRACKET: 219, 94 | BACK_SLASH: 220, 95 | CLOSE_BRACKET: 221, 96 | SINGLE_QUOTE: 222, 97 | }; 98 | -------------------------------------------------------------------------------- /packages/examples/playground/particles/src/fire.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { Emitter, ParticleSystem } from "inks2d/effects/particles"; 3 | import { Spritemap } from "inks2d/graphics"; 4 | import { Point } from "inks2d/math"; 5 | import { randomInt } from "inks2d/math"; 6 | 7 | const g = new Engine(512, 512, 60, false, "none", "black"); 8 | 9 | class Main extends Scene { 10 | constructor() { 11 | super(); 12 | } 13 | 14 | async start(engine: Engine) { 15 | super.start(engine); 16 | 17 | const emitter = new Emitter(); 18 | const flames = new ParticleSystem( 19 | () => { 20 | const sprite = new Spritemap( 21 | g.loader.store["assets/flames.png"], 22 | 256, 23 | 256 24 | ); 25 | sprite.blendMode = "lighter"; 26 | sprite.frame = randomInt(0, 3); 27 | g.stage.addChild(sprite); 28 | 29 | return sprite; 30 | }, 31 | new Point(g.stage.halfWidth, g.stage.halfHeight + 150), 32 | new Point(0, -0.1), 33 | 10, // numParticles 34 | true, // randomSpacing 35 | -150, 36 | 0, // min/max Angle 37 | 20, 38 | 100, // min/max Size 39 | -0.1, 40 | 1, // min/max Speed 41 | 0.01, 42 | 100, // min/max Scale Speed 43 | 0.005, 44 | 0.02, // min/max Alpha Speed 45 | 0.1, 46 | 0.5 // min/max Rotation Speed 47 | ); 48 | 49 | emitter.addParticle("flames", flames, 0.05); 50 | emitter.play("flames"); 51 | } 52 | } 53 | 54 | g.loader.onComplete = () => { 55 | g.scene = new Main(); 56 | g.centerscreen = true; 57 | g.start(); 58 | }; 59 | 60 | g.loader.load(["assets/flames.png"]); 61 | -------------------------------------------------------------------------------- /packages/lib/src/geom/Circle.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "DisplayObject"; 2 | 3 | export class Circle extends DisplayObject { 4 | public startAngle: number = 0; 5 | public endAngle: number = 2 * Math.PI; 6 | public isPie: boolean = false; 7 | 8 | constructor( 9 | width: number = 32, 10 | height: number = 32, 11 | fillStyle: string = "gray", 12 | strokeStyle: string = "none", 13 | lineWidth: number = 0, 14 | ) { 15 | super(); 16 | 17 | this.width = width; 18 | this.height = height; 19 | 20 | this.fillStyle = fillStyle; 21 | this.strokeStyle = strokeStyle; 22 | this.lineWidth = lineWidth; 23 | 24 | this.name = "Circle"; 25 | } 26 | 27 | get diameter(): number { 28 | return this.width; 29 | } 30 | 31 | set diameter(value: number) { 32 | this.width = value; 33 | this.height = value; 34 | } 35 | 36 | get radius(): number { 37 | return this.halfWidth; 38 | } 39 | 40 | set radius(value: number) { 41 | this.width = value * 2; 42 | this.height = value * 2; 43 | } 44 | 45 | render(ctx: CanvasRenderingContext2D): void { 46 | ctx.strokeStyle = this.strokeStyle; 47 | ctx.lineWidth = this.lineWidth; 48 | ctx.fillStyle = this.fillStyle; 49 | ctx.beginPath(); 50 | 51 | if (this.isPie) { 52 | ctx.moveTo( 53 | this.radius + -this.diameter * this.pivot.x, 54 | this.radius + -this.diameter * this.pivot.y, 55 | ); 56 | } 57 | 58 | ctx.arc( 59 | this.radius + -this.diameter * this.pivot.x, 60 | this.radius + -this.diameter * this.pivot.y, 61 | this.radius, 62 | this.startAngle, 63 | this.endAngle, 64 | false, 65 | ); 66 | 67 | if (this.strokeStyle !== "none") ctx.stroke(); 68 | if (this.fillStyle !== "none") ctx.fill(); 69 | if (this.mask && this.mask) ctx.clip(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/lib/src/math/index.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "DisplayObject"; 2 | 3 | export { Point } from "./Point"; 4 | export { Vector2D } from "./Vector2D"; 5 | 6 | export const randomInt = (min: number, max: number): number => { 7 | return Math.floor(Math.random() * (max - min + 1)) + min; 8 | }; 9 | 10 | export const randomFloat = (min: number, max: number): number => { 11 | return min + Math.random() * (max - min); 12 | }; 13 | 14 | export const randomOption = (arr: any[]): any => { 15 | const i = randomInt(0, arr.length - 1); 16 | return arr[i]; 17 | }; 18 | 19 | export const shuffleArray = (arr: any[]): any[] => { 20 | arr = arr.concat([]); 21 | 22 | for (let i = arr.length - 1; i > 0; i--) { 23 | const id = Math.floor(Math.random() * i); 24 | const a = arr[i]; 25 | const b = arr[id]; 26 | 27 | arr[id] = a; 28 | arr[i] = b; 29 | } 30 | 31 | return arr; 32 | }; 33 | 34 | export const toRadians = (angle: number): number => { 35 | return (angle * Math.PI) / 180; 36 | }; 37 | 38 | export const toAngle = (radians: number): number => { 39 | return (radians * 180) / Math.PI; 40 | }; 41 | 42 | export const round = (num: number): number => { 43 | return (0.5 + num) | 0; 44 | }; 45 | 46 | export const angle = (s1: DisplayObject, s2: DisplayObject): number => { 47 | return Math.atan2( 48 | s2.localCenterY - s1.localCenterY, 49 | s2.localCenterX - s1.localCenterX, 50 | ); 51 | }; 52 | 53 | export const distance = (s1: DisplayObject, s2: DisplayObject): number => { 54 | const vx = s2.localCenterX - s1.localCenterX; 55 | const vy = s2.localCenterY - s1.localCenterY; 56 | 57 | return Math.sqrt(vx * vx + vy * vy); 58 | }; 59 | 60 | export const valueWrap = (value: number, min: number, max: number): number => { 61 | if (value < min) return min; 62 | else if (value > max) return max; 63 | return value; 64 | }; 65 | -------------------------------------------------------------------------------- /packages/lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "lib": ["ESNext", "DOM"], 8 | "strict": true, 9 | "sourceMap": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "noImplicitOverride": true, 16 | "skipLibCheck": true, 17 | "outDir": "./dist", 18 | // "outFile": "./dist/index.d.ts", 19 | "rootDir": "./src", 20 | "baseUrl": "./src", 21 | "declaration": true, 22 | "declarationDir": "./dist/", 23 | "emitDeclarationOnly": true, 24 | "allowJs": false, 25 | "paths": { 26 | "inks2d/collision": ["collision/", "collision/*"], 27 | "inks2d/effects/particles": ["effects/particles", "effects/particles/*"], 28 | "inks2d/effects/sfx": ["effects/sfx", "effects/sfx/*"], 29 | "inks2d/effects/tweens": ["effects/tweens/", "effects/tweens/*"], 30 | "inks2d/effects/utils": ["effects/utils/", "effects/utils/*"], 31 | "inks2d/extras": ["extras/", "extras/*"], 32 | "inks2d/geom": ["geom/", "geom/*"], 33 | "inks2d/graphics": ["graphics/", "graphics/*"], 34 | "inks2d/group": ["group/", "group/*"], 35 | "inks2d/inputs": ["inputs/", "inputs/*"], 36 | "inks2d/math": ["math/", "math/*"], 37 | "inks2d/misc": ["misc/", "misc/*"], 38 | "inks2d/misc/effects": ["misc/effects", "misc/effects/*"], 39 | "inks2d/misc/general": ["misc/general", "misc/general/*"], 40 | "inks2d/misc/math": ["misc/math", "misc/math/*"], 41 | "inks2d/misc/tiles": ["misc/tiles", "misc/tiles/*"], 42 | "inks2d/text": ["text/", "text/*"], 43 | "inks2d/tiles": ["tiles/", "tiles/*"], 44 | "inks2d/utils": ["utils/", "utils/*"], 45 | "inks2d": ["src/", "src/*"] 46 | } 47 | }, 48 | "include": ["src"] 49 | } 50 | -------------------------------------------------------------------------------- /packages/examples/playground/collision/src/hitTestCircle.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { hitTestCircle } from "inks2d/collision"; 3 | import { Circle } from "inks2d/geom"; 4 | import { Point } from "inks2d/math"; 5 | import { contain } from "inks2d/utils"; 6 | 7 | const g = new Engine(512, 512); 8 | 9 | class Main extends Scene { 10 | private _c1: Circle = new Circle(50, 50, "red"); 11 | private _c2: Circle = new Circle(60, 60, "green"); 12 | 13 | constructor() { 14 | super(); 15 | } 16 | 17 | async start(engine: Engine) { 18 | super.start(engine); 19 | 20 | this._c1.position = new Point(g.stage.width / 2, g.stage.height / 2); 21 | this._c1.velocity = new Point(2, 2); 22 | this._c1.friction = new Point(1, 1); 23 | this._c1.alpha = 0.5; 24 | g.stage.addChild(this._c1); 25 | 26 | this._c2.position = new Point(100, 250); 27 | this._c2.velocity = new Point(2, 2); 28 | this._c2.friction = new Point(1, 1); 29 | this._c2.alpha = 0.5; 30 | g.stage.addChild(this._c2); 31 | } 32 | 33 | update() { 34 | this._c1.update(); 35 | this._c2.update(); 36 | contain( 37 | this._c1, 38 | { 39 | x: this._c1.width / 2, 40 | y: this._c1.height / 2, 41 | width: g.stage.width - this._c1.width / 2, 42 | height: g.stage.height - this._c1.height / 2, 43 | }, 44 | true 45 | ); 46 | contain( 47 | this._c2, 48 | { 49 | x: this._c2.width / 2, 50 | y: this._c2.height / 2, 51 | width: g.stage.width - this._c2.width / 2, 52 | height: g.stage.height - this._c2.height / 2, 53 | }, 54 | true 55 | ); 56 | 57 | hitTestCircle(this._c1, this._c2, false, true, true, true); 58 | // hitTestRectangle(this._r1, this._r2, true, true, true); 59 | } 60 | } 61 | 62 | g.scene = new Main(); 63 | g.centerscreen = true; 64 | g.start(); 65 | -------------------------------------------------------------------------------- /packages/lib/src/Stage.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "./DisplayObject"; 2 | 3 | /** 4 | * 5 | * Updated by Engine, main game DisplayObject that holds all currently active display objects. 6 | * 7 | * > ⚠️ You can **ONLY** use this class by the {@link Scene.stage | Scene.stage} property. 8 | * 9 | * ```ts 10 | * import { Engine, Scene } from "inks2d"; 11 | * import { Rectangle } from "inks2d/graphics"; 12 | * import { Point } from "inks2d/math"; 13 | * 14 | * const g = new Engine(500, 500); 15 | * 16 | * class Main extends Scene { 17 | * constructor() { 18 | * super(); 19 | * } 20 | * 21 | * start(e: Engine) { 22 | * super.start(e); 23 | * 24 | * const box = new Rectangle(); 25 | * box.position = new Point(g.stage.width / 2, g.stage.height / 2); 26 | * g.stage.addChild(box); 27 | * } 28 | * } 29 | * 30 | * g.scene = new Main(); 31 | * g.start(); 32 | * 33 | * ``` 34 | * 35 | * @category inks2d 36 | */ 37 | export class Stage extends DisplayObject { 38 | private readonly _graphics: CanvasRenderingContext2D; 39 | 40 | constructor(canvas: HTMLCanvasElement, width: number, height: number) { 41 | super(); 42 | 43 | this.width = width; 44 | this.height = height; 45 | this._graphics = canvas.getContext("2d") as CanvasRenderingContext2D; 46 | } 47 | 48 | /** 49 | * Graphics object where drawing commands can occur. 50 | */ 51 | get graphics(): CanvasRenderingContext2D { 52 | return this._graphics; 53 | } 54 | 55 | private updateChildren(children: DisplayObject[]): void { 56 | for (let i = children.length - 1; i >= 0; i--) { 57 | const child = children[i]; 58 | 59 | child.update && child.update(); 60 | 61 | if (child.children && child.children.length > 0) { 62 | this.updateChildren(child.children); 63 | continue; 64 | } 65 | } 66 | } 67 | 68 | override update(): void { 69 | this.updateChildren(this.children); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/lib/src/Scene.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EC_BUTTONS, 3 | EC_DRAGGABLE_SPRITES, 4 | EC_PARTICLES, 5 | EC_SHAKING_SPRITES, 6 | } from "./EngineConstants"; 7 | import { type Engine } from "./Engine"; 8 | import { Stage } from "./Stage"; 9 | 10 | /** 11 | * 12 | * The game scene that holds the main active DisplayObject, the {@link Stage | game stage}. Useful for organization, eg. "Menu", "Level1", "Start Screen", etc. 13 | * 14 | * ```ts 15 | * import { Engine, Scene } from "inks2d"; 16 | * 17 | * const g = new Engine(500, 500); 18 | * 19 | * class Main extends Scene { 20 | * constructor(){ 21 | * super(); 22 | * } 23 | * 24 | * start(e: Engine) { 25 | * super.start(e); 26 | * } 27 | * } 28 | * 29 | * g.scene = new Main(); 30 | * g.start(); 31 | * ``` 32 | * 33 | * @category inks2d 34 | */ 35 | export class Scene { 36 | private _engine?: Engine; 37 | private readonly _helpers: Record = {}; 38 | 39 | /** 40 | * Override this. Called after Scene has been added to the Engine. 41 | * 42 | * @param engine The game engine current instance. 43 | */ 44 | start(engine: Engine): void { 45 | const viewportSize = engine.getViewportSize(); 46 | this._engine = engine; 47 | this._helpers.stage = new Stage( 48 | this._engine.canvas, 49 | viewportSize.width, 50 | viewportSize.height, 51 | ); 52 | } 53 | 54 | /** 55 | * Returns the scene stage. 56 | */ 57 | get stage(): Stage { 58 | return this._helpers.stage as Stage; 59 | } 60 | 61 | /** 62 | * Override this. Updates the game, updating the Scene and display objects. 63 | */ 64 | update(): void { 65 | this.stage.update(); 66 | } 67 | 68 | /** 69 | * Override this. Called when Scene is changed, and the active Scene is no longer this. 70 | */ 71 | destroy(): void { 72 | EC_DRAGGABLE_SPRITES.length = 0; 73 | EC_BUTTONS.length = 0; 74 | EC_PARTICLES.length = 0; 75 | EC_SHAKING_SPRITES.length = 0; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/src/scenes/StartScreen.ts: -------------------------------------------------------------------------------- 1 | import { Engine } from "inks2d"; 2 | import { Sprite, Spritemap } from "inks2d/graphics"; 3 | import { Group } from "inks2d/group"; 4 | import { Text } from "inks2d/text"; 5 | import { Gamescreen } from "./Gamescreen"; 6 | import { makeInteractive, wait } from "inks2d/utils"; 7 | import { fadeOut } from "inks2d/effects/utils"; 8 | 9 | export class StartScreen extends Group { 10 | private _g: Engine; 11 | 12 | constructor(g: Engine) { 13 | super(); 14 | this._g = g; 15 | } 16 | 17 | added(): void { 18 | const bg = new Sprite(this._g.loader.store["assets/images/bg.png"]); 19 | bg.pivot.x = bg.pivot.y = 0; 20 | this.addChild(bg); 21 | 22 | const title = new Text("Flappy Beans", 20, "white"); 23 | title.family = "prstartk"; 24 | title.strokeStyle = "#603913"; 25 | title.lineWidth = 1; 26 | title.position.x = 145; 27 | title.position.y = 85; 28 | this.addChild(title); 29 | 30 | const bean = new Spritemap( 31 | this._g.loader.store["assets/images/bean.png"], 32 | 32, 33 | 32 34 | ); 35 | bean.position.x = this._g.stage.width / 2 - 110; 36 | bean.position.y = this._g.stage.height / 2; 37 | bean.addAnimation("fly", [0, 1, 2], 200, true); 38 | bean.play("fly"); 39 | this.addChild(bean); 40 | 41 | const play = new Sprite(this._g.loader.store["assets/images/play.png"]); 42 | play.pivot.x = play.pivot.y = 0.5; 43 | makeInteractive(play); 44 | play.customProperties.buttonProps.release = () => { 45 | play.customProperties.buttonProps.enabled = false; 46 | 47 | fadeOut(play, 250); 48 | fadeOut(title, 250); 49 | 50 | wait(250).then(() => { 51 | this._g.stage.removeChild(this); 52 | this._g.stage.addChild(new Gamescreen(this._g)); 53 | }); 54 | }; 55 | play.position.x = this._g.stage.width / 2; 56 | play.position.y = 400; 57 | this.addChild(play); 58 | } 59 | 60 | udpate() {} 61 | } 62 | -------------------------------------------------------------------------------- /packages/lib/src/geom/Rectangle.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "DisplayObject"; 2 | 3 | export class Rectangle extends DisplayObject { 4 | constructor( 5 | width: number = 32, 6 | height: number = 32, 7 | fillStyle: string = "gray", 8 | strokeStyle: string = "none", 9 | lineWidth: number = 0, 10 | radius: number = 0, 11 | ) { 12 | super(); 13 | 14 | this.width = width; 15 | this.height = height; 16 | 17 | this.fillStyle = fillStyle; 18 | this.strokeStyle = strokeStyle; 19 | this.lineWidth = lineWidth; 20 | 21 | this.customProperties.corners = { 22 | topLeft: radius, 23 | topRight: radius, 24 | bottomLeft: radius, 25 | bottomRight: radius, 26 | }; 27 | } 28 | 29 | render(ctx: CanvasRenderingContext2D): void { 30 | const x = -this.width * this.pivot.x; 31 | const y = -this.height * this.pivot.y; 32 | const r = x + this.width; 33 | const b = y + this.height; 34 | 35 | ctx.strokeStyle = this.strokeStyle; 36 | ctx.lineWidth = this.lineWidth; 37 | ctx.fillStyle = this.fillStyle; 38 | ctx.beginPath(); 39 | ctx.moveTo(x, y); 40 | 41 | ctx.lineTo(r - this.customProperties.corners.topRight, y); 42 | ctx.quadraticCurveTo( 43 | r, 44 | y, 45 | r, 46 | y + (this.customProperties.corners.topRight as number), 47 | ); 48 | 49 | ctx.lineTo(r, y + this.height - this.customProperties.corners.bottomRight); 50 | ctx.quadraticCurveTo( 51 | r, 52 | b, 53 | r - this.customProperties.corners.bottomRight, 54 | b, 55 | ); 56 | 57 | ctx.lineTo(x + (this.customProperties.corners.bottomLeft as number), b); 58 | ctx.quadraticCurveTo(x, b, x, b - this.customProperties.corners.bottomLeft); 59 | 60 | ctx.lineTo(x, y + (this.customProperties.corners.topLeft as number)); 61 | ctx.quadraticCurveTo( 62 | x, 63 | y, 64 | x + (this.customProperties.corners.topLeft as number), 65 | y, 66 | ); 67 | 68 | if (this.strokeStyle !== "none") ctx.stroke(); 69 | if (this.fillStyle !== "none") ctx.fill(); 70 | if (this.mask && this.mask) ctx.clip(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/examples/playground/spritemap/src/spritemap.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { Spritemap } from "inks2d/graphics"; 3 | 4 | const g = new Engine(512, 512); 5 | 6 | class Main extends Scene { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | async start(engine: Engine) { 12 | super.start(engine); 13 | 14 | const warriorLeft = new Spritemap( 15 | g.loader.store["assets/warrior_1.png"], 16 | 32, 17 | 32 18 | ); 19 | warriorLeft.addAnimation("left", [3, 4, 5], 200, true); 20 | warriorLeft.addAnimation("right", "3-5", 200, true); 21 | warriorLeft.position.x = g.stage.width / 2 - 64; 22 | warriorLeft.position.y = g.stage.height / 2; 23 | warriorLeft.scale.x = 2; 24 | warriorLeft.scale.y = 2; 25 | warriorLeft.frame = 3; 26 | warriorLeft.play("left"); 27 | g.stage.addChild(warriorLeft); 28 | 29 | const warriorDown = new Spritemap( 30 | g.loader.store["assets/warrior_1.png"], 31 | 32, 32 | 32 33 | ); 34 | warriorDown.addAnimation("left", [3, 4, 5], 200, true); 35 | warriorDown.addAnimation("right", "3-5", 200, true); 36 | warriorDown.position.x = g.stage.width / 2; 37 | warriorDown.position.y = g.stage.height / 2 + 64; 38 | warriorDown.scale.x = 2; 39 | warriorDown.scale.y = 2; 40 | warriorDown.frame = 1; 41 | g.stage.addChild(warriorDown); 42 | 43 | const warriorRight = new Spritemap( 44 | g.loader.store["assets/warrior_1.png"], 45 | 32, 46 | 32 47 | ); 48 | warriorRight.addAnimation("left", [3, 4, 5], 200, true); 49 | warriorRight.addAnimation("right", "6-8", 200, true); 50 | warriorRight.position.x = g.stage.width / 2 + 64; 51 | warriorRight.position.y = g.stage.height / 2; 52 | warriorRight.scale.x = 2; 53 | warriorRight.scale.y = 2; 54 | warriorRight.frame = 6; 55 | warriorRight.play("right"); 56 | g.stage.addChild(warriorRight); 57 | } 58 | } 59 | 60 | g.loader.onComplete = () => { 61 | g.scene = new Main(); 62 | g.centerscreen = true; 63 | g.start(); 64 | }; 65 | 66 | g.loader.load(["assets/warrior_1.png"]); 67 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/_github/workflows/build-android.yml: -------------------------------------------------------------------------------- 1 | name: Build Android 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | name: Build APK 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout source 14 | uses: actions/checkout@v3 15 | - id: check 16 | uses: EndBug/version-check@v2.1.0 17 | with: 18 | diff-search: true 19 | file-name: ./package.json 20 | 21 | - name: Setup Java 22 | if: steps.check.outputs.changed == 'true' 23 | uses: actions/setup-java@v3 24 | with: 25 | distribution: "zulu" 26 | java-version: "17" 27 | 28 | - name: Setup Node 29 | if: steps.check.outputs.changed == 'true' 30 | uses: actions/setup-node@v3 31 | with: 32 | node-version: "16" 33 | 34 | - name: Install 35 | if: steps.check.outputs.changed == 'true' 36 | run: npm ci 37 | 38 | - name: Setup Android 39 | if: steps.check.outputs.changed == 'true' 40 | run: npm run setup:android && npx cap sync 41 | 42 | - name: Build app bundle 43 | if: steps.check.outputs.changed == 'true' 44 | run: cd android && ./gradlew bundle 45 | 46 | - name: Extract Android signing key from env 47 | if: steps.check.outputs.changed == 'true' 48 | run: | 49 | echo "${{ secrets.RELEASE_KEYSTORE }}" > android/release.jks.base64 50 | base64 -d android/release.jks.base64 > android/release.decrypted.jks 51 | 52 | - name: Sign dev build 53 | if: steps.check.outputs.changed == 'true' 54 | run: jarsigner -keystore android/release.decrypted.jks -storepass "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" -signedjar ./android/app/build/outputs/bundle/release/app-release-signed.aab ./android/app/build/outputs/bundle/release/app-release.aab release 55 | 56 | - name: Upload release bundle 57 | if: steps.check.outputs.changed == 'true' 58 | uses: actions/upload-artifact@v3 59 | with: 60 | name: app-release 61 | path: android/app/build/outputs/bundle/release/app-release-signed.aab 62 | retention-days: 60 63 | -------------------------------------------------------------------------------- /packages/lib/src/text/Text.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "DisplayObject"; 2 | 3 | export class Text extends DisplayObject { 4 | private _size: string; 5 | 6 | public family: string = "verdana"; 7 | public weight: string = "normal"; 8 | public style: string = "normal"; 9 | public align: { 10 | v: "top" | "bottom" | "center"; 11 | h: "left" | "right" | "center"; 12 | } = { v: "center", h: "center" }; 13 | 14 | public content: string; 15 | public color: string; 16 | public leading: number = 0; 17 | 18 | constructor( 19 | content: string = "Inks2D!", 20 | size: number = 16, 21 | color: string = "red", 22 | ) { 23 | super(); 24 | 25 | this._size = `${size}px`; 26 | this.content = content; 27 | this.color = color; 28 | } 29 | 30 | private getProperties(): string { 31 | return `${this.style} ${this.weight} ${this._size} ${this.family}`; 32 | } 33 | 34 | get size(): number { 35 | return parseInt(this._size); 36 | } 37 | 38 | set size(value: number) { 39 | this._size = `${value}px`; 40 | } 41 | 42 | render(ctx: CanvasRenderingContext2D): void { 43 | const vAlign = this.align.v === "center" ? "middle" : this.align.v; 44 | 45 | ctx.font = this.getProperties(); 46 | ctx.strokeStyle = this.strokeStyle; 47 | ctx.lineWidth = this.lineWidth; 48 | ctx.fillStyle = this.color; 49 | ctx.textBaseline = vAlign; 50 | ctx.textAlign = this.align.h; 51 | 52 | this.content = `${this.content}`; 53 | const content = this.content.split("\n"); 54 | const h = Math.floor(ctx.measureText("M").width); 55 | 56 | this.height += this.leading * (content.length - 1); 57 | 58 | ctx.save(); 59 | ctx.translate( 60 | -this.width * this.pivot.x + this.width / 2, 61 | -this.height * this.pivot.y + h / 2, 62 | ); 63 | 64 | this.height = 0; 65 | for (let i = 0; i < content.length; i++) { 66 | const lText = content[i]; 67 | const newWidth = Math.floor(ctx.measureText(lText).width); 68 | 69 | if (newWidth > this.width) { 70 | this.width = newWidth; 71 | } 72 | 73 | this.height += h + this.leading; 74 | 75 | ctx.fillText(lText, 0, i * (h + this.leading)); 76 | 77 | if (this.lineWidth !== 0) 78 | ctx.strokeText(lText, 0, i * (h + this.leading)); 79 | } 80 | this.height -= this.leading; 81 | 82 | ctx.restore(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /packages/lib/src/tiles/index.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "DisplayObject"; 2 | import { Point } from "inks2d/math"; 3 | 4 | export { Map } from "./Map"; 5 | export { MapCamera } from "./MapCamera"; 6 | 7 | export const getIndex = ( 8 | pos: Point, 9 | tileWidth: number, 10 | tileHeight: number, 11 | mapWidthInTiles: number, 12 | ): number => { 13 | const index = new Point(); 14 | 15 | index.x = Math.floor(pos.x / tileWidth); 16 | index.y = Math.floor(pos.y / tileHeight); 17 | 18 | return index.x + index.y * mapWidthInTiles; 19 | }; 20 | 21 | export const getPoints = ( 22 | sprite: DisplayObject, 23 | global: boolean = false, 24 | ): Record => { 25 | const bounds = global ? sprite.localBounds() : sprite.globalBounds(); 26 | /** 27 | * The bottom and left corner points are 1 pixel 28 | * less than the sprite’s width and height so that 29 | * the points remain inside the sprite, and not 30 | * outside it; 31 | */ 32 | return { 33 | topLeft: new Point(bounds.x, bounds.y), 34 | topRight: new Point(bounds.x + bounds.width - 1, bounds.y), 35 | bottomLeft: new Point(bounds.x, bounds.y + bounds.height - 1), 36 | bottomRight: new Point( 37 | bounds.x + bounds.width - 1, 38 | bounds.y + bounds.height - 1, 39 | ), 40 | }; 41 | }; 42 | 43 | export const hitTestTile = ( 44 | sprite: DisplayObject, 45 | mapArray: number[], 46 | gidToCheck: number, 47 | pointsToCheck: "every" | "some" | "center" = "some", 48 | tileWidth: number, 49 | tileHeight: number, 50 | mapWidthInTiles: number, 51 | ): Record => { 52 | const collision: Record = {}; 53 | let collisionPoints: Record = getPoints(sprite); 54 | 55 | const checkPoints = (key: string): boolean => { 56 | const point = collisionPoints[key]; 57 | collision.index = getIndex(point, tileWidth, tileHeight, mapWidthInTiles); 58 | 59 | collision.gid = mapArray[collision.index]; 60 | 61 | if (collision.gid === gidToCheck) { 62 | return true; 63 | } 64 | 65 | return false; 66 | }; 67 | 68 | const methodToExec = pointsToCheck === "center" ? "some" : pointsToCheck; 69 | 70 | if (pointsToCheck === "center") { 71 | const point = { 72 | center: new Point(sprite.localCenterX, sprite.localCenterY), 73 | }; 74 | collisionPoints = point; 75 | } 76 | 77 | collision.hasContact = 78 | Object.keys(collisionPoints)[methodToExec](checkPoints); 79 | 80 | return collision; 81 | }; 82 | -------------------------------------------------------------------------------- /packages/lib/src/geom/Triangle.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "DisplayObject"; 2 | import { Point, Vector2D } from "inks2d/math"; 3 | 4 | export class Triangle extends DisplayObject { 5 | private readonly _inclination: "right" | "left" = "right"; 6 | constructor( 7 | width: number = 30, 8 | height: number = 30, 9 | inclination: "right" | "left" = "right", 10 | fillStyle: string = "gray", 11 | strokeStyle: string = "none", 12 | lineWidth: number = 0, 13 | position: Point = new Point(0, 0), 14 | ) { 15 | super(); 16 | 17 | this._inclination = inclination; 18 | 19 | this.width = width; 20 | this.height = height; 21 | this.position = new Point(position.x, position.y); 22 | 23 | this.fillStyle = fillStyle; 24 | this.strokeStyle = strokeStyle; 25 | this.lineWidth = lineWidth; 26 | } 27 | 28 | get inclination(): "right" | "left" { 29 | return this._inclination; 30 | } 31 | 32 | get hypotenuse(): Vector2D { 33 | const vec = new Vector2D(); 34 | 35 | vec.a.x = this.position.x - this.width * this.pivot.x; 36 | vec.a.y = this.position.y - this.height * this.pivot.y; 37 | 38 | vec.b.x = this.position.x - this.width * this.pivot.x + this.width; 39 | vec.b.y = this.position.y - this.height * this.pivot.y + this.height; 40 | 41 | if (this._inclination === "left") { 42 | vec.a.x = this.position.x - this.width * this.pivot.x; 43 | vec.a.y = this.position.y - this.height * this.pivot.y + this.height; 44 | 45 | vec.b.x = this.position.x - this.width * this.pivot.x + this.width; 46 | vec.b.y = this.position.y - this.height * this.pivot.y; 47 | } 48 | 49 | return vec; 50 | } 51 | 52 | render(ctx: CanvasRenderingContext2D): void { 53 | ctx.strokeStyle = this.strokeStyle; 54 | ctx.lineWidth = this.lineWidth; 55 | ctx.fillStyle = this.fillStyle; 56 | 57 | const x = -(this.width * this.pivot.x); 58 | const y = -(this.height * this.pivot.y); 59 | 60 | ctx.beginPath(); 61 | if (this._inclination === "right") { 62 | ctx.moveTo(x, y); 63 | ctx.lineTo(x, y + this.height); 64 | ctx.lineTo(x + this.width, y + this.height); 65 | ctx.lineTo(x, y); 66 | } else { 67 | ctx.moveTo(x + this.width, y); 68 | ctx.lineTo(x, y + this.height); 69 | ctx.lineTo(x + this.width, y + this.height); 70 | ctx.lineTo(x + this.width, y); 71 | } 72 | 73 | if (this.strokeStyle !== "none") ctx.stroke(); 74 | if (this.fillStyle !== "none") ctx.fill(); 75 | if (this.mask && this.mask) ctx.clip(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/examples/playground/collision/src/hitTestCircleTriangle.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { hitTestCircleTriangle } from "inks2d/collision"; 3 | import { Circle, Triangle } from "inks2d/geom"; 4 | import { Keyboard, Keys } from "inks2d/inputs"; 5 | import { Point } from "inks2d/math"; 6 | 7 | const g = new Engine(512, 512); 8 | 9 | class Main extends Scene { 10 | private _c1: Circle = new Circle(20, 20, "red"); 11 | private _tLeft: Triangle = new Triangle(250, 120, "right", "green"); 12 | private _tRight: Triangle = new Triangle(90, 50, "left", "green"); 13 | 14 | constructor() { 15 | super(); 16 | } 17 | 18 | async start(engine: Engine) { 19 | super.start(engine); 20 | 21 | this._c1.position = new Point(100, 100); 22 | this._c1.alpha = 0.5; 23 | this._c1.friction = new Point(1, 1); 24 | g.stage.addChild(this._c1); 25 | 26 | this._tLeft.position = new Point( 27 | g.stage.width / 2 - 50, 28 | g.stage.height / 2 29 | ); 30 | this._tLeft.velocity = new Point(2, 2); 31 | this._tLeft.alpha = 0.5; 32 | g.stage.addChild(this._tLeft); 33 | 34 | this._tRight.position = new Point( 35 | g.stage.width / 2 + 150, 36 | g.stage.height / 2 - 50 37 | ); 38 | this._tRight.velocity = new Point(2, 2); 39 | this._tRight.alpha = 0.5; 40 | g.stage.addChild(this._tRight); 41 | 42 | this.createKeyboardControls(); 43 | } 44 | 45 | createKeyboardControls() { 46 | const leftArrow = new Keyboard(Keys.LEFT_ARROW); 47 | leftArrow.press = () => { 48 | this._c1.velocity.x = -1; 49 | }; 50 | leftArrow.release = () => { 51 | this._c1.velocity.x = 0; 52 | }; 53 | 54 | const rightArrow = new Keyboard(Keys.RIGHT_ARROW); 55 | rightArrow.press = () => { 56 | this._c1.velocity.x = 1; 57 | }; 58 | rightArrow.release = () => { 59 | this._c1.velocity.x = 0; 60 | }; 61 | 62 | const upArrow = new Keyboard(Keys.UP_ARROW); 63 | upArrow.press = () => { 64 | this._c1.velocity.y = -1; 65 | }; 66 | upArrow.release = () => { 67 | this._c1.velocity.y = 0; 68 | }; 69 | 70 | const downArrow = new Keyboard(Keys.DOWN_ARROW); 71 | downArrow.press = () => { 72 | this._c1.velocity.y = 1; 73 | }; 74 | downArrow.release = () => { 75 | this._c1.velocity.y = 0; 76 | }; 77 | } 78 | 79 | update() { 80 | this._c1.update(); 81 | hitTestCircleTriangle(this._c1, this._tLeft, true, true, false); 82 | hitTestCircleTriangle(this._c1, this._tRight, true, true, false); 83 | } 84 | } 85 | 86 | g.scene = new Main(); 87 | g.centerscreen = true; 88 | g.start(); 89 | -------------------------------------------------------------------------------- /packages/lib/src/effects/tweens/Interpolation.ts: -------------------------------------------------------------------------------- 1 | export const Interpolation = { 2 | Linear: (v: number[], k: number) => { 3 | const m = v.length - 1; 4 | const f = m * k; 5 | const i = Math.floor(f); 6 | const fn = Interpolation.Utils.Linear; 7 | 8 | if (k < 0) { 9 | return fn(v[0], v[1], f); 10 | } 11 | 12 | if (k > 1) { 13 | return fn(v[m], v[m - 1], m - f); 14 | } 15 | 16 | return fn(v[i], v[i + 1 > m ? m : i + 1], f - i); 17 | }, 18 | 19 | Bezier: (v: number[], k: number) => { 20 | let b = 0; 21 | const n = v.length - 1; 22 | const pw = Math.pow; 23 | const bn = Interpolation.Utils.Bernstein; 24 | 25 | for (let i = 0; i <= n; i++) { 26 | b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i); 27 | } 28 | 29 | return b; 30 | }, 31 | 32 | CatmullRom: (v: number[], k: number) => { 33 | const m = v.length - 1; 34 | let f = m * k; 35 | let i = Math.floor(f); 36 | const fn = Interpolation.Utils.CatmullRom; 37 | 38 | if (v[0] === v[m]) { 39 | if (k < 0) { 40 | i = Math.floor((f = m * (1 + k))); 41 | } 42 | 43 | return fn( 44 | v[(i - 1 + m) % m], 45 | v[i], 46 | v[(i + 1) % m], 47 | v[(i + 2) % m], 48 | f - i, 49 | ); 50 | } else { 51 | if (k < 0) { 52 | return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]); 53 | } 54 | 55 | if (k > 1) { 56 | return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]); 57 | } 58 | 59 | return fn( 60 | v[i ? i - 1 : 0], 61 | v[i], 62 | v[m < i + 1 ? m : i + 1], 63 | v[m < i + 2 ? m : i + 2], 64 | f - i, 65 | ); 66 | } 67 | }, 68 | 69 | Utils: { 70 | Linear: (p0: number, p1: number, t: number) => { 71 | return (p1 - p0) * t + p0; 72 | }, 73 | 74 | Bernstein: (n: number, i: number) => { 75 | const fc = Interpolation.Utils.Factorial; 76 | return fc(n) / fc(i) / fc(n - i); 77 | }, 78 | 79 | Factorial: (() => { 80 | const a = [1]; 81 | 82 | return (n: number) => { 83 | let s = 1; 84 | 85 | if (a[n]) { 86 | return a[n]; 87 | } 88 | 89 | for (let i = n; i > 1; i--) { 90 | s *= i; 91 | } 92 | 93 | a[n] = s; 94 | return s; 95 | }; 96 | })(), 97 | 98 | CatmullRom: (p0: number, p1: number, p2: number, p3: number, t: number) => { 99 | const v0 = (p2 - p0) * 0.5; 100 | const v1 = (p3 - p1) * 0.5; 101 | const t2 = t * t; 102 | const t3 = t * t2; 103 | 104 | return ( 105 | (2 * p1 - 2 * p2 + v0 + v1) * t3 + 106 | (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + 107 | v0 * t + 108 | p1 109 | ); 110 | }, 111 | }, 112 | }; 113 | -------------------------------------------------------------------------------- /packages/examples/playground/collision/src/hitTestRectangleTriangle.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene } from "inks2d"; 2 | import { hitTestRectangleTriangle } from "inks2d/collision"; 3 | import { Rectangle, Triangle } from "inks2d/geom"; 4 | import { Keyboard, Keys } from "inks2d/inputs"; 5 | import { Point } from "inks2d/math"; 6 | 7 | const g = new Engine(512, 512); 8 | 9 | class Main extends Scene { 10 | private _r1: Rectangle = new Rectangle(20, 20, "red"); 11 | private _tLeft: Triangle = new Triangle(250, 120, "right", "green"); 12 | private _tRight: Triangle = new Triangle(90, 50, "left", "green"); 13 | 14 | constructor() { 15 | super(); 16 | } 17 | 18 | async start(engine: Engine) { 19 | super.start(engine); 20 | 21 | this._r1.position = new Point(100, 100); 22 | this._r1.alpha = 0.5; 23 | this._r1.friction = new Point(1, 1); 24 | g.stage.addChild(this._r1); 25 | 26 | this._tLeft.position = new Point( 27 | g.stage.width / 2 - 50, 28 | g.stage.height / 2 29 | ); 30 | this._tLeft.velocity = new Point(2, 2); 31 | this._tLeft.alpha = 0.5; 32 | g.stage.addChild(this._tLeft); 33 | 34 | this._tRight.position = new Point( 35 | g.stage.width / 2 + 150, 36 | g.stage.height / 2 - 50 37 | ); 38 | this._tRight.velocity = new Point(2, 2); 39 | this._tRight.alpha = 0.5; 40 | g.stage.addChild(this._tRight); 41 | 42 | this.createKeyboardControls(); 43 | } 44 | 45 | createKeyboardControls() { 46 | const leftArrow = new Keyboard(Keys.LEFT_ARROW); 47 | leftArrow.press = () => { 48 | this._r1.velocity.x = -1; 49 | }; 50 | leftArrow.release = () => { 51 | this._r1.velocity.x = 0; 52 | }; 53 | 54 | const rightArrow = new Keyboard(Keys.RIGHT_ARROW); 55 | rightArrow.press = () => { 56 | this._r1.velocity.x = 1; 57 | }; 58 | rightArrow.release = () => { 59 | this._r1.velocity.x = 0; 60 | }; 61 | 62 | const upArrow = new Keyboard(Keys.UP_ARROW); 63 | upArrow.press = () => { 64 | this._r1.velocity.y = -1; 65 | }; 66 | upArrow.release = () => { 67 | this._r1.velocity.y = 0; 68 | }; 69 | 70 | const downArrow = new Keyboard(Keys.DOWN_ARROW); 71 | downArrow.press = () => { 72 | this._r1.velocity.y = 1; 73 | }; 74 | downArrow.release = () => { 75 | this._r1.velocity.y = 0; 76 | }; 77 | } 78 | 79 | update() { 80 | this._r1.update(); 81 | hitTestRectangleTriangle(this._r1, this._tLeft, true, true, false); 82 | hitTestRectangleTriangle(this._r1, this._tRight, true, true, false); 83 | } 84 | } 85 | 86 | g.scene = new Main(); 87 | g.centerscreen = true; 88 | g.start(); 89 | -------------------------------------------------------------------------------- /packages/lib/src/tiles/MapCamera.ts: -------------------------------------------------------------------------------- 1 | import { type DisplayObject } from "DisplayObject"; 2 | import { Point } from "inks2d/math"; 3 | import { type Map } from "./Map"; 4 | 5 | export class MapCamera { 6 | public scale: Point = new Point(); 7 | public width: number = 0; 8 | public height: number = 0; 9 | 10 | private readonly _map: Map; 11 | private _x: number = 0; 12 | private _y: number = 0; 13 | 14 | constructor(map: Map, canvas: HTMLCanvasElement) { 15 | this.width = canvas.width; 16 | this.height = canvas.height; 17 | this._map = map; 18 | } 19 | 20 | get x(): number { 21 | return this._x; 22 | } 23 | 24 | set x(value: number) { 25 | this._x = value; 26 | this._map.position.x = -this._x; 27 | } 28 | 29 | get y(): number { 30 | return this._y; 31 | } 32 | 33 | set y(value: number) { 34 | this._y = value; 35 | this._map.position.y = -this._y; 36 | } 37 | 38 | get rightInnerBoundary(): number { 39 | return this._x + this.width / 2 + this.width / 4; 40 | } 41 | 42 | get leftInnerBoundary(): number { 43 | return this._x + this.width / 2 - this.width / 4; 44 | } 45 | 46 | get topInnerBoundary(): number { 47 | return this._y + this.height / 2 - this.height / 4; 48 | } 49 | 50 | get bottomInnerBoundary(): number { 51 | return this._y + this.height / 2 + this.height / 4; 52 | } 53 | 54 | follow(sprite: DisplayObject): void { 55 | if (sprite.position.x < this.leftInnerBoundary) { 56 | this.x = sprite.position.x - this.width / 4; 57 | } 58 | 59 | if (sprite.position.y < this.topInnerBoundary) { 60 | this.y = sprite.position.y - this.height / 4; 61 | } 62 | 63 | if (sprite.position.x + sprite.width > this.rightInnerBoundary) { 64 | this.x = sprite.position.x + sprite.width - (this.width / 4) * 3; 65 | } 66 | 67 | if (sprite.position.y + sprite.height > this.bottomInnerBoundary) { 68 | this.y = sprite.position.y + sprite.height - (this.height / 4) * 3; 69 | } 70 | 71 | if (this.x < 0) { 72 | this.x = 0; 73 | } 74 | 75 | if (this.y < 0) { 76 | this.y = 0; 77 | } 78 | 79 | if (this.x + this.width > this._map.mapWidth) { 80 | this.x = this._map.mapWidth - this.width; 81 | } 82 | 83 | if (this.y + this.height > this._map.mapHeight) { 84 | this.y = this._map.mapHeight - this.height; 85 | } 86 | } 87 | 88 | centerOver(sprite: DisplayObject): void { 89 | this.x = sprite.position.x + sprite.halfWidth - this.width / 2; 90 | this.y = sprite.position.y + sprite.halfHeight - this.height / 2; 91 | 92 | if (this.x < 0) { 93 | this.x = 0; 94 | } 95 | 96 | if (this.y < 0) { 97 | this.y = 0; 98 | } 99 | 100 | if (this.x + this.width > this._map.mapWidth) { 101 | this.x = this._map.mapWidth - this.width; 102 | } 103 | 104 | if (this.y + this.height > this._map.mapHeight) { 105 | this.y = this._map.mapHeight - this.height; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /packages/lib/src/extras/Backdrop.ts: -------------------------------------------------------------------------------- 1 | import { Rectangle } from "inks2d/geom"; 2 | import { Sprite } from "inks2d/graphics"; 3 | import { Grid } from "inks2d/group"; 4 | 5 | export class Backdrop extends Rectangle { 6 | private _tileGrid: Grid; 7 | 8 | constructor(source: any, width: number, height: number) { 9 | super(width, height, "none", "none"); 10 | 11 | let tileWidth: number, tileHeight: number, columns: number, rows: number; 12 | 13 | this.pivot.x = this.pivot.y = 0; 14 | this.renderOutside = true; 15 | this.mask = true; 16 | 17 | // Is it a texture atlas? 18 | if (source.frame) { 19 | tileWidth = source.frame.w; 20 | tileHeight = source.frame.h; 21 | } else { 22 | tileWidth = source.width; 23 | tileHeight = source.height; 24 | } 25 | 26 | /** 27 | * Calculates the number of rows and columns. 28 | * The number of rows and columns have to be always 1 29 | * bigger than the total number of tiles that fill 30 | * the rectangle. It gives an aditional row and column, 31 | * allowing us to create infite scroll effect; 32 | */ 33 | 34 | /** 35 | * 1. Columns 36 | * 37 | * If the rectangle width is bigger than the tile width, 38 | * calculates the number columns. 39 | */ 40 | if (width >= tileWidth) { 41 | columns = Math.round(width / tileWidth) + 1; 42 | } else { 43 | /** 44 | * If the rectangle width is smaller than the tile width, 45 | * defines the columns to 2, which is the minimum; 46 | */ 47 | columns = 2; 48 | } 49 | 50 | /** 51 | * 2. Rows 52 | * 53 | * If the rectangle height is bigger than the tile height, 54 | * calculates the number rows. 55 | */ 56 | if (height >= tileHeight) { 57 | rows = Math.round(height / tileHeight) + 1; 58 | } else { 59 | /** 60 | * If the rectangle height is smaller than the tile height, 61 | * defines the rows to 2, which is the minimum; 62 | */ 63 | rows = 2; 64 | } 65 | 66 | this._tileGrid = new Grid( 67 | columns, 68 | rows, 69 | tileWidth, 70 | tileHeight, 71 | false, 72 | 0, 73 | 0, 74 | () => { 75 | const tile = new Sprite(source); 76 | tile.pivot.x = tile.pivot.y = 0; 77 | 78 | return tile; 79 | }, 80 | ); 81 | 82 | this._tileGrid.pivot.x = this._tileGrid.pivot.y = 0; 83 | this._tileGrid.dynamicSize = true; 84 | this._tileGrid.renderOutside = true; 85 | 86 | this._tileGrid.position.x = -(this._tileGrid.width - this.width); 87 | this._tileGrid.position.y = -(this._tileGrid.height - this.height); 88 | } 89 | 90 | override added(): void { 91 | this.addChild(this._tileGrid); 92 | } 93 | 94 | public setTileX(value: number): void { 95 | this._tileGrid.position.x += value; 96 | 97 | if (this._tileGrid.position.x >= value) { 98 | this._tileGrid.position.x = -(this._tileGrid.width - this.width - value); 99 | return; 100 | } 101 | 102 | if (this._tileGrid.position.x < -(this._tileGrid.width - this.width)) { 103 | this._tileGrid.position.x = value; 104 | } 105 | } 106 | 107 | public setTileY(value: number): void { 108 | this._tileGrid.position.y += value; 109 | 110 | if (this._tileGrid.position.y >= value) { 111 | this._tileGrid.position.y = -( 112 | this._tileGrid.height - 113 | this.height - 114 | value 115 | ); 116 | return; 117 | } 118 | 119 | if (this._tileGrid.position.y < -(this._tileGrid.height - this.height)) { 120 | this._tileGrid.position.y = value; 121 | } 122 | } 123 | 124 | public setAlpha(value: number): void { 125 | this._tileGrid.alpha = value; 126 | } 127 | 128 | public getAlpha(): number { 129 | return this._tileGrid.alpha; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /packages/lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inks2d", 3 | "version": "0.1.3", 4 | "description": "A free no-dependency Typescript game engine designed for developing 2D games", 5 | "author": "Phillipe Martins", 6 | "bugs": "https://github.com/inkasadev/inks2d/issues", 7 | "license": "MIT", 8 | "licenseUrl": "https://www.opensource.org/licenses/mit-license.php", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/inkasadev/inks2d.git" 12 | }, 13 | "keywords": [ 14 | "2d", 15 | "HTML5", 16 | "canvas", 17 | "game", 18 | "javascript", 19 | "physics", 20 | "tweens", 21 | "typescript", 22 | "web audio" 23 | ], 24 | "main": "dist/index.js", 25 | "types": "dist/index.d.ts", 26 | "type": "module", 27 | "files": [ 28 | "dist/*", 29 | "media/*" 30 | ], 31 | "exports": { 32 | ".": "./dist/index.js", 33 | "./collision": "./dist/collision/index.js", 34 | "./effects/particles": "./dist/effects/particles/index.js", 35 | "./effects/sfx": "./dist/effects/sfx/index.js", 36 | "./effects/tweens": "./dist/effects/tweens/index.js", 37 | "./effects/utils": "./dist/effects/utils/index.js", 38 | "./extras": "./dist/extras/index.js", 39 | "./geom": "./dist/geom/index.js", 40 | "./graphics": "./dist/graphics/index.js", 41 | "./group": "./dist/group/index.js", 42 | "./inputs": "./dist/inputs/index.js", 43 | "./math": "./dist/math/index.js", 44 | "./text": "./dist/text/index.js", 45 | "./tiles": "./dist/tiles/index.js", 46 | "./utils": "./dist/utils/index.js" 47 | }, 48 | "typesVersions": { 49 | "*": { 50 | "collision": [ 51 | "dist/collision/index.d.ts" 52 | ], 53 | "effects/particles": [ 54 | "dist/effects/particles/index.d.ts" 55 | ], 56 | "effects/sfx": [ 57 | "dist/effects/sfx/index.d.ts" 58 | ], 59 | "effects/tweens": [ 60 | "dist/effects/tweens/index.d.ts" 61 | ], 62 | "effects/utils": [ 63 | "dist/effects/utils/index.d.ts" 64 | ], 65 | "extras": [ 66 | "dist/extras/index.d.ts" 67 | ], 68 | "geom": [ 69 | "dist/geom/index.d.ts" 70 | ], 71 | "graphics": [ 72 | "dist/graphics/index.d.ts" 73 | ], 74 | "group": [ 75 | "dist/group/index.d.ts" 76 | ], 77 | "inputs": [ 78 | "dist/inputs/index.d.ts" 79 | ], 80 | "math": [ 81 | "dist/math/index.d.ts" 82 | ], 83 | "text": [ 84 | "dist/text/index.d.ts" 85 | ], 86 | "tiles": [ 87 | "dist/tiles/index.d.ts" 88 | ], 89 | "utils": [ 90 | "dist/utils/index.d.ts" 91 | ] 92 | } 93 | }, 94 | "scripts": { 95 | "dev": "tsup --watch --onSuccess \"node scripts/autocomplete.js\"", 96 | "build": "tsup && node scripts/autocomplete.js", 97 | "docs:dev": "typedoc --watch", 98 | "docs:build": "typedoc && node scripts/copyReadmeToDocs.js", 99 | "prettier": "prettier --ignore-path .gitignore \"**/*.+(ts|json)\"", 100 | "format": "npm run prettier -- --write", 101 | "check-format": "npm run prettier -- --list-different", 102 | "lint": "eslint src/** --parser-options={tsconfigRootDir:null}", 103 | "validate": "npm run format && npm run lint", 104 | "prepublishOnly": "node scripts/copyReadme.js", 105 | "postpack": "node scripts/removeReadme.js", 106 | "release": "npm publish" 107 | }, 108 | "devDependencies": { 109 | "@typescript-eslint/eslint-plugin": "^5.57.1", 110 | "@typescript-eslint/parser": "^5.57.1", 111 | "eslint": "^8.37.0", 112 | "eslint-config-prettier": "^8.8.0", 113 | "eslint-config-standard-with-typescript": "^34.0.1", 114 | "eslint-plugin-import": "^2.27.5", 115 | "eslint-plugin-node": "^11.1.0", 116 | "eslint-plugin-promise": "^6.1.1", 117 | "prettier": "^2.8.7", 118 | "tsup": "^6.7.0", 119 | "typedoc": "^0.23.28", 120 | "typescript": "^5.0.2" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/src/objects/Wnd_GameOver.ts: -------------------------------------------------------------------------------- 1 | import { Engine } from "inks2d"; 2 | import { Sprite, Spritemap } from "inks2d/graphics"; 3 | import { Group } from "inks2d/group"; 4 | import { Point } from "inks2d/math"; 5 | import { Text } from "inks2d/text"; 6 | import { makeInteractive } from "inks2d/utils"; 7 | import { StartScreen } from "../scenes/StartScreen"; 8 | import { Gamescreen } from "../scenes/Gamescreen"; 9 | import { Easing, Tween } from "inks2d/effects/tweens"; 10 | import { _gameConfig } from "../gameConfig"; 11 | 12 | export class Wnd_GameOver extends Group { 13 | private _g: Engine; 14 | private _gamescreen: Group; 15 | 16 | constructor(g: Engine, gamescreen: Group) { 17 | super(); 18 | 19 | this._g = g; 20 | this._gamescreen = gamescreen; 21 | 22 | this.pivot = new Point(); 23 | this.dynamicSize = true; 24 | 25 | _gameConfig.game.best = 26 | _gameConfig.game.best < _gameConfig.game.score 27 | ? _gameConfig.game.score 28 | : _gameConfig.game.best; 29 | } 30 | 31 | added(): void { 32 | const resultBg = new Sprite( 33 | this._g.loader.store["assets/images/result_bg.png"] 34 | ); 35 | resultBg.pivot = new Point(); 36 | this.addChild(resultBg); 37 | 38 | const badges = new Spritemap( 39 | this._g.loader.store["assets/images/badges.png"], 40 | 45, 41 | 45 42 | ); 43 | badges.frame = 44 | _gameConfig.game.score < _gameConfig.config.silverBadgeIn 45 | ? 0 46 | : _gameConfig.game.score >= _gameConfig.config.silverBadgeIn && 47 | _gameConfig.game.score < _gameConfig.config.goldBadgeIn 48 | ? 1 49 | : 2; 50 | badges.position.x = 57; 51 | badges.position.y = 62; 52 | this.addChild(badges); 53 | 54 | const txtScore = new Text("0", 21, "white"); 55 | txtScore.align.h = "right"; 56 | txtScore.family = "prstartk"; 57 | txtScore.position.x = 206; 58 | txtScore.position.y = 45; 59 | this.addChild(txtScore); 60 | 61 | const txtBest = new Text("0", 21, "white"); 62 | txtBest.align.h = "right"; 63 | txtBest.family = "prstartk"; 64 | txtBest.content = _gameConfig.game.best.toString(); 65 | txtBest.position.x = 206; 66 | txtBest.position.y = 83; 67 | this.addChild(txtBest); 68 | 69 | const menu = new Sprite(this._g.loader.store["assets/images/menu.png"]); 70 | menu.pivot.x = menu.pivot.y = 0.5; 71 | makeInteractive(menu); 72 | menu.customProperties.buttonProps.release = () => { 73 | this._gamescreen.parent?.removeChild(this._gamescreen); 74 | 75 | const startscreen = new StartScreen(this._g); 76 | this._g.stage.addChild(startscreen); 77 | }; 78 | menu.position.x = 70; 79 | menu.position.y = 113; 80 | menu.visible = false; 81 | this.addChild(menu); 82 | 83 | const restart = new Sprite( 84 | this._g.loader.store["assets/images/restart.png"] 85 | ); 86 | restart.pivot.x = restart.pivot.y = 0.5; 87 | makeInteractive(restart); 88 | restart.customProperties.buttonProps.release = () => { 89 | this._g.stage.removeChild(this._gamescreen); 90 | 91 | const gamescreen = new Gamescreen(this._g); 92 | this._g.stage.addChild(gamescreen); 93 | }; 94 | restart.position.x = 160; 95 | restart.position.y = 112; 96 | restart.visible = false; 97 | this.addChild(restart); 98 | 99 | const tween = new Tween(); 100 | tween.onUpdate = ({ score }) => { 101 | txtScore.content = parseInt(score).toString(); 102 | }; 103 | tween.onComplete = () => { 104 | menu.visible = true; 105 | restart.visible = true; 106 | }; 107 | tween 108 | .from({ score: 0 }) 109 | .to({ score: _gameConfig.game.score }) 110 | .duration(_gameConfig.game.score * 500) 111 | .easing(Easing.Quartic.Out) 112 | .start(); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /packages/examples/games/flappy-beans/src/scenes/SplashScreen.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject, Engine, Scene } from "inks2d"; 2 | import { Sprite } from "inks2d/graphics"; 3 | import { Rectangle } from "inks2d/geom"; 4 | import { Text } from "inks2d/text"; 5 | import { wait } from "inks2d/utils"; 6 | 7 | export class SplashScreen extends Scene { 8 | private _assetsToLoad: string[]; 9 | private _callback: () => void; 10 | private _forceClick: boolean; 11 | private _loader: Rectangle = new Rectangle(1080, 1920, "black"); 12 | private _g: Engine; 13 | 14 | constructor( 15 | assetsToLoad: string[], 16 | callback: () => void | undefined, 17 | forceClick: boolean, 18 | g: Engine 19 | ) { 20 | super(); 21 | 22 | this._assetsToLoad = assetsToLoad; 23 | this._callback = callback; 24 | this._forceClick = forceClick; 25 | this._g = g; 26 | } 27 | 28 | start(e: Engine): void { 29 | super.start(e); 30 | 31 | this._loader.pivot.x = this._loader.pivot.y = 0; 32 | this._loader.width = this._g.stage.width; 33 | this._loader.height = this._g.stage.height; 34 | this._g.stage.addChild(this._loader); 35 | 36 | if (this._assetsToLoad) { 37 | this._g.loader.loadFont("./assets/fonts/ubuntu.ttf", () => {}); 38 | this._g.loader.loadImage("./assets/images/splash-logo.png", () => { 39 | this.loadAssets(); 40 | }); 41 | } 42 | } 43 | 44 | private loadAssets(): void { 45 | const logo = new Sprite( 46 | this._g.loader.store["./assets/images/splash-logo.png"] 47 | ); 48 | logo.pivot.x = logo.pivot.y = 0.5; 49 | this.updateObjScale(logo); 50 | this._loader.putCenter(logo); 51 | this._loader.addChild(logo); 52 | 53 | const maxWidth = this._g.stage.width / 2.5; 54 | 55 | const back = new Rectangle(maxWidth, 50, "gray", "black", 2, 5); 56 | back.pivot.x = 0; 57 | this.updateObjSize(back); 58 | back.width = maxWidth - 3; 59 | back.position.x = 540 - 210; 60 | back.position.y = 960 + logo.height - 230; 61 | this.updateObjPos(back); 62 | this._loader.addChild(back); 63 | 64 | const front = new Rectangle(0, 50, "#ddd", "none", 2, 1); 65 | front.pivot.x = 0; 66 | this.updateObjSize(front); 67 | front.position.x = 540 - 210; 68 | front.position.y = 960 + logo.height - 230; 69 | this.updateObjPos(front); 70 | this._loader.addChild(front); 71 | 72 | this._g.loader.onUpdate = (loaded, total) => { 73 | const ratio = Math.floor((loaded * 100) / total); 74 | front.width = (ratio * maxWidth) / 100; 75 | }; 76 | 77 | this._g.loader.onComplete = () => { 78 | this.startGame(); 79 | }; 80 | 81 | this._g.loader.load(this._assetsToLoad); 82 | } 83 | 84 | private updateObjScale(obj: DisplayObject): void { 85 | obj.scale.x = (this._g.stage.width * obj.scale.x) / 1080; 86 | obj.scale.y = (this._g.stage.height * obj.scale.y) / 1920; 87 | } 88 | 89 | private updateObjSize(obj: DisplayObject): void { 90 | obj.width = (this._g.stage.width * obj.width) / 1080; 91 | obj.height = (this._g.stage.height * obj.height) / 1920; 92 | } 93 | 94 | private updateObjPos(obj: DisplayObject): void { 95 | obj.position.x = (this._g.stage.width * obj.position.x) / 1080; 96 | obj.position.y = (this._g.stage.height * obj.position.y) / 1920; 97 | } 98 | 99 | private startGame(): void { 100 | const logoWidth = this._loader.children[0].height; 101 | if (!this._forceClick) { 102 | wait(2000).then(() => { 103 | if (this._callback) this._callback(); 104 | }); 105 | return; 106 | } 107 | 108 | const start = new Text("TAP TO START", 48, "white"); 109 | start.family = "ubuntu"; 110 | start.pivot.x = start.pivot.y = 0.5; 111 | start.position.x = 540 + 8; 112 | start.position.y = 960 + logoWidth - 150; 113 | start.size = (this._g.stage.width * start.size) / 1080; 114 | this.updateObjPos(start); 115 | this._loader.addChild(start); 116 | 117 | this._g.pointer.release = () => { 118 | this._g.pointer.release = undefined; 119 | if (this._callback) this._callback(); 120 | }; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /packages/lib/scripts/create_release.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import cp from "child_process"; 3 | import https from "https"; 4 | import { fileURLToPath } from "url"; 5 | import { join, dirname } from "node:path"; 6 | import { readFile, writeFile } from "fs/promises"; 7 | import pkg from "../package.json" assert { type: "json" }; 8 | 9 | const _dirname = 10 | typeof __dirname !== "undefined" 11 | ? __dirname 12 | : dirname(fileURLToPath(import.meta.url)); 13 | 14 | const REMOTE = "origin"; 15 | const REPO = "inkasadev/inks2d"; 16 | const CHANGELOG_MD = join(_dirname, "../CHANGELOG.md"); 17 | 18 | /** 19 | * @param {string} cmd 20 | * @returns {Promise} 21 | */ 22 | function exec(cmd) { 23 | return new Promise((resolve, reject) => { 24 | cp.exec(cmd, { encoding: "utf-8" }, (err, stdout, stderr) => { 25 | if (err) return reject(err); 26 | 27 | if (stderr.trim().length) { 28 | return reject(new Error(stderr)); 29 | } 30 | 31 | resolve(stdout.trim()); 32 | }); 33 | }); 34 | } 35 | 36 | async function createGitHubRelease(args) { 37 | const data = JSON.stringify(args); 38 | 39 | const options = { 40 | hostname: "api.github.com", 41 | path: `/repos/${REPO}/releases`, 42 | method: "POST", 43 | headers: { 44 | Authorization: `token ${process.env.GITHUB_TOKEN}`, 45 | Accept: "application/vnd.github.v3+json", 46 | "Content-Type": "application/json", 47 | "Content-Length": data.length, 48 | "User-Agent": "Node", 49 | }, 50 | }; 51 | 52 | return new Promise((resolve, reject) => { 53 | const req = https.request(options, (res) => { 54 | if (res.statusCode !== 201) { 55 | reject(new Error(res.statusMessage || "Unknown status")); 56 | } 57 | 58 | const result = []; 59 | res.on("data", (d) => result.push(d.toString("utf-8"))); 60 | res.on("close", () => resolve(result.join(""))); 61 | }); 62 | 63 | req.on("error", reject); 64 | req.write(data); 65 | req.end(); 66 | }); 67 | } 68 | 69 | async function main() { 70 | const lastTag = await exec("git describe --tags --abbrev=0"); 71 | const currentVersion = `v${pkg.version}`; 72 | const [_major, _minor, patch] = currentVersion.substring(1).split("."); 73 | 74 | if (lastTag == currentVersion) { 75 | console.log("No version change, not publishing."); 76 | return; 77 | } 78 | 79 | console.log(`Creating release ${currentVersion}`); 80 | console.log("Updating changelog in ", CHANGELOG_MD); 81 | 82 | const heading = patch === "0" ? "#" : "##"; 83 | let fullChangelog = await readFile(CHANGELOG_MD, "utf-8"); 84 | let start = fullChangelog.indexOf(`${heading} ${currentVersion}`); 85 | 86 | // If this version isn't in the changelog yet, take everything under # Unreleased and include that 87 | // as this version. 88 | if (start === -1) { 89 | start = fullChangelog.indexOf("# Unreleased"); 90 | 91 | if (start === -1) { 92 | start = 0; 93 | } else { 94 | start += "# Unreleased".length; 95 | } 96 | 97 | const date = new Date(); 98 | const dateStr = [ 99 | date.getUTCFullYear(), 100 | (date.getUTCMonth() + 1).toString().padStart(2, "0"), // +1 because getUTCMonth returns 0 for January 101 | date.getUTCDate().toString().padStart(2, "0"), 102 | ].join("-"); 103 | 104 | fullChangelog = 105 | "# Unreleased\n\n" + 106 | `${heading} ${currentVersion} (${dateStr})` + 107 | fullChangelog.substring(start); 108 | start = fullChangelog.indexOf(`${heading} ${currentVersion}`); 109 | 110 | await writeFile(CHANGELOG_MD, fullChangelog); 111 | await exec(`git add "${CHANGELOG_MD}"`); 112 | await exec(`git commit -m "chore: update changelog for release"`); 113 | await exec(`git push ${REMOTE}`).catch(() => {}); 114 | } 115 | 116 | start = fullChangelog.indexOf("\n", start) + 1; 117 | 118 | let end = fullChangelog.indexOf("# v0.", start); 119 | end = fullChangelog.lastIndexOf("\n", end); 120 | 121 | console.log("Creating tag..."); 122 | 123 | // Delete the tag if it exists already. 124 | await exec(`git tag -d ${currentVersion}`).catch(() => void 0); 125 | await exec(`git tag ${currentVersion}`); 126 | await exec(`git push ${REMOTE} refs/tags/${currentVersion} --quiet --force`); 127 | 128 | await createGitHubRelease({ 129 | tag_name: currentVersion, 130 | name: currentVersion, 131 | body: fullChangelog.substring(start, end), 132 | }); 133 | 134 | console.log("OK"); 135 | } 136 | 137 | main().catch((err) => { 138 | console.error(err); 139 | process.exitCode = 1; 140 | }); 141 | -------------------------------------------------------------------------------- /packages/create-inks2d/scripts/create_release.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import cp from "child_process"; 3 | import https from "https"; 4 | import { fileURLToPath } from "url"; 5 | import { join, dirname } from "path"; 6 | import { readFile, writeFile } from "fs/promises"; 7 | import pkg from "../package.json" assert { type: "json" }; 8 | 9 | const _dirname = 10 | typeof __dirname !== "undefined" 11 | ? __dirname 12 | : dirname(fileURLToPath(import.meta.url)); 13 | 14 | const REMOTE = "origin"; 15 | const REPO = "inkasadev/inks2d"; 16 | const CHANGELOG_MD = join(_dirname, "../CHANGELOG.md"); 17 | 18 | /** 19 | * @param {string} cmd 20 | * @returns {Promise} 21 | */ 22 | function exec(cmd) { 23 | return new Promise((resolve, reject) => { 24 | cp.exec(cmd, { encoding: "utf-8" }, (err, stdout, stderr) => { 25 | if (err) return reject(err); 26 | 27 | if (stderr.trim().length) { 28 | return reject(new Error(stderr)); 29 | } 30 | 31 | resolve(stdout.trim()); 32 | }); 33 | }); 34 | } 35 | 36 | async function createGitHubRelease(args) { 37 | const data = JSON.stringify(args); 38 | 39 | const options = { 40 | hostname: "api.github.com", 41 | path: `/repos/${REPO}/releases`, 42 | method: "POST", 43 | headers: { 44 | Authorization: `token ${process.env.GITHUB_TOKEN}`, 45 | Accept: "application/vnd.github.v3+json", 46 | "Content-Type": "application/json", 47 | "Content-Length": data.length, 48 | "User-Agent": "Node", 49 | }, 50 | }; 51 | 52 | return new Promise((resolve, reject) => { 53 | const req = https.request(options, (res) => { 54 | if (res.statusCode !== 201) { 55 | reject(new Error(res.statusMessage || "Unknown status")); 56 | } 57 | 58 | const result = []; 59 | res.on("data", (d) => result.push(d.toString("utf-8"))); 60 | res.on("close", () => resolve(result.join(""))); 61 | }); 62 | 63 | req.on("error", reject); 64 | req.write(data); 65 | req.end(); 66 | }); 67 | } 68 | 69 | async function main() { 70 | const lastTag = await exec("git describe --tags --abbrev=0"); 71 | const currentVersion = `v${pkg.version}`; 72 | const currentVersionForTag = `create-inks2d@${pkg.version}`; 73 | const [_major, _minor, patch] = currentVersion.substring(1).split("."); 74 | 75 | if (lastTag == currentVersion) { 76 | console.log("No version change, not publishing."); 77 | return; 78 | } 79 | 80 | console.log(`Creating release ${currentVersion}`); 81 | console.log("Updating changelog..."); 82 | 83 | const heading = patch === "0" ? "#" : "##"; 84 | let fullChangelog = await readFile(CHANGELOG_MD, "utf-8"); 85 | let start = fullChangelog.indexOf(`${heading} ${currentVersion}`); 86 | 87 | // If this version isn't in the changelog yet, take everything under # Unreleased and include that 88 | // as this version. 89 | if (start === -1) { 90 | start = fullChangelog.indexOf("# Unreleased"); 91 | 92 | if (start === -1) { 93 | start = 0; 94 | } else { 95 | start += "# Unreleased".length; 96 | } 97 | 98 | const date = new Date(); 99 | const dateStr = [ 100 | date.getUTCFullYear(), 101 | (date.getUTCMonth() + 1).toString().padStart(2, "0"), // +1 because getUTCMonth returns 0 for January 102 | date.getUTCDate().toString().padStart(2, "0"), 103 | ].join("-"); 104 | 105 | fullChangelog = 106 | "# Unreleased\n\n" + 107 | `${heading} ${currentVersion} (${dateStr})` + 108 | fullChangelog.substring(start); 109 | start = fullChangelog.indexOf(`${heading} ${currentVersion}`); 110 | 111 | console.log("Writing changelog in", CHANGELOG_MD); 112 | 113 | await writeFile(CHANGELOG_MD, fullChangelog); 114 | await exec(`git add "${CHANGELOG_MD}"`); 115 | await exec(`git commit -m "Update changelog for release"`); 116 | await exec(`git push ${REMOTE}`).catch(() => {}); 117 | } 118 | 119 | start = fullChangelog.indexOf("\n", start) + 1; 120 | 121 | let end = fullChangelog.indexOf("# v0.", start); 122 | end = fullChangelog.lastIndexOf("\n", end); 123 | 124 | console.log("Creating tag..."); 125 | 126 | // Delete the tag if it exists already. 127 | await exec(`git tag -d ${currentVersionForTag}`).catch(() => void 0); 128 | await exec(`git tag ${currentVersionForTag}`); 129 | await exec( 130 | `git push ${REMOTE} refs/tags/${currentVersionForTag} --quiet --force`, 131 | ); 132 | 133 | await createGitHubRelease({ 134 | tag_name: currentVersionForTag, 135 | name: currentVersionForTag, 136 | body: fullChangelog.substring(start, end), 137 | }); 138 | 139 | console.log("OK"); 140 | } 141 | 142 | main().catch((err) => { 143 | console.error(err); 144 | process.exitCode = 1; 145 | }); 146 | -------------------------------------------------------------------------------- /packages/lib/src/effects/particles/Emitter.ts: -------------------------------------------------------------------------------- 1 | import { EC_EMITTERS } from "EngineConstants"; 2 | import { type ParticleSystem } from "./ParticleSystem"; 3 | 4 | interface ParticleData { 5 | particle: ParticleSystem; 6 | delay: number; 7 | elapsed: number; 8 | last: number; 9 | playing: boolean; 10 | } 11 | 12 | /** 13 | * 14 | * Particle emitter used for emitting and rendering particle sprites. 15 | * 16 | * ```ts 17 | * import { Engine, Scene } from "inks2d"; 18 | * import { Emitter, ParticleSystem } from "inks2d/effects/particles"; 19 | * import { Spritemap } from "inks2d/graphics"; 20 | * import { Point } from "inks2d/math"; 21 | * 22 | * const g = new Engine(512, 512, 60, false, "none", "black"); 23 | * 24 | * class Main extends Scene { 25 | * constructor() { 26 | * super(); 27 | * } 28 | * 29 | * async start(engine: Engine) { 30 | * super.start(engine); 31 | * 32 | * const emitter = new Emitter(); 33 | * const flames = new ParticleSystem(() => { 34 | * const sprite = new Spritemap( 35 | * g.loader.store["assets/flames.png"], 36 | * 256, 37 | * 256 38 | * ); 39 | * sprite.blendMode = "lighter"; 40 | * g.stage.addChild(sprite); 41 | * 42 | * return sprite; 43 | * }); 44 | * 45 | * flames.setPosition(new Point(g.stage.halfWidth, g.stage.halfHeight)); 46 | * emitter.addParticle("flames", flames, 0.05); 47 | * emitter.play("flames"); 48 | * } 49 | * } 50 | * 51 | * g.loader.onComplete = () => { 52 | * g.scene = new Main(); 53 | * g.start(); 54 | * }; 55 | * 56 | * g.loader.load(["assets/flames.png"]); 57 | * ``` 58 | */ 59 | export class Emitter { 60 | private _particles: Record = {}; 61 | private readonly _playing: string[] = []; 62 | private _particleNames: string[] = []; 63 | 64 | /** 65 | * Constructor. 66 | */ 67 | constructor() { 68 | EC_EMITTERS.push(this); 69 | } 70 | 71 | public _____updateEmitter(gameElapsed: number): void { 72 | for (let i = 0; i < this._playing.length; i++) { 73 | const name = this._playing[i]; 74 | const particleData = this._particles[name]; 75 | 76 | if (!particleData.playing) continue; 77 | 78 | const particle = particleData.particle; 79 | const delay = particleData.delay; 80 | 81 | let elapsed = particleData.elapsed; 82 | const last = particleData.last; 83 | 84 | elapsed += gameElapsed; 85 | 86 | if (elapsed >= delay) { 87 | particle.emit(); 88 | elapsed = 0; 89 | } 90 | 91 | this._particles[name].elapsed = elapsed; 92 | this._particles[name].last = last; 93 | } 94 | } 95 | 96 | /** 97 | * Adds a Particle System in the emitter. 98 | * 99 | * @param name Name of the particle system. 100 | * @param particle ParticleSystem object. 101 | * @param delay Delay between emits. 102 | */ 103 | public addParticle( 104 | name: string, 105 | particle: ParticleSystem, 106 | delay: number, 107 | ): void { 108 | this._particles[name] = { 109 | particle, 110 | delay, 111 | elapsed: 0, 112 | last: 0, 113 | playing: false, 114 | }; 115 | 116 | this._particleNames = Object.keys(this._particles); 117 | } 118 | 119 | /** 120 | * Play a specific Particle System. 121 | * 122 | * @param name Name of the particle system (defined in {@link Emitter.addParticle | addParticle}). 123 | */ 124 | public play(name: string): void { 125 | if (!this._particles[name] || this._particles[name].playing) return; 126 | 127 | this._particles[name].playing = true; 128 | this._playing.push(name); 129 | } 130 | 131 | /** 132 | * Stop a specific Particle System. 133 | * 134 | * @param name Name of the particle system (defined in {@link Emitter.addParticle | addParticle}). 135 | */ 136 | public stop(name: string): void { 137 | if (!this._particles[name] || this._particles[name].playing) return; 138 | 139 | this._particles[name].playing = false; 140 | this._playing.splice(this._playing.indexOf(name), 1); 141 | } 142 | 143 | /** 144 | * Play all Particle Systems. 145 | */ 146 | public playAll(): void { 147 | for (let i = 0; i < this._particleNames.length; i++) { 148 | const name = this._particleNames[i]; 149 | this._particles[name].playing = true; 150 | this._playing.push(name); 151 | } 152 | } 153 | 154 | /** 155 | * Stop all Particle Systems. 156 | */ 157 | public stopAll(): void { 158 | for (let i = 0; i < this._particleNames.length; i++) { 159 | const name = this._particleNames[i]; 160 | this._particles[name].playing = false; 161 | this._playing.splice(this._playing.indexOf(name), 1); 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /packages/lib/src/graphics/Spritemap.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "DisplayObject"; 2 | 3 | type Animation = Record< 4 | string, 5 | { 6 | frames: number[]; 7 | speed: number; 8 | loop: boolean; 9 | } 10 | >; 11 | 12 | export class Spritemap extends DisplayObject { 13 | private readonly _framesPerRow: number; 14 | private _frames: number[] = []; 15 | private _loop: boolean = true; 16 | private _speed: number = 0; 17 | private _currentFrame: number = 0; 18 | private _currentAnim: string = ""; 19 | private _frameInAnim: number = 0; 20 | private _complete: boolean = false; 21 | private _playing: boolean = false; 22 | 23 | private _animations: Animation = {}; 24 | private readonly _source: any; 25 | private _sourceX: number = 0; 26 | private _sourceY: number = 0; 27 | private readonly _sourceWidth: number; 28 | private readonly _sourceHeight: number; 29 | 30 | private _elapsed: number = 0; 31 | 32 | public onAnimStart?: (currentAnim: string, currentFrame: number) => void; 33 | public onAnimUpdate?: (currentAnim: string, currentFrame: number) => void; 34 | public onAnimComplete?: (currentAnim: string, currentFrame: number) => void; 35 | 36 | constructor(source: any, frameWidth: number, frameHeight: number) { 37 | super(); 38 | 39 | this._source = source; 40 | this._framesPerRow = this._source.width / frameWidth; 41 | this._sourceWidth = frameWidth; 42 | this._sourceHeight = frameHeight; 43 | 44 | this.width = frameWidth; 45 | this.height = frameHeight; 46 | 47 | this.bounds.width = this.width; 48 | this.bounds.height = this.height; 49 | } 50 | 51 | get complete(): boolean { 52 | return this._complete; 53 | } 54 | 55 | get animationName(): string { 56 | return this._currentAnim; 57 | } 58 | 59 | get frame(): number { 60 | return this._currentFrame; 61 | } 62 | 63 | set frame(value: number) { 64 | this._sourceX = Math.floor(value % this._framesPerRow) * this._sourceWidth; 65 | this._sourceY = Math.floor(value / this._framesPerRow) * this._sourceHeight; 66 | this._currentFrame = value; 67 | } 68 | 69 | addAnimation( 70 | name: string, 71 | frames: string | number | number[], 72 | speed: number, 73 | loop: boolean, 74 | ): void { 75 | let frameList: number[] = []; 76 | const framesType = typeof frames; 77 | 78 | if (framesType === "string") { 79 | const beginEndFrames = (frames as string).split("-"); 80 | const begin = parseInt(beginEndFrames[0]); 81 | const end = parseInt(beginEndFrames[1]); 82 | 83 | for (let i = begin; i <= end; i++) frameList.push(i); 84 | } else if (framesType === "object") { 85 | frameList = frames as number[]; 86 | } else if (framesType === "number") { 87 | frameList = [frames as number]; 88 | } 89 | 90 | this._animations[name] = { 91 | frames: frameList, 92 | speed, 93 | loop, 94 | }; 95 | } 96 | 97 | play(name: string): void { 98 | if (!this._animations[name]) return; 99 | 100 | this._frames = this._animations[name].frames; 101 | this._speed = this._animations[name].speed; 102 | this._loop = this._animations[name].loop; 103 | this._currentFrame = this._frames[0]; 104 | this._currentAnim = name; 105 | this._frameInAnim = 0; 106 | this._complete = false; 107 | this._playing = true; 108 | 109 | if (this.onAnimStart != null) 110 | this.onAnimStart(this._currentAnim, this._currentFrame); 111 | } 112 | 113 | pause(): void { 114 | this._playing = false; 115 | } 116 | 117 | resume(): void { 118 | this._playing = true; 119 | } 120 | 121 | render(ctx: CanvasRenderingContext2D): void { 122 | this.updtFrame(this.customProperties.elapsed); 123 | 124 | ctx.drawImage( 125 | this._source, 126 | this._sourceX, 127 | this._sourceY, 128 | this._sourceWidth, 129 | this._sourceHeight, 130 | -this.width * this.pivot.x, 131 | -this.height * this.pivot.y, 132 | this.width, 133 | this.height, 134 | ); 135 | } 136 | 137 | private updtFrame(elapsed: number): void { 138 | if (this._frames.length === 0 || this._complete || !this._playing) return; 139 | 140 | this._elapsed += elapsed; 141 | 142 | if (this._elapsed >= this._speed) { 143 | this.frame = this._frames[this._frameInAnim++]; 144 | 145 | if (this.onAnimUpdate != null) 146 | this.onAnimUpdate(this._currentAnim, this._currentFrame); 147 | 148 | if (this._frameInAnim >= this._frames.length) { 149 | if (this._loop) { 150 | this._frameInAnim = 0; 151 | } else { 152 | this._complete = true; 153 | this._playing = false; 154 | 155 | if (this.onAnimComplete != null) 156 | this.onAnimComplete(this._currentAnim, this._currentFrame); 157 | } 158 | } 159 | 160 | this._elapsed = 0; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /packages/lib/src/effects/tweens/Easing.ts: -------------------------------------------------------------------------------- 1 | export const Easing = { 2 | Linear: { 3 | None: (k: number) => { 4 | return k; 5 | }, 6 | }, 7 | 8 | Quadratic: { 9 | In: (k: number) => { 10 | return k * k; 11 | }, 12 | 13 | Out: (k: number) => { 14 | return k * (2 - k); 15 | }, 16 | 17 | InOut: (k: number) => { 18 | if ((k *= 2) < 1) { 19 | return 0.5 * k * k; 20 | } 21 | return -0.5 * (--k * (k - 2) - 1); 22 | }, 23 | }, 24 | 25 | Cubic: { 26 | In: (k: number) => { 27 | return k * k * k; 28 | }, 29 | 30 | Out: (k: number) => { 31 | return --k * k * k + 1; 32 | }, 33 | 34 | InOut: (k: number) => { 35 | if ((k *= 2) < 1) { 36 | return 0.5 * k * k * k; 37 | } 38 | return 0.5 * ((k -= 2) * k * k + 2); 39 | }, 40 | }, 41 | 42 | Quartic: { 43 | In: (k: number) => { 44 | return k * k * k * k; 45 | }, 46 | 47 | Out: (k: number) => { 48 | return 1 - --k * k * k * k; 49 | }, 50 | 51 | InOut: (k: number) => { 52 | if ((k *= 2) < 1) { 53 | return 0.5 * k * k * k * k; 54 | } 55 | return -0.5 * ((k -= 2) * k * k * k - 2); 56 | }, 57 | }, 58 | 59 | Quintic: { 60 | In: (k: number) => { 61 | return k * k * k * k * k; 62 | }, 63 | 64 | Out: (k: number) => { 65 | return --k * k * k * k * k + 1; 66 | }, 67 | 68 | InOut: (k: number) => { 69 | if ((k *= 2) < 1) { 70 | return 0.5 * k * k * k * k * k; 71 | } 72 | return 0.5 * ((k -= 2) * k * k * k * k + 2); 73 | }, 74 | }, 75 | 76 | Sinusoidal: { 77 | In: (k: number) => { 78 | return 1 - Math.cos((k * Math.PI) / 2); 79 | }, 80 | 81 | Out: (k: number) => { 82 | return Math.sin((k * Math.PI) / 2); 83 | }, 84 | 85 | InOut: (k: number) => { 86 | return 0.5 * (1 - Math.cos(Math.PI * k)); 87 | }, 88 | }, 89 | 90 | Exponential: { 91 | In: (k: number) => { 92 | return k === 0 ? 0 : Math.pow(1024, k - 1); 93 | }, 94 | 95 | Out: (k: number) => { 96 | return k === 1 ? 1 : 1 - Math.pow(2, -10 * k); 97 | }, 98 | 99 | InOut: (k: number) => { 100 | if (k === 0) { 101 | return 0; 102 | } 103 | 104 | if (k === 1) { 105 | return 1; 106 | } 107 | 108 | if ((k *= 2) < 1) { 109 | return 0.5 * Math.pow(1024, k - 1); 110 | } 111 | 112 | return 0.5 * (-Math.pow(2, -10 * (k - 1)) + 2); 113 | }, 114 | }, 115 | 116 | Circular: { 117 | In: (k: number) => { 118 | return 1 - Math.sqrt(1 - k * k); 119 | }, 120 | 121 | Out: (k: number) => { 122 | return Math.sqrt(1 - --k * k); 123 | }, 124 | 125 | InOut: (k: number) => { 126 | if ((k *= 2) < 1) { 127 | return -0.5 * (Math.sqrt(1 - k * k) - 1); 128 | } 129 | return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1); 130 | }, 131 | }, 132 | 133 | Elastic: { 134 | In: (k: number) => { 135 | if (k === 0) { 136 | return 0; 137 | } 138 | 139 | if (k === 1) { 140 | return 1; 141 | } 142 | 143 | return -Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI); 144 | }, 145 | 146 | Out: (k: number) => { 147 | if (k === 0) { 148 | return 0; 149 | } 150 | 151 | if (k === 1) { 152 | return 1; 153 | } 154 | 155 | return Math.pow(2, -10 * k) * Math.sin((k - 0.1) * 5 * Math.PI) + 1; 156 | }, 157 | 158 | InOut: (k: number) => { 159 | if (k === 0) { 160 | return 0; 161 | } 162 | 163 | if (k === 1) { 164 | return 1; 165 | } 166 | 167 | k *= 2; 168 | 169 | if (k < 1) { 170 | return ( 171 | -0.5 * Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI) 172 | ); 173 | } 174 | 175 | return ( 176 | 0.5 * Math.pow(2, -10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI) + 1 177 | ); 178 | }, 179 | }, 180 | 181 | Back: { 182 | In: (k: number) => { 183 | const s = 1.70158; 184 | return k * k * ((s + 1) * k - s); 185 | }, 186 | 187 | Out: (k: number) => { 188 | const s = 1.70158; 189 | return --k * k * ((s + 1) * k + s) + 1; 190 | }, 191 | 192 | InOut: (k: number) => { 193 | const s = 1.70158 * 1.525; 194 | if ((k *= 2) < 1) { 195 | return 0.5 * (k * k * ((s + 1) * k - s)); 196 | } 197 | 198 | return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2); 199 | }, 200 | }, 201 | 202 | Bounce: { 203 | In: (k: number) => { 204 | return 1 - Easing.Bounce.Out(1 - k); 205 | }, 206 | 207 | Out: (k: number) => { 208 | if (k < 1 / 2.75) { 209 | return 7.5625 * k * k; 210 | } else if (k < 2 / 2.75) { 211 | return 7.5625 * (k -= 1.5 / 2.75) * k + 0.75; 212 | } else if (k < 2.5 / 2.75) { 213 | return 7.5625 * (k -= 2.25 / 2.75) * k + 0.9375; 214 | } else { 215 | return 7.5625 * (k -= 2.625 / 2.75) * k + 0.984375; 216 | } 217 | }, 218 | 219 | InOut: (k: number) => { 220 | if (k < 0.5) { 221 | return Easing.Bounce.In(k * 2) * 0.5; 222 | } 223 | 224 | return Easing.Bounce.Out(k * 2 - 1) * 0.5 + 0.5; 225 | }, 226 | }, 227 | }; 228 | -------------------------------------------------------------------------------- /packages/lib/src/effects/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "DisplayObject"; 2 | import { Point, randomInt } from "inks2d/math"; 3 | import { Easing, Interpolation, Tween } from "inks2d/effects/tweens"; 4 | import { Sound } from "inks2d/effects/sfx"; 5 | import { EC_TWEENS } from "EngineConstants"; 6 | 7 | export const fadeIn = ( 8 | sprite: DisplayObject, 9 | duration: number, 10 | easing = Easing.Linear.None, 11 | interpolation = Interpolation.Linear, 12 | ): Tween => { 13 | const tween = new Tween(); 14 | tween.onUpdate = (props) => { 15 | sprite.alpha = props.alpha; 16 | }; 17 | tween 18 | .from({ alpha: sprite.alpha }) 19 | .to({ alpha: 1 }) 20 | .duration(duration) 21 | .easing(easing) 22 | .interpolation(interpolation) 23 | .start(); 24 | 25 | return tween; 26 | }; 27 | 28 | export const fadeOut = ( 29 | sprite: DisplayObject, 30 | duration: number, 31 | easing = Easing.Linear.None, 32 | interpolation = Interpolation.Linear, 33 | ): Tween => { 34 | const tween = new Tween(); 35 | tween.onUpdate = (props) => { 36 | sprite.alpha = props.alpha; 37 | }; 38 | tween 39 | .from({ alpha: sprite.alpha }) 40 | .to({ alpha: 0 }) 41 | .duration(duration) 42 | .easing(easing) 43 | .interpolation(interpolation) 44 | .start(); 45 | 46 | return tween; 47 | }; 48 | 49 | export const pulse = ( 50 | sprite: DisplayObject, 51 | minAlpha: number, 52 | duration: number, 53 | easing = Easing.Linear.None, 54 | interpolation = Interpolation.Linear, 55 | ): Tween => { 56 | const tween = new Tween(); 57 | tween.onUpdate = (props) => { 58 | sprite.alpha = props.alpha; 59 | }; 60 | tween 61 | .from({ alpha: sprite.alpha }) 62 | .to({ alpha: minAlpha }) 63 | .duration(duration) 64 | .yoyo(true) 65 | .easing(easing) 66 | .interpolation(interpolation) 67 | .start(); 68 | 69 | return tween; 70 | }; 71 | 72 | export const slide = ( 73 | sprite: DisplayObject, 74 | to: Record, 75 | duration: number, 76 | easing = Easing.Linear.None, 77 | interpolation = Interpolation.Linear, 78 | ): Tween => { 79 | const tween = new Tween(); 80 | tween.onUpdate = (props) => { 81 | sprite.position = new Point(props.x, props.y); 82 | }; 83 | tween 84 | .from({ x: sprite.position.x, y: sprite.position.y }) 85 | .to(to) 86 | .duration(duration) 87 | .easing(easing) 88 | .interpolation(interpolation) 89 | .start(); 90 | 91 | return tween; 92 | }; 93 | 94 | export const blink = ( 95 | sprite: DisplayObject, 96 | duration: number, 97 | yoyo: boolean = true, 98 | easing = Easing.Linear.None, 99 | interpolation = Interpolation.Linear, 100 | ): Tween => { 101 | const tween = new Tween(); 102 | tween.onUpdate = (props) => { 103 | sprite.visible = !!Math.round(props.updateVisible); 104 | }; 105 | tween 106 | .from({ updateVisible: 0 }) 107 | .to({ updateVisible: 1 }) 108 | .duration(duration) 109 | .yoyo(yoyo) 110 | .easing(easing) 111 | .interpolation(interpolation) 112 | .start(); 113 | 114 | return tween; 115 | }; 116 | 117 | export const removeTween = (...obj: Tween[]): void => { 118 | if (obj.length === 1) { 119 | let tween = obj[0]; 120 | let id = EC_TWEENS.indexOf(tween); 121 | 122 | if (id !== -1) { 123 | tween.pause(); 124 | EC_TWEENS.splice(id, 1); 125 | } 126 | 127 | return; 128 | } 129 | 130 | obj.forEach((tween) => { 131 | let id = EC_TWEENS.indexOf(tween); 132 | 133 | if (id !== -1) { 134 | tween.pause(); 135 | EC_TWEENS.splice(id, 1); 136 | } 137 | }); 138 | }; 139 | 140 | export const playSfx = ( 141 | frequencyValue: number, 142 | attack: number = 0, 143 | decay: number = 1, 144 | type: "sine" | "triangle" | "square" | "sawtooth" = "sine", 145 | volumeValue: number = 1, 146 | panValue: number = 1, 147 | wait: number = 0, 148 | // pitchBendAmount: number = 0, 149 | // reverse: boolean = false, 150 | randomValue: number = 0, 151 | // dissonance: number = 0, 152 | // echo: number[] | undefined = undefined, 153 | // reverb: number[] | undefined = undefined, 154 | ): void => { 155 | const sfx = new Sound(""); 156 | 157 | sfx.oscillatorNode.connect(sfx.volumeNode); 158 | sfx.volumeNode.connect(sfx.panNode); 159 | sfx.panNode.connect(sfx.audioContextDestination); 160 | 161 | sfx.volume = volumeValue; 162 | sfx.pan = panValue; 163 | sfx.oscillatorNode.type = type; 164 | 165 | if (randomValue > 0) { 166 | frequencyValue = randomInt( 167 | frequencyValue - randomValue / 2, 168 | frequencyValue + randomValue / 2, 169 | ); 170 | } 171 | 172 | sfx.oscillatorNode.frequency.value = frequencyValue; 173 | 174 | if (attack > 0) sfx.fadeIn(sfx.volume, wait); 175 | if (decay > 0) sfx.fadeOut(sfx.volume, wait); 176 | 177 | sfx.oscillatorNode.start(wait); 178 | 179 | /* 180 | const oscillator = this._actx.createOscillator(); 181 | 182 | oscillator.connect(this._volumeNode); 183 | this._volumeNode.connect(this._panNode); 184 | this._panNode.connect(this._actx.destination); 185 | 186 | 187 | oscillator.start(this._actx.currentTime + wait); 188 | */ 189 | // snd.playSfx(frequencyValue, type, wait, randomValue); 190 | }; 191 | -------------------------------------------------------------------------------- /packages/lib/src/extras/SplashScreen.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "DisplayObject"; 2 | import { Engine } from "Engine"; 3 | import { Scene } from "Scene"; 4 | import { Rectangle } from "inks2d/geom"; 5 | import { Sprite } from "inks2d/graphics"; 6 | import { Text } from "inks2d/text"; 7 | import { wait } from "inks2d/utils"; 8 | 9 | export class SplashScreen extends Scene { 10 | private _loader: Rectangle = new Rectangle(1080, 1920, "black"); 11 | private _assetsToLoad: string[]; 12 | private _logo: DisplayObject = new Rectangle(0, 0); 13 | private _splashImage: string | DisplayObject; 14 | private _callback: () => void; 15 | private _forceClick: boolean; 16 | // @ts-ignore 17 | private _g: Engine; 18 | private _ctaText: string; 19 | private _yOffset: number; 20 | 21 | constructor( 22 | assetsToLoad: string[], 23 | callback: () => void, 24 | yOffset: number = 0, 25 | splashImage: string | DisplayObject, 26 | forceClick: boolean = false, 27 | ctaText: string = "TAP TO START", 28 | ) { 29 | super(); 30 | 31 | this._assetsToLoad = assetsToLoad; 32 | this._splashImage = splashImage; 33 | this._callback = callback; 34 | this._forceClick = forceClick; 35 | this._ctaText = ctaText; 36 | this._yOffset = yOffset; 37 | } 38 | 39 | override start(e: Engine): void { 40 | super.start(e); 41 | this._g = e; 42 | 43 | this._loader.pivot.x = this._loader.pivot.y = 0; 44 | this._loader.width = this._g.stage.width; 45 | this._loader.height = this._g.stage.height; 46 | this._g.stage.addChild(this._loader); 47 | 48 | if (!this._splashImage) { 49 | this.loadAssets(); 50 | return; 51 | } 52 | 53 | if (typeof this._splashImage === "string") { 54 | this._g.loader.onComplete = () => { 55 | const maxWidth = this._g.stage.width / 2.5; 56 | const maxHeight = this._g.stage.height / 2.5; 57 | const minSize = Math.min(Math.min(maxWidth, maxHeight), 300); 58 | const targetArea = minSize * minSize; 59 | 60 | this._logo = new Sprite( 61 | this._g.loader.store[this._splashImage as string], 62 | ); 63 | const newWidth = Math.sqrt( 64 | (this._logo.width / this._logo.height) * targetArea, 65 | ); 66 | const newHeight = targetArea / newWidth; 67 | this._logo.width = Math.round(newWidth); 68 | this._logo.height = Math.round( 69 | newHeight - (this._logo.width - newWidth), 70 | ); 71 | this.loadAssets(); 72 | }; 73 | this._g.loader.load([this._splashImage]); 74 | return; 75 | } 76 | 77 | this._logo = this._splashImage; 78 | this.loadAssets(); 79 | } 80 | 81 | private loadAssets(): void { 82 | const maxWidth = this._g.stage.width / 2.5; 83 | 84 | this._logo.pivot.x = this._logo.pivot.y = 0.5; 85 | this._loader.putCenter(this._logo); 86 | this._loader.addChild(this._logo); 87 | 88 | const back = new Rectangle(maxWidth, 25, "gray", "black", 2, 5); 89 | back.pivot.x = 0; 90 | this.updateObjSize(back); 91 | back.width = maxWidth - 3; 92 | back.position.x = 540 - 210; 93 | back.position.y = 960; 94 | this.updateObjPos(back); 95 | back.position.y += this._logo.height / 2 + back.height + this._yOffset; 96 | this._loader.addChild(back); 97 | 98 | const front = new Rectangle(0, 25, "#ddd", "none", 2, 1); 99 | front.pivot.x = 0; 100 | this.updateObjSize(front); 101 | front.position.x = 540 - 210; 102 | front.position.y = 960; 103 | this.updateObjPos(front); 104 | front.position.y += this._logo.height / 2 + back.height + this._yOffset; 105 | this._loader.addChild(front); 106 | 107 | this._g.loader.onUpdate = (loaded, total) => { 108 | const ratio = Math.floor((loaded * 100) / total); 109 | front.width = (ratio * maxWidth) / 100; 110 | }; 111 | 112 | if (this._assetsToLoad.length === 0) { 113 | this._g.loader.onUpdate(1, 1); 114 | this.startGame(); 115 | return; 116 | } 117 | 118 | this._g.loader.onComplete = () => { 119 | this.startGame(); 120 | }; 121 | 122 | this._g.loader.load(this._assetsToLoad); 123 | } 124 | 125 | private updateObjSize(obj: DisplayObject): void { 126 | obj.width = (this._g.stage.width * obj.width) / 1080; 127 | obj.height = (this._g.stage.height * obj.height) / 1920; 128 | } 129 | 130 | private updateObjPos(obj: DisplayObject): void { 131 | obj.position.x = (this._g.stage.width * obj.position.x) / 1080; 132 | obj.position.y = (this._g.stage.height * obj.position.y) / 1920; 133 | } 134 | 135 | private startGame(): void { 136 | if (!this._forceClick) { 137 | wait(2000).then(() => { 138 | if (this._callback) this._callback(); 139 | }); 140 | return; 141 | } 142 | 143 | const start = new Text(this._ctaText, 48, "white"); 144 | start.family = "ubuntu"; 145 | start.pivot.x = start.pivot.y = 0.5; 146 | start.position.x = 540 + 8; 147 | start.position.y = 960; 148 | start.size = (this._g.stage.width * start.size) / 1080; 149 | this.updateObjPos(start); 150 | start.position.y += 151 | this._logo.height / 2 + 152 | this._loader.children[1].height * 3.5 + 153 | this._yOffset; 154 | this._loader.addChild(start); 155 | 156 | this._g.pointer.release = () => { 157 | this._g.pointer.release = undefined; 158 | if (this._callback) this._callback(); 159 | }; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /packages/lib/src/effects/tweens/Tween.ts: -------------------------------------------------------------------------------- 1 | import { EC_TWEENS } from "EngineConstants"; 2 | import { Easing } from "./Easing"; 3 | import { Interpolation } from "./Interpolation"; 4 | import { wait } from "inks2d/utils"; 5 | 6 | export class Tween { 7 | private _from: Record = {}; 8 | private _to: Record = {}; 9 | private _playing = false; 10 | private _duration: number = 0; 11 | private _delay: number = 0; 12 | private _easingType: (k: number) => number = Easing.Linear.None; 13 | private _interpolationType: (v: number[], k: number) => number = 14 | Interpolation.Linear; 15 | 16 | private _props: Record = {}; 17 | private _properties: string[] = []; 18 | private _elapsed: number = 0; 19 | // private _last: number = 0; 20 | private _chainedTweens: Tween[] = []; 21 | private _yoyo: boolean = false; 22 | private _repeat: number = 0; 23 | private _repeatCount: number = 1; 24 | private _repeatYoyo: boolean = false; 25 | 26 | public onComplete?: (props: Record) => void; 27 | public onStart?: (props: Record) => void; 28 | public onUpdate?: (props: Record) => void; 29 | public onStop?: (props: Record) => void; 30 | 31 | constructor() { 32 | EC_TWEENS.push(this); 33 | } 34 | 35 | private end(): void { 36 | const temp = this._from; 37 | this._playing = false; 38 | 39 | EC_TWEENS.splice(EC_TWEENS.indexOf(this), 1); 40 | 41 | if (this._yoyo) { 42 | this.from(this._to).to(temp).start(); 43 | return; 44 | } 45 | 46 | if (this._repeatCount < this._repeat) { 47 | if (this._repeatYoyo) this.from(this._to).to(temp).start(); 48 | else this.start(); 49 | 50 | this._repeatCount++; 51 | return; 52 | } 53 | 54 | if (this.onUpdate) this.onUpdate(this._props); 55 | 56 | if (this.onComplete) this.onComplete(this._props); 57 | 58 | this._chainedTweens.forEach((tween) => { 59 | tween.start(); 60 | }); 61 | } 62 | 63 | get playing(): boolean { 64 | return this._playing; 65 | } 66 | 67 | public from(value: Record): Tween { 68 | this._from = value; 69 | this._properties = Object.keys(this._from); 70 | 71 | this._properties.forEach((prop: string) => { 72 | this._props[prop] = this._from[prop]; 73 | }); 74 | 75 | return this; 76 | } 77 | 78 | public to(value: Record): Tween { 79 | this._to = value; 80 | 81 | return this; 82 | } 83 | 84 | public start(): void { 85 | if (this._playing) EC_TWEENS.splice(EC_TWEENS.indexOf(this), 1); 86 | 87 | this._elapsed = 0; 88 | // this._last = 0; 89 | 90 | wait(this._delay).then(() => { 91 | this._playing = true; 92 | this._properties.forEach((prop) => { 93 | this._props[prop] = this._from[prop]; 94 | }); 95 | 96 | EC_TWEENS.push(this); 97 | 98 | if (this.onStart != null) this.onStart(this._props); 99 | }); 100 | } 101 | 102 | public update(elapsed: number): void { 103 | if (!this._playing) return; 104 | 105 | /* 106 | const now = Date.now(); 107 | 108 | if (now - this._last >= 1000) { 109 | this._last = now; 110 | } 111 | */ 112 | 113 | // this._elapsed += now - this._last; 114 | this._elapsed += elapsed; 115 | // this._last = now; 116 | 117 | if (this._elapsed > this._duration) { 118 | this._elapsed = 0; 119 | this._properties.forEach((prop) => { 120 | if (this._to[prop] instanceof Array) { 121 | this._props[prop] = this._to[prop][this._to[prop].length - 1]; 122 | return; 123 | } 124 | 125 | this._props[prop] = this._to[prop]; 126 | }); 127 | 128 | this.end(); 129 | return; 130 | } 131 | 132 | // this._last = now; 133 | const normalizedTime = this._elapsed / this._duration; 134 | const curvedTime = this._easingType(normalizedTime); 135 | 136 | this._properties.forEach((prop) => { 137 | if (this._to[prop] instanceof Array) { 138 | const interpolatedTime = this._interpolationType( 139 | this._to[prop], 140 | curvedTime, 141 | ); 142 | 143 | this._props[prop] = interpolatedTime; 144 | return; 145 | } 146 | 147 | this._props[prop] = 148 | this._to[prop] * curvedTime + this._from[prop] * (1 - curvedTime); 149 | }); 150 | 151 | if (this.onUpdate) this.onUpdate(this._props); 152 | } 153 | 154 | public easing(type: (k: number) => number): Tween { 155 | this._easingType = type; 156 | 157 | return this; 158 | } 159 | 160 | public interpolation(type: (v: number[], k: number) => number): Tween { 161 | this._interpolationType = type; 162 | 163 | return this; 164 | } 165 | 166 | public play(): void { 167 | this._playing = true; 168 | } 169 | 170 | public pause(): void { 171 | this._playing = false; 172 | } 173 | 174 | public chain(...twenToChain: Tween[]): Tween { 175 | this._chainedTweens = twenToChain; 176 | 177 | return this; 178 | } 179 | 180 | public yoyo(value: boolean): Tween { 181 | this._yoyo = value; 182 | 183 | return this; 184 | } 185 | 186 | public repeat(value: number, repeatYoyo: boolean): Tween { 187 | this._repeat = value; 188 | this._repeatYoyo = repeatYoyo; 189 | 190 | return this; 191 | } 192 | 193 | public delay(value: number): Tween { 194 | this._delay = value; 195 | 196 | return this; 197 | } 198 | 199 | public duration(value: number): Tween { 200 | this._duration = value; 201 | 202 | return this; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /packages/create-inks2d/platform-mobile/README.md: -------------------------------------------------------------------------------- 1 | # inks2d Mobile Scaffolding 2 | 3 | ## Table of contents 4 | - [Environment Setup](#environment-setup) 5 | - [Create and config your Android project](#create-and-config-your-android-project) 6 | - [Github Action Workflow](#github-action-workflow) 7 | - [Generating a key store](#generating-a-key-store) 8 | - [Adding the key store and password as GitHub Secrets](#adding-the-key-store-and-password-as-github-secrets) 9 | - [For Windows users](#for-windows-users) 10 | - [Splash Screens and Icons](#splash-screens-and-icons) 11 | 12 | ## Environment Setup 13 | 14 | inks2d uses [Capacitor](https://capacitorjs.com/) to build performant mobile games that run natively on iOS and Android. In order to create games for one or both platforms, you need to install the desired platform-specific dependencies. Please follow the official [Capacitor Environment Setup Guide by clicking here](https://capacitorjs.com/docs/getting-started/environment-setup). 15 | 16 | ## Create and config your Android project 17 | 18 | After the environment setup is configured, you can install the Android platform. 19 | 20 | ```bash 21 | $ npm run setup:android 22 | ``` 23 | 24 | ### Github Action Workflow 25 | 26 | > **Disclaimer:** _This session has been deeply based on [this article](https://dev.to/khromov/build-your-capacitor-android-app-bundle-using-github-actions-24do). I'm just reproducing it here to keep this content alive under my umbrella._ 27 | 28 | This project comes with a Github Action workflow that will produce a signed app bundle, ready for upload to Google Play Console. Follow this to know how to configure it. 29 | 30 | At a high level, we will: 31 | 32 | - Set up our key store and signing keys 33 | - Adding our key store and signing keys to GitHub Secrets 34 | 35 | ### Generating a key store 36 | 37 | In case you don’t have a key store to sign your app releases, you can generate one with this command: 38 | 39 | ```bash 40 | $ keytool -genkey -v -keystore inks2d-game.keystore -keyalg RSA -keysize 2048 -validity 10000 -alias release 41 | ``` 42 | 43 | > **Note:** If you change the .keystore filename in this command, please don't forget to also add it with the new filename to .gitignore, so you don't accidentally commit it to your repo. **The keystore should be kept secret**. 44 | 45 | ### Adding the key store and password as GitHub Secrets 46 | 47 | In GitHub we can add secrets for our repository under **Settings > Secrets > Actions**. 48 | 49 | We quickly run into a snag however, because secrets can only be strings, and the key store is actually binary data. To convert your .keystore file in a string, you can use websites like [Base64 Guru](https://base64.guru/converter/encode/file). 50 | 51 | Now we can add it as a secret named `RELEASE_KEYSTORE`. It should look something like this: 52 | 53 | ![android workflow 001](.readme/android_workflow_001.png) 54 | 55 | > **Note:** Don't worry about the base64 representation - will convert it back to a file in our GitHub Action. 56 | 57 | Let’s also add the key store password as `RELEASE_KEYSTORE_PASSWORD`. Now it should look like this: 58 | 59 | ![android workflow 002](.readme/android_workflow_002.png) 60 | 61 | After pushing your change you can navigate to the Actions tab in your repo where you should see your build running. 62 | 63 | > **Note:** If your build does not finish, please try building locally using the workflow steps above, there might be something wrong with your Capacitor configuration. 64 | 65 | Once your build has run successfully, you can download your bundle directly from the build run page! 66 | 67 | ![android workflow 002](.readme/android_workflow_003.png) 68 | 69 | > **Note:** You might want to tweak some things in the `build-android.yml` file, such as the branch to build `on`, preferred Java version, and how much retention you want for your output artifacts using the `retention-days` configuration option. 70 | 71 | From here you can directly upload the signed bundle on the Google Play Console! 72 | 73 | > **Note:** Don't forget that you have to bump the versionCode in `android/app/build.gradle` for every new version you intend to upload to the Play store. 74 | 75 | ### For Windows users 76 | 77 | If you are facing the following error: 78 | 79 | ``` 80 | Run ./gradlew build 81 | /home/runner/work/_temp/46af0a17-aff6-47ea-ab02-304aec292273.sh: line 1: ./gradlew: Permission denied 82 | ``` 83 | 84 | It happens because the `gradlew` file did not get added as executable in your repository. To solve it, please run this command: 85 | 86 | ```bash 87 | git update-index --chmod=+x android/gradlew 88 | ``` 89 | 90 | Then commit and push this change. 91 | 92 | ## Splash Screens and Icons 93 | 94 | This project cames with a easy way for you generate Splash Screens and Icons for your game. 95 | 96 | First, provide icon and splash screen source images using this folder/filename structure: 97 | 98 | ``` 99 | resources/ 100 | ├── icon-only.png 101 | ├── icon-foreground.png 102 | ├── icon-background.png 103 | ├── splash.png 104 | └── splash-dark.png 105 | ``` 106 | 107 | - Icon files should be at least 1024px x 1024px. 108 | - Splash screen files should be at least 2732px x 2732px. 109 | - The format can be jpg or png. 110 | 111 | Then generate using the following command: 112 | 113 | ```bash 114 | $ npm run assets:android 115 | ``` -------------------------------------------------------------------------------- /packages/lib/src/graphics/Sprite.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject } from "DisplayObject"; 2 | 3 | export class Sprite extends DisplayObject { 4 | private _currentFrame: number = 0; 5 | private _source: any; 6 | private _sourceX: number = 0; 7 | private _sourceY: number = 0; 8 | private _sourceWidth: number = 0; 9 | private _sourceHeight: number = 0; 10 | private _tilesetFrame: Record = {}; 11 | 12 | constructor(source: any) { 13 | super(); 14 | 15 | if (source instanceof Image) { 16 | this.createFromImage(source); 17 | } else if (source.frame) { 18 | this.createFromAtlas(source); // JSON 19 | } else if (source.image && !source.data) { 20 | this.createFromTileset(source); 21 | } else if (source.image && source.data) { 22 | this.createFromTilesetFrames(source); 23 | } else if (source instanceof Array) { 24 | if (source[0] && source[0].source) { 25 | this.createFromAtlasFrames(source); 26 | } else if (source[0] instanceof Image) { 27 | this.createFromImages(source); 28 | } else { 29 | throw new Error( 30 | `The Image sources in ${JSON.stringify(source)} are not recognized`, 31 | ); 32 | } 33 | } else { 34 | throw new Error( 35 | `The image source ${JSON.stringify(source)} is not recognized`, 36 | ); 37 | } 38 | } 39 | 40 | get currentFrame(): number { 41 | return this._currentFrame; 42 | } 43 | 44 | private createFromImage(source: any): void { 45 | if (!(source instanceof Image)) { 46 | throw new Error(`source is not an image object`); 47 | } else { 48 | this._source = source; 49 | this._sourceX = 0; 50 | this._sourceY = 0; 51 | this.width = source.width; 52 | this.height = source.height; 53 | this._sourceWidth = source.width; 54 | this._sourceHeight = source.height; 55 | } 56 | } 57 | 58 | private createFromAtlas(source: any): void { 59 | this._tilesetFrame = source; 60 | this._source = this._tilesetFrame.source; 61 | this._sourceX = this._tilesetFrame.frame.x; 62 | this._sourceY = this._tilesetFrame.frame.y; 63 | this.width = this._tilesetFrame.frame.w; 64 | this.height = this._tilesetFrame.frame.h; 65 | this._sourceWidth = this._tilesetFrame.frame.w; 66 | this._sourceHeight = this._tilesetFrame.frame.h; 67 | } 68 | 69 | private createFromTileset(source: any): void { 70 | if (!(source.image instanceof Image)) { 71 | throw new Error(`source.image is not an image object`); 72 | } else { 73 | this._source = source.image; 74 | this._sourceX = source.x; 75 | this._sourceY = source.y; 76 | this.width = source.width; 77 | this.height = source.height; 78 | this._sourceWidth = source.width; 79 | this._sourceHeight = source.height; 80 | } 81 | } 82 | 83 | private createFromTilesetFrames(source: any): void { 84 | if (!(source.image instanceof Image)) { 85 | throw new Error(`source.image is not an image object`); 86 | } else { 87 | this._source = source.image; 88 | this.frames = source.data; 89 | 90 | this._sourceX = this.frames[0][0]; 91 | this._sourceY = this.frames[0][1]; 92 | this.width = source.width; 93 | this.height = source.height; 94 | this._sourceWidth = source.width; 95 | this._sourceHeight = source.height; 96 | } 97 | } 98 | 99 | private createFromAtlasFrames(source: any): void { 100 | this.frames = source; 101 | this._source = source[0].source; 102 | this._sourceX = source[0].frame.x; 103 | this._sourceY = source[0].frame.y; 104 | this.width = source[0].frame.w; 105 | this.height = source[0].frame.h; 106 | this._sourceWidth = source[0].frame.w; 107 | this._sourceHeight = source[0].frame.h; 108 | } 109 | 110 | private createFromImages(source: any): void { 111 | this.frames = source; 112 | this._source = source[0]; 113 | this._sourceX = 0; 114 | this._sourceY = 0; 115 | this.width = source[0].width; 116 | this.height = source[0].height; 117 | this._sourceWidth = source[0].width; 118 | this._sourceHeight = source[0].height; 119 | } 120 | 121 | gotoAndStop(frameNumber: number): void { 122 | if (this.frames.length === 0) return; 123 | 124 | if (this.frames.length > 0 && frameNumber < this.frames.length) { 125 | if (this.frames[0] instanceof Array) { 126 | this._sourceX = this.frames[frameNumber][0]; 127 | this._sourceY = this.frames[frameNumber][1]; 128 | } else if (this.frames[frameNumber].frame) { 129 | this._sourceX = this.frames[frameNumber].frame.x; 130 | this._sourceY = this.frames[frameNumber].frame.y; 131 | 132 | this._sourceWidth = this.frames[frameNumber].frame.w; 133 | this._sourceHeight = this.frames[frameNumber].frame.h; 134 | 135 | this.width = this.frames[frameNumber].frame.w; 136 | this.height = this.frames[frameNumber].frame.h; 137 | } else { 138 | this._source = this.frames[frameNumber]; 139 | 140 | this._sourceX = 0; 141 | this._sourceY = 0; 142 | 143 | this.width = this._source.width; 144 | this.height = this._source.height; 145 | 146 | this._sourceWidth = this._source.width; 147 | this._sourceHeight = this._source.height; 148 | } 149 | 150 | this._currentFrame = frameNumber; 151 | return; 152 | } 153 | 154 | throw new Error(`Frame number ${frameNumber} does not exist`); 155 | } 156 | 157 | render(ctx: CanvasRenderingContext2D): void { 158 | ctx.drawImage( 159 | this._source, 160 | this._sourceX, 161 | this._sourceY, 162 | this._sourceWidth, 163 | this._sourceHeight, 164 | -this.width * this.pivot.x, 165 | -this.height * this.pivot.y, 166 | this.width, 167 | this.height, 168 | ); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /packages/lib/src/math/Point.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Main game engine class. Manages the game loop. 4 | * 5 | */ 6 | export class Point { 7 | private _x: number; 8 | private _y: number; 9 | 10 | /** 11 | * Constructor. 12 | * 13 | * @param x 14 | * @param y 15 | */ 16 | constructor(x: number = 0, y: number = 0) { 17 | this._x = x; 18 | this._y = y; 19 | } 20 | 21 | /** 22 | * Generates a copy of this vector. 23 | * 24 | * @returns Point A copy of this vector 25 | */ 26 | public clone(): Point { 27 | return new Point(this._x, this._y); 28 | } 29 | 30 | /** 31 | * Sets this vector's x and y values (and thus length) to zero. 32 | * 33 | * @returns Point A reference to this vector. 34 | */ 35 | public zero(): Point { 36 | this._x = 0; 37 | this._y = 0; 38 | 39 | return this; 40 | } 41 | 42 | /** 43 | * Whether or not this vector is equal to zero, 44 | * i.e. its x, y and length are zero. 45 | * 46 | * @returns Boolean True if vector is zero, otherwise false. 47 | */ 48 | public isZero(): boolean { 49 | return this._x === 0 && this._y === 0; 50 | } 51 | 52 | /***/ 53 | get x(): number { 54 | return this._x; 55 | } 56 | 57 | /** 58 | * Gets/Sets the x value of this vector. 59 | */ 60 | set x(value: number) { 61 | this._x = value; 62 | } 63 | 64 | /***/ 65 | get y(): number { 66 | return this._y; 67 | } 68 | 69 | /** 70 | * Gets/Sets the y value of this vector. 71 | */ 72 | set y(value: number) { 73 | this._y = value; 74 | } 75 | 76 | /***/ 77 | get length(): number { 78 | return Math.sqrt(this.lengthSquared); 79 | } 80 | 81 | /** 82 | * Gets/Sets the length or magnitude of this vector. 83 | * Changing the length will change the x and y 84 | * but not the angle of this vector. 85 | */ 86 | set length(value: number) { 87 | this._x = Math.cos(this.angle) * value; 88 | this._y = Math.sin(this.angle) * value; 89 | } 90 | 91 | /** 92 | * Gets the length of this vector, squared. 93 | */ 94 | get lengthSquared(): number { 95 | return this._x * this._x + this._y * this._y; 96 | } 97 | 98 | /***/ 99 | get angle(): number { 100 | return Math.atan2(this._y, this._x); 101 | } 102 | 103 | /** 104 | * Gets/Sets the angle of this vector. 105 | * Changing the angle also changes the x and y but 106 | * retains the same length. 107 | */ 108 | set angle(value: number) { 109 | this._x = Math.cos(value) * this.length; 110 | this._y = Math.sin(value) * this.length; 111 | } 112 | 113 | public set(x: number = 0, y: number = 0): void { 114 | this.x = x; 115 | this.y = y; 116 | } 117 | 118 | /** 119 | * Ensures the length of the vector is no longer than 120 | * the given value. 121 | * 122 | * @param max The maximum value this vector should be. 123 | * @returns 124 | */ 125 | public truncate(max: number): Point { 126 | this.length = Math.min(max, this.length); 127 | return this; 128 | } 129 | 130 | /** 131 | * Whether or not this vector is normalized, i.e. its 132 | * length is equal to one. 133 | * 134 | * @returns Boolean True if length is one, otherwise false. 135 | */ 136 | public isNormalized(): boolean { 137 | return this.length === 1; 138 | } 139 | 140 | /** 141 | * Calculates the distance from this vector to another 142 | * given vector. 143 | * 144 | * @param v2 A Point instance 145 | * @returns Number The distance from this vector to the 146 | * vector passed as a parameter. 147 | */ 148 | public dist(v2: Point): number { 149 | return Math.sqrt(this.distSquared(v2)); 150 | } 151 | 152 | /** 153 | * Calculates the distance squared from this vector to another 154 | * given vector. 155 | * 156 | * @param v2 A Point instance 157 | * @returns Number The distance squared from this vector 158 | * to the vector passed as a parameter. 159 | */ 160 | public distSquared(v2: Point): number { 161 | const dx = v2.x - this.x; 162 | const dy = v2.y - this.y; 163 | 164 | return dx * dx + dy * dy; 165 | } 166 | 167 | /** 168 | * Adds a vector to this vector, creating a new 169 | * Point instance to hold the result. 170 | * 171 | * @param v2 A Point instance 172 | * @returns Point A new vector containing the results of 173 | * the addition. 174 | */ 175 | public add(v2: Point): Point { 176 | return new Point(this._x + v2.x, this._y + v2.y); 177 | } 178 | 179 | /** 180 | * Subtracts a vector from this vector, creating a new 181 | * Point instance to hold the result. 182 | * 183 | * @param v2 A Point instance 184 | * @returns Point A new vector containing the results of 185 | * the subtraction. 186 | */ 187 | public subtract(v2: Point): Point { 188 | return new Point(this._x - v2.x, this._y - v2.y); 189 | } 190 | 191 | /** 192 | * Multiplies this vector by a value, creating a new 193 | * Point instance to hold the result. 194 | * 195 | * @param value A number 196 | * @returns Point A new vector containing the results of 197 | * the multiplication. 198 | */ 199 | public multiply(value: number): Point { 200 | return new Point(this._x * value, this._y * value); 201 | } 202 | 203 | /** 204 | * Divides this vector by a value, creating a new Point 205 | * instance to hold the result. 206 | * 207 | * @param value A number 208 | * @returns Point A new vector containing the results of 209 | * the division. 210 | */ 211 | public divide(value: number): Point { 212 | return new Point(this._x / value, this._y / value); 213 | } 214 | 215 | /** 216 | * Indicates whether this vector and another Point instance 217 | * are equal in value. 218 | * 219 | * @param v2 A Point instance 220 | * @returns Boolean True if the other vector is equal to 221 | * this one, false if not. 222 | */ 223 | public equals(v2: Point): boolean { 224 | return this._x === v2.x && this._y === v2.y; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /packages/examples/games/memory-game/src/scenes/GameScreen.ts: -------------------------------------------------------------------------------- 1 | import { Group } from "inks2d/group"; 2 | import { Spritemap } from "inks2d/graphics"; 3 | import { Engine } from "inks2d"; 4 | import { makeInteractive, wait } from "inks2d/utils"; 5 | import { Tween } from "inks2d/effects/tweens"; 6 | import { shuffleArray } from "inks2d/math"; 7 | import { Text } from "inks2d/text"; 8 | import { TransitionScreen } from "inks2d/extras"; 9 | import { _gameConfig } from "../gameConfig"; 10 | 11 | export class GameScreen extends Group { 12 | private _g: Engine; 13 | private _cardsData: Record[] = []; 14 | private _cardsFlipped: Spritemap[] = []; 15 | private _canPlay: boolean = true; 16 | private _moves: number = 0; 17 | private _pairsRemaing: number = 10; 18 | private _txtScore: Text; 19 | 20 | constructor(g: Engine) { 21 | super(); 22 | this._g = g; 23 | 24 | for (let i = 1; i < 11; i++) { 25 | this._cardsData.push({ 26 | id: i, 27 | frame: i, 28 | }); 29 | 30 | this._cardsData.push({ 31 | id: i, 32 | frame: i + 10, 33 | }); 34 | } 35 | 36 | this._cardsData = shuffleArray(this._cardsData); 37 | } 38 | 39 | added(): void { 40 | this.layer = 0; 41 | this.createCards(); 42 | 43 | const best = _gameConfig.game.best === -1 ? "???" : _gameConfig.game.best; 44 | this._txtScore = new Text( 45 | `moves: ${this._moves}\nbest: ${best}`, 46 | 32, 47 | "white" 48 | ); 49 | this._txtScore.family = "Gemunu Libre"; 50 | this._txtScore.leading = 10; 51 | this._txtScore.align.h = "left"; 52 | this._txtScore.align.v = "top"; 53 | this._txtScore.position.x = 25; 54 | this._txtScore.position.y = this._g.stage.height - 65; 55 | this._txtScore.layer = 3; 56 | this.addChild(this._txtScore); 57 | } 58 | 59 | private createCards(): void { 60 | for (let i = 0; i < _gameConfig.game.numberOfCards; i++) { 61 | const x: number = 65; 62 | const y: number = 162; 63 | const gap: number = 10; 64 | const cardData = this._cardsData.pop(); 65 | 66 | const card = new Spritemap( 67 | this._g.loader.store["assets/images/cards.png"], 68 | 85, 69 | 115 70 | ); 71 | card.frame = 0; 72 | card.position.x = 73 | x + Math.floor(i % _gameConfig.game.cardsPerLine) * (card.width + gap); 74 | card.position.y = 75 | y + Math.floor(i / _gameConfig.game.cardsPerLine) * (card.height + gap); 76 | card.customProperties.id = cardData?.id; 77 | card.customProperties.frame = cardData?.frame; 78 | card.customProperties.isFlipping = false; 79 | card.customProperties.isFlipped = false; 80 | 81 | makeInteractive(card); 82 | card.customProperties.buttonProps.tap = () => { 83 | if ( 84 | !this._canPlay || 85 | card.customProperties.isFlipped || 86 | card.customProperties.isFlipping 87 | ) 88 | return; 89 | 90 | card.customProperties.isFlipping = true; 91 | this._cardsFlipped.push(card); 92 | this.flipCard(card); 93 | 94 | if (this._cardsFlipped.length >= 2) { 95 | this._canPlay = false; 96 | const isMatch = this.checkMatch(); 97 | 98 | if (!isMatch) { 99 | wait(1250).then(() => { 100 | this.flipCard(this._cardsFlipped[0]); 101 | this.flipCard(this._cardsFlipped[1]); 102 | 103 | card.customProperties.isFlipped = false; 104 | this._moves++; 105 | this._cardsFlipped = []; 106 | this._canPlay = true; 107 | this.updateGui(); 108 | }); 109 | 110 | return; 111 | } 112 | 113 | this._moves++; 114 | this._pairsRemaing--; 115 | this._cardsFlipped = []; 116 | this._canPlay = true; 117 | this.updateGui(); 118 | 119 | if (this._pairsRemaing === 0) { 120 | this._canPlay = false; 121 | 122 | if (_gameConfig.game.best === -1) { 123 | _gameConfig.game.best = this._moves; 124 | } else { 125 | _gameConfig.game.best = 126 | _gameConfig.game.best < this._moves 127 | ? _gameConfig.game.best 128 | : this._moves; 129 | } 130 | 131 | wait(1500).then(() => { 132 | const transition = new TransitionScreen(1000, this._g, "#475c8d"); 133 | transition.layer = 1; 134 | this._g.stage.addChild(transition); 135 | 136 | transition.onBetween = () => { 137 | this._g.stage.removeChild(this); 138 | 139 | const gamescreen = new GameScreen(this._g); 140 | this._g.stage.addChild(gamescreen); 141 | }; 142 | transition.start(); 143 | }); 144 | } 145 | } 146 | }; 147 | this.addChild(card); 148 | } 149 | } 150 | 151 | private checkMatch(): boolean { 152 | const card1 = this._cardsFlipped[0].customProperties.id; 153 | const card2 = this._cardsFlipped[1].customProperties.id; 154 | 155 | return card1 === card2; 156 | } 157 | 158 | private flipCard(card: Spritemap): void { 159 | const tween1 = new Tween(); 160 | tween1.onUpdate = ({ scaleX, scaleY }) => { 161 | card.scale.x = scaleX; 162 | card.scale.y = scaleY; 163 | }; 164 | tween1.onComplete = () => { 165 | if (card.frame !== 0) { 166 | card.frame = 0; 167 | return; 168 | } 169 | 170 | card.frame = card.customProperties.frame; 171 | }; 172 | tween1 173 | .from({ scaleX: 1, scaleY: 1 }) 174 | .to({ scaleX: 0, scaleY: 1.2 }) 175 | .duration(250); 176 | 177 | const tween2 = new Tween(); 178 | tween2.onUpdate = ({ scaleX, scaleY }) => { 179 | card.scale.x = scaleX; 180 | card.scale.y = scaleY; 181 | }; 182 | tween2.onComplete = () => { 183 | card.customProperties.isFlipping = false; 184 | card.customProperties.isFlipped = !!card.frame; 185 | }; 186 | tween2 187 | .from({ scaleX: 0, scaleY: 1.2 }) 188 | .to({ scaleX: 1, scaleY: 1 }) 189 | .duration(250); 190 | 191 | tween1.chain(tween2); 192 | tween1.start(); 193 | } 194 | 195 | private updateGui(): void { 196 | const best = _gameConfig.game.best === -1 ? "???" : _gameConfig.game.best; 197 | this._txtScore.content = `moves: ${this._moves}\nbest: ${best}`; 198 | } 199 | } 200 | --------------------------------------------------------------------------------