├── .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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------