diff --git a/js/FileLoader.js b/js/FileLoader.js new file mode 100644 index 0000000..5dfefbd --- /dev/null +++ b/js/FileLoader.js @@ -0,0 +1,28 @@ +export default class FileLoader +{ + constructor(filename) + { + this.filename = filename; + this.content = ''; + this.loadContent(); + } + + getContent() + { + return this.content; + } + + loadContent() + { + let request = new XMLHttpRequest(); + + request.onreadystatechange = () => { + if (request.status === 200 && request.readyState === 4) { + this.content = request.responseText; + } + }; + + request.open('GET', this.filename, false); + request.send(); + } +} \ No newline at end of file diff --git a/js/Movable.js b/js/Movable.js index 62cd10b..fa518e2 100644 --- a/js/Movable.js +++ b/js/Movable.js @@ -10,6 +10,7 @@ export default class Movable }; this.position = new GeometryPoint(); this.speed = speed; + this.jumpHeight = 35; this.fallSpeed = 0; } @@ -39,6 +40,39 @@ export default class Movable return this.animations[this.currentAnimation].getRect(); } + getPositionFootLeft() + { + return new GeometryPoint( + this.position.x - this.animations[this.currentAnimation].getWidth() * 0.5, this.position.y + ); + } + + getPositionFootRight() + { + return new GeometryPoint( + this.position.x + this.animations[this.currentAnimation].getWidth() * 0.5, this.position.y + ); + } + + jump() + { + this.fallSpeed -= this.jumpHeight; + this.isJumping = true; + } + + getFootHeight() + { + return new GeometryPoint( + this.position.x, + this.position.y + this.animations[this.currentAnimation].getHeight() + ); + } + + setFootHeight(height) + { + this.position.y = height - this.animations[this.currentAnimation].getHeight(); + } + draw(context) { this.animations[this.currentAnimation].setFootPosition(this.position.x, this.position.y); diff --git a/js/MrCroc.js b/js/MrCroc.js index 341fd81..492d22a 100644 --- a/js/MrCroc.js +++ b/js/MrCroc.js @@ -4,10 +4,11 @@ import RetroAnimation from "./retro/RetroAnimation.js"; export default class MrCroc extends Movable { constructor() { - super(new RetroAnimation('graphics/mr-croc-walk-right.png', 2, 5), 5); - - this.addAnimation('WALK_RIGHT', new RetroAnimation('graphics/mr-croc-walk-right.png', 2, 5, 10)); - this.addAnimation('WALK_LEFT', new RetroAnimation('graphics/mr-croc-walk-left.png', 2, 5, 10)); + let SCALE = 3; + super(new RetroAnimation('graphics/mr-croc-walk-right.png', 2, SCALE), 7); + this.isJumping = false; + this.addAnimation('WALK_RIGHT', new RetroAnimation('graphics/mr-croc-walk-right.png', 2, SCALE, 10)); + this.addAnimation('WALK_LEFT', new RetroAnimation('graphics/mr-croc-walk-left.png', 2, SCALE, 10)); } moveRight(timestamp, delta = 1) diff --git a/js/geometry/GeometryRect.js b/js/geometry/GeometryRect.js index b987f23..65de13a 100644 --- a/js/geometry/GeometryRect.js +++ b/js/geometry/GeometryRect.js @@ -1,7 +1,6 @@ import GeometryPoint from "./GeometryPoint.js"; import GeometryStroke from "./GeometryStroke.js"; import GeometryPointCollection from "./GeometryPointCollection.js"; -import GeometryLine from "./GeometryLine.js"; export default class GeometryRect { @@ -26,12 +25,17 @@ export default class GeometryRect */ isContainingPoint(geometryPoint) { - let containsHorizontally = geometryPoint.x >= this.position.x && geometryPoint.x <= this.position.x + this.width; - let containsVertically = geometryPoint.y >= this.position.y && geometryPoint.y <= this.position.y + this.height; + let containsHorizontally = geometryPoint.x >= this.position.x && geometryPoint.x < this.position.x + this.width; + let containsVertically = geometryPoint.y >= this.position.y && geometryPoint.y < this.position.y + this.height; return containsHorizontally && containsVertically; } + isEqual(rect) + { + return rect.x === this.x && rect.y === this.y && rect.width === this.width && rect.height === this.height; + } + hasIntersectionWithRect(rect) { return this.getBorderIntersectonsWithRect(rect).getLength() > 0; @@ -170,8 +174,8 @@ export default class GeometryRect collection.addGeometryPoint(this.position); collection.addGeometryPoint(new GeometryPoint(this.position.x + this.width, this.position.y)); - collection.addGeometryPoint(new GeometryPoint(this.position.x, this.position.y + this.height)); - collection.addGeometryPoint(new GeometryPoint(this.position.x + this.width, this.position.y + this.height)); + collection.addGeometryPoint(new GeometryPoint(this.position.x, this.position.y + this.height - 1)); + collection.addGeometryPoint(new GeometryPoint(this.position.x + this.width, this.position.y + this.height - 1)); return collection; } diff --git a/js/geometry/GeometryRectCollection.js b/js/geometry/GeometryRectCollection.js new file mode 100644 index 0000000..f6f4db0 --- /dev/null +++ b/js/geometry/GeometryRectCollection.js @@ -0,0 +1,133 @@ +export default class GeometryRectCollection +{ + constructor() + { + this.rects = []; + } + + isContainingRect(rect) + { + for (let r = 0; r < this.rects.length; r++) { + if (rect.isEqual(this.rects[r])) { + return true; + } + } + + return false; + } + + getUniqueWidth() + { + if (this.getLength() === 0) { + return null; + } else if (this.getLength() === 1) { + return this.rects[0]; + } + + let widths = [this.rects[0].width]; + + for (let r = 0; r < this.getLength(); r++) { + if (widths.indexOf(this.rects[r].width) !== -1) { + return this.rects[r]; + } + + widths.push(rect.width); + } + + return null; + } + + getUniqueHeight() + { + if (this.getLength() === 0) { + return null; + } else if (this.getLength() === 1) { + return this.rects[0].height; + } + + let heights = [this.rects[0].height]; + + for (let r = 0; r < this.getLength(); r++) { + if (heights.indexOf(rect.height) !== -1) { + return rect.height; + } + + heights.push(rect.height); + } + + return null; + } + + addRect(rect) + { + if (!this.isContainingRect(rect)) { + this.rects.push(rect); + } + } + + getLength() + { + return this.rects.length; + } + + getEqualWidths() + { + if (this.getLength() === 0) { + return null; + } + + let width = this.rects[0].width; + + for (let r = 1; r < this.getLength(); r++) { + if (this.rects[r].width === width) { + return false; + } + } + + return true; + } + + hasEqualWidths() + { + if (this.getLength() === 0) { + return false; + } + + let width = this.rects[0].width; + + for (let r = 1; r < this.getLength(); r++) { + if (this.rects[r].width !== width) { + return false; + } + } + + return true; + } + + hasEqualHeights() + { + if (this.getLength() === 0) { + return false; + } + + let height = this.rects[0].height; + + for (let r = 1; r < this.getLength(); r++) { + if (this.rects[r].height !== height) { + return false; + } + } + + return true; + } + + forEach(callback) + { + this.rects.forEach(callback); + } + + getRect(index) + { + return this.rects[index]; + } +} \ No newline at end of file diff --git a/js/module.js b/js/module.js index 128d987..2b8295e 100644 --- a/js/module.js +++ b/js/module.js @@ -1,10 +1,8 @@ -import RetroSprite from "./retro/RetroSprite.js"; import Key from "./Key.js"; import MrCroc from "./MrCroc.js"; import RetroArchitecture from "./retro/RetroArchitecture.js"; - -const MEDIA_READY_EVENT = 'mediaready'; -const IMAGE_READY_EVENT = 'imgready'; +import FileLoader from "./FileLoader.js"; +import GeometryPoint from "./geometry/GeometryPoint.js"; class ImageLoader { @@ -52,12 +50,59 @@ function MainLoop(timestamp) mrCroc.moveRight(timestamp, delta); } - if (lastRendered >= FRAME_DURATION) { - context.clearRect(0, 0, window.innerWidth, window.innerHeight); + lastGroundHeight = Math.min( + architecture.getGroundHeight(mrCroc.getPositionFootLeft()), + architecture.getGroundHeight(mrCroc.getPositionFootRight()) + ); - ground.draw(context); - mrCroc.draw(context); + mrCroc.position.y += mrCroc.fallSpeed; + + groundHeight = Math.min( + architecture.getGroundHeight(mrCroc.getPositionFootLeft()), + architecture.getGroundHeight(mrCroc.getPositionFootRight()) + ); + + if (mrCroc.position.y < lastGroundHeight) { + mrCroc.fallSpeed += GRAVITY * delta; + } + /* + + if (architecture.hasRectCollision(mrCroc.getRect())) { + mrCroc.position.x = lastPosition.x; + } + let archIntersections = architecture.getCollisionRects(mrCroc.getRect()); + + let width = archIntersections.getUniqueWidth(); + + if (width !== null) { + if (width.x === mrCroc.position.x) { + mrCroc.position.x += width.width; + } else { + mrCroc.position.x = width.x - mrCroc.getRect().width; + } + } + + */ + + if (mrCroc.position.y !== lastGroundHeight) { + if (lastGroundHeight < groundHeight) { + mrCroc.position.y = lastGroundHeight; + mrCroc.fallSpeed = 0; + } else { + mrCroc.fallSpeed += GRAVITY * delta; + } + } else { + if (!mrCroc.isJumping && mrCroc.fallSpeed === 0 && KeyJump.isPressed()) { + mrCroc.jump(); + } else if (!KeyJump.isPressed()) { + mrCroc.isJumping = false; + } + } + + if (timestamp - lastRendered >= FRAME_DURATION) { + context.clearRect(0, 0, window.innerWidth, window.innerHeight); architecture.draw(context); + mrCroc.draw(context); lastRendered = timestamp; } @@ -69,16 +114,23 @@ function MainLoop(timestamp) const FPS = 60; const FRAME_DURATION = 1000 / FPS; const GAME_SPEED = 1; +const GRAVITY = 1; -const LEVEL = '{"tileset": "graphics/tileset.png", "tiles": 1, "scale": 3, "rows": 9, "columns": 16, "matrix": [[0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0], [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0], [null, null, null, null, null, 0, 0, 0, 0, 0, null, null, null, null, null, 0], [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0], [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null], [0, 0, 0, 0, 0, null, null, null, null, null, null, null, null, null, null, null], [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null], [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null], [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]]}'; +let levelJson = new FileLoader('levels/test.json'); +const LEVEL = levelJson.getContent(); + +console.log(LEVEL); let lastRendered = undefined; let lastTimestamp = undefined; +let groundHeight = undefined; +let lastGroundHeight = undefined; let context; -let ground, mrCroc, architecture; +let mrCroc, architecture; let KeyLeft = new Key('ArrowLeft'); let KeyRight = new Key('ArrowRight'); +let KeyJump = new Key('Space'); let loader = new ImageLoader(); @@ -99,15 +151,12 @@ imgBackground.src = 'graphics/ground.jpg'; loader.addImage(imgBackground); let imgArch = new Image(); -imgArch.src = 'graphics/tileset.png'; +imgArch.src = 'graphics/maria-world.jpg'; loader.addImage(imgArch); window.addEventListener( 'imagesloaded', () => { - ground = new RetroSprite('graphics/ground.jpg', 4); - ground.position.y = window.innerHeight - ground.getRect().height; - let canvas = document.getElementById('canvas'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; @@ -117,9 +166,11 @@ window.addEventListener( architecture = RetroArchitecture.createFromJson(LEVEL); mrCroc = new MrCroc(); - mrCroc.position.x = 100; - mrCroc.position.y = 480; - ground.draw(context); + mrCroc.position.x = 300; + mrCroc.position.y = 100; + + architecture.draw(context); + mrCroc.draw(context); window.requestAnimationFrame(MainLoop); } diff --git a/js/retro/RetroArchitecture.js b/js/retro/RetroArchitecture.js index d8b9ffe..750531c 100644 --- a/js/retro/RetroArchitecture.js +++ b/js/retro/RetroArchitecture.js @@ -1,6 +1,6 @@ import RetroSprite from "./RetroSprite.js"; -import GeometryRect from "../geometry/GeometryRect.js"; import RetroArchitectureTile from "./RetroArchitectureTile.js"; +import GeometryRectCollection from "../geometry/GeometryRectCollection.js"; export default class RetroArchitecture { @@ -24,6 +24,29 @@ export default class RetroArchitecture } } + getCollisionRects(rect) + { + let posX = Math.floor(rect.position.x / this.tileWidth) - 2; + let posY = Math.floor(rect.position.y / this.tileHeight) - 2; + let rangeX = parseInt( posX + rect.width / this.tileWidth) + 4; + let rangeY = parseInt(posY + rect.height / this.tileHeight) + 4; + + let collection = new GeometryRectCollection(); + + for (let y = Math.max(0, posY); y < rangeY; y++) { + for (let x = Math.max(0, posX); x < rangeX; x++) { + if (y < this.matrix.length && x < this.matrix[y].length && this.matrix[y][x] !== null) { + let intersection = this.matrix[y][x].rect.getRectIntersection(rect); + if (intersection !== null) { + collection.addRect(intersection); + } + } + } + } + + return collection; + } + hasRectCollision(rect) { let posX = Math.floor(rect.position.x / this.tileWidth) - 2; diff --git a/js/retro/RetroSprite.js b/js/retro/RetroSprite.js index 0553290..b21e3f6 100644 --- a/js/retro/RetroSprite.js +++ b/js/retro/RetroSprite.js @@ -46,6 +46,21 @@ export default class RetroSprite return this.canvas.height; } + getFootHeight() + { + return new GeometryPoint(this.position.x, this.position.y + this.getHeight()); + } + + getPositionLeftFoot() + { + return new GeometryPoint(this.position.x - this.getWidth() * 0.5, this.position.y); + } + + getPositionRightFoot() + { + return new GeometryPoint(this.position.x + this.getWidth() * 0.5, this.position.y); + } + draw(context) { context.drawImage(this.canvas, this.position.x, this.position.y);