diff --git a/.eslintrc.json b/.eslintrc.json index f9cf921..4c2ec9c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,6 +14,8 @@ } }, "rules": { + "no-empty-function": 0, + "max-len": 0, "no-new": 0, "no-restricted-imports": ["error", { "paths": ["pixi.js"] }], "@typescript-eslint/no-unused-expressions": [ @@ -21,7 +23,6 @@ { "allowShortCircuit": true, "allowTernary": true } ], "no-mixed-operators": "off", - "@typescript-eslint/no-parameter-properties": 1, "jsdoc/multiline-blocks": [ 1, { "noMultilineBlocks": true, "minimumLengthForMultiline": 115 } @@ -35,7 +36,6 @@ "jsdoc/check-values": 1, "jsdoc/empty-tags": 1, "jsdoc/implements-on-classes": 1, - "jsdoc/newline-after-description": [1, "never"], "jsdoc/no-multi-asterisks": [1, { "allowWhitespace": true }], "jsdoc/require-param": 1, "jsdoc/require-param-description": 0, diff --git a/src/Game.ts b/src/Game.ts index 7c1e1eb..b89842d 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -1,3 +1,4 @@ +/* eslint-disable new-cap */ import { Assets } from '@pixi/assets'; import { app } from './main'; import { AppScreen } from './components/basic/AppScreen'; @@ -10,20 +11,22 @@ export type SceneData = { window?: Windows, type?: GameTypes, restart?: boolean, -} +}; /** Interface for app screens constructors */ -interface AppScreenConstructor { +interface AppScreenConstructor +{ new (data?: any): AppScreen; assetBundles?: string[]; } -/** - * Class for controlling visibility of all scenes, +/** + * Class for controlling visibility of all scenes, * preload assets for each of them and create/update/resize them. - * + * * It is also a navigation controller of the app. - **/ -class Game { // We DO NOT export this class, as we want to have only one instance of it, it is exported on the bottom of this file + */ +class Game +{ // We DO NOT export this class, as we want to have only one instance of it, it is exported on the bottom of this file private currentScreen?: AppScreen; // Current screen being displayed private currentScreenResize?: () => void; // Resize function to avoid problems with scope private loadScreen?: AppScreen; // Default load screen @@ -33,77 +36,106 @@ class Game { // We DO NOT export this class, as we want to have only one instanc public bg!: Background; // background layout - /** Set the default load screen */ - public setLoadScreen(screen: AppScreenConstructor) { + /** + * Set the default load screen + * @param screen + */ + public setLoadScreen(screen: AppScreenConstructor) + { this.loadScreen = new screen(); // Create a new instance of the load screen given in `screen` parameter } /** Create game background */ - addBG () { + addBG() + { if (this.bg) { return; } - + this.bg = new Background(); // Create a new instance of the background layout this.bg.resize(this._w, this._h); // Resize background as it is a layout and it needs to know its size in order it's core functionality to work app.stage.addChild(this.bg); // Add background to the stage } - /** Add screen to the stage, link update & resize functions */ - private async addScreen(screen: AppScreen) { + /** + * Add screen to the stage, link update & resize functions + * @param screen + */ + private async addScreen(screen: AppScreen) + { // Add screen to stage app.stage.addChild(screen); // Add screen to the stage // Add screen's resize handler, if available - if (screen.resize) { + if (screen.resize) + { this.currentScreenResize = () => screen.resize; // Encapsulate resize in another function that can be removed later, to avoid scope issues with addEventListener screen.resize(this._w, this._h); // Trigger a first resize } - if (screen.onUpdate) { // Add update function if it exists + if (screen.onUpdate) + { // Add update function if it exists app.ticker.add(screen.onUpdate, screen); // Add update function to the ticker } - if (screen.show) { // Show the new screen if it has a show method + if (screen.show) + { // Show the new screen if it has a show method await screen.show(); // Wait for the screen to be shown } } - /** Remove screen from the stage, unlink update & resize functions */ - private async removeScreen(screen: AppScreen) { - if (screen.hide) { // Hide screen if method is available + /** + * Remove screen from the stage, unlink update & resize functions + * @param screen + */ + private async removeScreen(screen: AppScreen) + { + if (screen.hide) + { // Hide screen if method is available await screen.hide(); // Wait for the screen to be hidden } - if (this.currentScreenResize) { // Unlink resize handler if exists + if (this.currentScreenResize) + { // Unlink resize handler if exists window.removeEventListener('resize', this.currentScreenResize); // Remove resize listener } - if (screen.onUpdate) { // Unlink update function if method is available + if (screen.onUpdate) + { // Unlink update function if method is available app.ticker.remove(screen.onUpdate, screen); // Remove update function from the ticker } - if (screen.parent) { // Remove screen from its parent (usually app.stage, if not changed) + if (screen.parent) + { // Remove screen from its parent (usually app.stage, if not changed) screen.parent.removeChild(screen); // Remove screen from its parent } } - /** Hide current screen (if there is one) and present a new screen. */ - public async showScreen(screen: AppScreenConstructor, data?: SceneData) { - if (this.currentScreen) { // If there is a screen already created, hide and destroy it + /** + * Hide current screen (if there is one) and present a new screen. + * @param screen + * @param data + */ + public async showScreen(screen: AppScreenConstructor, data?: SceneData) + { + if (this.currentScreen) + { // If there is a screen already created, hide and destroy it await this.removeScreen(this.currentScreen); // Remove current screen this.currentScreen.destroy(); // Destroy current screen } // Load assets for the new screen, if available - if (screen.assetBundles && !areBundlesLoaded(screen.assetBundles)) { + if (screen.assetBundles && !areBundlesLoaded(screen.assetBundles)) + { // If assets are not loaded yet, show loading screen, if there is one - if (this.loadScreen) { + if (this.loadScreen) + { this.addScreen(this.loadScreen); // Add loading screen to the stage } await Assets.loadBundle(screen.assetBundles); // Load all assets required by this new screen - if (this.loadScreen) { // Hide loading screen, if exists + if (this.loadScreen) + { // Hide loading screen, if exists this.removeScreen(this.loadScreen); // Remove loading screen from the stage } } diff --git a/src/components/CheckBox.ts b/src/components/CheckBox.ts index d538c7d..ec8b4cd 100644 --- a/src/components/CheckBox.ts +++ b/src/components/CheckBox.ts @@ -3,10 +3,11 @@ import { Sprite } from '@pixi/sprite'; import { TextStyle } from '@pixi/text'; import { colors } from '../config/colors'; -/** Extends a BasicCheckBox class and apply config to it, - * so that instance can be used without need to config it. */ -export class CheckBox extends BasicCheckBox { - constructor(options: CheckBoxOptions) { +/** Extends a BasicCheckBox class and apply config to it, so that instance can be used without need to config it. */ +export class CheckBox extends BasicCheckBox +{ + constructor(options: CheckBoxOptions) + { super({ style: { // style is an object with checkbox assets and text styles checked: createCheckBox( // sprite(Container), that shows when checkbox is checked @@ -28,46 +29,55 @@ export class CheckBox extends BasicCheckBox { text: options.text, // text that will be displayed on the checkbox }); - if (options.onChange) { // if callback function is provided + if (options.onChange) + { // if callback function is provided this.onCheck.connect(options.onChange); // connect checkbox change event to the provided callback } } } -/** Creates a sprite(Container) with checkbox assets. - * This function is abstracted from a class as it is used inside a `super` call, +/** + * Creates a sprite(Container) with checkbox assets. + * This function is abstracted from a class as it is used inside a `super` call, * and `this.createCheckBox` can not be called before `super` call. -*/ + * @param checkboxBG + * @param checkboxFG + */ function createCheckBox( checkboxBG: string, // texture key for the background of the checkbox checkboxFG?: string // texture key for the foreground of the checkbox - ): Sprite { +): Sprite +{ const bg = Sprite.from(checkboxBG); // create a sprite from the provided texture key - - if (checkboxBG === 'RoundSubstrate') { // if the background is 'RoundSubstrate' texture + + if (checkboxBG === 'RoundSubstrate') + { // if the background is 'RoundSubstrate' texture bg.scale.set(0.4); // scale it down } - if (checkboxFG) { // if the foreground is provided + if (checkboxFG) + { // if the foreground is provided const checkBox = Sprite.from(checkboxFG); // create a sprite from the provided texture key - + checkBox.anchor.set(0.5); // set anchor to the center checkBox.x = bg.width / 2; // set x position to the center of the background checkBox.y = bg.height / 2; // set y position to the center of the background - if (checkboxBG === 'RoundSubstrate' && checkboxFG === 'Radio') { // if the background is 'RoundSubstrate' texture and the foreground is 'Radio' texture + if (checkboxBG === 'RoundSubstrate' && checkboxFG === 'Radio') + { // if the background is 'RoundSubstrate' texture and the foreground is 'Radio' texture checkBox.scale.set(1.85); // scale it up checkBox.x += 37; // move it to the right checkBox.y += 38; // move it down } - if (checkboxBG === 'RoundSubstrate' && checkboxFG === 'CheckBox') { // if the background is 'RoundSubstrate' texture and the foreground is 'CheckBox' texture + if (checkboxBG === 'RoundSubstrate' && checkboxFG === 'CheckBox') + { // if the background is 'RoundSubstrate' texture and the foreground is 'CheckBox' texture checkBox.scale.set(2); // scale it up checkBox.x += 50; // move it to the right checkBox.y += 30; // move it down } - + bg.addChild(checkBox); // add the foreground to the background to get a single sprite(Container) } @@ -75,7 +85,7 @@ function createCheckBox( } /** Type for the component settings description. */ -export type CheckBoxOptions = { +export type CheckBoxOptions = { checked: boolean, // initial checkbox state checkboxBG: string, // texture key for the background of the checkbox checkboxFG: string, // texture key for the foreground of the checkbox @@ -86,4 +96,4 @@ export type CheckBoxOptions = { y: number, // y offset } onChange?: (checked: boolean) => void, // callback function that will be called when checkbox state is changed -} \ No newline at end of file +}; diff --git a/src/components/CloseButton.ts b/src/components/CloseButton.ts index da07293..3c2311f 100644 --- a/src/components/CloseButton.ts +++ b/src/components/CloseButton.ts @@ -1,12 +1,14 @@ -import { FancyButton } from "@pixi/ui"; -import { Counter } from "./basic/Counter"; +/* eslint-disable accessor-pairs */ +import { FancyButton } from '@pixi/ui'; +import { Counter } from './basic/Counter'; -/** FancyButton config to be applied on FancyButton, - * so that component can be used without setting all the configs. */ -export class CloseButton extends FancyButton { +/** FancyButton config to be applied on FancyButton, so that component can be used without setting all the configs. */ +export class CloseButton extends FancyButton +{ counter!: Counter; // counter component that will be added to the button. It will show the number of notifications - - constructor(onclick: () => void) { + + constructor(onclick: () => void) + { super({ defaultView: `SmallButton-pressed`, // this is a key to the texture atlas for default button state view hoverView: `SmallButton-hover`, // this is a key to the texture atlas for hover button state view @@ -34,12 +36,13 @@ export class CloseButton extends FancyButton { }); this.onPress.connect(onclick); // connect button press event to the provided callback - + this.anchor.set(0.5); // set button anchor to the center, this is needed for the button to scale correctly when animated this.scale.set(0.8); // scale down the button } - set notifications(amount: number) { // set the number of notifications + set notifications(amount: number) + { // set the number of notifications this.counter.number.text = String(amount); // set the text of the counter } -}; \ No newline at end of file +} diff --git a/src/components/DottedProgress.ts b/src/components/DottedProgress.ts index 1c237d0..a8531bc 100644 --- a/src/components/DottedProgress.ts +++ b/src/components/DottedProgress.ts @@ -1,23 +1,27 @@ -import { Content, Layout } from "@pixi/layout"; -import { SmallIconButton } from "./SmallIconButton"; +import { Content, Layout } from '@pixi/layout'; +import { SmallIconButton } from './SmallIconButton'; import { Sprite } from '@pixi/sprite'; -import { Button } from "@pixi/ui"; +import { Button } from '@pixi/ui'; /** Layout based component for the volume control progress bar like controller. */ -export class DottedProgress extends Layout { +export class DottedProgress extends Layout +{ public _val = 0; // current value constructor(private options: { steps: number, // number of steps (blocks) of the progress bar value: number, // current value of the progress bar onChange: (value: number) => void, // callback that will be called when progress bar value changes - }) { + }) + { const dots: Content[] = []; // array of dots (blocks) of the progress bar, elements of this array are layout configs - for (let i = 0; i < options.steps; i++) { // create all the dots (blocks) of the progress bar + for (let i = 0; i < options.steps; i++) + { // create all the dots (blocks) of the progress bar const button = new Button(Sprite.from('ProgressBlock')); // create a button that will be used as a dot (block) and a controller for the progress bar - button.onDown.connect(() => { // connect button down event to the callback that will change the value of the progress bar + button.onDown.connect(() => + { // connect button down event to the callback that will change the value of the progress bar this.val = i + 1; // set the value of the progress bar to the index of the dot (block) + 1 }); @@ -46,22 +50,28 @@ export class DottedProgress extends Layout { const minusButton = new SmallIconButton( // create a button that will decrease the value of the progress bar 'MinusIcon', // icon texture name of the button - () => { // callback for the button press - if (this._val > 0) { // check if the value of the progress bar is greater than 0 + () => + { // callback for the button press + if (this._val > 0) + { // check if the value of the progress bar is greater than 0 this.val--; // decrease the value of the progress bar } } ); + minusButton.scale.set(0.65); // scale down the button const plusButton = new SmallIconButton( // create a button that will increase the value of the progress bar 'PlusIcon', // icon texture name of the button - () => { // callback for the button press - if (this.val < options.steps) { // check if the value of the progress bar is less than the number of steps (blocks) of the progress bar + () => + { // callback for the button press + if (this.val < options.steps) + { // check if the value of the progress bar is less than the number of steps (blocks) of the progress bar this.val++; // increase the value of the progress bar } } ); + plusButton.scale.set(0.65); // scale down the button super({ // create the Layout component @@ -103,24 +113,29 @@ export class DottedProgress extends Layout { } // set the value of the progress bar - public set val(val: number) { + public set val(val: number) + { this._val = val; // set the value of the progress bar this.options.onChange(val); // call the callback that will be called when progress bar value changes this.updateState(); // update the state of the progress bar } // get the value of the progress bar - public get val(): number { + public get val(): number + { return this._val; // return the value of the progress bar } // update the state of the progress bar - private updateState() { + private updateState() + { const dots = this.content.getByID(`dots`)?.children as Layout[]; // get the dots (blocks) of the progress bar - dots.forEach((dot, i) => { // iterate through all the dots (blocks) of the progress bar + dots.forEach((dot, i) => + { // iterate through all the dots (blocks) of the progress bar const fill = dot.content.getByID(`fill`) as Layout; // get the fill layout of the dot (block) by it's ID + fill.visible = i < this._val; // set the visibility of the fill layout of the dot (block) to true if the index of the dot (block) is less than the value of the progress bar }); } -} \ No newline at end of file +} diff --git a/src/components/FancyText.ts b/src/components/FancyText.ts index 4f8708d..c7fba02 100644 --- a/src/components/FancyText.ts +++ b/src/components/FancyText.ts @@ -10,24 +10,29 @@ export type FancyTextOptions = { style?: Partial; }; -export class FancyTextTexture { +export class FancyTextTexture +{ private list: List; texture: RenderTexture; - constructor({ images, text, style }: FancyTextOptions) { + constructor({ images, text, style }: FancyTextOptions) + { const textData: { image?: string; text: string; }[] = []; - if (images) { + if (images) + { let pointer = 0; - images?.forEach((image) => { + images?.forEach((image) => + { const index = text.indexOf(image); - if (index !== -1) { + if (index !== -1) + { textData.push({ image, text: text.slice(pointer, index), @@ -39,7 +44,8 @@ export class FancyTextTexture { pointer = index; }); - if (pointer < text.length) { + if (pointer < text.length) + { textData.push({ text: text.slice(pointer) }); } } @@ -48,12 +54,16 @@ export class FancyTextTexture { type: 'horizontal', }); - textData.forEach((data) => { + textData.forEach((data) => + { const text = new BitmapText(data.text, style); + this.list.addChild(text); - if (data.image) { + if (data.image) + { const sprite = Sprite.from(data.image); + sprite.scale.set(text.height / sprite.height); this.list.addChild(sprite); } diff --git a/src/components/Hint.ts b/src/components/Hint.ts index 5262564..a52786b 100644 --- a/src/components/Hint.ts +++ b/src/components/Hint.ts @@ -1,20 +1,22 @@ -import { Sprite } from "@pixi/sprite"; -import { Layout } from "@pixi/layout"; -import gsap, {Back} from 'gsap'; +import { Sprite } from '@pixi/sprite'; +import { Layout } from '@pixi/layout'; +import gsap, { Back } from 'gsap'; import { colors } from '../config/colors'; /** Layout based component to show prompts or dialogs. */ -export class Hint extends Layout { +export class Hint extends Layout +{ private startY!: number; // initial y position of the hint, used to reset it before and after tweening constructor( text: string, // text to show in the hint type: 'up' | 'down' = 'down' // type of the hint, up or down - ) { - const hint = - type === 'down' - ? Sprite.from('HintDown') // sprite for the down hint - : Sprite.from(`Hint`); // sprite for the up hint + ) + { + const hint + = type === 'down' + ? Sprite.from('HintDown') // sprite for the down hint + : Sprite.from(`Hint`); // sprite for the up hint super({ // Layout constructor accepts an object with all the config content: { // content is an object with all the layers of the hint @@ -31,49 +33,60 @@ export class Hint extends Layout { } } }, - styles:{ // styles is an object with all the styles that will be applied to the layout + styles: { // styles is an object with all the styles that will be applied to the layout position: 'left', // position Layout to the left of parent background: hint, // background is a sprite marginLeft: 25, // set margin left to 25px marginTop: 120, // set margin top to 120px } }); - + this.startY = hint.height - 20; // set initial y position of the hint } - /** Method is automatically called when Layout is shown. (See Game.ts) */ - public async show(force = false) {// force is a boolean that indicates if the hint should be shown without tweening + /** + * Method is automatically called when Layout is shown. (See Game.ts) + * @param force + */ + public async show(force = false) + { // force is a boolean that indicates if the hint should be shown without tweening if (this.alpha === 1) return; // if the hint is already visible, return gsap.killTweensOf(this); // kill all tweens of the hint - - if (force) { // if force is true, show the hint without tweening + + if (force) + { // if force is true, show the hint without tweening this.alpha = 1; // set alpha to 1 + return; } - + // set initial animation state values this.alpha = 0; // set alpha to 0 this.y = this.startY + 20; // set y position to initial y position + 20px - + await gsap.to(this, { // tween the hint alpha: 1, // set alpha to 1 y: '-=20', // set y position to initial y position duration: 0.2, // tween duration ease: Back.easeOut.config(1.7), // tween ease }); - } - /** Method is automatically called when Layout is hidden. See Game.ts */ - public async hide(force = false) {// force is a boolean that indicates if the hint should be hidden without tweening + /** + * Method is automatically called when Layout is hidden. See Game.ts + * @param force + */ + public async hide(force = false) + { // force is a boolean that indicates if the hint should be hidden without tweening if (this.alpha === 0) return; // if the hint is already hidden, return gsap.killTweensOf(this); // kill all tweens of the hint - - if (force) { // if force is true, hide the hint without tweening + + if (force) + { // if force is true, hide the hint without tweening this.alpha = 0; // set alpha to 0 + return; } @@ -85,5 +98,5 @@ export class Hint extends Layout { }); this.y = this.startY - 20; // set y position to initial y position - 20px - } -} \ No newline at end of file + } +} diff --git a/src/components/IMatter.ts b/src/components/IMatter.ts index 823c6dc..3b318b9 100644 --- a/src/components/IMatter.ts +++ b/src/components/IMatter.ts @@ -1,6 +1,7 @@ import { Body } from 'matter-js'; -export interface IMatter { +export interface IMatter +{ body: Body; beforeUnload(): void; update(): void; diff --git a/src/components/LoadingSpinner.ts b/src/components/LoadingSpinner.ts index 7fd8bac..5b475b8 100644 --- a/src/components/LoadingSpinner.ts +++ b/src/components/LoadingSpinner.ts @@ -1,13 +1,15 @@ -import { Sprite } from "@pixi/sprite" -import { colors } from "../config/colors"; -import i18n from "../config/i18n" +import { Sprite } from '@pixi/sprite'; +import { colors } from '../config/colors'; +import i18n from '../config/i18n'; import { Layout } from '@pixi/layout'; /** Layout based component for the loading spinner. */ -export class LoadingSpinner extends Layout { - constructor() { - +export class LoadingSpinner extends Layout +{ + constructor() + { const spinnerSprite = Sprite.from('spinner'); // create a sprite + spinnerSprite.anchor.set(0.5); // set anchor to the center of the sprite super({ // Layout constructor accepts an object with all the config @@ -42,4 +44,4 @@ export class LoadingSpinner extends Layout { } }); } -} \ No newline at end of file +} diff --git a/src/components/MenuButton.ts b/src/components/MenuButton.ts index 72175a1..451eb5f 100644 --- a/src/components/MenuButton.ts +++ b/src/components/MenuButton.ts @@ -2,16 +2,16 @@ import { Layout } from '@pixi/layout'; import { SmallIconButton } from './SmallIconButton'; import { Hint } from './Hint'; -/** Layout based component for the buttons with hint and counters, - * used in LevelsWindow. */ -export class MenuButton extends Layout { +/** Layout based component for the buttons with hint and counters, used in LevelsWindow. */ +export class MenuButton extends Layout +{ constructor( icon: string, // icon texture name for the button text: string, // text for the button hint onclick: () => void, // callback for the button press - notifications: number = 0 // number of notifications to show on the button - ) { - + notifications = 0 // number of notifications to show on the button + ) + { const button = new SmallIconButton(icon, onclick, notifications); // create the SmallIconButton instance (see SmallIconButton.ts) const buttonHint = new Hint(text); // create the hint component (see Hint.ts) @@ -41,4 +41,4 @@ export class MenuButton extends Layout { } }); } -} \ No newline at end of file +} diff --git a/src/components/PixiLogo.ts b/src/components/PixiLogo.ts index 7dcd54e..a4d87b3 100644 --- a/src/components/PixiLogo.ts +++ b/src/components/PixiLogo.ts @@ -1,10 +1,10 @@ -import { Sprite } from "@pixi/sprite" -import i18n from "../config/i18n" -import { LayoutOptions } from "@pixi/layout"; +import { Sprite } from '@pixi/sprite'; +import i18n from '../config/i18n'; +import { LayoutOptions } from '@pixi/layout'; -/* Layout based component for the pixi logo. +/* Layout based component for the pixi logo. * This is implemented as a function that returns Layout configuration object to show - * that you can use functions to generate Layout configuration objects and the layout instance will be created + * that you can use functions to generate Layout configuration objects and the layout instance will be created * inside the Layout system nad it is not necessary to create it manually. */ @@ -33,4 +33,4 @@ export const PixiLogo = (): LayoutOptions => ({ // function that returns Layout maxHeight: '5%', // set max height to 20% of parent, so it will scale down to fit the screen height height: 84 // set block height to 30px, so we can stick children to the tob & bottom of it } -}); \ No newline at end of file +}); diff --git a/src/components/Slider.ts b/src/components/Slider.ts index 664b333..07d9f1b 100644 --- a/src/components/Slider.ts +++ b/src/components/Slider.ts @@ -1,21 +1,21 @@ import { Slider as BasicSlider } from '@pixi/ui'; import { SmallIconButton } from './SmallIconButton'; -/** Slider config to be applied on Slider component. - * So it can be used without setting all the configs. - */ -export class Slider extends BasicSlider { +/** Slider config to be applied on Slider component. So it can be used without setting all the configs. */ +export class Slider extends BasicSlider +{ constructor(options: { min: number, // min value of the slider max: number, // max value of the slider value?: number, // current value of the slider onChange: (val: number) => void, // callback that will be called when slider value changes - }) { + }) + { const sliderButton = new SmallIconButton( // create a button that will be used as a slider 'SliderIcon', // icon for the button () => {}, // callback for the button press, empty because we don't need it, all the interaction events are handled inside the Slider component ); - + sliderButton.scale.set(0.45); // scale down the button super({ // create the Slider component @@ -28,4 +28,4 @@ export class Slider extends BasicSlider { this.onChange.connect(options.onChange); // connect slider change event to the provided callback } -} \ No newline at end of file +} diff --git a/src/components/SmallButton.ts b/src/components/SmallButton.ts index a87abc9..7092ff2 100644 --- a/src/components/SmallButton.ts +++ b/src/components/SmallButton.ts @@ -1,23 +1,27 @@ -import { Sprite } from "@pixi/sprite"; -import { Text } from "@pixi/text"; -import { colors } from "../config/colors"; -import { FancyButton } from "@pixi/ui"; +import { Sprite } from '@pixi/sprite'; +import { Text } from '@pixi/text'; +import { colors } from '../config/colors'; +import { FancyButton } from '@pixi/ui'; -/** Layout based component for the small button. - * It applies all the configs to the FancyButton component, +/** + * Layout based component for the small button. + * It applies all the configs to the FancyButton component, * so it can be used without setting the configs. */ -export class SmallButton extends FancyButton { - constructor(text: string, onclick: () => void, locked = false) { +export class SmallButton extends FancyButton +{ + constructor(text: string, onclick: () => void, locked = false) + { let icon; // icon is a sprite that will be added to the button - if (locked) { // if button is locked, we gonna add a lock icon to the button + if (locked) + { // if button is locked, we gonna add a lock icon to the button icon = Sprite.from('LockIcon'); // create a sprite from the 'LockIcon' texture atlas } - const textElement = - locked // if button is locked, we gonna hide the text - ? undefined + const textElement + = locked // if button is locked, we gonna hide the text + ? undefined : new Text(text, { // create a text element fill: colors.text, // set text color fontSize: 75, // set text font size @@ -58,7 +62,7 @@ export class SmallButton extends FancyButton { this.scale.set(0.9); // scale down the button this.anchor.set(0.5); // set button anchor to the center, this is needed for the button to scale correctly when animated - + this.onPress.connect(onclick); // connect button press event to the provided callback } -}; \ No newline at end of file +} diff --git a/src/components/SmallIconButton.ts b/src/components/SmallIconButton.ts index f6385ca..c43e7ac 100644 --- a/src/components/SmallIconButton.ts +++ b/src/components/SmallIconButton.ts @@ -1,15 +1,17 @@ -import { FancyButton } from "@pixi/ui"; -import { Counter } from "./basic/Counter"; +import { FancyButton } from '@pixi/ui'; +import { Counter } from './basic/Counter'; /** Config is applied to a FancyButton, so it can be used without setting a config. */ -export class SmallIconButton extends FancyButton { +export class SmallIconButton extends FancyButton +{ counter!: Counter; // counter component that will be added to the button. It will show the number of notifications - + constructor( icon: string, // icon for the button onclick: () => void, // callback for the button press notifications = 0 // number of notifications to show on the button - ) { + ) + { super({ // create the FancyButton component defaultView: `SmallButton`, // this is a key to the texture atlas for default button state view hoverView: `SmallButton-hover`, // this is a key to the texture atlas for hover button state view @@ -39,7 +41,8 @@ export class SmallIconButton extends FancyButton { this.onPress.connect(onclick); // connect button press event to the provided callback - if (notifications) { // if there are notifications to show + if (notifications) + { // if there are notifications to show this.counter = new Counter(notifications); // create the counter component this.counter.x = this.width / 2 - 20; // set the counter position @@ -47,11 +50,13 @@ export class SmallIconButton extends FancyButton { this.innerView.addChild(this.counter as any); // add the counter to the button } - + this.anchor.set(0.5); // set button anchor to the center, this is needed for the button to scale correctly when animated } - set notifications(amount: number) { // set the number of notifications + // eslint-disable-next-line accessor-pairs + set notifications(amount: number) + { // set the number of notifications this.counter.number.text = String(amount); // set the text of the counter } -}; \ No newline at end of file +} diff --git a/src/components/Switch.ts b/src/components/Switch.ts index 609b99c..ceb663a 100644 --- a/src/components/Switch.ts +++ b/src/components/Switch.ts @@ -3,16 +3,15 @@ import { Sprite } from '@pixi/sprite'; import { Text } from '@pixi/text'; import { colors } from '../config/colors'; -/** Extends a BasicCheckBox class and apply config to it, - * so that instance can be used without need to config it. -*/ -export class Switch extends BasicCheckBox { +/** Extends a BasicCheckBox class and apply config to it, so that instance can be used without need to config it. */ +export class Switch extends BasicCheckBox +{ constructor( text: string, // text that will be displayed on the checkbox checked:boolean, // initial checkbox state callback?: (checked: boolean) => void // callback function that will be called when checkbox state changes - ) { - + ) + { super({ style: { // style is an object with checkbox assets and text styles checked: createCheckBox(true), // sprite(Container), that shows when checkbox is checked @@ -27,20 +26,22 @@ export class Switch extends BasicCheckBox { text, // text that will be displayed on the checkbox }); - if (callback) { // if callback function is provided + if (callback) + { // if callback function is provided this.onCheck.connect(callback); // connect checkbox change event to the provided callback } } } // this is extracted to a function as we are using it inside a `super` call so it can't be called as `this.createCheckBox` -function createCheckBox(checked: boolean) { // creates a sprite(Container) with checkbox assets +function createCheckBox(checked: boolean) +{ // creates a sprite(Container) with checkbox assets const bg = Sprite.from('SwitchBG'); // create a background sprite from the provided texture key const icon1 = Sprite.from('PauseIcon'); // create a sprite from the provided texture key const icon2 = Sprite.from('PauseIcon'); // create a sprite from the provided texture key const handle = Sprite.from(checked ? 'SmallButton' : 'SmallButton-pressed'); // create a sprite from the provided texture key const stateText = new Text(checked ? 'ON' : 'OFF', TEXT_STYLE); // create a text component - + stateText.anchor.set(0.5); // set text component anchor to the center bg.addChild(stateText); // add text component to the background sprite @@ -63,7 +64,6 @@ function createCheckBox(checked: boolean) { // creates a sprite(Container) with handle.y = bg.height / 2; // set sprite position handle.x = (bg.width / 2) + (checked ? handle.width / 2 : -handle.width / 2); // set sprite position - bg.addChild(handle); // add handle to the background sprite handle.addChild(icon1); // add icon1 to the handle sprite handle.addChild(icon2); // add icon2 to the handle sprite diff --git a/src/components/basic/AppScreen.ts b/src/components/basic/AppScreen.ts index 0a39d2d..3f39da2 100644 --- a/src/components/basic/AppScreen.ts +++ b/src/components/basic/AppScreen.ts @@ -1,4 +1,3 @@ - import gsap from 'gsap'; import { Layout, Styles } from '@pixi/layout'; import { Windows } from '../../config/windows'; @@ -6,15 +5,18 @@ import { ViewController } from '../../controllers/ViewController'; import { Window } from './Window'; import { getUrlParam } from '../../utils/gtUrlParams'; -/** Layout based component to place screens content. +/** + * Layout based component to place screens content. * Should be used as a base class for all screens in the app. * Should be added to the app.stage. */ -export class AppScreen extends Layout { +export class AppScreen extends Layout +{ protected views: ViewController; // view controller, used to manage windows protected defaultWindow!: Windows; // default window to show - constructor(id: string, styles?: Styles) { + constructor(id: string, styles?: Styles) + { // created blank layout with id and styles, content will be added in the child classes super({ id, @@ -29,63 +31,89 @@ export class AppScreen extends Layout { this.views = new ViewController(); // create view controller } - /** Method is automatically called on every update. See Game.ts */ - public onUpdate(_delta: number) { + /** + * Method is automatically called on every update. See Game.ts + * @param _delta + */ + public onUpdate(_delta: number) + { /* Override this method to update the screen */ - }; + } - /** Method is automatically called on every resize. See Game.ts + /** + * Method is automatically called on every resize. See Game.ts * IMPORTANT: This method is propagating resize to all the layout system, * that is doing all the "magic" behind it. * DO NOT FORGET TO CALL super.resize() IN THE CHILD CLASS IN CASE OF OVERRIDING THIS METHOD - */ - public resize(_w: number, _h: number) { + * @param _w + * @param _h + */ + public resize(_w: number, _h: number) + { super.resize(_w, _h); // propagate resize to the layout system - }; + } /** Method is automatically called when Layout is shown. See Game.ts */ - public async show() { + public async show() + { gsap.killTweensOf(this); // kill all tweens of this object this.alpha = 0; // set alpha to 0 await gsap.to(this, { alpha: 1, duration: 0.2, ease: 'linear' }); // fade in } /** Method is automatically called when Layout is hidden. See Game.ts */ - public async hide() { + public async hide() + { gsap.killTweensOf(this); // kill all tweens of this object await gsap.to(this, { alpha: 0, duration: 0.2, ease: 'linear' }); // fade out } - /** Add window to the view controller and screen. */ + /** + * Add window to the view controller and screen. + * @param window + * @param content + */ public addWindow( - window: Windows, // window id + window: Windows, // window id content: Window // window content component - ) { + ) + { this.views.add(window, content); // add window to the view controller - + this.addContent({ // add window to layout system [window]: this.views.get(window) // get window from the view controller and add it to layout system }); } - /** Show active window. */ + /** + * Show active window. + * @param activeWindow + */ public async showActiveWindow( activeWindow?: Windows // window id to show - ) { + ) + { const window = getUrlParam('window'); // get window param from url, used for debugging (TODO: remove this on production) - + // If window param is set, try to show it. If it fails, show default window. - if (window) { - try { + if (window) + { + try + { await this.views.show(Windows[window as keyof typeof Windows]); // try to show window + return; - } catch (e) { // if window is not found, show message in console + } + catch (e) + { // if window is not found, show message in console const error: Error = e as Error; // cast error to Error type + console.error(error.message.replace('"undefined"', window)); // show error message } } - if (activeWindow || this.defaultWindow) { + if (activeWindow || this.defaultWindow) + { await this.views.show(activeWindow ?? this.defaultWindow); // show active window or default window } } diff --git a/src/components/basic/Background.ts b/src/components/basic/Background.ts index 41a2acb..a8cd844 100644 --- a/src/components/basic/Background.ts +++ b/src/components/basic/Background.ts @@ -1,19 +1,21 @@ import { Sprite } from '@pixi/sprite'; import { Layout } from '@pixi/layout'; import { PixiLogo } from '../PixiLogo'; -import { MotionBlurFilter } from "@pixi/filter-motion-blur"; +import { MotionBlurFilter } from '@pixi/filter-motion-blur'; import { gsap } from 'gsap'; /* Layout based component for the background. - * This is where all the layers of the background should be added and controlled. + * This is where all the layers of the background should be added and controlled. * For example to add a parallax effect, you would add a new layers here and control their positions. */ -export class Background extends Layout { +export class Background extends Layout +{ filter: MotionBlurFilter; bgSprite: Sprite; animation: gsap.core.Timeline; - constructor() { + constructor() + { const bg = Sprite.from('bg'); super({ @@ -23,7 +25,8 @@ export class Background extends Layout { content: bg, // content is the PIXI sprite that will be added to the layer styles: { // styles is an object with all the styles that will be applied to the layer position: 'center', // center Layout in the middle of parent - maxHeight: '100%', // set max height to 100% of parent, so it will scale down to fit the screen height + // set max height to 100% of parent, so it will scale down to fit the screen height + maxHeight: '100%', minWidth: '100%', // set min width to 100% of parent, so it will scale up to fit the screen width } }, @@ -43,17 +46,20 @@ export class Background extends Layout { this.animation = gsap.timeline(); } - resetFilter() { + resetFilter() + { this.filter.velocity.set(0); this.filter.kernelSize = 0; } - pause() { - this.animation.pause() + pause() + { + this.animation.pause(); } - swing(power: number, duration: number = 0.5, delay: number = 0) { - this.animation.to(this, 0.1, {x:`+=${power}`, yoyo:true, repeat:-1, duration, delay}); - this.animation.to(this, 0.1, {x:`-=${power}`, yoyo:true, repeat:-1, duration}); + swing(power: number, duration = 0.5, delay = 0) + { + this.animation.to(this, 0.1, { x: `+=${power}`, yoyo: true, repeat: -1, duration, delay }); + this.animation.to(this, 0.1, { x: `-=${power}`, yoyo: true, repeat: -1, duration }); } -} \ No newline at end of file +} diff --git a/src/components/basic/Button.ts b/src/components/basic/Button.ts index fce457f..efdc7bc 100644 --- a/src/components/basic/Button.ts +++ b/src/components/basic/Button.ts @@ -1,11 +1,13 @@ -import { Text } from "@pixi/text"; -import { FancyButton } from "@pixi/ui"; -import { Layout } from "@pixi/layout"; -import { colors } from "../../config/colors"; +import { Text } from '@pixi/text'; +import { FancyButton } from '@pixi/ui'; +import { Layout } from '@pixi/layout'; +import { colors } from '../../config/colors'; /** Creates a Layout with button as content and apply styles. */ -export class Button extends Layout { - constructor(text: string, onclick: () => void, styles?: any) { +export class Button extends Layout +{ + constructor(text: string, onclick: () => void, styles?: any) + { const button = new FancyButton({ // create a button defaultView: `Button`, // this is a key to the texture atlas for default button state view hoverView: `Button-hover`, // this is a key to the texture atlas for hover button state view @@ -43,7 +45,8 @@ export class Button extends Layout { button.onPress.connect(onclick); // connect button press event to the provided callback - button.anchor.set(0.5); // set button anchor to the center, this is needed for the button to scale correctly when animated + // set button anchor to the center, this is needed for the button to scale correctly when animated + button.anchor.set(0.5); super({ // create layout with button as content content: button, // button is the content of the layout @@ -54,4 +57,4 @@ export class Button extends Layout { } }); } -} \ No newline at end of file +} diff --git a/src/components/basic/Counter.ts b/src/components/basic/Counter.ts index a9c16e8..f72bd48 100644 --- a/src/components/basic/Counter.ts +++ b/src/components/basic/Counter.ts @@ -1,17 +1,19 @@ -import { Sprite } from "@pixi/sprite"; +import { Sprite } from '@pixi/sprite'; import { colors } from '../../config/colors'; -import { Container } from "@pixi/display"; -import { Text } from "@pixi/text"; +import { Container } from '@pixi/display'; +import { Text } from '@pixi/text'; -/** Component to show a number. - * To be used to point a notification amount as a part of other components. */ -export class Counter extends Container { +/** Component to show a number. To be used to point a notification amount as a part of other components. */ +export class Counter extends Container +{ number: Text; // pixi text that will be used to show the number - constructor(text: number) { + constructor(text: number) + { super(); const bg = Sprite.from(`Radio-hover`); // create bg sprite + bg.anchor.set(0.5); // set anchor to the center this.number = new Text(String(text), { // create pixi text @@ -26,4 +28,4 @@ export class Counter extends Container { this.addChild(bg, this.number); // add bg and number to the container } -} \ No newline at end of file +} diff --git a/src/components/basic/RoundMatterBody.ts b/src/components/basic/RoundMatterBody.ts index 544c397..80bd36c 100644 --- a/src/components/basic/RoundMatterBody.ts +++ b/src/components/basic/RoundMatterBody.ts @@ -4,7 +4,8 @@ import { Graphics } from '@pixi/graphics'; import { ColorSource, Ticker } from '@pixi/core'; import { IDestroyOptions } from '@pixi/display'; -export class RoundMatterBody extends Graphics implements IMatter { +export class RoundMatterBody extends Graphics implements IMatter +{ body!: Body; constructor( @@ -16,7 +17,8 @@ export class RoundMatterBody extends Graphics implements IMatter { color?: ColorSource; }, options?: IChamferableBodyDefinition, - ) { + ) + { super(); const { x, y, radius, color } = params; @@ -34,17 +36,20 @@ export class RoundMatterBody extends Graphics implements IMatter { Ticker.shared.add(this.update, this); } - update() { + update() + { this.x = this.body.position.x; this.y = this.body.position.y; this.rotation = this.body.angle; - if (this.y - this.height > window.innerHeight) { + if (this.y - this.height > window.innerHeight) + { this.destroy(); } } - destroy(options?: boolean | IDestroyOptions | undefined): void { + destroy(options?: boolean | IDestroyOptions | undefined): void + { Ticker.shared.remove(this.update, this); this.parent?.removeChild(this); Composite.remove(this.world, this.body); @@ -53,7 +58,8 @@ export class RoundMatterBody extends Graphics implements IMatter { beforeUnload() {} - resetPosition() { + resetPosition() + { Body.setPosition(this.body, { x: 0, y: 0 }); Body.setVelocity(this.body, { x: 0, y: 0 }); Body.setAngularVelocity(this.body, 0); diff --git a/src/components/basic/SquareMatterBody.ts b/src/components/basic/SquareMatterBody.ts index 29008e8..bc3e377 100644 --- a/src/components/basic/SquareMatterBody.ts +++ b/src/components/basic/SquareMatterBody.ts @@ -4,7 +4,8 @@ import { Graphics } from '@pixi/graphics'; import { ColorSource, Ticker } from '@pixi/core'; import { IDestroyOptions } from '@pixi/display'; -export class SquareMatterBody extends Graphics implements IMatter { +export class SquareMatterBody extends Graphics implements IMatter +{ body!: Body; constructor( @@ -17,7 +18,8 @@ export class SquareMatterBody extends Graphics implements IMatter { color: ColorSource; }, options?: IChamferableBodyDefinition, - ) { + ) + { super(); const { x, y, width, height, color } = params; @@ -30,17 +32,20 @@ export class SquareMatterBody extends Graphics implements IMatter { Ticker.shared.add(this.update, this); } - update() { + update() + { this.x = this.body.position.x; this.y = this.body.position.y; this.rotation = this.body.angle; - if (this.y - this.height > window.innerHeight) { + if (this.y - this.height > window.innerHeight) + { this.destroy(); } } - destroy(options?: boolean | IDestroyOptions | undefined): void { + destroy(options?: boolean | IDestroyOptions | undefined): void + { Ticker.shared.remove(this.update, this); this.parent?.removeChild(this); Composite.remove(this.world, this.body); @@ -49,13 +54,15 @@ export class SquareMatterBody extends Graphics implements IMatter { beforeUnload() {} - resetPosition() { + resetPosition() + { Body.setPosition(this.body, { x: 0, y: 0 }); Body.setVelocity(this.body, { x: 0, y: 0 }); Body.setAngularVelocity(this.body, 0); } - setPos(x: number, y: number) { + setPos(x: number, y: number) + { Body.setPosition(this.body, { x, y }); } } diff --git a/src/components/basic/Window.ts b/src/components/basic/Window.ts index 253fed6..bdf4c50 100644 --- a/src/components/basic/Window.ts +++ b/src/components/basic/Window.ts @@ -1,17 +1,17 @@ import { Sprite } from '@pixi/sprite'; -import gsap, {Back} from 'gsap'; +import gsap, { Back } from 'gsap'; import { colors } from '../../config/colors'; import { Layout, Styles } from '@pixi/layout'; -/** Layout base component with the config for a base window class to be extended - * for creation of any window component. - */ -export class Window extends Layout { +/** Layout base component with the config for a base window class to be extended for creation of any window component. */ +export class Window extends Layout +{ constructor(options: { title: string, // text title of the window styles?: Styles, // styles of the window ribbonStyles?: Styles // styles of the ribbon - }) { + }) + { super({ // Layout constructor accepts an object with all the config id: `Window-${options.title}`, // id of the component, can be used to access it laterS content: { // Content of the component @@ -56,32 +56,39 @@ export class Window extends Layout { this.createContent(); // add content to the component } - /** Method that is automatically called on after window creation. + /** + * Method that is automatically called on after window creation. * To be override by other windows, for the content to be added to the window. */ - public createContent() { + public createContent() + { // override this method to add content to the window } - - /** Method is automatically called when Layout is shown. See Game.ts. + + /** + * Method is automatically called when Layout is shown. See Game.ts. * It is used to animate the window when it is shown. + * @param force */ - public async show(force = false) { // force parameter is used to show the window without animation + public async show(force = false) + { // force parameter is used to show the window without animation if (this.alpha === 1) return; // if window is already visible, return gsap.killTweensOf(this); // kill all tweens of the window - - if (force) { // if force is true, show the window without animation + + if (force) + { // if force is true, show the window without animation this.alpha = 1; // set alpha to 1 this.visible = true; // set visible to true + return; } - + // set initial animation state this.alpha = 0; // alpha to 0 this.y += 100; // move 100px down this.visible = true; // set visible to true - + await gsap.to(this, { // animate the window alpha: 1, // alpha to 1 y: '-=100', // move 100px up @@ -90,17 +97,22 @@ export class Window extends Layout { }); } - /** Method is automatically called when Layout is shown. See Game.ts. + /** + * Method is automatically called when Layout is shown. See Game.ts. * It is used to animate the window when it is hided. + * @param force */ - public async hide(force = false) { // force parameter is used to hide the window without animation + public async hide(force = false) + { // force parameter is used to hide the window without animation if (this.alpha === 0) return; // if window is already hidden, return gsap.killTweensOf(this); // kill all tweens of the window - - if (force) { // if force is true, hide the window without animation + + if (force) + { // if force is true, hide the window without animation this.alpha = 0; // set alpha to 0 this.visible = false; // set visible to false + return; } @@ -114,4 +126,4 @@ export class Window extends Layout { this.visible = false; // set visible to false this.y -= 100; // move 100px up } -} \ No newline at end of file +} diff --git a/src/components/windows/InfoWindow.ts b/src/components/windows/InfoWindow.ts index 4a96fdf..955f831 100644 --- a/src/components/windows/InfoWindow.ts +++ b/src/components/windows/InfoWindow.ts @@ -1,16 +1,18 @@ -import i18n from "../../config/i18n"; -import { Window } from "../basic/Window"; -import { ViewController } from "../../controllers/ViewController"; +import i18n from '../../config/i18n'; +import { Window } from '../basic/Window'; +import { ViewController } from '../../controllers/ViewController'; import { CloseButton } from '../CloseButton'; -import { Button } from "../basic/Button"; -import { Sprite } from "@pixi/sprite"; -import { Text } from "@pixi/text"; +import { Button } from '../basic/Button'; +import { Sprite } from '@pixi/sprite'; +import { Text } from '@pixi/text'; import { colors } from '../../config/colors'; -import { ScrollBox } from "@pixi/ui"; +import { ScrollBox } from '@pixi/ui'; /** Layout based component for the info window. */ -export class InfoWindow extends Window { // extends Window class to get all the window functionality - constructor(private views: ViewController, text: string) { // pass the ViewController to the constructor to be able to control the views +export class InfoWindow extends Window +{ // extends Window class to get all the window functionality + constructor(private views: ViewController, text: string) + { // pass the ViewController to the constructor to be able to control the views // give config differences to the Window base component(Layout) super({ // Window constructor accepts an object with all the config title: i18n.titleScreen.info.title, // text title of the window @@ -25,21 +27,28 @@ export class InfoWindow extends Window { // extends Window class to get all the } /** Create content of the component. Automatically called by extended class(see Window.ts). */ - override createContent() { // override the createContent method from the Window class + override createContent() + { // override the createContent method from the Window class this.addCloseButton(); // add close button to the window this.addButton( // add accept button to the window i18n.titleScreen.info.accept, // button text - () => { // button callback + () => + { // button callback this.views.goBack(); // go back to the previous view }); } - /** Creates a button in bottomCenter of the window */ + /** + * Creates a button in bottomCenter of the window + * @param title + * @param callback + */ private addButton( title: string, // button text callback: () => void, // button callback - ) { + ) + { const button = new Button(title, () => callback(), { fontSize: 60 }); // create a button with the given text and callback @@ -56,8 +65,10 @@ export class InfoWindow extends Window { // extends Window class to get all the } /** Creates a close button in topRight of the window */ - private addCloseButton() { - const closeButton = new CloseButton(() => { // create a close button with the given callback + private addCloseButton() + { + const closeButton = new CloseButton(() => + { // create a close button with the given callback this.views.goBack(); // go back to the previous view }); @@ -72,9 +83,14 @@ export class InfoWindow extends Window { // extends Window class to get all the }); } - /** Creates scrollable text blok and adds it to the window layout. */ - private addText(text: string) { + /** + * Creates scrollable text blok and adds it to the window layout. + * @param text + */ + private addText(text: string) + { const textBG = Sprite.from('SmallSubstrate'); // create a sprite from the texture with id 'SmallSubstrate' + textBG.scale.set(1.2); // scale the sprite us a bit this.addContent({ // add the text to the layout system of the Window @@ -88,7 +104,7 @@ export class InfoWindow extends Window { // extends Window class to get all the padding: 25, // padding of the scroll box, will be applied to the content offset from the box items: [ // array of the items that will be added to the scroll box, positioned basing on their sizes and margins one after another to be scrolled new Text(text, { // create a `Text` instance with the given text - fill: colors.text, // set text color + fill: colors.text, // set text color fontSize: 24, // set text size fontFamily: 'Days One', // set text font stroke: colors.hoverStroke, // set text stroke color @@ -109,4 +125,4 @@ export class InfoWindow extends Window { // extends Window class to get all the } }); } -} \ No newline at end of file +} diff --git a/src/components/windows/PauseWindow.ts b/src/components/windows/PauseWindow.ts index f321ad7..4349084 100644 --- a/src/components/windows/PauseWindow.ts +++ b/src/components/windows/PauseWindow.ts @@ -1,15 +1,17 @@ -import { Sprite } from "@pixi/sprite"; -import i18n from "../../config/i18n"; -import { Button } from "../basic/Button"; -import { Window as BasicWindow } from "../basic/Window"; +import { Sprite } from '@pixi/sprite'; +import i18n from '../../config/i18n'; +import { Button } from '../basic/Button'; +import { Window as BasicWindow } from '../basic/Window'; import { game } from '../../Game'; import { GameScreen, GameTypes } from '../../screens/GameScreen'; -import { LayoutOptions } from "@pixi/layout"; -import { gitHubURL } from "../../config"; +import { LayoutOptions } from '@pixi/layout'; +import { gitHubURL } from '../../config'; /** Game menu component. */ -export class PauseWindow extends BasicWindow { - constructor() { // pass the ViewController to the constructor to be able to control the views +export class PauseWindow extends BasicWindow +{ + constructor() + { // pass the ViewController to the constructor to be able to control the views // give config differences to the Window base component super({ // Window constructor accepts an object with all the config title: i18n.titleScreen.menu.title, // menu title text @@ -26,21 +28,23 @@ export class PauseWindow extends BasicWindow { } /** Create content of the component. Automatically called by extended class (see Window.ts). */ - override createContent() { + override createContent() + { const menuButtons: { [name: string]: LayoutOptions; } = {}; // create an array to store menu buttons const items: { [name: string]: string } = i18n.titleScreen.menu.items; - for (const gameType in items) { + for (const gameType in items) + { const text = items[gameType]; // get the text for the button from the i18n file - + menuButtons[gameType] = { // levels is the id of the button content: new Button( // create a levels window navigational button - text, // button text - () => this.selectMenuItem(gameType as GameTypes), // button click callback - ), // content is the button component + text, // button text + () => this.selectMenuItem(gameType as GameTypes), // button click callback + ), // content is the button component styles: { // styles is an object with all the styles that will be applied to the button marginTop: 10, // move the button 10px down from the neighbour buttons } @@ -59,16 +63,21 @@ export class PauseWindow extends BasicWindow { }); } - /** Select menu item. */ - private selectMenuItem(gameType: GameTypes | 'repo') { - switch (gameType) { + /** + * Select menu item. + * @param gameType + */ + private selectMenuItem(gameType: GameTypes | 'repo') + { + switch (gameType) + { case 'repo': (window as any).open(gitHubURL, '_blank').focus(); break; default: - game.showScreen(GameScreen, { // show the game screen + game.showScreen(GameScreen, { // show the game screen type: gameType, // pass the level type to the game screen - }) + }); } } -} \ No newline at end of file +} diff --git a/src/config/assets.ts b/src/config/assets.ts index b1bdb70..100b807 100644 --- a/src/config/assets.ts +++ b/src/config/assets.ts @@ -1,4 +1,4 @@ -import { ResolverManifest } from "@pixi/assets"; +import { ResolverManifest } from '@pixi/assets'; export const assetsManifest: ResolverManifest = { bundles: [ @@ -47,79 +47,79 @@ export const assetsManifest: ResolverManifest = { srcs: 'assets/Examples/BG.png', }, { - name:'SmallButton-disabled', + name: 'SmallButton-disabled', srcs: 'assets/Buttons/SmallButton-disabled.png' }, { - name:'SmallButton-hover', + name: 'SmallButton-hover', srcs: 'assets/Buttons/SmallButton-hover.png' }, { - name:'SmallButton', + name: 'SmallButton', srcs: 'assets/Buttons/SmallButton.png' }, { - name:'Button-pressed', + name: 'Button-pressed', srcs: 'assets/Buttons/Button-pressed.png' }, { - name:'SmallButton-pressed', + name: 'SmallButton-pressed', srcs: 'assets/Buttons/SmallButton-pressed.png' }, { - name:'SmallButton-substrate', + name: 'SmallButton-substrate', srcs: 'assets/Window/SmallButton-substrate.png' }, { - name:'Button-hover', + name: 'Button-hover', srcs: 'assets/Buttons/Button-hover.png' }, { - name:'Button-disabled', + name: 'Button-disabled', srcs: 'assets/Buttons/Button-disabled.png' }, { - name:'Button', + name: 'Button', srcs: 'assets/Buttons/Button.png' }, { - name:'ValueBG', + name: 'ValueBG', srcs: 'assets/Progress/ValueBG.png' }, { - name:'MediumSubstrate', + name: 'MediumSubstrate', srcs: 'assets/Window/MediumSubstrate.png' }, { - name:'Substrate', + name: 'Substrate', srcs: 'assets/Window/Substrate.png' }, { - name:'MenuWindow', + name: 'MenuWindow', srcs: 'assets/Window/MenuWindow.png' }, { - name:'Ribbon', + name: 'Ribbon', srcs: 'assets/Window/Ribbon.png' }, { - name:'Window', + name: 'Window', srcs: 'assets/Window/Window.png' }, { - name:'HomeIcon', + name: 'HomeIcon', srcs: 'assets/Icons/HomeIcon.png' }, { - name:'CloseIcon', + name: 'CloseIcon', srcs: 'assets/Icons/CloseIcon.png' }, { - name:'InfoIcon', + name: 'InfoIcon', srcs: 'assets/Icons/InfoIcon.png' }, { - name:'SmallSubstrate', + name: 'SmallSubstrate', srcs: 'assets/Window/SmallSubstrate.png' }, ], diff --git a/src/config/challenges.ts b/src/config/challenges.ts index 9b01ae0..8ed127d 100644 --- a/src/config/challenges.ts +++ b/src/config/challenges.ts @@ -4,8 +4,7 @@ export const challenges: { sprites: ` Create 144 sprites (NOT graphics object), that are stacked on each other like cards in a deck, (so object above covers bottom one, but not completely). Every second 1 object from top of stack goes to other stack - animation of moving should be 2 seconds long. So at the end of whole process you should have reversed stack. Display number of fps in left top corner and make sure, that this demo runs well on mobile devices.`, - - + emoji: ` Create a tool that will allow mixed text and images in an easy way (for example displaying text with emoticons or prices with money icon). @@ -15,4 +14,4 @@ export const challenges: { Particles - make a demo that shows an awesome fire effect. Please keep number of images low (max 10 sprites on screen at once). Feel free to use existing libraries how you would use them in a real project.`, -} \ No newline at end of file +}; diff --git a/src/config/colors.ts b/src/config/colors.ts index 50ccac5..7fe2680 100644 --- a/src/config/colors.ts +++ b/src/config/colors.ts @@ -7,4 +7,4 @@ export const colors = { disabledStroke: 0x474747, levelBG: 0xf5e3a9, border: 0xFFFFFF, -} \ No newline at end of file +}; diff --git a/src/config/fireGameConfig.ts b/src/config/fireGameConfig.ts index 1ca6a11..7d1bcd1 100644 --- a/src/config/fireGameConfig.ts +++ b/src/config/fireGameConfig.ts @@ -1,134 +1,134 @@ export const fireWidthSmoke = (x: number, y: number, w: number, h: number) => ({ - "lifetime": { - "min": 0.5, - "max": 0.7 + lifetime: { + min: 0.5, + max: 0.7 }, - "frequency": 0.00008, - "emitterLifetime": 0, - "maxParticles": 10000, - "addAtBack": false, - "pos": { - "x": 0, - "y": 0 + frequency: 0.00008, + emitterLifetime: 0, + maxParticles: 10000, + addAtBack: false, + pos: { + x: 0, + y: 0 }, - "behaviors": [ + behaviors: [ { - "type": "alpha", - "config": { - "alpha": { - "list": [ + type: 'alpha', + config: { + alpha: { + list: [ { - "value": 0.62, - "time": 0 + value: 0.62, + time: 0 }, { - "value": 0, - "time": 0.6 + value: 0, + time: 0.6 }, { - "value": 0, - "time": 0.7 + value: 0, + time: 0.7 }, { - "value": 0.8, - "time": 0.71 + value: 0.8, + time: 0.71 }, { - "value": 0, - "time": 1 + value: 0, + time: 1 } ], - "isStepped": false + isStepped: false } } }, { - "type": "moveSpeed", - "config": { - "speed": { - "list": [ + type: 'moveSpeed', + config: { + speed: { + list: [ { - "value": 500, - "time": 0 + value: 500, + time: 0 }, { - "value": 450, - "time": 0.7 + value: 450, + time: 0.7 }, { - "value": 450, - "time": 1 + value: 450, + time: 1 } ], - "isStepped": true + isStepped: true }, - "minMult": 1 + minMult: 1 } }, { - "type": "scale", - "config": { - "scale": { - "list": [ + type: 'scale', + config: { + scale: { + list: [ { - "value": 0.25, - "time": 0 + value: 0.25, + time: 0 }, { - "value": 0.75, - "time": 1 + value: 0.75, + time: 1 } ], - "isStepped": false + isStepped: false }, - "minMult": 1 + minMult: 1 } }, { - "type": "color", - "config": { - "color": { - "list": [ + type: 'color', + config: { + color: { + list: [ { - "value": "fff191", - "time": 0 + value: 'fff191', + time: 0 }, { - "value": "ff622c", - "time": 0.6 + value: 'ff622c', + time: 0.6 }, { - "value": "333232", - "time": 0.7 + value: '333232', + time: 0.7 }, { - "value": "333333", - "time": 1 + value: '333333', + time: 1 } ], - "isStepped": false + isStepped: false } } }, { - "type": "rotation", - "config": { - "accel": 0, - "minSpeed": 50, - "maxSpeed": 50, - "minStart": 265, - "maxStart": 275 + type: 'rotation', + config: { + accel: 0, + minSpeed: 50, + maxSpeed: 50, + minStart: 265, + maxStart: 275 } }, { - "type": "textureRandom", - "config": { - "textures": fireTextures + type: 'textureRandom', + config: { + textures: fireTextures } }, { - "type": "spawnShape", - "config": { + type: 'spawnShape', + config: { type: 'rect', data: { x, @@ -143,8 +143,10 @@ export const fireWidthSmoke = (x: number, y: number, w: number, h: number) => ({ export type Quality = 'low' | 'medium' | 'high'; -export function getQualityData(quality: Quality): { frequency: number, maxParticles: number } { - switch (quality) { +export function getQualityData(quality: Quality): { frequency: number, maxParticles: number } +{ + switch (quality) + { case 'low': return { frequency: 0.0008, @@ -166,104 +168,103 @@ export function getQualityData(quality: Quality): { frequency: number, maxPartic export const fireConfig = ( width: number, quality: Quality -) => { - return { - "lifetime": { - "min": 0.5, - "max": 0.7 +) => + ({ + lifetime: { + min: 0.5, + max: 0.7 }, - "frequency": getQualityData(quality).frequency, - "emitterLifetime": 0, - "maxParticles": getQualityData(quality).maxParticles, - "addAtBack": false, - "pos": { - "x": 0, - "y": 0 + frequency: getQualityData(quality).frequency, + emitterLifetime: 0, + maxParticles: getQualityData(quality).maxParticles, + addAtBack: false, + pos: { + x: 0, + y: 0 }, - "behaviors": fireBehaviors(width) - } -}; + behaviors: fireBehaviors(width) + }); -export const fireBehaviors = (width: number) => { - return [ +export const fireBehaviors = (width: number) => + [ { - "type": "alpha", - "config": { - "alpha": { - "list": [ + type: 'alpha', + config: { + alpha: { + list: [ { - "time": 0, - "value": 0.62 + time: 0, + value: 0.62 }, { - "time": 1, - "value": 0 + time: 1, + value: 0 } ] } } }, { - "type": "moveSpeedStatic", - "config": { - "min": 500, - "max": 500 + type: 'moveSpeedStatic', + config: { + min: 500, + max: 500 } }, { - "type": "scale", - "config": { - "scale": { - "list": [ + type: 'scale', + config: { + scale: { + list: [ { - "time": 0, - "value": 0.25 + time: 0, + value: 0.25 }, { - "time": 1, - "value": 0.75 + time: 1, + value: 0.75 } ] }, - "minMult": 1 + minMult: 1 } }, { - "type": "color", - "config": { - "color": { - "list": [ + type: 'color', + config: { + color: { + list: [ { - "time": 0, - "value": "fff191" + time: 0, + value: 'fff191' }, { - "time": 1, - "value": "ff622c" + time: 1, + value: 'ff622c' } ] } } }, { - "type": "rotation", - "config": { - "accel": 0, - "minSpeed": 50, - "maxSpeed": 50, - "minStart": 265, - "maxStart": 275 + type: 'rotation', + config: { + accel: 0, + minSpeed: 50, + maxSpeed: 50, + minStart: 265, + maxStart: 275 } }, { - "type": "textureRandom", - "config": { - "textures": fireTextures + type: 'textureRandom', + config: { + textures: fireTextures } }, { - "type": "spawnShape", - "config": { + type: 'spawnShape', + config: { type: 'rect', data: { x: 0, @@ -274,66 +275,64 @@ export const fireBehaviors = (width: number) => { } } ]; -} -export const explode = () => { - return { - "alpha": { - "start": 0.8, - "end": 0.1 +export const explode = () => + ({ + alpha: { + start: 0.8, + end: 0.1 }, - "scale": { - "start": 1, - "end": 0.3, - "minimumScaleMultiplier": 1 + scale: { + start: 1, + end: 0.3, + minimumScaleMultiplier: 1 }, - "color": { - "start": "#fb1010", - "end": "#f5b830" + color: { + start: '#fb1010', + end: '#f5b830' }, - "speed": { - "start": 200, - "end": 100, - "minimumSpeedMultiplier": 1 + speed: { + start: 200, + end: 100, + minimumSpeedMultiplier: 1 }, - "acceleration": { - "x": 0, - "y": 0 + acceleration: { + x: 0, + y: 0 }, - "maxSpeed": 0, - "startRotation": { - "min": 0, - "max": 360 + maxSpeed: 0, + startRotation: { + min: 0, + max: 360 }, - "noRotation": false, - "rotationSpeed": { - "min": 0, - "max": 0 + noRotation: false, + rotationSpeed: { + min: 0, + max: 0 }, - "lifetime": { - "min": 0.5, - "max": 0.5 + lifetime: { + min: 0.5, + max: 0.5 }, - "blendMode": "normal", - "frequency": 0.008, - "emitterLifetime": 0.31, - "maxParticles": 1000, - "pos": { - "x": 0, - "y": 0 + blendMode: 'normal', + frequency: 0.008, + emitterLifetime: 0.31, + maxParticles: 1000, + pos: { + x: 0, + y: 0 }, - "addAtBack": false, - "spawnType": "circle", - "spawnCircle": { - "x": 0, - "y": 0, - "r": 10 + addAtBack: false, + spawnType: 'circle', + spawnCircle: { + x: 0, + y: 0, + r: 10 } - } -} + }); export const fireTextures = [ - "assets/particle.png", - "assets/fireParticle.png", - "assets/smoke.png", -]; \ No newline at end of file + 'assets/particle.png', + 'assets/fireParticle.png', + 'assets/smoke.png', +]; diff --git a/src/config/i18n.ts b/src/config/i18n.ts index b87bd63..70759c6 100644 --- a/src/config/i18n.ts +++ b/src/config/i18n.ts @@ -2,25 +2,25 @@ // To be replaced with a i18n controller to manage languages export default { loadingScreen: { - loading: "LOADING", - poweredBy: "POWERED BY", + loading: 'LOADING', + poweredBy: 'POWERED BY', }, titleScreen: { info: { - title: "INFO", - accept: "OK", + title: 'INFO', + accept: 'OK', }, menu: { - title: "MENU", + title: 'MENU', items: { - sprites: "SPRITES", - emoji: "TEXT & IMAGES", - fire: "PARTICLES FIRE", - repo: "Code on GitHub", + sprites: 'SPRITES', + emoji: 'TEXT & IMAGES', + fire: 'PARTICLES FIRE', + repo: 'Code on GitHub', } }, }, gameScreen: { - resume: "Resume", + resume: 'Resume', } -} \ No newline at end of file +}; diff --git a/src/config/index.ts b/src/config/index.ts index 106b6d4..e420929 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1 +1 @@ -export const gitHubURL = 'https://github.com/CyberDex/pixi-experiments'; \ No newline at end of file +export const gitHubURL = 'https://github.com/CyberDex/pixi-experiments'; diff --git a/src/config/spritesGameConfig.ts b/src/config/spritesGameConfig.ts index a9f3f29..31a5086 100644 --- a/src/config/spritesGameConfig.ts +++ b/src/config/spritesGameConfig.ts @@ -9,4 +9,4 @@ export default { repeatDelay: 1, duration: 2, spritesAmount: 15, -} \ No newline at end of file +}; diff --git a/src/config/windows.ts b/src/config/windows.ts index 14d8f95..f1c98f0 100644 --- a/src/config/windows.ts +++ b/src/config/windows.ts @@ -1,4 +1,5 @@ -export enum Windows { +export enum Windows + { pause = 1, info = 4, } diff --git a/src/controllers/ViewController.ts b/src/controllers/ViewController.ts index 8a20424..f7a49f0 100644 --- a/src/controllers/ViewController.ts +++ b/src/controllers/ViewController.ts @@ -1,25 +1,26 @@ -import { Window } from "../components/basic/Window"; -import { Windows } from "../config/windows"; +import { Window } from '../components/basic/Window'; +import { Windows } from '../config/windows'; -/** - * View controller. Manages the windows (see Window.ts). - */ -export class ViewController { +/** View controller. Manages the windows (see Window.ts). */ +export class ViewController +{ public history: Windows[] = []; // history of the windows that were shown public windows: { [ key: string ]: Window } = {}; // windows registry public active!: Windows | null; // currently active window - + // add window to the registry add( id: Windows, // id of the window window: Window, // window component visible = false // window visibility flag - ) { + ) + { this.windows[id] = window; // add window to the registry - if (!visible) { // if window is not visible + if (!visible) + { // if window is not visible window.hide(true); // hide the window } } @@ -27,21 +28,24 @@ export class ViewController { // remove window from the registry remove( id: Windows // id of the window - ) { + ) + { delete this.windows[id]; // remove window from the registry - } + } // show window async show( id: Windows, // id of the window force = false // force parameter is used to show the window without animation - ) { - if (!this.windows[id]) { // if window does not exist + ) + { + if (!this.windows[id]) + { // if window does not exist throw new Error(`Window "${Windows[id]}" does not exist`); // throw error } - + if (this.active === id) return; // if window is already active, return - + await this.hideActive(); // hide window this.history.push(id); // add window to the history @@ -54,9 +58,10 @@ export class ViewController { // hide active window async hideActive( force = false // force parameter is used to hide the window without animation - ) { - - if (this.active) { // if there is an active window + ) + { + if (this.active) + { // if there is an active window await this.hide(this.active, force); // hide active window } } @@ -65,9 +70,9 @@ export class ViewController { async hide( id?: Windows, // id of the window force = false // force parameter is used to hide the window without animation - ) { - - if (!id) { return } // if id is not set, return + ) + { + if (!id) { return; } // if id is not set, return this.active = null; // set active window ID to null @@ -75,24 +80,29 @@ export class ViewController { } // get window by id - get ( + get( id: Windows // id of the window - ) { + ) + { return this.windows[id]; // return window by id } // shw previous window from the history - async goBack() { + async goBack() + { if (!this.history.length) return; // if history is empty, return this.history.pop(); // remove last window from the history - + const prevID = this.history.length - 1; - if (prevID <= 0) { + if (prevID <= 0) + { this.hideActive(); // hide active window - } else { + } + else + { await this.show(this.history[prevID]); // show previous window from the history } } -} \ No newline at end of file +} diff --git a/src/games/EmojiGame.ts b/src/games/EmojiGame.ts index 997a955..c1eaf0d 100644 --- a/src/games/EmojiGame.ts +++ b/src/games/EmojiGame.ts @@ -12,7 +12,8 @@ import { SquareMatterBody } from '../components/basic/SquareMatterBody'; const combinations = ['000', '001', '010', '011', '100', '101', '110', '111']; -export class EmojiGame extends GameBase implements IMatterGame { +export class EmojiGame extends GameBase implements IMatterGame +{ private _widthCache = 0; private _heightCache = 0; @@ -22,7 +23,8 @@ export class EmojiGame extends GameBase implements IMatterGame { paused = false; activated = false; - constructor(scene: AppScreen) { + constructor(scene: AppScreen) + { super({}); scene.addChild(this); @@ -30,7 +32,8 @@ export class EmojiGame extends GameBase implements IMatterGame { Runner.run(this.engine); } - async init() { + async init() + { await initEmojis(); BitmapFont.from('DO', { @@ -64,24 +67,32 @@ export class EmojiGame extends GameBase implements IMatterGame { this.start(); } - private getWord(): string { + private getWord(): string + { return getRandomItem(config.words); } - private getEmoji(): string { + private getEmoji(): string + { const type = getRandomInRange(1, config.spritesAmount); + return `emoji${type}`; } - private generateText(): FancyTextOptions { + private generateText(): FancyTextOptions + { const parts = getRandomItem(combinations).split(''); let text = ''; - let images: string[] = []; + const images: string[] = []; - parts.forEach((part: '0' | '1') => { - if (part === '0') { + parts.forEach((part: '0' | '1') => + { + if (part === '0') + { text += ` ${this.getWord()}`; - } else { + } + else + { const emoji = this.getEmoji(); text += ` ${emoji}`; @@ -95,7 +106,8 @@ export class EmojiGame extends GameBase implements IMatterGame { }; } - private addText() { + private addText() + { if (this.paused) return; const texture = new FancyTextTexture({ @@ -117,24 +129,29 @@ export class EmojiGame extends GameBase implements IMatterGame { setTimeout(() => this.addText(), config.repeatDelay * 1000); } - start() { + start() + { this.addText(); } - pause() { + pause() + { this.paused = true; } - resume() { + resume() + { this.paused = false; this.addText(); } - resize(width: number, height: number): void { + resize(width: number, height: number): void + { this._widthCache = width; this._heightCache = height; - if (this.bottomLine) { + if (this.bottomLine) + { this.bottomLine.setPos(width / 2, height); } } diff --git a/src/games/FireGame.ts b/src/games/FireGame.ts index bc6fa02..925ed86 100644 --- a/src/games/FireGame.ts +++ b/src/games/FireGame.ts @@ -1,19 +1,20 @@ -import { AppScreen } from "../components/basic/AppScreen"; -import { IGame } from "./IGame"; -import { GameBase } from "./GameBase"; -import { Quality, fireConfig, getQualityData } from "../config/fireGameConfig"; -import { Assets } from "@pixi/assets"; -import { TilingSprite } from "@pixi/sprite-tiling"; -import { Texture } from "@pixi/core"; -import { app } from "../main" -import { gsap } from "gsap"; +import { AppScreen } from '../components/basic/AppScreen'; +import { IGame } from './IGame'; +import { GameBase } from './GameBase'; +import { Quality, fireConfig, getQualityData } from '../config/fireGameConfig'; +import { Assets } from '@pixi/assets'; +import { TilingSprite } from '@pixi/sprite-tiling'; +import { Texture } from '@pixi/core'; +import { app } from '../main'; +import { gsap } from 'gsap'; import { Emitter } from '@pixi/particle-emitter'; -import { game } from "../Game"; +import { game } from '../Game'; -export class FireGame extends GameBase implements IGame { +export class FireGame extends GameBase implements IGame +{ private tintTexture!: Texture; private fireEmitter!: Emitter; - private elapsed: number = 0; + private elapsed = 0; private tint!: TilingSprite; private quality: Quality = 'low'; private safeQuality!: Quality; @@ -22,19 +23,21 @@ export class FireGame extends GameBase implements IGame { low: number; high: number; } = { - low: 0, - high: 0, - }; + low: 0, + high: 0, + }; paused = false; activated = false; - - constructor(scene: AppScreen) { + + constructor(scene: AppScreen) + { super({}); scene.addChild(this); } - async init() { + async init() + { await Assets.loadBundle('fire'); this.addViews(); @@ -42,7 +45,8 @@ export class FireGame extends GameBase implements IGame { this.start(); } - private addViews() { + private addViews() + { this.tintTexture = Texture.from('fireGradient'); this.tint = new TilingSprite(this.tintTexture, 1, window.innerHeight); @@ -53,12 +57,14 @@ export class FireGame extends GameBase implements IGame { this.tint.visible = false; this.tint.tileScale.set(window.innerHeight / this.tintTexture.height); - + this.addChildAt(this.tint, 0); } - private bern() { - if (this.fireEmitter) { + private bern() + { + if (this.fireEmitter) + { this.fireEmitter.destroy(); this.tint.visible = false; @@ -80,17 +86,22 @@ export class FireGame extends GameBase implements IGame { game.bg.swing(2, 2, 2); - const interval = setInterval(() => { - if (velocity < 40) { + const interval = setInterval(() => + { + if (velocity < 40) + { velocity++; - } else { + } + else + { clearInterval(interval); } - if (kernelSize < 15) { + if (kernelSize < 15) + { kernelSize++; } - + game.bg.filter.velocity.set(velocity); game.bg.filter.kernelSize = kernelSize; }, 100); @@ -104,12 +115,18 @@ export class FireGame extends GameBase implements IGame { this.fireEmitter.emit = true; } - private qualityDown() { - if (this.quality === 'low') { + private qualityDown() + { + if (this.quality === 'low') + { return; - } else if (this.quality === 'medium') { + } + else if (this.quality === 'medium') + { this.quality = 'low'; - } else if (this.quality === 'high') { + } + else if (this.quality === 'high') + { this.quality = 'medium'; } @@ -122,12 +139,18 @@ export class FireGame extends GameBase implements IGame { this.updateQuality(); } - private qualityUp() { - if (this.quality === 'high') { + private qualityUp() + { + if (this.quality === 'high') + { return; - } else if (this.quality === 'low') { + } + else if (this.quality === 'low') + { this.quality = 'medium'; - } else if (this.quality === 'medium') { + } + else if (this.quality === 'medium') + { this.quality = 'high'; } @@ -136,7 +159,8 @@ export class FireGame extends GameBase implements IGame { this.updateQuality(); } - private updateQuality() { + private updateQuality() + { console.log('quality', this.quality); this.fireEmitter.frequency = getQualityData(this.quality).frequency; @@ -144,53 +168,65 @@ export class FireGame extends GameBase implements IGame { } // TODO: improve quality adjust, use more frequency & maxParticles states - private adjustQuality() { + private adjustQuality() + { // console.log({ // fps: app.ticker.FPS, // data: this.fps, // quality: this.quality // }); - - if (this.quality !== 'low' && app.ticker.FPS < 60) { + + if (this.quality !== 'low' && app.ticker.FPS < 60) + { this.fps.low++; - if (this.fps.low > 10) { + if (this.fps.low > 10) + { this.qualityDown(); } } - if (this.safeQuality) { + if (this.safeQuality) + { return; } - if (this.quality !== 'high') { - if (app.ticker.FPS && app.ticker.FPS >= 60) { + if (this.quality !== 'high') + { + if (app.ticker.FPS && app.ticker.FPS >= 60) + { this.fps.high++; - if (this.fps.high > 300) { + if (this.fps.high > 300) + { this.qualityUp(); } } } } - start() { + start() + { this.bern(); } - pause() { + pause() + { this.paused = true; } - resume() { + resume() + { this.paused = false; this.bern(); } - - update() { + + update() + { const now = Date.now(); - - if (this.fireEmitter) { + + if (this.fireEmitter) + { this.fireEmitter?.update((now - this.elapsed) * 0.001); this.adjustQuality(); @@ -198,12 +234,14 @@ export class FireGame extends GameBase implements IGame { this.elapsed = now; } } - - resize(_width: number, height: number): void { + + resize(_width: number, height: number): void + { this.x = 0; this.y = height; - if (this.tint) { + if (this.tint) + { this.tint.width = window.innerWidth; this.tint.height = window.innerHeight; this.tint.x = 0; @@ -212,8 +250,9 @@ export class FireGame extends GameBase implements IGame { this.tint.tileScale.set(window.innerHeight / this.tintTexture.height); } - if (this.fireEmitter && this.widthCache < window.innerWidth) { + if (this.fireEmitter && this.widthCache < window.innerWidth) + { this.bern(); } } -} \ No newline at end of file +} diff --git a/src/games/GameBase.ts b/src/games/GameBase.ts index f07f997..2ef9655 100644 --- a/src/games/GameBase.ts +++ b/src/games/GameBase.ts @@ -5,12 +5,14 @@ export type State = { [key: string]: number; }; -export class GameBase extends Container { +export class GameBase extends Container +{ private _state: State = {}; onStateChange: Signal<(key: string, value: number) => void>; - constructor(state: State) { + constructor(state: State) + { super(); this._state = state; @@ -18,10 +20,12 @@ export class GameBase extends Container { this.onStateChange = new Signal(); } - get state() { + get state() + { return { get: (key: any) => this._state[key], - set: (key: any, value: any) => { + set: (key: any, value: any) => + { this._state[key] = value; this.onStateChange.emit(key, value); diff --git a/src/games/IGame.ts b/src/games/IGame.ts index 148421f..5e881c0 100644 --- a/src/games/IGame.ts +++ b/src/games/IGame.ts @@ -1,7 +1,8 @@ import { Signal } from 'typed-signals'; import { Engine } from 'matter-js'; -export interface IGame { +export interface IGame +{ x: number; y: number; items?: any[]; @@ -20,6 +21,7 @@ export interface IGame { resize?(width: number, height: number): void; } -export interface IMatterGame extends IGame { +export interface IMatterGame extends IGame +{ engine: Engine; } diff --git a/src/games/SpritesGame.ts b/src/games/SpritesGame.ts index 28e2335..8b2a696 100644 --- a/src/games/SpritesGame.ts +++ b/src/games/SpritesGame.ts @@ -8,7 +8,8 @@ import { Elastic, gsap } from 'gsap'; import { initEmojis } from '../utils/preload'; import { GameBase } from './GameBase'; -export class SpritesGame extends GameBase implements IGame { +export class SpritesGame extends GameBase implements IGame +{ private stack1: Container = new Container(); private stack2: Container = new Container(); @@ -17,7 +18,8 @@ export class SpritesGame extends GameBase implements IGame { paused = false; activated = false; - constructor(scene: AppScreen) { + constructor(scene: AppScreen) + { super({ activeItemID: 1, activeStack: 2, @@ -25,7 +27,8 @@ export class SpritesGame extends GameBase implements IGame { scene.addChild(this); } - async init() { + async init() + { await initEmojis(); this.createContent(config.spritesCount); @@ -33,7 +36,8 @@ export class SpritesGame extends GameBase implements IGame { this.start(); } - private async createContent(count: number) { + private async createContent(count: number) + { this.innerView = new Container(); this.addChild(this.innerView); @@ -53,7 +57,8 @@ export class SpritesGame extends GameBase implements IGame { const start = performance.now(); - for (let i = 0; i < count; i++) { + for (let i = 0; i < count; i++) + { const type = getRandomInRange(1, config.spritesAmount); const sprite = Sprite.from(`emoji${type}`); @@ -73,15 +78,18 @@ export class SpritesGame extends GameBase implements IGame { console.log(`${count} sprites created in ${end - start} ms`); } - private get activeStack(): Container { + private get activeStack(): Container + { return this.state.get('activeStack') === 1 ? this.stack1 : this.stack2; } - private get passiveStack(): Container { + private get passiveStack(): Container + { return this.state.get('activeStack') === 1 ? this.stack2 : this.stack1; } - private async shoot() { + private async shoot() + { if (this.paused) return; const itemID = this.state.get('activeItemID'); @@ -89,8 +97,10 @@ export class SpritesGame extends GameBase implements IGame { this.activated = true; - this.moveItem(activeItem).then(() => { - if (itemID === 0) { + this.moveItem(activeItem).then(() => + { + if (itemID === 0) + { this.reshuffle(); } }); @@ -98,18 +108,21 @@ export class SpritesGame extends GameBase implements IGame { this.shake(this.passiveStack, 1); this.shake(this.activeStack, -1); - if (itemID > 0) { + if (itemID > 0) + { setTimeout(() => this.shoot(), config.repeatDelay * 1000); } } - private reshuffle() { + private reshuffle() + { this.items.reverse(); this.swapStacks(); } - private swapStacks() { + private swapStacks() + { gsap.to(this.activeStack, { x: this.passiveStack.x, y: this.passiveStack.y, @@ -120,7 +133,8 @@ export class SpritesGame extends GameBase implements IGame { this.passiveStack.y = this.activeStack.y; } - private restart() { + private restart() + { this.state.set('activeStack', this.state.get('activeStack') === 1 ? 2 : 1); this.activeStack.zIndex = 0; @@ -129,13 +143,15 @@ export class SpritesGame extends GameBase implements IGame { this.start(); } - private moveItem(item: Container): Promise { - return new Promise((resolve) => { + private moveItem(item: Container): Promise + { + return new Promise((resolve) => + { const posX = item.x; const posY = item.y; - const angle = - getRandomInRange(1, config.stackRotationScatter) * getRandomItem([1, -1]) * 4; + const angle + = getRandomInRange(1, config.stackRotationScatter) * getRandomItem([1, -1]) * 4; item.zIndex = -this.state.get('activeItemID'); @@ -144,7 +160,8 @@ export class SpritesGame extends GameBase implements IGame { y: this.stackDistance.y + posY, angle, duration: config.duration, - onComplete: () => { + onComplete: () => + { this.activeStack.addChild(item); item.x = posX; @@ -159,8 +176,9 @@ export class SpritesGame extends GameBase implements IGame { }); } - private shake(stack: Container, direction: number) { - let shake = getRandomInRange(4, 20) * direction; + private shake(stack: Container, direction: number) + { + const shake = getRandomInRange(4, 20) * direction; gsap.fromTo( stack, @@ -179,7 +197,8 @@ export class SpritesGame extends GameBase implements IGame { ); } - private get stackDistance(): { x: number; y: number } { + private get stackDistance(): { x: number; y: number } + { if (!this.activeStack || !this.passiveStack) return { x: 0, y: 0 }; return { @@ -188,21 +207,25 @@ export class SpritesGame extends GameBase implements IGame { }; } - start() { + start() + { this.state.set('activeItemID', this.items.length - 1); this.shoot(); } - pause() { + pause() + { this.paused = true; } - resume() { + resume() + { this.paused = false; this.shoot(); } - resize(width: number, height: number): void { + resize(width: number, height: number): void + { this.x = (width - config.width) / 2; this.y = (height - config.height) / 2; } diff --git a/src/screens/GameScreen.ts b/src/screens/GameScreen.ts index 6ce3442..40a1caa 100644 --- a/src/screens/GameScreen.ts +++ b/src/screens/GameScreen.ts @@ -20,7 +20,8 @@ import { getUrlParam } from '../utils/gtUrlParams'; export type GameTypes = 'sprites' | 'emoji' | 'fire'; -export class GameScreen extends AppScreen { +export class GameScreen extends AppScreen +{ // GameScreen extends AppScreen, which is a Layout with a few additional features public static assetBundles = ['game']; // asset bundles that will be loaded before the screen is shown private gameType: GameTypes = 'sprites'; // game type @@ -28,17 +29,20 @@ export class GameScreen extends AppScreen { private resumeButton!: Button; private paused = false; - constructor(options?: SceneData) { + constructor(options?: SceneData) + { // constructor accepts an object with data that will be passed to the screen when it is shown super('GameScreen'); // Creates Layout with id 'GameScreen' game.addBG(); - if (getUrlParam('game')) { + if (getUrlParam('game')) + { this.gameType = getUrlParam('game') as GameTypes; } - if (options?.type) { + if (options?.type) + { this.gameType = options?.type; // set game type } @@ -53,15 +57,18 @@ export class GameScreen extends AppScreen { this.addEvents(); } - /** Create windows. - * Windows are Layout based components that are shown on top of the screen. + /** + * Create windows. Windows are Layout based components that are shown on top of the screen. + * @param activeWindow */ private createWindows( activeWindow?: Windows, // active window to show - ) { + ) + { const task = challenges[this.gameType]; - if (task) { + if (task) + { this.addWindow(Windows.info, new InfoWindow(this.views, task)); // create InfoWindow this.addInfoButton(); // add info button component to the screen @@ -70,11 +77,14 @@ export class GameScreen extends AppScreen { } } - /** Add pause button component to the screen. + /** + * Add pause button component to the screen. * Pause button suits to pause the game and show the pause window and the title screen. */ - private addBackButton() { - const button = new SmallIconButton('HomeIcon', () => { + private addBackButton() + { + const button = new SmallIconButton('HomeIcon', () => + { // create a button with a custom icon game.showScreen( // show TitleScreen with default window (pauseWindow) opened @@ -107,14 +117,17 @@ export class GameScreen extends AppScreen { }); } - private addResumeButton() { + private addResumeButton() + { this.resumeButton = new Button( i18n.gameScreen.resume, - () => { + () => + { gsap.to(this.resumeButton, { alpha: 0, duration: 0.5, - onComplete: () => { + onComplete: () => + { this.resumeButton.visible = false; this.resumeButton.scale.set(1); this.resumeButton.alpha = 1; @@ -147,8 +160,10 @@ export class GameScreen extends AppScreen { }); } - private addInfoButton() { - const button = new SmallIconButton('InfoIcon', () => { + private addInfoButton() + { + const button = new SmallIconButton('InfoIcon', () => + { // create a button with a custom icon this.views.show(Windows.info); }); @@ -173,15 +188,20 @@ export class GameScreen extends AppScreen { } /** Create game. */ - private createGame() { - switch (this.gameType) { + private createGame() + { + switch (this.gameType) + { case 'sprites': this.game = new SpritesGame(this); this.game.init(); - this.game.onStateChange.connect((prop: string, val: number) => { - if (prop === 'activeItemID') { + this.game.onStateChange.connect((prop: string, val: number) => + { + if (prop === 'activeItemID') + { const total = this.game.items?.length ?? 0; const progress = total - val - 1; + this.updateInfo('progress', `${progress} / ${total}`); } }); @@ -198,7 +218,8 @@ export class GameScreen extends AppScreen { } } - private addInfoPanel(id: string, position: Position, value?: string) { + private addInfoPanel(id: string, position: Position, value?: string) + { const bg = Sprite.from('ValueBG'); this.addContent({ @@ -207,8 +228,8 @@ export class GameScreen extends AppScreen { content: value ?? ' ', styles: { display: 'block', - marginTop: 20, color: 'white', + marginTop: -8, fontSize: 30, fontFamily: 'Days One', textAlign: 'center', @@ -231,35 +252,43 @@ export class GameScreen extends AppScreen { }); } - private updateInfo(panelID: string, value: string) { + private updateInfo(panelID: string, value: string) + { const panel = this.getChildByID(panelID)?.children[0] as Text; - if (panel) { + if (panel) + { panel.text = value; } } /** Method that is called one every game tick (see Game.ts) */ - onUpdate() { - if (this.game?.update) { + onUpdate() + { + if (this.game?.update) + { this.game.update(); } } - override resize(width: number, height: number) { + override resize(width: number, height: number) + { super.resize(width, height); - if (this.game?.resize) { + if (this.game?.resize) + { this.game.resize(width, height); } } - private addEvents() { + private addEvents() + { window.onfocus = () => this.pause(); window.onblur = () => (this.paused = true); } - private pause() { + private pause() + { if (!this.paused) return; if (this.gameType === 'fire') return; diff --git a/src/screens/LoadScreen.ts b/src/screens/LoadScreen.ts index c404097..e9e506c 100644 --- a/src/screens/LoadScreen.ts +++ b/src/screens/LoadScreen.ts @@ -1,17 +1,16 @@ - import { AppScreen } from '../components/basic/AppScreen'; import { LoadingSpinner } from '../components/LoadingSpinner'; import { PixiLogo } from '../components/PixiLogo'; import { colors } from '../config/colors'; import { Layout } from '@pixi/layout'; -/** Load screen. - * To be used to show loading animation while assets are being loaded. -*/ -export class LoadScreen extends AppScreen { // extends AppScreen that extends Layout that extends PIXI.Container +/** Load screen. To be used to show loading animation while assets are being loaded. */ +export class LoadScreen extends AppScreen +{ // extends AppScreen that extends Layout that extends PIXI.Container public static assetBundles = ['preload']; // set section of assets to preload for this screen. Section is defined in assets.json. Handled by AssetLoader. - - constructor() { + + constructor() + { super('LoadScreen'); // Creates Layout with id 'LoadScreen' this.addContent({ // add children components (can be sprite or any container based pixi instance like Layout) to the Layout @@ -24,14 +23,16 @@ export class LoadScreen extends AppScreen { // extends AppScreen that extends La }); } - public onUpdate() { + public onUpdate() + { const spinner = this.getChildByID('spinner'); // get spinner Layout from the children tree - + if (spinner // if spinner is found - // and it is a layout (we know it is a layout as we set it in LoadingSpinner class, + // and it is a layout (we know it is a layout as we set it in LoadingSpinner class, // but we check it for typescript and just to be sure) && spinner instanceof Layout // instanceof is a typescript operator that checks if the instance is of the type - ) { + ) + { // we know it is first child as we set it in LoadingSpinner class const spinnerSprite = spinner.content.firstChild; // get first child of the spinner Layout diff --git a/src/screens/TitleScreen.ts b/src/screens/TitleScreen.ts index 66c02ab..1770cc1 100644 --- a/src/screens/TitleScreen.ts +++ b/src/screens/TitleScreen.ts @@ -3,28 +3,30 @@ import { PauseWindow } from '../components/windows/PauseWindow'; import { Windows } from '../config/windows'; import { game, SceneData } from '../Game'; -/** Title screen. - * To be used to show when game is on pause or before the game starts. -*/ -export class TitleScreen extends AppScreen { // extends AppScreen that extends Layout that extends PIXI.Container +/** Title screen. To be used to show when game is on pause or before the game starts. */ +export class TitleScreen extends AppScreen +{ // extends AppScreen that extends Layout that extends PIXI.Container public static assetBundles = ['game']; // set section of assets to preload for this screen. Section is defined in assets.json. Handled by AssetLoader. - + override defaultWindow = Windows.pause; // default window to show - constructor(options?: SceneData) { + constructor(options?: SceneData) + { super('TitleScreen'); // Creates Layout with id 'TitleScreen' - - game.addBG(); + + game.addBG(); this.createWindows(options?.window); // create windows } - /** Create windows. - * Windows are Layout based components that are shown on top of the screen. - */ + /** + * Create windows. Windows are Layout based components that are shown on top of the screen. + * @param activeWindow + */ private createWindows( activeWindow?: Windows // active window to show - ) { + ) + { this.addWindow(Windows.pause, new PauseWindow()); // create PauseWindow this.showActiveWindow(activeWindow); // show active window diff --git a/src/utils/gtUrlParams.ts b/src/utils/gtUrlParams.ts index 27058ab..551a762 100644 --- a/src/utils/gtUrlParams.ts +++ b/src/utils/gtUrlParams.ts @@ -1,8 +1,13 @@ -/** Helper to get value from url string */ +/** + * Helper to get value from url string + * @param param + */ export function getUrlParam( param: string // param name - ): string | null { +): string | null +{ const queryString = window.location.search; // get query string from url const urlParams = new URLSearchParams(queryString); // create url params object + return urlParams.get(param); // get param value } diff --git a/src/utils/preload.ts b/src/utils/preload.ts index 9d8ec1b..4a560b5 100644 --- a/src/utils/preload.ts +++ b/src/utils/preload.ts @@ -1,11 +1,12 @@ -import { Assets, ResolverAssetsArray } from "@pixi/assets"; -import { assetsManifest } from "../config/assets"; +import { Assets, ResolverAssetsArray } from '@pixi/assets'; +import { assetsManifest } from '../config/assets'; import { Spritesheet } from '@pixi/spritesheet'; -import { BaseTexture } from "@pixi/core"; +import { BaseTexture } from '@pixi/core'; import emojiData from '../config/emoji.json'; /** Initialize and start background loading of all assets */ -export async function initAssets() { +export async function initAssets() +{ // Init PixiJS assets with this asset manifest await Assets.init({ manifest: assetsManifest }); @@ -19,15 +20,19 @@ export async function initAssets() { Assets.backgroundLoadBundle(allBundles); } -export function isBundleLoaded(bundle: string) { +export function isBundleLoaded(bundle: string) +{ const bundleManifest = assetsManifest.bundles.find((b) => b.name === bundle); - if (!bundleManifest) { + if (!bundleManifest) + { return false; } - for (const asset of bundleManifest.assets as ResolverAssetsArray) { - if (!Assets.cache.has(asset.name as string)) { + for (const asset of bundleManifest.assets as ResolverAssetsArray) + { + if (!Assets.cache.has(asset.name as string)) + { return false; } } @@ -35,9 +40,12 @@ export function isBundleLoaded(bundle: string) { return true; } -export function areBundlesLoaded(bundles: string[]) { - for (const name of bundles) { - if (!isBundleLoaded(name)) { +export function areBundlesLoaded(bundles: string[]) +{ + for (const name of bundles) + { + if (!isBundleLoaded(name)) + { return false; } } @@ -45,7 +53,8 @@ export function areBundlesLoaded(bundles: string[]) { return true; } -export async function initEmojis() { +export async function initEmojis() +{ await Assets.loadBundle('emoji'); const spritesheet = new Spritesheet( @@ -54,4 +63,4 @@ export async function initEmojis() { ); await spritesheet.parse(); -} \ No newline at end of file +}