From b9a264c6b660c47af206936b69e2c8a16a889cc1 Mon Sep 17 00:00:00 2001 From: korifey91 Date: Sat, 18 Jan 2020 21:26:49 +0300 Subject: [PATCH 01/20] New Feature: add Character class --- src/classes/Character.js | 105 +++++++++++++++++++++++++++++++++++ src/classes/DynamicSprite.js | 16 ++++++ 2 files changed, 121 insertions(+) create mode 100644 src/classes/Character.js create mode 100644 src/classes/DynamicSprite.js diff --git a/src/classes/Character.js b/src/classes/Character.js new file mode 100644 index 0000000..f63ed57 --- /dev/null +++ b/src/classes/Character.js @@ -0,0 +1,105 @@ +/** + * ex. await new Character(); + * ex. new Character().then(character => { // some logic }) + */ +import DynamicSprite from './DynamicSprite'; + +export default class Character { + moveSettings = { + moveSprite: null, + moveRightCode: 'ArrowRight', + moveLeftCode: 'ArrowLeft', + alternativeMoveRightCode: 'KeyD', + alternativeMoveLeftCode: 'KeyA', + }; + + jumpSettings = { + jumpSprite: null, + jumpCode: 'ArrowUp', + alternativeJumpCode: 'KeyW', + }; + + attackSettings = { + attackSprite: null, + attackCode: 'Space', + }; + + position = { + x: null, + y: null, + }; + + constructor({ + mainSprite, + moveSettings: { + moveSprite, + moveSpriteMeta, + moveRightCode, + moveLeftCode, + alternativeMoveRightCode, + alternativeMoveLeftCode, + }, + jumpSettings: { + jumpSprite, + jumpSpriteMeta, + jumpCode, + alternativeJumpCode, + }, + attackSettings: { + attackSprite, + attackSpriteMeta, + attackCode, + }, + }) { + if (mainSprite == null) throw new Error('mainSprite is required!'); + // move codes override + if (moveRightCode) this.moveSettings.moveRightCode = moveRightCode; + if (moveLeftCode) this.moveSettings.moveLeftCode = moveLeftCode; + if (alternativeMoveRightCode) this.moveSettings.alternativeMoveRightCode = alternativeMoveRightCode; + if (alternativeMoveLeftCode) this.moveSettings.alternativeMoveLeftCode = alternativeMoveLeftCode; + // jump codes override + if (jumpCode) this.jumpSettings.jumpCode = jumpCode; + if (alternativeJumpCode) this.jumpSettings.alternativeJumpCode = alternativeJumpCode; + // attack code override + if (attackCode) this.attackSettings.attackCode = attackCode; + + return (async () => { + if (typeof mainSprite === 'string') this.mainSprite = await this._loadImage(mainSprite); + if (moveSprite instanceof Object) this.moveSettings.moveSprite = await this._loadDynamicSprite(moveSprite, moveSpriteMeta); + if (jumpSprite instanceof Object) this.jumpSettings.jumpSprite = await this._loadDynamicSprite(jumpSprite, jumpSpriteMeta); + if (attackSprite instanceof Object) this.attackSettings.attackSprite = await this._loadDynamicSprite(attackSprite, attackSpriteMeta); + + this._offscreenCanvas = new OffscreenCanvas(this.mainSprite.width, this.mainSprite.height); + + return this; + })(); + } + + move() {} + moveRight() {} + moveLeft() {} + jump() {} + stop() {} + attack() {} + + async _loadImage(src) { + const image = new Image(); + await new Promise((resolve, reject) => { + image.onload = resolve; + image.onerror = reject; + image.src = src; + }); + + return image; + } + + async _loadDynamicSprite(sprites, meta) { + if (sprites instanceof Array && sprites.length > 1) { + const promises = sprites.map(async ({ src }) => await this._loadImage(src)); + await Promise.all(promises); + + return new DynamicSprite(promises, meta); + } + else throw new Error('Sprites for dynamic motion should be an array of images') + } +} diff --git a/src/classes/DynamicSprite.js b/src/classes/DynamicSprite.js new file mode 100644 index 0000000..ff6adf0 --- /dev/null +++ b/src/classes/DynamicSprite.js @@ -0,0 +1,16 @@ +/** + * Dynamic Sprite is used to animate multiple images (like GIFs) + */ +export default class DynamicSprite { + meta = { + frameDuration: 300, + }; + + constructor(sprites, meta) { + if (sprites == null) throw new Error('Sprites is required!'); + if (sprites instanceof Array) { + this.frames = sprites; + this.meta = meta || this.meta; + } + } +} From 697aac14e5d4b14e7c5385e5585415c22caf9eab Mon Sep 17 00:00:00 2001 From: korifey91 Date: Sun, 19 Jan 2020 18:56:34 +0300 Subject: [PATCH 02/20] New Feature: add Character class Dev: add static create method for Character.js Dev: add validations for flipbooks Dev: add static create method for Flipbook.js Dev: add Sprite class --- src/classes/Character.js | 119 +++++++++++++++++++++-------------- src/classes/DynamicSprite.js | 16 ----- src/classes/Flipbook.js | 39 ++++++++++++ src/classes/Sprite.js | 18 ++++++ 4 files changed, 128 insertions(+), 64 deletions(-) delete mode 100644 src/classes/DynamicSprite.js create mode 100644 src/classes/Flipbook.js create mode 100644 src/classes/Sprite.js diff --git a/src/classes/Character.js b/src/classes/Character.js index f63ed57..e33e1c2 100644 --- a/src/classes/Character.js +++ b/src/classes/Character.js @@ -1,12 +1,15 @@ -/** - * ex. await new Character(); - * ex. new Character().then(character => { // some logic }) - */ -import DynamicSprite from './DynamicSprite'; +import Flipbook from './Flipbook'; +import Sprite from './Sprite'; + +const ERROR_HELP_TEXT = 'Use Character.create method to create character with a set of sprites'; export default class Character { + mainSettings = { + mainFlipbook: null, + }; + moveSettings = { - moveSprite: null, + moveFlipbook: null, moveRightCode: 'ArrowRight', moveLeftCode: 'ArrowLeft', alternativeMoveRightCode: 'KeyD', @@ -14,13 +17,13 @@ export default class Character { }; jumpSettings = { - jumpSprite: null, + jumpFlipbook: null, jumpCode: 'ArrowUp', alternativeJumpCode: 'KeyW', }; attackSettings = { - attackSprite: null, + attackFlipbook: null, attackCode: 'Space', }; @@ -29,29 +32,63 @@ export default class Character { y: null, }; + /** + * The main method to create a character + * @param {string | Object} mainFlipbook - sprite or + * @param moveSettings + * @param jumpSettings + * @param attackSettings + * @returns {Promise} + */ + static async create({ + mainFlipbook, + moveSettings = {}, + jumpSettings = {}, + attackSettings = {}, + }) { + const { moveFlipbook, moveFlipbookMeta } = moveSettings; + const { jumpFlipbook, jumpFlipbookMeta } = jumpSettings; + const { attackFlipbook, attackFlipbookMeta } = attackSettings; + + const characterSettings = { + moveSettings: { ...moveSettings }, + jumpSettings: { ...jumpSettings }, + attackSettings: { ...attackSettings }, + }; + if (typeof mainFlipbook === 'string') characterSettings.mainFlipbook = await new Sprite(mainFlipbook).load(); + if (mainFlipbook instanceof Object) characterSettings.mainFlipbook = await Flipbook.create(mainFlipbook); + if (moveFlipbook instanceof Object) characterSettings.moveSettings.moveFlipbook = await Flipbook.create(moveFlipbook, moveFlipbookMeta); + if (jumpFlipbook instanceof Object) characterSettings.jumpSettings.jumpFlipbook = await Flipbook.create(jumpFlipbook, jumpFlipbookMeta); + if (attackFlipbook instanceof Object) characterSettings.attackSettings.attackFlipbook = await Flipbook.create(attackFlipbook, attackFlipbookMeta); + + return new Character(characterSettings); + } + constructor({ - mainSprite, + mainFlipbook, moveSettings: { - moveSprite, - moveSpriteMeta, + moveFlipbook, moveRightCode, moveLeftCode, alternativeMoveRightCode, alternativeMoveLeftCode, - }, + } = {}, jumpSettings: { - jumpSprite, - jumpSpriteMeta, + jumpFlipbook, jumpCode, alternativeJumpCode, - }, + } = {}, attackSettings: { - attackSprite, - attackSpriteMeta, + attackFlipbook, attackCode, - }, + } = {}, }) { - if (mainSprite == null) throw new Error('mainSprite is required!'); + this._validateFlipbooks(mainFlipbook, moveFlipbook, jumpFlipbook, attackFlipbook); + + this.mainSettings.mainFlipbook = mainFlipbook; + this.moveSettings.moveFlipbook = moveFlipbook; + this.jumpSettings.jumpFlipbook = jumpFlipbook; + this.attackSettings.attackFlipbook = attackFlipbook; // move codes override if (moveRightCode) this.moveSettings.moveRightCode = moveRightCode; if (moveLeftCode) this.moveSettings.moveLeftCode = moveLeftCode; @@ -62,17 +99,8 @@ export default class Character { if (alternativeJumpCode) this.jumpSettings.alternativeJumpCode = alternativeJumpCode; // attack code override if (attackCode) this.attackSettings.attackCode = attackCode; - - return (async () => { - if (typeof mainSprite === 'string') this.mainSprite = await this._loadImage(mainSprite); - if (moveSprite instanceof Object) this.moveSettings.moveSprite = await this._loadDynamicSprite(moveSprite, moveSpriteMeta); - if (jumpSprite instanceof Object) this.jumpSettings.jumpSprite = await this._loadDynamicSprite(jumpSprite, jumpSpriteMeta); - if (attackSprite instanceof Object) this.attackSettings.attackSprite = await this._loadDynamicSprite(attackSprite, attackSpriteMeta); - - this._offscreenCanvas = new OffscreenCanvas(this.mainSprite.width, this.mainSprite.height); - - return this; - })(); + + this._offscreenCanvas = new OffscreenCanvas(this.mainFlipbook.width, this.mainFlipbook.height); } move() {} @@ -82,24 +110,19 @@ export default class Character { stop() {} attack() {} - async _loadImage(src) { - const image = new Image(); - await new Promise((resolve, reject) => { - image.onload = resolve; - image.onerror = reject; - image.src = src; - }); + _validateFlipbooks(mainFlipbook, moveFlipbook, jumpFlipbook, attackFlipbook) { + const isMainFlipbookValid = mainFlipbook != null && (mainFlipbook instanceof Sprite || mainFlipbook instanceof Flipbook); + const isMoveFlipbookValid = moveFlipbook != null && moveFlipbook instanceof Flipbook; + const isJumpFlipbookValid = jumpFlipbook != null && jumpFlipbook instanceof Flipbook; + const isAttackFlipbookValid = attackFlipbook != null && attackFlipbook instanceof Flipbook; - return image; - } - - async _loadDynamicSprite(sprites, meta) { - if (sprites instanceof Array && sprites.length > 1) { - const promises = sprites.map(async ({ src }) => await this._loadImage(src)); - await Promise.all(promises); - - return new DynamicSprite(promises, meta); - } - else throw new Error('Sprites for dynamic motion should be an array of images') + const invalidFlipbooks = []; + + if (!isMainFlipbookValid) invalidFlipbooks.push('mainFlipbook'); + if (!isMoveFlipbookValid) invalidFlipbooks.push('moveFlipbook'); + if (!isJumpFlipbookValid) invalidFlipbooks.push('jumpFlipbook'); + if (!isAttackFlipbookValid) invalidFlipbooks.push('attackFlipbook'); + + if (invalidFlipbooks.length) throw new Error(`${invalidFlipbooks} are required! ${ERROR_HELP_TEXT}`) } } diff --git a/src/classes/DynamicSprite.js b/src/classes/DynamicSprite.js deleted file mode 100644 index ff6adf0..0000000 --- a/src/classes/DynamicSprite.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Dynamic Sprite is used to animate multiple images (like GIFs) - */ -export default class DynamicSprite { - meta = { - frameDuration: 300, - }; - - constructor(sprites, meta) { - if (sprites == null) throw new Error('Sprites is required!'); - if (sprites instanceof Array) { - this.frames = sprites; - this.meta = meta || this.meta; - } - } -} diff --git a/src/classes/Flipbook.js b/src/classes/Flipbook.js new file mode 100644 index 0000000..413c91a --- /dev/null +++ b/src/classes/Flipbook.js @@ -0,0 +1,39 @@ +/** + * Flipbook is used to animate multiple images (like GIFs) + */ +import Sprite from './Sprite'; + +export default class Flipbook { + meta = { + frameDuration: 300, + }; + + /** + * The main method to create Flipbook. + * @param {string[]} sprites - array of image links + * @param {Object} meta - meta info for Flippbok + * @param {number} meta.frameDuration - duration between frames + * @returns {Promise} + */ + static async create(sprites, meta) { + if (sprites == null) throw new Error('Sprites are required!'); + + let flipbookFrames; + + if (sprites instanceof Array && sprites.length > 1) { + flipbookFrames = sprites.map(sprite => new Sprite(sprite).load()); + await Promise.all(flipbookFrames); + } + else throw new TypeError('Sprites of Flipbook should be an array of image links'); + + return new Flipbook(flipbookFrames, meta); + } + + constructor(sprites, meta) { + if (sprites instanceof Array && sprites.every(sprite => (sprite instanceof Sprite))) { + this.frames = sprites; + if (meta && meta.frameDuration) this.meta = meta; + } + else throw new Error('Use Flipbook.create method') + } +} diff --git a/src/classes/Sprite.js b/src/classes/Sprite.js new file mode 100644 index 0000000..9e5d083 --- /dev/null +++ b/src/classes/Sprite.js @@ -0,0 +1,18 @@ +export default class Sprite { + constructor(src) { + if (typeof src === 'string') this.src = src; + else throw new TypeError('Sprite source link should be a string'); + } + + async load() { + const image = new Image(); + await new Promise((resolve, reject) => { + image.onload = resolve; + image.onerror = reject; + image.src = this.src; + }); + this.image = image; + + return this; + } +} From 24e50a4d7741d0679e5d7f2050d8e8612d3867df Mon Sep 17 00:00:00 2001 From: korifey91 Date: Sun, 19 Jan 2020 19:04:26 +0300 Subject: [PATCH 03/20] New Feature: add Character class Dev: add jsdoc description for Character.create method --- src/classes/Character.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/classes/Character.js b/src/classes/Character.js index e33e1c2..86f2971 100644 --- a/src/classes/Character.js +++ b/src/classes/Character.js @@ -34,10 +34,20 @@ export default class Character { /** * The main method to create a character - * @param {string | Object} mainFlipbook - sprite or - * @param moveSettings - * @param jumpSettings - * @param attackSettings + * @param {Sprite | Flipbook} mainFlipbook - Sprite or Flipbook instance + * @param {Object} moveSettings - settings for move action + * @param {Flipbook} moveSettings.moveFlipbook + * @param {string} moveSettings.moveRightCode - main right move action code + * @param {string} moveSettings.moveLeftCode - main left move action code + * @param {string} moveSettings.alternativeMoveRightCode - alternative right move action code + * @param {string} moveSettings.alternativeMoveLeftCode - alternative left move action code + * @param {Object} jumpSettings - settings for jump action + * @param {Flipbook} jumpSettings.jumpFlipbook + * @param {string} jumpSettings.jumpCode - main jump action code + * @param {string} jumpSettings.alternativeJumpCode - alternative jump action code + * @param {Object} attackSettings - settings for attack actions + * @param {Flipbook} attackSettings.attackFlipbook + * @param {string} attackSettings.attackCode - main attack action code * @returns {Promise} */ static async create({ From 633f8888fbe191580dd84f4f3792c356ec34f9ab Mon Sep 17 00:00:00 2001 From: korifey91 Date: Sun, 19 Jan 2020 19:15:50 +0300 Subject: [PATCH 04/20] New Feature: add Character class Dev: fix jsdoc description for Character.create method Dev: add jsdoc description for Character constructor --- src/classes/Character.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/classes/Character.js b/src/classes/Character.js index 86f2971..e604138 100644 --- a/src/classes/Character.js +++ b/src/classes/Character.js @@ -34,19 +34,19 @@ export default class Character { /** * The main method to create a character - * @param {Sprite | Flipbook} mainFlipbook - Sprite or Flipbook instance + * @param {string | string[]} mainFlipbook - url or array of url * @param {Object} moveSettings - settings for move action - * @param {Flipbook} moveSettings.moveFlipbook + * @param {string[]} moveSettings.moveFlipbook - array of url * @param {string} moveSettings.moveRightCode - main right move action code * @param {string} moveSettings.moveLeftCode - main left move action code * @param {string} moveSettings.alternativeMoveRightCode - alternative right move action code * @param {string} moveSettings.alternativeMoveLeftCode - alternative left move action code * @param {Object} jumpSettings - settings for jump action - * @param {Flipbook} jumpSettings.jumpFlipbook + * @param {string[]} jumpSettings.jumpFlipbook - array of url * @param {string} jumpSettings.jumpCode - main jump action code * @param {string} jumpSettings.alternativeJumpCode - alternative jump action code * @param {Object} attackSettings - settings for attack actions - * @param {Flipbook} attackSettings.attackFlipbook + * @param {string[]} attackSettings.attackFlipbook - array of url * @param {string} attackSettings.attackCode - main attack action code * @returns {Promise} */ @@ -74,6 +74,23 @@ export default class Character { return new Character(characterSettings); } + /** + * @param {Sprite | Flipbook} mainFlipbook - Sprite or Flipbook instance + * @param {Object} moveSettings - settings for move action + * @param {Flipbook} moveSettings.moveFlipbook + * @param {string} moveSettings.moveRightCode - main right move action code + * @param {string} moveSettings.moveLeftCode - main left move action code + * @param {string} moveSettings.alternativeMoveRightCode - alternative right move action code + * @param {string} moveSettings.alternativeMoveLeftCode - alternative left move action code + * @param {Object} jumpSettings - settings for jump action + * @param {Flipbook} jumpSettings.jumpFlipbook + * @param {string} jumpSettings.jumpCode - main jump action code + * @param {string} jumpSettings.alternativeJumpCode - alternative jump action code + * @param {Object} attackSettings - settings for attack actions + * @param {Flipbook} attackSettings.attackFlipbook + * @param {string} attackSettings.attackCode - main attack action code + * @returns {Character} + */ constructor({ mainFlipbook, moveSettings: { From d85dc25085ff9981f0f66b68030a742a7e3022b2 Mon Sep 17 00:00:00 2001 From: korifey91 Date: Sun, 19 Jan 2020 19:31:30 +0300 Subject: [PATCH 05/20] New Feature: add Character class Dev: refactor Flipbook.js --- src/classes/Flipbook.js | 44 ++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/classes/Flipbook.js b/src/classes/Flipbook.js index 413c91a..77708fb 100644 --- a/src/classes/Flipbook.js +++ b/src/classes/Flipbook.js @@ -1,39 +1,43 @@ +import Sprite from './Sprite'; /** * Flipbook is used to animate multiple images (like GIFs) */ -import Sprite from './Sprite'; - export default class Flipbook { meta = { frameDuration: 300, }; + _spriteUrls = []; + _sprites = []; /** * The main method to create Flipbook. * @param {string[]} sprites - array of image links - * @param {Object} meta - meta info for Flippbok - * @param {number} meta.frameDuration - duration between frames + * @param {Object} options - meta info for Flippbok + * @param {number} options.frameDuration - duration between frames * @returns {Promise} */ - static async create(sprites, meta) { + static async create(sprites, options) { + const instance = new this(sprites, options); + await instance.init(); + return instance; + } + + constructor(sprites, options) { if (sprites == null) throw new Error('Sprites are required!'); - - let flipbookFrames; - - if (sprites instanceof Array && sprites.length > 1) { - flipbookFrames = sprites.map(sprite => new Sprite(sprite).load()); - await Promise.all(flipbookFrames); - } - else throw new TypeError('Sprites of Flipbook should be an array of image links'); - - return new Flipbook(flipbookFrames, meta); + if (options && options.frameDuration) this.options = options; + this._spriteUrls = sprites; + } + + async init() { + for (const url of this._spriteUrls) this._sprites.push(new Sprite(url)); + await this.load(); } - constructor(sprites, meta) { - if (sprites instanceof Array && sprites.every(sprite => (sprite instanceof Sprite))) { - this.frames = sprites; - if (meta && meta.frameDuration) this.meta = meta; + async load() { + try { + await Promise.all(this._sprites.map((sprite) => sprite.load())); + } catch (error) { + throw new TypeError('Sprites of Flipbook should be an array of image links'); } - else throw new Error('Use Flipbook.create method') } } From 2d29838b505f77a2693c4acd33727bfbf5c839fa Mon Sep 17 00:00:00 2001 From: korifey91 Date: Sun, 19 Jan 2020 19:51:10 +0300 Subject: [PATCH 06/20] New Feature: add Character class Dev: add start\stop methods for Flipbook.js --- src/classes/Character.js | 8 ++++---- src/classes/Flipbook.js | 26 ++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/classes/Character.js b/src/classes/Character.js index e604138..8d92b3e 100644 --- a/src/classes/Character.js +++ b/src/classes/Character.js @@ -66,10 +66,10 @@ export default class Character { attackSettings: { ...attackSettings }, }; if (typeof mainFlipbook === 'string') characterSettings.mainFlipbook = await new Sprite(mainFlipbook).load(); - if (mainFlipbook instanceof Object) characterSettings.mainFlipbook = await Flipbook.create(mainFlipbook); - if (moveFlipbook instanceof Object) characterSettings.moveSettings.moveFlipbook = await Flipbook.create(moveFlipbook, moveFlipbookMeta); - if (jumpFlipbook instanceof Object) characterSettings.jumpSettings.jumpFlipbook = await Flipbook.create(jumpFlipbook, jumpFlipbookMeta); - if (attackFlipbook instanceof Object) characterSettings.attackSettings.attackFlipbook = await Flipbook.create(attackFlipbook, attackFlipbookMeta); + if (mainFlipbook instanceof Array) characterSettings.mainFlipbook = await Flipbook.create(mainFlipbook); + if (moveFlipbook instanceof Array) characterSettings.moveSettings.moveFlipbook = await Flipbook.create(moveFlipbook, moveFlipbookMeta); + if (jumpFlipbook instanceof Array) characterSettings.jumpSettings.jumpFlipbook = await Flipbook.create(jumpFlipbook, jumpFlipbookMeta); + if (attackFlipbook instanceof Array) characterSettings.attackSettings.attackFlipbook = await Flipbook.create(attackFlipbook, attackFlipbookMeta); return new Character(characterSettings); } diff --git a/src/classes/Flipbook.js b/src/classes/Flipbook.js index 77708fb..347b262 100644 --- a/src/classes/Flipbook.js +++ b/src/classes/Flipbook.js @@ -3,11 +3,14 @@ import Sprite from './Sprite'; * Flipbook is used to animate multiple images (like GIFs) */ export default class Flipbook { - meta = { + options = { frameDuration: 300, }; _spriteUrls = []; _sprites = []; + _currentSprite = null; + _currentSpriteIndex = 0; + _timer = null; /** * The main method to create Flipbook. @@ -23,7 +26,7 @@ export default class Flipbook { } constructor(sprites, options) { - if (sprites == null) throw new Error('Sprites are required!'); + if (sprites == null || sprites.length < 1) throw new Error('Sprites are required!'); if (options && options.frameDuration) this.options = options; this._spriteUrls = sprites; } @@ -40,4 +43,23 @@ export default class Flipbook { throw new TypeError('Sprites of Flipbook should be an array of image links'); } } + + get currentSprite() { + return this._currentSprite; + } + + start() { + this._timer = setInterval(() => { + this._currentSprite = this._sprites[this._currentSpriteIndex]; + const nextSpriteIndex = this._currentSpriteIndex + 1; + + if (nextSpriteIndex > this._sprites.length) this._currentSpriteIndex = 0; + else this._currentSpriteIndex = nextSpriteIndex; + }, this.options.frameDuration) + } + + stop() { + clearInterval(this._timer); + this._currentSpriteIndex = 0; + } } From 9ca7e1f2d0e3512c257f3c3ac1efdb3bb3377e74 Mon Sep 17 00:00:00 2001 From: korifey91 Date: Sun, 19 Jan 2020 19:52:09 +0300 Subject: [PATCH 07/20] New Feature: add Character class Dev: fix stop method in Flipbook.js --- src/classes/Flipbook.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/classes/Flipbook.js b/src/classes/Flipbook.js index 347b262..f097fb5 100644 --- a/src/classes/Flipbook.js +++ b/src/classes/Flipbook.js @@ -61,5 +61,6 @@ export default class Flipbook { stop() { clearInterval(this._timer); this._currentSpriteIndex = 0; + this._currentSprite = this._sprites[this._currentSpriteIndex]; } } From 8aa014efc6f3b92c2d7b125baee802fed77c0306 Mon Sep 17 00:00:00 2001 From: korifey91 Date: Sun, 19 Jan 2020 21:44:48 +0300 Subject: [PATCH 08/20] New Feature: add Character class Dev: add position to Character.js --- src/classes/Character.js | 24 ++++++++++++++++++++---- src/classes/Flipbook.js | 9 +++++++++ src/classes/Sprite.js | 8 ++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/classes/Character.js b/src/classes/Character.js index 8d92b3e..fd94f23 100644 --- a/src/classes/Character.js +++ b/src/classes/Character.js @@ -28,12 +28,15 @@ export default class Character { }; position = { - x: null, - y: null, + x: 0, + y: 0, }; /** * The main method to create a character + * @param {Object} position - initial Character position + * @param {number} position.x - canvas coordinates + * @param {number} position.y - canvas coordinates * @param {string | string[]} mainFlipbook - url or array of url * @param {Object} moveSettings - settings for move action * @param {string[]} moveSettings.moveFlipbook - array of url @@ -51,6 +54,7 @@ export default class Character { * @returns {Promise} */ static async create({ + position, mainFlipbook, moveSettings = {}, jumpSettings = {}, @@ -61,6 +65,7 @@ export default class Character { const { attackFlipbook, attackFlipbookMeta } = attackSettings; const characterSettings = { + position, moveSettings: { ...moveSettings }, jumpSettings: { ...jumpSettings }, attackSettings: { ...attackSettings }, @@ -75,6 +80,9 @@ export default class Character { } /** + * @param {Object} position - initial Character position + * @param {number} position.x - canvas coordinates + * @param {number} position.y - canvas coordinates * @param {Sprite | Flipbook} mainFlipbook - Sprite or Flipbook instance * @param {Object} moveSettings - settings for move action * @param {Flipbook} moveSettings.moveFlipbook @@ -92,6 +100,7 @@ export default class Character { * @returns {Character} */ constructor({ + position, mainFlipbook, moveSettings: { moveFlipbook, @@ -126,8 +135,15 @@ export default class Character { if (alternativeJumpCode) this.jumpSettings.alternativeJumpCode = alternativeJumpCode; // attack code override if (attackCode) this.attackSettings.attackCode = attackCode; - - this._offscreenCanvas = new OffscreenCanvas(this.mainFlipbook.width, this.mainFlipbook.height); + + // initial position overrides + if (typeof position.x === 'number') this.position.x = position.x; + if (typeof position.y === 'number') this.position.y = position.y; + + // TODO change offscreencanvas + // this._offscreenCanvas = new OffscreenCanvas(mainFlipbook.width, mainFlipbook.height); + // this._renderer = this._offscreenCanvas.getContext('2d'); + // this._renderer.imageSmoothingEnabled = false; } move() {} diff --git a/src/classes/Flipbook.js b/src/classes/Flipbook.js index f097fb5..fd11b41 100644 --- a/src/classes/Flipbook.js +++ b/src/classes/Flipbook.js @@ -34,6 +34,7 @@ export default class Flipbook { async init() { for (const url of this._spriteUrls) this._sprites.push(new Sprite(url)); await this.load(); + this._currentSprite = this._sprites[0]; } async load() { @@ -63,4 +64,12 @@ export default class Flipbook { this._currentSpriteIndex = 0; this._currentSprite = this._sprites[this._currentSpriteIndex]; } + + get width() { + return this._currentSprite.width; + } + + get height() { + return this._currentSprite.height; + } } diff --git a/src/classes/Sprite.js b/src/classes/Sprite.js index 9e5d083..8f9a8b4 100644 --- a/src/classes/Sprite.js +++ b/src/classes/Sprite.js @@ -15,4 +15,12 @@ export default class Sprite { return this; } + + get width() { + return this.image.width; + } + + get height() { + return this.image.height; + } } From f878ee073b2c13b7bad5e7fccbafabe79048f940 Mon Sep 17 00:00:00 2001 From: korifey91 Date: Mon, 20 Jan 2020 19:14:56 +0300 Subject: [PATCH 09/20] New Feature: add Character class Dev: add _offscreenCanvas --- src/classes/Character.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/classes/Character.js b/src/classes/Character.js index fd94f23..190367c 100644 --- a/src/classes/Character.js +++ b/src/classes/Character.js @@ -140,10 +140,11 @@ export default class Character { if (typeof position.x === 'number') this.position.x = position.x; if (typeof position.y === 'number') this.position.y = position.y; - // TODO change offscreencanvas - // this._offscreenCanvas = new OffscreenCanvas(mainFlipbook.width, mainFlipbook.height); - // this._renderer = this._offscreenCanvas.getContext('2d'); - // this._renderer.imageSmoothingEnabled = false; + this._offscreenCanvas = document.createElement('canvas'); + this._offscreenCanvas.width = mainFlipbook.width; + this._offscreenCanvas.height = mainFlipbook.height; + this._renderer = this._offscreenCanvas.getContext('2d'); + this._renderer.imageSmoothingEnabled = false; } move() {} From bce09c8ef7512822103efbf021e7d9f1283e7676 Mon Sep 17 00:00:00 2001 From: korifey91 Date: Mon, 20 Jan 2020 22:07:22 +0300 Subject: [PATCH 10/20] New Feature: add Character class Dev: add actionHandling(not finished yet) - Character.js Dev: add _offscreenCanvas - Flipbook.js Dev: add currentSprite getter - Sprite.js --- src/classes/Character.js | 138 +++++++++++++++++++++++++++++++++++---- src/classes/Flipbook.js | 33 +++++++--- src/classes/Sprite.js | 10 ++- 3 files changed, 158 insertions(+), 23 deletions(-) diff --git a/src/classes/Character.js b/src/classes/Character.js index 190367c..8b682c1 100644 --- a/src/classes/Character.js +++ b/src/classes/Character.js @@ -4,8 +4,33 @@ import Sprite from './Sprite'; const ERROR_HELP_TEXT = 'Use Character.create method to create character with a set of sprites'; export default class Character { + _coreElement = null; + _actionHandlerHash = { + [this.moveSettings.moveLeftCode]: this.moveLeft, + [this.moveSettings.alternativeMoveLeftCode]: this.moveLeft, + [this.moveSettings.moveRightCode]: this.moveRight, + [this.moveSettings.alternativeMoveRightCode]: this.moveRight, + [this.jumpSettings.jumpCode]: this.jump, + [this.jumpSettings.alternativeJumpCode]: this.jump, + [this.attackSettings.attackCode]: this.attack, + }; + _prevActionType = 'STOP'; + _currentActionType = 'STOP'; + _hooks = { + onStop: null, + onMove: null, + }; + + flipbook = null; mainSettings = { mainFlipbook: null, + /** + * The function should return a boolean value which indicates can a Character move or not. + * + * @callback checkPosition + * @returns {boolean} + */ + checkPosition: null, }; moveSettings = { @@ -33,11 +58,13 @@ export default class Character { }; /** - * The main method to create a character + * @constructs The main method to create a character + * @param {HTMLCanvasElement} coreElement - canvas on which Character will be rendered * @param {Object} position - initial Character position * @param {number} position.x - canvas coordinates * @param {number} position.y - canvas coordinates * @param {string | string[]} mainFlipbook - url or array of url + * @param {checkPosition} checkPosition - function to check any collisions and possibility to move. * @param {Object} moveSettings - settings for move action * @param {string[]} moveSettings.moveFlipbook - array of url * @param {string} moveSettings.moveRightCode - main right move action code @@ -54,8 +81,10 @@ export default class Character { * @returns {Promise} */ static async create({ + coreElement, position, mainFlipbook, + checkPosition= () => true, moveSettings = {}, jumpSettings = {}, attackSettings = {}, @@ -65,13 +94,15 @@ export default class Character { const { attackFlipbook, attackFlipbookMeta } = attackSettings; const characterSettings = { + coreElement, position, + checkPosition, moveSettings: { ...moveSettings }, jumpSettings: { ...jumpSettings }, attackSettings: { ...attackSettings }, }; if (typeof mainFlipbook === 'string') characterSettings.mainFlipbook = await new Sprite(mainFlipbook).load(); - if (mainFlipbook instanceof Array) characterSettings.mainFlipbook = await Flipbook.create(mainFlipbook); + else if (mainFlipbook instanceof Array) characterSettings.mainFlipbook = await Flipbook.create(mainFlipbook); if (moveFlipbook instanceof Array) characterSettings.moveSettings.moveFlipbook = await Flipbook.create(moveFlipbook, moveFlipbookMeta); if (jumpFlipbook instanceof Array) characterSettings.jumpSettings.jumpFlipbook = await Flipbook.create(jumpFlipbook, jumpFlipbookMeta); if (attackFlipbook instanceof Array) characterSettings.attackSettings.attackFlipbook = await Flipbook.create(attackFlipbook, attackFlipbookMeta); @@ -80,10 +111,12 @@ export default class Character { } /** + * @param {HTMLCanvasElement} coreElement - canvas on which Character will be rendered * @param {Object} position - initial Character position * @param {number} position.x - canvas coordinates * @param {number} position.y - canvas coordinates * @param {Sprite | Flipbook} mainFlipbook - Sprite or Flipbook instance + * @param {checkPosition} checkPosition - function to check any collisions and possibility to move. * @param {Object} moveSettings - settings for move action * @param {Flipbook} moveSettings.moveFlipbook * @param {string} moveSettings.moveRightCode - main right move action code @@ -100,8 +133,10 @@ export default class Character { * @returns {Character} */ constructor({ + coreElement, position, mainFlipbook, + checkPosition, moveSettings: { moveFlipbook, moveRightCode, @@ -119,6 +154,9 @@ export default class Character { attackCode, } = {}, }) { + if (coreElement) this._coreElement = coreElement; + else throw new Error('coreElement is required for Character!'); + this._validateFlipbooks(mainFlipbook, moveFlipbook, jumpFlipbook, attackFlipbook); this.mainSettings.mainFlipbook = mainFlipbook; @@ -140,19 +178,58 @@ export default class Character { if (typeof position.x === 'number') this.position.x = position.x; if (typeof position.y === 'number') this.position.y = position.y; - this._offscreenCanvas = document.createElement('canvas'); - this._offscreenCanvas.width = mainFlipbook.width; - this._offscreenCanvas.height = mainFlipbook.height; - this._renderer = this._offscreenCanvas.getContext('2d'); - this._renderer.imageSmoothingEnabled = false; + this._createOffscreenCanvas(); + this._initListeners(); + } + + get currentActionType() { + return this._currentActionType; + } + + set currentActionType(actionName) { + if (this._currentActionType === actionName) return; + if (this._prevActionType !== this.currentActionType) this._prevActionType = this._currentActionType; + if (actionName === 'STOP') { + if (this._hooks.onStop instanceof Function) this._hooks.onStop(); + } + this._currentActionType = actionName; } move() {} - moveRight() {} - moveLeft() {} - jump() {} - stop() {} - attack() {} + moveRight() { + this.currentActionType = 'MOVE_RIGHT'; + this.flipbook = this.moveSettings.moveFlipbook; + this.moveSettings.moveFlipbook.start(); + } + moveLeft() { + this.currentActionType = 'MOVE_LEFT'; + } + jump() { + this.currentActionType = 'JUMP'; + this.flipbook = this.jumpSettings.jumpFlipbook; + this.jumpSettings.jumpFlipbook.start(); + } + stop() { + this.currentActionType = 'STOP'; + this.moveSettings.moveFlipbook.stop(); + this.jumpSettings.jumpFlipbook.stop(); + this.attackSettings.attackFlipbook.stop(); + this.flipbook = this.mainSettings.mainFlipbook; + if (this.mainSettings.mainFlipbook instanceof Flipbook) this.mainSettings.mainFlipbook.start(); + } + attack() { + this.currentActionType = 'ATTACK'; + this.flipbook = this.attackSettings.attackFlipbook; + this.attackSettings.attackFlipbook.start(); + } + + /** + * You have to call destroy method if a Character will disappear to prevent memory leaks + */ + destroy() { + this._coreElement.removeEventListener('keydown', this._keydownEventHandler); + this._coreElement.removeEventListener('keyup', this._keyupEventHandler); + } _validateFlipbooks(mainFlipbook, moveFlipbook, jumpFlipbook, attackFlipbook) { const isMainFlipbookValid = mainFlipbook != null && (mainFlipbook instanceof Sprite || mainFlipbook instanceof Flipbook); @@ -169,4 +246,41 @@ export default class Character { if (invalidFlipbooks.length) throw new Error(`${invalidFlipbooks} are required! ${ERROR_HELP_TEXT}`) } + + _createOffscreenCanvas() { + this._offscreenCanvas = document.createElement('canvas'); + this._offscreenCanvas.width = this.mainSettings.mainFlipbook.width; + this._offscreenCanvas.height = this.mainSettings.mainFlipbook.height; + this._renderer = this._offscreenCanvas.getContext('2d'); + this._renderer.imageSmoothingEnabled = false; + } + + _initListeners() { + this._coreElement.addEventListener('keydown', this._keydownEventHandler, { passive: true }); + this._coreElement.addEventListener('keyup', this._keyupEventHandler, { passive: true }); + } + + _keydownEventHandler(event) { + this._actionHandlerHash[event.code](event); + } + + _keyupEventHandler(event) { + this.stop(); + } + + get _width() { + return this.flipbook.currentSprite.width; + } + + get _height() { + return this.flipbook.currentSprite.height; + } + + _changePosition(dx = 0, dy = 0) { + if (this.mainSettings.checkPosition(this.position.x + dx, this.position.y + dy, this._width, this._height)) { + this.position.x += dx; + this.position.y += dy; + if (this._hooks.onMove instanceof Function) this._hooks.onMove(); + } + } } diff --git a/src/classes/Flipbook.js b/src/classes/Flipbook.js index fd11b41..38fe4be 100644 --- a/src/classes/Flipbook.js +++ b/src/classes/Flipbook.js @@ -5,18 +5,20 @@ import Sprite from './Sprite'; export default class Flipbook { options = { frameDuration: 300, + mirror: false, }; _spriteUrls = []; _sprites = []; _currentSprite = null; _currentSpriteIndex = 0; _timer = null; - + /** - * The main method to create Flipbook. + * @constructs The main method to create Flipbook. * @param {string[]} sprites - array of image links - * @param {Object} options - meta info for Flippbok - * @param {number} options.frameDuration - duration between frames + * @param {Object} [options] - meta info for Flippbok + * @param {number} [options.frameDuration=300] - duration between frames + * @param {boolean} [options.mirror=false] - if true all sprites would be mirrored * @returns {Promise} */ static async create(sprites, options) { @@ -35,6 +37,7 @@ export default class Flipbook { for (const url of this._spriteUrls) this._sprites.push(new Sprite(url)); await this.load(); this._currentSprite = this._sprites[0]; + this._createOffscreenCanvas(); } async load() { @@ -46,12 +49,14 @@ export default class Flipbook { } get currentSprite() { - return this._currentSprite; + return this._offscreenCanvas; } - + // TODO need to implement drawing on _offscreenCanvas when _currentSprite changes. And consider Mirror option. + // it should be separate private method. start() { this._timer = setInterval(() => { this._currentSprite = this._sprites[this._currentSpriteIndex]; + this._updateSize(); const nextSpriteIndex = this._currentSpriteIndex + 1; if (nextSpriteIndex > this._sprites.length) this._currentSpriteIndex = 0; @@ -66,10 +71,22 @@ export default class Flipbook { } get width() { - return this._currentSprite.width; + return this._offscreenCanvas.width; } get height() { - return this._currentSprite.height; + return this._offscreenCanvas.height; + } + + _createOffscreenCanvas() { + this._offscreenCanvas = document.createElement('canvas'); + this._updateSize(); + this._renderer = this._offscreenCanvas.getContext('2d'); + this._renderer.imageSmoothingEnabled = false; + } + + _updateSize() { + this._offscreenCanvas.width = this._currentSprite.width; + this._offscreenCanvas.height = this._currentSprite.height; } } diff --git a/src/classes/Sprite.js b/src/classes/Sprite.js index 8f9a8b4..181d5f0 100644 --- a/src/classes/Sprite.js +++ b/src/classes/Sprite.js @@ -11,16 +11,20 @@ export default class Sprite { image.onerror = reject; image.src = this.src; }); - this.image = image; + this._image = image; return this; } get width() { - return this.image.width; + return this._image.width; } get height() { - return this.image.height; + return this._image.height; + } + + get currentSprite() { + return this._image; } } From 6f412d05bc09692eddec37505c52ebbdab66f3f4 Mon Sep 17 00:00:00 2001 From: korifey91 Date: Tue, 21 Jan 2020 21:30:03 +0300 Subject: [PATCH 11/20] New Feature: add Character class Dev: add speed for Character.js Dev: add functionality to set sprites for different directions in Character.js Dev: add render method in Character.js Dev: add _render method in Flipbook.js --- src/classes/Character.js | 154 +++++++++++++++++++++++++++++---------- src/classes/Flipbook.js | 22 +++++- 2 files changed, 136 insertions(+), 40 deletions(-) diff --git a/src/classes/Character.js b/src/classes/Character.js index 8b682c1..a04b6fa 100644 --- a/src/classes/Character.js +++ b/src/classes/Character.js @@ -6,16 +6,17 @@ const ERROR_HELP_TEXT = 'Use Character.create method to create character with a export default class Character { _coreElement = null; _actionHandlerHash = { - [this.moveSettings.moveLeftCode]: this.moveLeft, - [this.moveSettings.alternativeMoveLeftCode]: this.moveLeft, - [this.moveSettings.moveRightCode]: this.moveRight, - [this.moveSettings.alternativeMoveRightCode]: this.moveRight, - [this.jumpSettings.jumpCode]: this.jump, - [this.jumpSettings.alternativeJumpCode]: this.jump, - [this.attackSettings.attackCode]: this.attack, + [this.moveSettings.moveLeftCode]: this.moveLeft.bind(this), + [this.moveSettings.alternativeMoveLeftCode]: this.moveLeft.bind(this), + [this.moveSettings.moveRightCode]: this.moveRight.bind(this), + [this.moveSettings.alternativeMoveRightCode]: this.moveRight.bind(this), + [this.jumpSettings.jumpCode]: this.jump.bind(this), + [this.jumpSettings.alternativeJumpCode]: this.jump.bind(this), + [this.attackSettings.attackCode]: this.attack.bind(this), }; _prevActionType = 'STOP'; _currentActionType = 'STOP'; + _direction = 'RIGHT'; _hooks = { onStop: null, onMove: null, @@ -31,10 +32,12 @@ export default class Character { * @returns {boolean} */ checkPosition: null, + speed: null, }; moveSettings = { - moveFlipbook: null, + moveRightFlipbook: null, + moveLeftFlipbook: null, moveRightCode: 'ArrowRight', moveLeftCode: 'ArrowLeft', alternativeMoveRightCode: 'KeyD', @@ -63,8 +66,10 @@ export default class Character { * @param {Object} position - initial Character position * @param {number} position.x - canvas coordinates * @param {number} position.y - canvas coordinates - * @param {string | string[]} mainFlipbook - url or array of url - * @param {checkPosition} checkPosition - function to check any collisions and possibility to move. + * @param {Object} mainSettings - main Character settings + * @param {string | string[]} mainSettings.mainFlipbook - url or array of url + * @param {checkPosition} mainSettings.checkPosition - function to check any collisions and possibility to move. + * @param {number} mainSettings.speed - speed of a Character in px per second * @param {Object} moveSettings - settings for move action * @param {string[]} moveSettings.moveFlipbook - array of url * @param {string} moveSettings.moveRightCode - main right move action code @@ -83,8 +88,7 @@ export default class Character { static async create({ coreElement, position, - mainFlipbook, - checkPosition= () => true, + mainSettings, moveSettings = {}, jumpSettings = {}, attackSettings = {}, @@ -96,16 +100,34 @@ export default class Character { const characterSettings = { coreElement, position, - checkPosition, + mainSettings, moveSettings: { ...moveSettings }, jumpSettings: { ...jumpSettings }, attackSettings: { ...attackSettings }, }; - if (typeof mainFlipbook === 'string') characterSettings.mainFlipbook = await new Sprite(mainFlipbook).load(); - else if (mainFlipbook instanceof Array) characterSettings.mainFlipbook = await Flipbook.create(mainFlipbook); - if (moveFlipbook instanceof Array) characterSettings.moveSettings.moveFlipbook = await Flipbook.create(moveFlipbook, moveFlipbookMeta); - if (jumpFlipbook instanceof Array) characterSettings.jumpSettings.jumpFlipbook = await Flipbook.create(jumpFlipbook, jumpFlipbookMeta); - if (attackFlipbook instanceof Array) characterSettings.attackSettings.attackFlipbook = await Flipbook.create(attackFlipbook, attackFlipbookMeta); + if (typeof mainSettings.mainFlipbook === 'string') characterSettings.mainSettings.mainFlipbook = await new Sprite(mainSettings.mainFlipbook).load(); + else if (mainSettings.mainFlipbook instanceof Array) characterSettings.mainSettings.mainFlipbook = await Flipbook.create(mainSettings.mainFlipbook); + if (moveFlipbook instanceof Array) { + characterSettings.moveSettings.moveRightFlipbook = await Flipbook.create(moveFlipbook, moveFlipbookMeta); + characterSettings.moveSettings.moveLeftFlipbook = await Flipbook.create(moveFlipbook, { + ...moveFlipbookMeta, + mirror: true, + }); + } + if (jumpFlipbook instanceof Array) { + characterSettings.jumpSettings.jumpRightFlipbook = await Flipbook.create(jumpFlipbook, jumpFlipbookMeta); + characterSettings.jumpSettings.jumpLeftFlipbook = await Flipbook.create(jumpFlipbook, { + ...jumpFlipbookMeta, + mirror: true, + }); + } + if (attackFlipbook instanceof Array) { + characterSettings.attackSettings.attackRightFlipbook = await Flipbook.create(attackFlipbook, attackFlipbookMeta); + characterSettings.attackSettings.attackLeftFlipbook = await Flipbook.create(attackFlipbook, { + ...attackFlipbookMeta, + mirror: true, + }); + } return new Character(characterSettings); } @@ -115,54 +137,64 @@ export default class Character { * @param {Object} position - initial Character position * @param {number} position.x - canvas coordinates * @param {number} position.y - canvas coordinates - * @param {Sprite | Flipbook} mainFlipbook - Sprite or Flipbook instance - * @param {checkPosition} checkPosition - function to check any collisions and possibility to move. + * @param {Object} mainSettings - main Character settings + * @param {Sprite | Flipbook} mainSettings.mainFlipbook - Sprite or Flipbook instance + * @param {checkPosition} mainSettings.checkPosition - function to check any collisions and possibility to move. + * @param {number} mainSettings.speed - speed of a Character in px per second * @param {Object} moveSettings - settings for move action - * @param {Flipbook} moveSettings.moveFlipbook + * @param {Flipbook} moveSettings.moveRightFlipbook + * @param {Flipbook} moveSettings.moveLeftFlipbook * @param {string} moveSettings.moveRightCode - main right move action code * @param {string} moveSettings.moveLeftCode - main left move action code * @param {string} moveSettings.alternativeMoveRightCode - alternative right move action code * @param {string} moveSettings.alternativeMoveLeftCode - alternative left move action code * @param {Object} jumpSettings - settings for jump action - * @param {Flipbook} jumpSettings.jumpFlipbook + * @param {Flipbook} jumpSettings.jumpRightFlipbook + * @param {Flipbook} jumpSettings.jumpLeftFlipbook * @param {string} jumpSettings.jumpCode - main jump action code * @param {string} jumpSettings.alternativeJumpCode - alternative jump action code * @param {Object} attackSettings - settings for attack actions - * @param {Flipbook} attackSettings.attackFlipbook + * @param {Flipbook} attackSettings.attackRightFlipbook + * @param {Flipbook} attackSettings.attackLeftFlipbook * @param {string} attackSettings.attackCode - main attack action code * @returns {Character} */ constructor({ coreElement, position, - mainFlipbook, - checkPosition, + mainSettings, moveSettings: { - moveFlipbook, + moveRightFlipbook, + moveLeftFlipbook, moveRightCode, moveLeftCode, alternativeMoveRightCode, alternativeMoveLeftCode, } = {}, jumpSettings: { - jumpFlipbook, + jumpRightFlipbook, + jumpLeftFlipbook, jumpCode, alternativeJumpCode, } = {}, attackSettings: { - attackFlipbook, + attackRightFlipbook, + attackLeftFlipbook, attackCode, } = {}, }) { if (coreElement) this._coreElement = coreElement; else throw new Error('coreElement is required for Character!'); - this._validateFlipbooks(mainFlipbook, moveFlipbook, jumpFlipbook, attackFlipbook); + this._validateFlipbooks(mainSettings.mainFlipbook, moveRightFlipbook, jumpRightFlipbook, attackRightFlipbook); - this.mainSettings.mainFlipbook = mainFlipbook; - this.moveSettings.moveFlipbook = moveFlipbook; - this.jumpSettings.jumpFlipbook = jumpFlipbook; - this.attackSettings.attackFlipbook = attackFlipbook; + this.mainSettings = mainSettings; + this.moveSettings.moveRightFlipbook = moveRightFlipbook; + this.moveSettings.moveLeftFlipbook = moveLeftFlipbook; + this.jumpSettings.jumpRightFlipbook = jumpRightFlipbook; + this.jumpSettings.jumpLeftFlipbook = jumpLeftFlipbook; + this.attackSettings.attackRightFlipbook = attackRightFlipbook; + this.attackSettings.attackLeftFlipbook = attackLeftFlipbook; // move codes override if (moveRightCode) this.moveSettings.moveRightCode = moveRightCode; if (moveLeftCode) this.moveSettings.moveLeftCode = moveLeftCode; @@ -180,6 +212,7 @@ export default class Character { this._createOffscreenCanvas(); this._initListeners(); + this._setOnChangeJumpFrame(); } get currentActionType() { @@ -195,16 +228,20 @@ export default class Character { this._currentActionType = actionName; } - move() {} moveRight() { - this.currentActionType = 'MOVE_RIGHT'; - this.flipbook = this.moveSettings.moveFlipbook; - this.moveSettings.moveFlipbook.start(); + this.currentActionType = 'MOVE'; + this._direction = 'RIGHT'; + this.flipbook = this.moveSettings.moveRightFlipbook; + this.moveSettings.moveRightFlipbook.start(); } moveLeft() { - this.currentActionType = 'MOVE_LEFT'; + this.currentActionType = 'MOVE'; + this._direction = 'LEFT'; + this.flipbook = this.moveSettings.moveLeftFlipbook; + this.moveSettings.moveLeftFlipbook.start(); } jump() { + if (this.currentActionType === 'JUMP') return; this.currentActionType = 'JUMP'; this.flipbook = this.jumpSettings.jumpFlipbook; this.jumpSettings.jumpFlipbook.start(); @@ -223,6 +260,31 @@ export default class Character { this.attackSettings.attackFlipbook.start(); } + /** + * The main method for rendering a Character. Return current frame of a Character. + * You can call it any time you want to rerender your scene. + * Frame will change based on Flipbook settings which you have passed as a argument. + * @returns {Image | HTMLCanvasElement} + */ + render() { + const offset = this._getOffset(); + if (this.currentActionType === 'RUN') { + if (this._direction === 'RIGHT') { + this._changePosition(offset); + } + if (this._direction === 'LEFT') { + this._changePosition(-offset); + } + } else if (this.currentActionType === 'JUMP') { + if (this._prevActionType === 'RUN') { + if (this._direction === 'RIGHT') this._changePosition(offset); + if (this._direction === 'LEFT') this._changePosition(-offset); + } + } + this._lastRenderTime = Date.now(); + return this.flipbook.currentSprite; + } + /** * You have to call destroy method if a Character will disappear to prevent memory leaks */ @@ -283,4 +345,20 @@ export default class Character { if (this._hooks.onMove instanceof Function) this._hooks.onMove(); } } + + _setOnChangeJumpFrame() { + const onChangeHandler = (frameNumber, frameCount) => { + if (frameNumber > 0 && frameNumber < 4) this.position.y -= 8; + if (frameNumber > 4 && frameNumber < 7) this.position.y += 8; + if (frameNumber === frameCount) { + this.actionType = this.#prevActionType; + } + } + } + + _getOffset() { + const timeChange = Date.now() - this._lastRenderTime; + const dt = timeChange / 1000.0; + return this.mainSettings.speed * dt; + } } diff --git a/src/classes/Flipbook.js b/src/classes/Flipbook.js index 38fe4be..0a340ea 100644 --- a/src/classes/Flipbook.js +++ b/src/classes/Flipbook.js @@ -51,12 +51,12 @@ export default class Flipbook { get currentSprite() { return this._offscreenCanvas; } - // TODO need to implement drawing on _offscreenCanvas when _currentSprite changes. And consider Mirror option. - // it should be separate private method. + start() { this._timer = setInterval(() => { this._currentSprite = this._sprites[this._currentSpriteIndex]; this._updateSize(); + this._render(); const nextSpriteIndex = this._currentSpriteIndex + 1; if (nextSpriteIndex > this._sprites.length) this._currentSpriteIndex = 0; @@ -89,4 +89,22 @@ export default class Flipbook { this._offscreenCanvas.width = this._currentSprite.width; this._offscreenCanvas.height = this._currentSprite.height; } + + _render() { + const isMirror = this.options.mirror; + this._renderer.clearRect(0, 0, this.width, this.height); + if (isMirror) this._renderer.scale(-1, 1); + this._renderer.drawImage( + this._currentSprite.currentSprite, + 0, + 0, + this._currentSprite.width, + this._currentSprite.height, + 0, + 0, + this.width * (isMirror ? -1 : 1), + this.height, + ); + if (isMirror) this._renderer.setTransform(1, 0, 0, 1, 0, 0); + } } From 47281d757c596e701cf8bf3d80127aca6b5b1615 Mon Sep 17 00:00:00 2001 From: korifey91 Date: Wed, 22 Jan 2020 22:15:36 +0300 Subject: [PATCH 12/20] New Feature: add Character class Dev: bug fix Dev: memory leaks fix --- index.html | 12 ++-- index.js | 71 ++++++++++++++++++++++- src/classes/Character.js | 120 ++++++++++++++++++++++++--------------- src/classes/Flipbook.js | 33 ++++++----- 4 files changed, 170 insertions(+), 66 deletions(-) diff --git a/index.html b/index.html index 5f26784..1f0cf21 100644 --- a/index.html +++ b/index.html @@ -7,12 +7,12 @@ - -
-
-
-
-
+ + + + + + diff --git a/index.js b/index.js index 9d7ed3c..a447e5c 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ import ResizeableCanvasMixin from './src/canvases/mixins/resizeable.js'; import TileableCanvasMixin from './src/canvases/mixins/tileable.js'; import DrawableCanvasMixin from './src/canvases/mixins/drawable.js'; import drawImageFromMap from './src/utils/drawImageFromMap.js'; +import Character from './src/classes/Character.js'; const MainCanvas = DrawableCanvasMixin(TileableCanvasMixin(ResizeableCanvasMixin(CustomCanvas))); @@ -52,4 +53,72 @@ const main = async () => { const tileMap = await MainTileMap.create({ el: document.getElementById('tileMap'), size: { width: 512, height: 512 } }); } -main(); +// main(); +async function test() { + const canvas = document.createElement('canvas'); + canvas.width = 500; + canvas.height = 500; + document.body.append(canvas); + const ctx = canvas.getContext('2d'); + const player = await Character.create( + { + coreElement: canvas, + position: { x: 0, y: 0 }, + mainSettings: { + mainFlipbook: './content/sources/PNG/Knight/knight.png', + speed: 300, + }, + moveSettings: { + moveFlipbook: [ + './content/sources/PNG/Knight/Run/run1.png', + './content/sources/PNG/Knight/Run/run2.png', + './content/sources/PNG/Knight/Run/run3.png', + './content/sources/PNG/Knight/Run/run4.png', + './content/sources/PNG/Knight/Run/run5.png', + './content/sources/PNG/Knight/Run/run6.png', + './content/sources/PNG/Knight/Run/run7.png', + './content/sources/PNG/Knight/Run/run8.png', + ], + }, + jumpSettings: { + jumpFlipbook: [ + './content/sources/PNG/Knight/Jump/jump1.png', + './content/sources/PNG/Knight/Jump/jump2.png', + './content/sources/PNG/Knight/Jump/jump3.png', + './content/sources/PNG/Knight/Jump/jump4.png', + './content/sources/PNG/Knight/Jump/jump5.png', + './content/sources/PNG/Knight/Jump/jump6.png', + './content/sources/PNG/Knight/Jump/jump7.png', + ], + }, + attackSettings: { + attackFlipbook: [ + './content/sources/PNG/Knight/Attack/attack0.png', + './content/sources/PNG/Knight/Attack/attack1.png', + './content/sources/PNG/Knight/Attack/attack2.png', + './content/sources/PNG/Knight/Attack/attack4.png', + ], + }, + } + ); + const tick = () => { + // setTimeout(this.tick, 1000); + requestAnimationFrame(tick); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage( + player.render(), + 0, + 0, + player.width, + player.height, + 0, + 0, + player.width, + player.height, + ); + }; + tick(); +} + +test(); + diff --git a/src/classes/Character.js b/src/classes/Character.js index a04b6fa..92dae76 100644 --- a/src/classes/Character.js +++ b/src/classes/Character.js @@ -1,19 +1,11 @@ -import Flipbook from './Flipbook'; -import Sprite from './Sprite'; +import Flipbook from './Flipbook.js'; +import Sprite from './Sprite.js'; const ERROR_HELP_TEXT = 'Use Character.create method to create character with a set of sprites'; export default class Character { _coreElement = null; - _actionHandlerHash = { - [this.moveSettings.moveLeftCode]: this.moveLeft.bind(this), - [this.moveSettings.alternativeMoveLeftCode]: this.moveLeft.bind(this), - [this.moveSettings.moveRightCode]: this.moveRight.bind(this), - [this.moveSettings.alternativeMoveRightCode]: this.moveRight.bind(this), - [this.jumpSettings.jumpCode]: this.jump.bind(this), - [this.jumpSettings.alternativeJumpCode]: this.jump.bind(this), - [this.attackSettings.attackCode]: this.attack.bind(this), - }; + _prevActionType = 'STOP'; _currentActionType = 'STOP'; _direction = 'RIGHT'; @@ -24,7 +16,8 @@ export default class Character { flipbook = null; mainSettings = { - mainFlipbook: null, + mainRightFlipbook: null, + mainLeftFlipbook: null, /** * The function should return a boolean value which indicates can a Character move or not. * @@ -45,13 +38,15 @@ export default class Character { }; jumpSettings = { - jumpFlipbook: null, + jumpRightFlipbook: null, + jumpLeftFlipbook: null, jumpCode: 'ArrowUp', alternativeJumpCode: 'KeyW', }; attackSettings = { - attackFlipbook: null, + attackRightFlipbook: null, + attackLeftFlipbook: null, attackCode: 'Space', }; @@ -105,8 +100,10 @@ export default class Character { jumpSettings: { ...jumpSettings }, attackSettings: { ...attackSettings }, }; - if (typeof mainSettings.mainFlipbook === 'string') characterSettings.mainSettings.mainFlipbook = await new Sprite(mainSettings.mainFlipbook).load(); - else if (mainSettings.mainFlipbook instanceof Array) characterSettings.mainSettings.mainFlipbook = await Flipbook.create(mainSettings.mainFlipbook); + if (typeof mainSettings.mainFlipbook === 'string') characterSettings.mainSettings.mainRightFlipbook = await Flipbook.create([mainSettings.mainFlipbook]); + if (typeof mainSettings.mainFlipbook === 'string') characterSettings.mainSettings.mainLeftFlipbook = await Flipbook.create([mainSettings.mainFlipbook], { mirror: true}); + if (mainSettings.mainFlipbook instanceof Array) characterSettings.mainSettings.mainRightFlipbook = await Flipbook.create(mainSettings.mainFlipbook); + if (mainSettings.mainFlipbook instanceof Array) characterSettings.mainSettings.mainLeftFlipbook = await Flipbook.create(mainSettings.mainFlipbook, { mirror: true }); if (moveFlipbook instanceof Array) { characterSettings.moveSettings.moveRightFlipbook = await Flipbook.create(moveFlipbook, moveFlipbookMeta); characterSettings.moveSettings.moveLeftFlipbook = await Flipbook.create(moveFlipbook, { @@ -138,7 +135,8 @@ export default class Character { * @param {number} position.x - canvas coordinates * @param {number} position.y - canvas coordinates * @param {Object} mainSettings - main Character settings - * @param {Sprite | Flipbook} mainSettings.mainFlipbook - Sprite or Flipbook instance + * @param {Flipbook} mainSettings.mainRightFlipbook + * @param {Flipbook} mainSettings.mainLeftFlipbook * @param {checkPosition} mainSettings.checkPosition - function to check any collisions and possibility to move. * @param {number} mainSettings.speed - speed of a Character in px per second * @param {Object} moveSettings - settings for move action @@ -186,9 +184,10 @@ export default class Character { if (coreElement) this._coreElement = coreElement; else throw new Error('coreElement is required for Character!'); - this._validateFlipbooks(mainSettings.mainFlipbook, moveRightFlipbook, jumpRightFlipbook, attackRightFlipbook); + this._validateFlipbooks(mainSettings.mainRightFlipbook, moveRightFlipbook, jumpRightFlipbook, attackRightFlipbook); this.mainSettings = mainSettings; + this.mainSettings.checkPosition = mainSettings.checkPosition || (() => true); this.moveSettings.moveRightFlipbook = moveRightFlipbook; this.moveSettings.moveLeftFlipbook = moveLeftFlipbook; this.jumpSettings.jumpRightFlipbook = jumpRightFlipbook; @@ -209,10 +208,20 @@ export default class Character { // initial position overrides if (typeof position.x === 'number') this.position.x = position.x; if (typeof position.y === 'number') this.position.y = position.y; - + + this._actionHandlerHash = { + [this.moveSettings.moveLeftCode]: this.moveLeft.bind(this), + [this.moveSettings.alternativeMoveLeftCode]: this.moveLeft.bind(this), + [this.moveSettings.moveRightCode]: this.moveRight.bind(this), + [this.moveSettings.alternativeMoveRightCode]: this.moveRight.bind(this), + [this.jumpSettings.jumpCode]: this.jump.bind(this), + [this.jumpSettings.alternativeJumpCode]: this.jump.bind(this), + [this.attackSettings.attackCode]: this.attack.bind(this), + }; this._createOffscreenCanvas(); this._initListeners(); this._setOnChangeJumpFrame(); + this.stop(); } get currentActionType() { @@ -220,44 +229,54 @@ export default class Character { } set currentActionType(actionName) { - if (this._currentActionType === actionName) return; - if (this._prevActionType !== this.currentActionType) this._prevActionType = this._currentActionType; + if (actionName === 'JUMP' && this.currentActionType === 'MOVE') this._prevActionType = 'MOVE'; + else this._prevActionType = null; + + if (this.currentActionType !== actionName) this._currentActionType = actionName; if (actionName === 'STOP') { if (this._hooks.onStop instanceof Function) this._hooks.onStop(); } - this._currentActionType = actionName; + // this._currentActionType = actionName; } moveRight() { + if (this.currentActionType === 'MOVE' && this._direction === 'RIGHT') return; this.currentActionType = 'MOVE'; this._direction = 'RIGHT'; this.flipbook = this.moveSettings.moveRightFlipbook; - this.moveSettings.moveRightFlipbook.start(); + this.flipbook.start(); } moveLeft() { + if (this.currentActionType === 'MOVE' && this._direction === 'LEFT') return; this.currentActionType = 'MOVE'; this._direction = 'LEFT'; this.flipbook = this.moveSettings.moveLeftFlipbook; - this.moveSettings.moveLeftFlipbook.start(); + this.flipbook.start(); } jump() { if (this.currentActionType === 'JUMP') return; this.currentActionType = 'JUMP'; - this.flipbook = this.jumpSettings.jumpFlipbook; - this.jumpSettings.jumpFlipbook.start(); + this.flipbook = this._direction === 'RIGHT' ? this.jumpSettings.jumpRightFlipbook : this.jumpSettings.jumpLeftFlipbook; + this.flipbook.start(); } stop() { - this.currentActionType = 'STOP'; - this.moveSettings.moveFlipbook.stop(); - this.jumpSettings.jumpFlipbook.stop(); - this.attackSettings.attackFlipbook.stop(); - this.flipbook = this.mainSettings.mainFlipbook; - if (this.mainSettings.mainFlipbook instanceof Flipbook) this.mainSettings.mainFlipbook.start(); + if (this.currentActionType === 'JUMP' && this._prevActionType === 'MOVE') { + this._stopAllFlipbooks(); + if (this._direction === 'RIGHT') this.moveRight(); + else this.moveLeft(); + } else { + this.currentActionType = 'STOP'; + this._stopAllFlipbooks(); + // if (this.flipbook instanceof Flipbook) this.flipbook.stop(); + this.flipbook = this._direction === 'RIGHT' ? this.mainSettings.mainRightFlipbook : this.mainSettings.mainLeftFlipbook; + if (this.flipbook instanceof Flipbook) this.flipbook.start(); + } } attack() { + if (this.currentActionType === 'ATTACK') return; this.currentActionType = 'ATTACK'; - this.flipbook = this.attackSettings.attackFlipbook; - this.attackSettings.attackFlipbook.start(); + this.flipbook = this._direction === 'RIGHT' ? this.attackSettings.attackRightFlipbook : this.attackSettings.attackLeftFlipbook; + this.flipbook.start(); } /** @@ -268,7 +287,7 @@ export default class Character { */ render() { const offset = this._getOffset(); - if (this.currentActionType === 'RUN') { + if (this.currentActionType === 'MOVE') { if (this._direction === 'RIGHT') { this._changePosition(offset); } @@ -276,7 +295,7 @@ export default class Character { this._changePosition(-offset); } } else if (this.currentActionType === 'JUMP') { - if (this._prevActionType === 'RUN') { + if (this._prevActionType === 'MOVE') { if (this._direction === 'RIGHT') this._changePosition(offset); if (this._direction === 'LEFT') this._changePosition(-offset); } @@ -289,8 +308,8 @@ export default class Character { * You have to call destroy method if a Character will disappear to prevent memory leaks */ destroy() { - this._coreElement.removeEventListener('keydown', this._keydownEventHandler); - this._coreElement.removeEventListener('keyup', this._keyupEventHandler); + window.removeEventListener('keydown', this._keydownEventHandler); + window.removeEventListener('keyup', this._keyupEventHandler); } _validateFlipbooks(mainFlipbook, moveFlipbook, jumpFlipbook, attackFlipbook) { @@ -318,28 +337,28 @@ export default class Character { } _initListeners() { - this._coreElement.addEventListener('keydown', this._keydownEventHandler, { passive: true }); - this._coreElement.addEventListener('keyup', this._keyupEventHandler, { passive: true }); + window.addEventListener('keydown', this._keydownEventHandler.bind(this), { passive: true }); + window.addEventListener('keyup', this._keyupEventHandler.bind(this), { passive: true }); } _keydownEventHandler(event) { - this._actionHandlerHash[event.code](event); + if (Object.keys(this._actionHandlerHash).includes(event.code)) this._actionHandlerHash[event.code](event); } _keyupEventHandler(event) { this.stop(); } - get _width() { + get width() { return this.flipbook.currentSprite.width; } - get _height() { + get height() { return this.flipbook.currentSprite.height; } _changePosition(dx = 0, dy = 0) { - if (this.mainSettings.checkPosition(this.position.x + dx, this.position.y + dy, this._width, this._height)) { + if (this.mainSettings.checkPosition(this.position.x + dx, this.position.y + dy, this.width, this.height)) { this.position.x += dx; this.position.y += dy; if (this._hooks.onMove instanceof Function) this._hooks.onMove(); @@ -351,7 +370,7 @@ export default class Character { if (frameNumber > 0 && frameNumber < 4) this.position.y -= 8; if (frameNumber > 4 && frameNumber < 7) this.position.y += 8; if (frameNumber === frameCount) { - this.actionType = this.#prevActionType; + this.actionType = this._prevActionType; } } } @@ -361,4 +380,15 @@ export default class Character { const dt = timeChange / 1000.0; return this.mainSettings.speed * dt; } + + _stopAllFlipbooks() { + this.mainSettings.mainLeftFlipbook.stop(); + this.mainSettings.mainRightFlipbook.stop(); + this.jumpSettings.jumpRightFlipbook.stop(); + this.jumpSettings.jumpLeftFlipbook.stop(); + this.moveSettings.moveRightFlipbook.stop(); + this.moveSettings.moveLeftFlipbook.stop(); + this.attackSettings.attackRightFlipbook.stop(); + this.attackSettings.attackLeftFlipbook.stop(); + } } diff --git a/src/classes/Flipbook.js b/src/classes/Flipbook.js index 0a340ea..e5293e0 100644 --- a/src/classes/Flipbook.js +++ b/src/classes/Flipbook.js @@ -1,10 +1,10 @@ -import Sprite from './Sprite'; +import Sprite from './Sprite.js'; /** * Flipbook is used to animate multiple images (like GIFs) */ export default class Flipbook { options = { - frameDuration: 300, + frameDuration: 100, mirror: false, }; _spriteUrls = []; @@ -27,9 +27,10 @@ export default class Flipbook { return instance; } - constructor(sprites, options) { + constructor(sprites, options = {}) { if (sprites == null || sprites.length < 1) throw new Error('Sprites are required!'); - if (options && options.frameDuration) this.options = options; + if (options.frameDuration) this.options.frameDuration = options.frameDuration; + if (options.mirror) this.options.mirror = options.mirror; this._spriteUrls = sprites; } @@ -53,15 +54,19 @@ export default class Flipbook { } start() { - this._timer = setInterval(() => { - this._currentSprite = this._sprites[this._currentSpriteIndex]; - this._updateSize(); - this._render(); - const nextSpriteIndex = this._currentSpriteIndex + 1; - - if (nextSpriteIndex > this._sprites.length) this._currentSpriteIndex = 0; - else this._currentSpriteIndex = nextSpriteIndex; - }, this.options.frameDuration) + this._updateSize(); + this._render(); + if (this._sprites.length > 1) { + this._timer = setInterval(() => { + this._currentSprite = this._sprites[this._currentSpriteIndex]; + this._updateSize(); + this._render(); + const nextSpriteIndex = this._currentSpriteIndex + 1; + + if (nextSpriteIndex >= this._sprites.length) this._currentSpriteIndex = 0; + else this._currentSpriteIndex = nextSpriteIndex; + }, this.options.frameDuration); + } } stop() { @@ -105,6 +110,6 @@ export default class Flipbook { this.width * (isMirror ? -1 : 1), this.height, ); - if (isMirror) this._renderer.setTransform(1, 0, 0, 1, 0, 0); + // if (isMirror) this._renderer.setTransform(1, 0, 0, 1, 0, 0); } } From cb99c64de0a457daeb9366c3f976dbb91f507f7e Mon Sep 17 00:00:00 2001 From: korifey91 Date: Thu, 23 Jan 2020 20:41:47 +0300 Subject: [PATCH 13/20] New Feature: add Character class Dev: fix mirroring. But it's hard code now. This settings should be passed through a some options. TBD --- src/classes/Flipbook.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/classes/Flipbook.js b/src/classes/Flipbook.js index e5293e0..e446419 100644 --- a/src/classes/Flipbook.js +++ b/src/classes/Flipbook.js @@ -105,11 +105,11 @@ export default class Flipbook { 0, this._currentSprite.width, this._currentSprite.height, - 0, + // TODO it's hard code now. It should be passed through some options. + isMirror ? 40 : 0, 0, this.width * (isMirror ? -1 : 1), this.height, ); - // if (isMirror) this._renderer.setTransform(1, 0, 0, 1, 0, 0); } } From b1a316e6474d9aeaa7a28ea3d7d123a7c524fbc8 Mon Sep 17 00:00:00 2001 From: korifey91 Date: Thu, 23 Jan 2020 21:02:48 +0300 Subject: [PATCH 14/20] New Feature: add Character class Dev: refactor folders and file names --- index.js | 18 +++--- .../MainTileMap.js => src/TileMap/index.js | 8 +-- .../tilemaps => src/TileMap}/tilemap.json | 0 {content/tilemaps => src/TileMap}/tilemap.png | Bin .../MainTileSet.js => src/TileSet/index.js | 18 +++--- .../tilesets => src/TileSet}/mainTileSet.png | Bin src/{ => utils}/classes/Character.js | 0 src/{ => utils}/classes/Cursor.js | 4 +- src/{ => utils}/classes/Flipbook.js | 0 src/{ => utils}/classes/Point.js | 0 src/{ => utils}/classes/Sprite.js | 0 src/{ => utils}/classes/Tile.js | 0 src/utils/classes/index.js | 15 +++++ "src/\320\241anvas/CanvasClassBuilder.js" | 56 ++++++++++++++++++ .../\320\241anvas/index.js" | 2 +- .../\320\241anvas/mixins/drawableCanvas.js" | 10 ++-- "src/\320\241anvas/mixins/index.js" | 11 ++++ .../\320\241anvas/mixins/resizeableCanvas.js" | 4 +- .../\320\241anvas/mixins/selectableCanvas.js" | 4 +- .../\320\241anvas/mixins/tileableCanvas.js" | 6 +- .../test.js => "src/\320\241anvas/test.js" | 4 +- 21 files changed, 121 insertions(+), 39 deletions(-) rename content/tilemaps/MainTileMap.js => src/TileMap/index.js (84%) rename {content/tilemaps => src/TileMap}/tilemap.json (100%) rename {content/tilemaps => src/TileMap}/tilemap.png (100%) rename content/tilesets/MainTileSet.js => src/TileSet/index.js (78%) rename {content/tilesets => src/TileSet}/mainTileSet.png (100%) rename src/{ => utils}/classes/Character.js (100%) rename src/{ => utils}/classes/Cursor.js (94%) rename src/{ => utils}/classes/Flipbook.js (100%) rename src/{ => utils}/classes/Point.js (100%) rename src/{ => utils}/classes/Sprite.js (100%) rename src/{ => utils}/classes/Tile.js (100%) create mode 100644 src/utils/classes/index.js create mode 100644 "src/\320\241anvas/CanvasClassBuilder.js" rename "src/canvases/\320\241ustomCanvas.js" => "src/\320\241anvas/index.js" (97%) rename src/canvases/mixins/drawable.js => "src/\320\241anvas/mixins/drawableCanvas.js" (96%) create mode 100644 "src/\320\241anvas/mixins/index.js" rename src/canvases/mixins/resizeable.js => "src/\320\241anvas/mixins/resizeableCanvas.js" (94%) rename src/canvases/mixins/selectable.js => "src/\320\241anvas/mixins/selectableCanvas.js" (95%) rename src/canvases/mixins/tileable.js => "src/\320\241anvas/mixins/tileableCanvas.js" (97%) rename src/canvases/test.js => "src/\320\241anvas/test.js" (78%) diff --git a/index.js b/index.js index a447e5c..2a0f900 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,11 @@ -import MainTileSet from './content/tilesets/MainTileSet.js'; -import MainTileMap from './content/tilemaps/MainTileMap.js'; -import CustomCanvas from './src/canvases/СustomCanvas.js'; -import ResizeableCanvasMixin from './src/canvases/mixins/resizeable.js'; -import TileableCanvasMixin from './src/canvases/mixins/tileable.js'; -import DrawableCanvasMixin from './src/canvases/mixins/drawable.js'; +import TileSet from './src/TileSet/index.js'; +import TileMap from './src/TileMap/index.js'; +import CustomCanvas from './src/Сanvas/index.js'; +import ResizeableCanvasMixin from './src/Сanvas/mixins/resizeableCanvas.js'; +import TileableCanvasMixin from './src/Сanvas/mixins/tileableCanvas.js'; +import DrawableCanvasMixin from './src/Сanvas/mixins/drawableCanvas.js'; import drawImageFromMap from './src/utils/drawImageFromMap.js'; -import Character from './src/classes/Character.js'; +import Character from './src/utils/classes/Character.js'; const MainCanvas = DrawableCanvasMixin(TileableCanvasMixin(ResizeableCanvasMixin(CustomCanvas))); @@ -37,7 +37,7 @@ const main = async () => { const mainCanvas = await MainCanvas.create({ el: document.getElementById('main'), size: { width: 512, height: 512 } }); const saveButton = createButton(document.body, 'Save', () => saveMap(mainCanvas)); const currentTileCanvas = await CustomCanvas.create({ el: document.getElementById('current'), size: { width: 64, height: 64 } }); - const mainTileSet = await MainTileSet.create({ el: document.getElementById('tileSet') }); + const mainTileSet = await TileSet.create({ el: document.getElementById('tileSet') }); mainTileSet.addEventListener(':multiSelect', ({ tiles }) => { mainCanvas.updateCurrentTiles(tiles); @@ -50,7 +50,7 @@ const main = async () => { } }); - const tileMap = await MainTileMap.create({ el: document.getElementById('tileMap'), size: { width: 512, height: 512 } }); + const tileMap = await TileMap.create({ el: document.getElementById('tileMap'), size: { width: 512, height: 512 } }); } // main(); diff --git a/content/tilemaps/MainTileMap.js b/src/TileMap/index.js similarity index 84% rename from content/tilemaps/MainTileMap.js rename to src/TileMap/index.js index 2616f1c..bf916fe 100644 --- a/content/tilemaps/MainTileMap.js +++ b/src/TileMap/index.js @@ -1,14 +1,14 @@ -import { DrawableCanvas } from '../../src/canvases/mixins/drawable.js'; +import { DrawableCanvas } from '../Сanvas/mixins/drawableCanvas.js'; -export default class MainTileMap extends DrawableCanvas { - _imageSrcLink = './content/tilemaps/tilemap.png'; +export default class TileMap extends DrawableCanvas { + _imageSrcLink = './content/TileMap/tilemap.png'; _imageSrc = null; _sourceTileMapSize = { width: null, height: null, }; - _metadataSrcLink = './content/tilemaps/tilemap.json'; + _metadataSrcLink = './content/TileMap/tilemap.json'; _metadataSrc = null; constructor(options= {}) { diff --git a/content/tilemaps/tilemap.json b/src/TileMap/tilemap.json similarity index 100% rename from content/tilemaps/tilemap.json rename to src/TileMap/tilemap.json diff --git a/content/tilemaps/tilemap.png b/src/TileMap/tilemap.png similarity index 100% rename from content/tilemaps/tilemap.png rename to src/TileMap/tilemap.png diff --git a/content/tilesets/MainTileSet.js b/src/TileSet/index.js similarity index 78% rename from content/tilesets/MainTileSet.js rename to src/TileSet/index.js index 6630950..38aab92 100644 --- a/content/tilesets/MainTileSet.js +++ b/src/TileSet/index.js @@ -1,14 +1,14 @@ -import { Tileable } from '../../src/canvases/mixins/tileable.js'; -import SelectableCanvasMixin from '../../src/canvases/mixins/selectable.js'; -import ResizeableCanvasMixin from '../../src/canvases/mixins/resizeable.js'; -import buildEvent from '../../src/utils/buildEvent.js'; -import Tile from '../../src/classes/Tile.js'; +import { TileableCanvas } from '../Сanvas/mixins/tileableCanvas.js'; +import SelectableCanvasMixin from '../Сanvas/mixins/selectableCanvas.js'; +import ResizeableCanvasMixin from '../Сanvas/mixins/resizeableCanvas.js'; +import buildEvent from '../utils/buildEvent.js'; +import Tile from '../utils/classes/Tile.js'; -export default class MainTileSet extends SelectableCanvasMixin(ResizeableCanvasMixin(Tileable)) { - _imageSrcLink = 'content/tilesets/mainTileSet.png'; +export default class TileSet extends SelectableCanvasMixin(ResizeableCanvasMixin(TileableCanvas)) { + _imageSrcLink = 'content/TileSet/mainTileSet.png'; _imageSrc = null; - _metadataSrcLink = 'content/tilesets/main-tile-set.json'; + _metadataSrcLink = 'content/TileSet/main-tile-set.json'; _metadataSrc = null; _onMultiSelect({ from, to }) { @@ -78,7 +78,7 @@ export default class MainTileSet extends SelectableCanvasMixin(ResizeableCanvasM await Promise.all(promises); } - //TODO need to check if it needed. We have such method in MainTileMap.js + //TODO need to check if it needed. We have such method in index.js async _loadMetadata() { this._metadataSrc = await (await fetch(this._metadataSrcLink)).json(); } diff --git a/content/tilesets/mainTileSet.png b/src/TileSet/mainTileSet.png similarity index 100% rename from content/tilesets/mainTileSet.png rename to src/TileSet/mainTileSet.png diff --git a/src/classes/Character.js b/src/utils/classes/Character.js similarity index 100% rename from src/classes/Character.js rename to src/utils/classes/Character.js diff --git a/src/classes/Cursor.js b/src/utils/classes/Cursor.js similarity index 94% rename from src/classes/Cursor.js rename to src/utils/classes/Cursor.js index 795c7fc..e60a39f 100644 --- a/src/classes/Cursor.js +++ b/src/utils/classes/Cursor.js @@ -1,5 +1,5 @@ -import drawImageFromMap from '../utils/drawImageFromMap.js'; -import getTilesRectSizes from '../utils/getTilesRectSizes.js'; +import drawImageFromMap from '../drawImageFromMap.js'; +import getTilesRectSizes from '../getTilesRectSizes.js'; const updateImageColorVolume = (imageData) => { let pixels = imageData.data; diff --git a/src/classes/Flipbook.js b/src/utils/classes/Flipbook.js similarity index 100% rename from src/classes/Flipbook.js rename to src/utils/classes/Flipbook.js diff --git a/src/classes/Point.js b/src/utils/classes/Point.js similarity index 100% rename from src/classes/Point.js rename to src/utils/classes/Point.js diff --git a/src/classes/Sprite.js b/src/utils/classes/Sprite.js similarity index 100% rename from src/classes/Sprite.js rename to src/utils/classes/Sprite.js diff --git a/src/classes/Tile.js b/src/utils/classes/Tile.js similarity index 100% rename from src/classes/Tile.js rename to src/utils/classes/Tile.js diff --git a/src/utils/classes/index.js b/src/utils/classes/index.js new file mode 100644 index 0000000..22735c9 --- /dev/null +++ b/src/utils/classes/index.js @@ -0,0 +1,15 @@ +import Character from './Character.js'; +import Cursor from './Cursor.js'; +import Flipbook from './Flipbook.js'; +import Point from './Point.js'; +import Sprite from './Sprite.js'; +import Tile from './Tile.js'; + +export { + Cursor, + Character, + Flipbook, + Point, + Sprite, + Tile, +} diff --git "a/src/\320\241anvas/CanvasClassBuilder.js" "b/src/\320\241anvas/CanvasClassBuilder.js" new file mode 100644 index 0000000..ec618aa --- /dev/null +++ "b/src/\320\241anvas/CanvasClassBuilder.js" @@ -0,0 +1,56 @@ +import Canvas from './index.js'; + +import SelectableCanvasMixin from './mixins/selectableCanvas.js'; +import ResizeableCanvasMixin from './mixins/resizeableCanvas.js'; +import TileableCanvasMixin from './mixins/tileableCanvas.js'; +import DrawableCanvasMixin from './mixins/drawableCanvas.js'; + +const MIXINS = { + selectable: SelectableCanvasMixin, + resizable: ResizeableCanvasMixin, + tileable: TileableCanvasMixin, + drawable: DrawableCanvasMixin, +}; + +export default class CanvasClassBuilder { + klass = Canvas; + mixins = { + selectable: false, + resizable: false, + tileable: false, + drawable: false, + }; + + applySelectableMixin() { + this.mixins.selectable = true; + return this; + } + + applyResizeableMixin() { + this.mixins.resizable = true; + return this; + } + + applyTileableMixin() { + this.mixins.tileable = true; + return this; + } + + applyDrawableMixin() { + this.mixins.drawable = true; + return this; + } + + build() { + let klass = this.klass; + for (const [key, flag] of Object.entries(this.mixins)) { + if (flag) klass = MIXINS[key](klass); + } + return klass; + } + + instantiate(options) { + const klass = this.build(); + return klass.create(options); + } +} diff --git "a/src/canvases/\320\241ustomCanvas.js" "b/src/\320\241anvas/index.js" similarity index 97% rename from "src/canvases/\320\241ustomCanvas.js" rename to "src/\320\241anvas/index.js" index 8ac5431..7ccfcb3 100644 --- "a/src/canvases/\320\241ustomCanvas.js" +++ "b/src/\320\241anvas/index.js" @@ -6,7 +6,7 @@ import throttle from '../utils/throttle.js'; const customCanvas = await CustomCanvas.create({ el: document.body, size: { width: 64, height: 64 } }); customCanvas.addEventListener('render', (event) => { if (tile != null) event.ctx.drawImage(tile, 0, 0, 64, 64); }); */ -export default class CustomCanvas extends EventTarget { +export default class Canvas extends EventTarget { static _metaClassNames = []; static async create(...args) { diff --git a/src/canvases/mixins/drawable.js "b/src/\320\241anvas/mixins/drawableCanvas.js" similarity index 96% rename from src/canvases/mixins/drawable.js rename to "src/\320\241anvas/mixins/drawableCanvas.js" index b5edaab..c925f77 100644 --- a/src/canvases/mixins/drawable.js +++ "b/src/\320\241anvas/mixins/drawableCanvas.js" @@ -1,13 +1,13 @@ import buildEvent from '../../utils/buildEvent.js'; -import Cursor from '../../classes/Cursor.js'; -import Point from '../../classes/Point.js'; -import CustomCanvas from '../СustomCanvas.js'; +import Cursor from '../../utils/classes/Cursor.js'; +import Point from '../../utils/classes/Point.js'; +import CustomCanvas from '../index.js'; import TileableCanvasMixin, { - Tileable, + TileableCanvas, BACKGROUND_LAYER, ZERO_LAYER, FOREGROUND_LAYER, -} from './tileable.js'; +} from './tileableCanvas.js'; const _onMouseEnterHandler = Symbol('_onMouseEnterHandler'); const _onMouseLeaveHandler = Symbol('_onMouseLeaveHandler'); diff --git "a/src/\320\241anvas/mixins/index.js" "b/src/\320\241anvas/mixins/index.js" new file mode 100644 index 0000000..41ba4af --- /dev/null +++ "b/src/\320\241anvas/mixins/index.js" @@ -0,0 +1,11 @@ +import SelectableCanvasMixin from './selectableCanvas.js'; +import ResizeableCanvasMixin from './resizeableCanvas.js'; +import TileableCanvasMixin from './tileableCanvas.js'; +import DrawableCanvasMixin from './drawableCanvas.js'; + +export { + SelectableCanvasMixin, + ResizeableCanvasMixin, + TileableCanvasMixin, + DrawableCanvasMixin, +}; diff --git a/src/canvases/mixins/resizeable.js "b/src/\320\241anvas/mixins/resizeableCanvas.js" similarity index 94% rename from src/canvases/mixins/resizeable.js rename to "src/\320\241anvas/mixins/resizeableCanvas.js" index 16edb1f..abcae3b 100644 --- a/src/canvases/mixins/resizeable.js +++ "b/src/\320\241anvas/mixins/resizeableCanvas.js" @@ -1,4 +1,4 @@ -import CustomCanvas from '../СustomCanvas.js'; +import CustomCanvas from '../index.js'; const INCREASE_SIZE_MULTIPLIER = 2; const DECREASE_SIZE_MULTIPLIER = 1 / 2; @@ -64,4 +64,4 @@ const ResizeableCanvasMixin = (BaseClass = CustomCanvas) => { export default ResizeableCanvasMixin; -export const Resizeable = ResizeableCanvasMixin(); +export const ResizeableCanvas = ResizeableCanvasMixin(); diff --git a/src/canvases/mixins/selectable.js "b/src/\320\241anvas/mixins/selectableCanvas.js" similarity index 95% rename from src/canvases/mixins/selectable.js rename to "src/\320\241anvas/mixins/selectableCanvas.js" index 86a1e1f..69ff372 100644 --- a/src/canvases/mixins/selectable.js +++ "b/src/\320\241anvas/mixins/selectableCanvas.js" @@ -1,5 +1,5 @@ import buildEvent from '../../utils/buildEvent.js'; -import CustomCanvas from '../СustomCanvas.js'; +import CustomCanvas from '../index.js'; const _onMouseDownHandler = Symbol('_onMouseDownHandler'); const _onMouseUpHandler = Symbol('_onMouseUpHandler'); @@ -76,4 +76,4 @@ const SelectableCanvasMixin = (BaseClass = CustomCanvas) => { export default SelectableCanvasMixin; -export const Selectable = SelectableCanvasMixin(); +export const SelectableCanvas = SelectableCanvasMixin(); diff --git a/src/canvases/mixins/tileable.js "b/src/\320\241anvas/mixins/tileableCanvas.js" similarity index 97% rename from src/canvases/mixins/tileable.js rename to "src/\320\241anvas/mixins/tileableCanvas.js" index 4e13050..02ea708 100644 --- a/src/canvases/mixins/tileable.js +++ "b/src/\320\241anvas/mixins/tileableCanvas.js" @@ -1,6 +1,6 @@ import buildEvent from '../../utils/buildEvent.js'; -import CustomCanvas from '../СustomCanvas.js'; -import Point from '../../classes/Point.js'; +import CustomCanvas from '../index.js'; +import Point from '../../utils/classes/Point.js'; const _onMouseMoveHandler = Symbol('_onMouseMoveHandler'); const _onMouseOutHandler = Symbol('_onMouseOutHandler'); @@ -212,4 +212,4 @@ const TileableCanvasMixin = (BaseClass = CustomCanvas) => { export default TileableCanvasMixin; -export const Tileable = TileableCanvasMixin(); +export const TileableCanvas = TileableCanvasMixin(); diff --git a/src/canvases/test.js "b/src/\320\241anvas/test.js" similarity index 78% rename from src/canvases/test.js rename to "src/\320\241anvas/test.js" index 944a706..fb1df16 100644 --- a/src/canvases/test.js +++ "b/src/\320\241anvas/test.js" @@ -1,5 +1,5 @@ -import CustomCanvas from './src/canvases/СustomCanvas.js'; -import ResizeableCanvasMixin from './src/canvases/mixins/resizeable.js'; +import CustomCanvas from './src/Сanvas/index.js'; +import ResizeableCanvasMixin from './src/Сanvas/mixins/resizeableCanvas.js'; class TestCanvas extends ResizeableCanvasMixin(CustomCanvas) { From 8796d40ae26a6aed094a3a5b354e2a0e9d97495f Mon Sep 17 00:00:00 2001 From: korifey91 Date: Sat, 25 Jan 2020 23:30:27 +0300 Subject: [PATCH 15/20] New Feature: add Character class Dev: add event handling to Flipbook.js Dev: fix _setOnChangeJumpFrame functionality in Character.js --- src/utils/classes/Character.js | 9 ++++++--- src/utils/classes/Flipbook.js | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/utils/classes/Character.js b/src/utils/classes/Character.js index 92dae76..97c2d07 100644 --- a/src/utils/classes/Character.js +++ b/src/utils/classes/Character.js @@ -367,12 +367,15 @@ export default class Character { _setOnChangeJumpFrame() { const onChangeHandler = (frameNumber, frameCount) => { - if (frameNumber > 0 && frameNumber < 4) this.position.y -= 8; - if (frameNumber > 4 && frameNumber < 7) this.position.y += 8; + const middleFrameNumber = Math.ceil(frameCount / 2); + if (frameNumber > 1 && frameNumber < middleFrameNumber) this.position.y -= 8; + if (frameNumber > middleFrameNumber && frameNumber < frameCount) this.position.y += 8; if (frameNumber === frameCount) { this.actionType = this._prevActionType; } - } + }; + this.jumpSettings.jumpLeftFlipbook.on('frameChange', onChangeHandler.bind(this)); + this.jumpSettings.jumpRightFlipbook.on('frameChange', onChangeHandler.bind(this)); } _getOffset() { diff --git a/src/utils/classes/Flipbook.js b/src/utils/classes/Flipbook.js index e446419..1f077af 100644 --- a/src/utils/classes/Flipbook.js +++ b/src/utils/classes/Flipbook.js @@ -1,4 +1,5 @@ import Sprite from './Sprite.js'; + /** * Flipbook is used to animate multiple images (like GIFs) */ @@ -9,9 +10,13 @@ export default class Flipbook { }; _spriteUrls = []; _sprites = []; + _eventHash = { + frameChange: [], + }; _currentSprite = null; _currentSpriteIndex = 0; _timer = null; + _availableEvents = ['frameChange']; /** * @constructs The main method to create Flipbook. @@ -61,6 +66,8 @@ export default class Flipbook { this._currentSprite = this._sprites[this._currentSpriteIndex]; this._updateSize(); this._render(); + this._callEventHandlers('frameChange', [this._currentSpriteIndex + 1, this._sprites.length]); + const nextSpriteIndex = this._currentSpriteIndex + 1; if (nextSpriteIndex >= this._sprites.length) this._currentSpriteIndex = 0; @@ -75,6 +82,17 @@ export default class Flipbook { this._currentSprite = this._sprites[this._currentSpriteIndex]; } + /** + * Standard way to add event handler + * @param event + * @param handler + */ + on(event, handler) { + if (typeof event === 'string' && this._availableEvents.includes(event) && handler instanceof Function) { + this._eventHash[event].push(handler); + } + } + get width() { return this._offscreenCanvas.width; } @@ -112,4 +130,10 @@ export default class Flipbook { this.height, ); } + + _callEventHandlers(event, argumentsList) { + if (this._eventHash[event].length) { + this._eventHash[event].forEach(handler => handler(...argumentsList)); + } + } } From d30924bb7972adba41606bccd9135365f50f9b1b Mon Sep 17 00:00:00 2001 From: korifey91 Date: Sat, 25 Jan 2020 23:33:58 +0300 Subject: [PATCH 16/20] New Feature: add Scene class --- index.js | 57 ++++++++++++++++++++------------------ src/Scene/index.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 src/Scene/index.js diff --git a/index.js b/index.js index 2a0f900..229cf93 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ import TileableCanvasMixin from './src/Сanvas/mixins/tileableCanvas.js'; import DrawableCanvasMixin from './src/Сanvas/mixins/drawableCanvas.js'; import drawImageFromMap from './src/utils/drawImageFromMap.js'; import Character from './src/utils/classes/Character.js'; +import Scene from './src/Scene'; const MainCanvas = DrawableCanvasMixin(TileableCanvasMixin(ResizeableCanvasMixin(CustomCanvas))); @@ -55,14 +56,14 @@ const main = async () => { // main(); async function test() { - const canvas = document.createElement('canvas'); - canvas.width = 500; - canvas.height = 500; - document.body.append(canvas); - const ctx = canvas.getContext('2d'); - const player = await Character.create( - { - coreElement: canvas, + // const canvas = document.createElement('canvas'); + // canvas.width = 500; + // canvas.height = 500; + // document.body.append(canvas); + // const ctx = canvas.getContext('2d'); + const scene = new Scene(document.body); + const player = await Character.create({ + coreElement: scene, position: { x: 0, y: 0 }, mainSettings: { mainFlipbook: './content/sources/PNG/Knight/knight.png', @@ -99,25 +100,27 @@ async function test() { './content/sources/PNG/Knight/Attack/attack4.png', ], }, - } - ); - const tick = () => { - // setTimeout(this.tick, 1000); - requestAnimationFrame(tick); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.drawImage( - player.render(), - 0, - 0, - player.width, - player.height, - 0, - 0, - player.width, - player.height, - ); - }; - tick(); + }); + + scene.addHero(player); + scene.start(); +// const tick = () => { +// // setTimeout(this.tick, 1000); +// requestAnimationFrame(tick); +// ctx.clearRect(0, 0, canvas.width, canvas.height); +// ctx.drawImage( +// player.render(), +// 0, +// 0, +// player.width, +// player.height, +// 0, +// 0, +// player.width, +// player.height, +// ); +// }; +// tick(); } test(); diff --git a/src/Scene/index.js b/src/Scene/index.js new file mode 100644 index 0000000..1c7ebe4 --- /dev/null +++ b/src/Scene/index.js @@ -0,0 +1,68 @@ +import { Character } from '../utils/classes'; + +/** + * @class Scene - The core of a game. + */ +export default class Scene { + // _hero = null; + // _ctx = null; + + /** + * @constructor Scene + * @param {HTMLElement} element - a place where game will be rendering + * @param {number} [width=500] - width of a game viewport + * @param {number} [height=500] - height of a game viewport + */ + constructor(element, width, height) { + this._canvas = document.createElement('canvas'); + this._canvas.width = width || 500; + this._canvas.height = height || 500; + element.append(this._canvas); + this._ctx = this._canvas.getContext('2d'); + } + + /** + * Method to add a main Hero to a game + * @param {Character} hero - Instance of the Character class which would be the main hero of a game. + */ + addHero(hero) { + if (hero instanceof Character) { + this._hero = hero; + } + else throw new Error('Hero should be an instance of the Character class.') + } + + start() { + this._paused = false; + this._render(); + } + + pause() { + this._paused = true; + } + + _render() { + if (this._paused) return; + + requestAnimationFrame(this.start.bind(this)); + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + this._renderBackground(); + this._renderHero(); + } + _renderBackground() {} + + _renderHero() { + const { position, width, height } = this._hero; + this._ctx.drawImage( + this._hero.render(), + 0, + 0, + width, + height, + position.x, + position.y, + width, + height, + ); + } +} From ccd8477148c79bdb101c22d391af5930891189b5 Mon Sep 17 00:00:00 2001 From: korifey91 Date: Mon, 27 Jan 2020 17:22:57 +0300 Subject: [PATCH 17/20] New Feature: add Scene class Dev: add keypress event handling for jump Dev: add check position to Scene --- src/Scene/index.js | 15 +++++++++++++-- src/utils/classes/Character.js | 21 ++++++++++++++++----- src/utils/classes/Flipbook.js | 1 + 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/Scene/index.js b/src/Scene/index.js index 1c7ebe4..a06a409 100644 --- a/src/Scene/index.js +++ b/src/Scene/index.js @@ -40,17 +40,28 @@ export default class Scene { pause() { this._paused = true; } - + + checkPosition(x, y, width, height) { + if (x <= 0) return false; + if (x + width >= this._canvas.width) return false; + if (y < 0) return false; + return y + height < this._canvas.height; + } + _render() { if (this._paused) return; - requestAnimationFrame(this.start.bind(this)); + requestAnimationFrame(this._render.bind(this)); this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); this._renderBackground(); + this._renderObjects(); this._renderHero(); } + _renderBackground() {} + _renderObjects() {} + _renderHero() { const { position, width, height } = this._hero; this._ctx.drawImage( diff --git a/src/utils/classes/Character.js b/src/utils/classes/Character.js index 97c2d07..a8db79e 100644 --- a/src/utils/classes/Character.js +++ b/src/utils/classes/Character.js @@ -339,16 +339,23 @@ export default class Character { _initListeners() { window.addEventListener('keydown', this._keydownEventHandler.bind(this), { passive: true }); window.addEventListener('keyup', this._keyupEventHandler.bind(this), { passive: true }); + window.addEventListener('keypress', this._keypressEventHandler.bind(this), { passive: true }); } _keydownEventHandler(event) { + if ([this.jumpSettings.jumpCode, this.jumpSettings.alternativeJumpCode].includes(event.code)) return; if (Object.keys(this._actionHandlerHash).includes(event.code)) this._actionHandlerHash[event.code](event); } _keyupEventHandler(event) { + if ([this.jumpSettings.jumpCode, this.jumpSettings.alternativeJumpCode].includes(event.code)) return; this.stop(); } + _keypressEventHandler(event) { + if ([this.jumpSettings.jumpCode, this.jumpSettings.alternativeJumpCode].includes(event.code)) this._actionHandlerHash[event.code](event); + } + get width() { return this.flipbook.currentSprite.width; } @@ -358,7 +365,7 @@ export default class Character { } _changePosition(dx = 0, dy = 0) { - if (this.mainSettings.checkPosition(this.position.x + dx, this.position.y + dy, this.width, this.height)) { + if (this._coreElement.checkPosition(this.position.x + dx, this.position.y + dy, this.width, this.height)) { this.position.x += dx; this.position.y += dy; if (this._hooks.onMove instanceof Function) this._hooks.onMove(); @@ -368,14 +375,18 @@ export default class Character { _setOnChangeJumpFrame() { const onChangeHandler = (frameNumber, frameCount) => { const middleFrameNumber = Math.ceil(frameCount / 2); - if (frameNumber > 1 && frameNumber < middleFrameNumber) this.position.y -= 8; - if (frameNumber > middleFrameNumber && frameNumber < frameCount) this.position.y += 8; + if (frameNumber > 1 && frameNumber < middleFrameNumber) this._changePosition(0, -8); + if (frameNumber > middleFrameNumber && frameNumber < frameCount) this._changePosition(0, 8); if (frameNumber === frameCount) { this.actionType = this._prevActionType; } }; - this.jumpSettings.jumpLeftFlipbook.on('frameChange', onChangeHandler.bind(this)); - this.jumpSettings.jumpRightFlipbook.on('frameChange', onChangeHandler.bind(this)); + const catchEndJumping = (frameNumber, frameCount) => { + if (frameNumber === frameCount) this.stop(); + }; + this.jumpSettings.jumpLeftFlipbook.on('frameChange', onChangeHandler); + this.jumpSettings.jumpRightFlipbook.on('frameChange', onChangeHandler); + this.jumpSettings.jumpRightFlipbook.on('frameChange', catchEndJumping); } _getOffset() { diff --git a/src/utils/classes/Flipbook.js b/src/utils/classes/Flipbook.js index 1f077af..ac1010f 100644 --- a/src/utils/classes/Flipbook.js +++ b/src/utils/classes/Flipbook.js @@ -59,6 +59,7 @@ export default class Flipbook { } start() { + this.stop(); this._updateSize(); this._render(); if (this._sprites.length > 1) { From 22da38f5569cab0c5202f713de472da24dbaf7ef Mon Sep 17 00:00:00 2001 From: korifey91 Date: Mon, 27 Jan 2020 22:05:30 +0300 Subject: [PATCH 18/20] New Feature: add Scene class Dev: fix all move and jump bugs --- src/utils/classes/Character.js | 57 ++++++++++++++++------------------ 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/utils/classes/Character.js b/src/utils/classes/Character.js index a8db79e..1b386e1 100644 --- a/src/utils/classes/Character.js +++ b/src/utils/classes/Character.js @@ -229,45 +229,44 @@ export default class Character { } set currentActionType(actionName) { - if (actionName === 'JUMP' && this.currentActionType === 'MOVE') this._prevActionType = 'MOVE'; - else this._prevActionType = null; - if (this.currentActionType !== actionName) this._currentActionType = actionName; if (actionName === 'STOP') { if (this._hooks.onStop instanceof Function) this._hooks.onStop(); } - // this._currentActionType = actionName; } moveRight() { - if (this.currentActionType === 'MOVE' && this._direction === 'RIGHT') return; + if (this._moving && this._direction === 'RIGHT') return; this.currentActionType = 'MOVE'; this._direction = 'RIGHT'; + this._moving = true; this.flipbook = this.moveSettings.moveRightFlipbook; this.flipbook.start(); } moveLeft() { - if (this.currentActionType === 'MOVE' && this._direction === 'LEFT') return; + if (this._moving && this._direction === 'LEFT') return; this.currentActionType = 'MOVE'; this._direction = 'LEFT'; + this._moving = true; this.flipbook = this.moveSettings.moveLeftFlipbook; this.flipbook.start(); } jump() { - if (this.currentActionType === 'JUMP') return; + if (this._jumping) return; + this._jumping = true; this.currentActionType = 'JUMP'; this.flipbook = this._direction === 'RIGHT' ? this.jumpSettings.jumpRightFlipbook : this.jumpSettings.jumpLeftFlipbook; this.flipbook.start(); } stop() { - if (this.currentActionType === 'JUMP' && this._prevActionType === 'MOVE') { + if (this._jumping) return; + if (!this._jumping && this._moving) { this._stopAllFlipbooks(); if (this._direction === 'RIGHT') this.moveRight(); - else this.moveLeft(); + if (this._direction === 'LEFT') this.moveLeft(); } else { this.currentActionType = 'STOP'; this._stopAllFlipbooks(); - // if (this.flipbook instanceof Flipbook) this.flipbook.stop(); this.flipbook = this._direction === 'RIGHT' ? this.mainSettings.mainRightFlipbook : this.mainSettings.mainLeftFlipbook; if (this.flipbook instanceof Flipbook) this.flipbook.start(); } @@ -287,19 +286,9 @@ export default class Character { */ render() { const offset = this._getOffset(); - if (this.currentActionType === 'MOVE') { - if (this._direction === 'RIGHT') { - this._changePosition(offset); - } - if (this._direction === 'LEFT') { - this._changePosition(-offset); - } - } else if (this.currentActionType === 'JUMP') { - if (this._prevActionType === 'MOVE') { - if (this._direction === 'RIGHT') this._changePosition(offset); - if (this._direction === 'LEFT') this._changePosition(-offset); - } - } + if (this._moving && this._direction === 'RIGHT') this._changePosition(offset); + if (this._moving && this._direction === 'LEFT') this._changePosition(-offset); + this._lastRenderTime = Date.now(); return this.flipbook.currentSprite; } @@ -339,23 +328,25 @@ export default class Character { _initListeners() { window.addEventListener('keydown', this._keydownEventHandler.bind(this), { passive: true }); window.addEventListener('keyup', this._keyupEventHandler.bind(this), { passive: true }); - window.addEventListener('keypress', this._keypressEventHandler.bind(this), { passive: true }); } _keydownEventHandler(event) { - if ([this.jumpSettings.jumpCode, this.jumpSettings.alternativeJumpCode].includes(event.code)) return; if (Object.keys(this._actionHandlerHash).includes(event.code)) this._actionHandlerHash[event.code](event); } _keyupEventHandler(event) { if ([this.jumpSettings.jumpCode, this.jumpSettings.alternativeJumpCode].includes(event.code)) return; + if ([ + this.moveSettings.moveRightCode, + this.moveSettings.alternativeMoveRightCode, + this.moveSettings.moveLeftCode, + this.moveSettings.alternativeMoveLeftCode, + ].includes(event.code)) { + this._moving = false; + } this.stop(); } - _keypressEventHandler(event) { - if ([this.jumpSettings.jumpCode, this.jumpSettings.alternativeJumpCode].includes(event.code)) this._actionHandlerHash[event.code](event); - } - get width() { return this.flipbook.currentSprite.width; } @@ -382,9 +373,13 @@ export default class Character { } }; const catchEndJumping = (frameNumber, frameCount) => { - if (frameNumber === frameCount) this.stop(); + if (frameNumber === frameCount) { + this._jumping = false; + this.stop(); + } }; this.jumpSettings.jumpLeftFlipbook.on('frameChange', onChangeHandler); + this.jumpSettings.jumpLeftFlipbook.on('frameChange', catchEndJumping); this.jumpSettings.jumpRightFlipbook.on('frameChange', onChangeHandler); this.jumpSettings.jumpRightFlipbook.on('frameChange', catchEndJumping); } @@ -396,6 +391,8 @@ export default class Character { } _stopAllFlipbooks() { + this._jumping = false; + this._moving = false; this.mainSettings.mainLeftFlipbook.stop(); this.mainSettings.mainRightFlipbook.stop(); this.jumpSettings.jumpRightFlipbook.stop(); From c1ffb893b51b2a87f4b7a0fd8df90cf639375ff3 Mon Sep 17 00:00:00 2001 From: korifey91 Date: Wed, 29 Jan 2020 20:37:12 +0300 Subject: [PATCH 19/20] New Feature: add Scene class Dev: add check any collisions in move process of Character.js Dev: add addObject for Scene Dev: add collisions detection functionality in Scene --- src/Scene/index.js | 31 ++++++++++++++++++++++++++++++- src/utils/classes/Character.js | 25 ++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/Scene/index.js b/src/Scene/index.js index a06a409..d065627 100644 --- a/src/Scene/index.js +++ b/src/Scene/index.js @@ -6,6 +6,8 @@ import { Character } from '../utils/classes'; export default class Scene { // _hero = null; // _ctx = null; + _staticObjects = []; + _dynamicObjects = []; /** * @constructor Scene @@ -31,6 +33,16 @@ export default class Scene { } else throw new Error('Hero should be an instance of the Character class.') } + + /** + * + * @param object + * @param {string} type - dynamic or static + */ + addObject(object, type) { + if (object && type === 'static') this._staticObjects.push(object); + if (object && type === 'dynamic') this._dynamicObjects.push(object); + } start() { this._paused = false; @@ -41,7 +53,7 @@ export default class Scene { this._paused = true; } - checkPosition(x, y, width, height) { + checkBeyondPosition(x, y, width, height) { if (x <= 0) return false; if (x + width >= this._canvas.width) return false; if (y < 0) return false; @@ -76,4 +88,21 @@ export default class Scene { height, ); } + + checkMoveCollisions(object) { + return this._staticObjects.some(obj => this._detectCollision(object, obj)) + } + + checkDamageCollisions(object) { + return this._dynamicObjects.some(obj => this._detectCollision(object, obj)) + } + + _detectCollision(a, b) { + const ax2 = a.position.x + a.width; + const ay2 = a.position.y + a.height; + const bx2 = b.position.x + b.width; + const by2 = b.position.y + b.height; + + return !(ax2 < b.x || a.x > bx2 || ay2 < b.y || a.y > by2); + } } diff --git a/src/utils/classes/Character.js b/src/utils/classes/Character.js index 1b386e1..2bab7e3 100644 --- a/src/utils/classes/Character.js +++ b/src/utils/classes/Character.js @@ -12,6 +12,7 @@ export default class Character { _hooks = { onStop: null, onMove: null, + onDamage: null, }; flipbook = null; @@ -57,7 +58,7 @@ export default class Character { /** * @constructs The main method to create a character - * @param {HTMLCanvasElement} coreElement - canvas on which Character will be rendered + * @param {Scene} coreElement - canvas on which Character will be rendered * @param {Object} position - initial Character position * @param {number} position.x - canvas coordinates * @param {number} position.y - canvas coordinates @@ -130,7 +131,7 @@ export default class Character { } /** - * @param {HTMLCanvasElement} coreElement - canvas on which Character will be rendered + * @param {Scene} coreElement - canvas on which Character will be rendered * @param {Object} position - initial Character position * @param {number} position.x - canvas coordinates * @param {number} position.y - canvas coordinates @@ -301,6 +302,18 @@ export default class Character { window.removeEventListener('keyup', this._keyupEventHandler); } + /** + * This method is used to add hooks for a character. + * Available hooks - onMove, onStop, onDamage + * @param hook + * @param handler + */ + on(hook, handler) { + const isValidHook = ['onMove', 'onStop', 'onDamage'].includes(hook); + const isValidHandler = handler instanceof Function; + if (isValidHook && isValidHandler) this._hooks[hook] = handler; + } + _validateFlipbooks(mainFlipbook, moveFlipbook, jumpFlipbook, attackFlipbook) { const isMainFlipbookValid = mainFlipbook != null && (mainFlipbook instanceof Sprite || mainFlipbook instanceof Flipbook); const isMoveFlipbookValid = moveFlipbook != null && moveFlipbook instanceof Flipbook; @@ -356,11 +369,17 @@ export default class Character { } _changePosition(dx = 0, dy = 0) { - if (this._coreElement.checkPosition(this.position.x + dx, this.position.y + dy, this.width, this.height)) { + const isWithin = this._coreElement.checkBeyondPosition(this.position.x + dx, this.position.y + dy, this.width, this.height); + const canMove = this._coreElement.checkMoveCollisions(this); + if (isWithin && canMove) { this.position.x += dx; this.position.y += dy; if (this._hooks.onMove instanceof Function) this._hooks.onMove(); } + const isDamageReceived = this._coreElement.checkDamageCollisions(this); + if (isDamageReceived) { + if (this._hooks.onDamage instanceof Function) this._hooks.onDamage(); + } } _setOnChangeJumpFrame() { From 87e57b6374b8ad4b385c45efc243325e7f660971 Mon Sep 17 00:00:00 2001 From: korifey91 Date: Wed, 29 Jan 2020 21:00:51 +0300 Subject: [PATCH 20/20] New Feature: add Scene class Dev: add method to render objects in Scene Dev: fix _changePosition in Character.js --- src/Scene/index.js | 42 ++++++++++++++++++++++++---------- src/utils/classes/Character.js | 4 ++-- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/Scene/index.js b/src/Scene/index.js index d065627..2244da0 100644 --- a/src/Scene/index.js +++ b/src/Scene/index.js @@ -59,7 +59,26 @@ export default class Scene { if (y < 0) return false; return y + height < this._canvas.height; } - + + /** + * If object has collision with any static object returns true. + * @param {Character} object + * @returns {boolean} + */ + checkMoveCollisions(object) { + return this._staticObjects.some(obj => this._detectCollision(object, obj)) + } + + /** + * If object has collision with any dynamic object returns true. + * It means that object receives a damage. + * @param {Character} object + * @returns {boolean} + */ + checkDamageCollisions(object) { + return this._dynamicObjects.some(obj => this._detectCollision(object, obj)) + } + _render() { if (this._paused) return; @@ -72,12 +91,19 @@ export default class Scene { _renderBackground() {} - _renderObjects() {} + _renderObjects() { + this._staticObjects.forEach(staticObject => this._renderObject(staticObject)); + this._dynamicObjects.forEach(dynamicObject => this._renderObject(dynamicObject)); + } _renderHero() { - const { position, width, height } = this._hero; + this._renderObject(this._hero); + } + + _renderObject(object) { + const { position, width, height } = object; this._ctx.drawImage( - this._hero.render(), + object.render(), 0, 0, width, @@ -89,14 +115,6 @@ export default class Scene { ); } - checkMoveCollisions(object) { - return this._staticObjects.some(obj => this._detectCollision(object, obj)) - } - - checkDamageCollisions(object) { - return this._dynamicObjects.some(obj => this._detectCollision(object, obj)) - } - _detectCollision(a, b) { const ax2 = a.position.x + a.width; const ay2 = a.position.y + a.height; diff --git a/src/utils/classes/Character.js b/src/utils/classes/Character.js index 2bab7e3..2f68fa6 100644 --- a/src/utils/classes/Character.js +++ b/src/utils/classes/Character.js @@ -370,8 +370,8 @@ export default class Character { _changePosition(dx = 0, dy = 0) { const isWithin = this._coreElement.checkBeyondPosition(this.position.x + dx, this.position.y + dy, this.width, this.height); - const canMove = this._coreElement.checkMoveCollisions(this); - if (isWithin && canMove) { + const hasMoveCollisions = this._coreElement.checkMoveCollisions(this); + if (isWithin && !hasMoveCollisions) { this.position.x += dx; this.position.y += dy; if (this._hooks.onMove instanceof Function) this._hooks.onMove();