diff --git a/index.html b/index.html
index f22181e..d789c45 100644
--- a/index.html
+++ b/index.html
@@ -8,6 +8,7 @@
position: absolute;
top: 0;
left: 0;
+ background-color: #6096ff;
}
diff --git a/js/Key.js b/js/Key.js
new file mode 100644
index 0000000..9821e99
--- /dev/null
+++ b/js/Key.js
@@ -0,0 +1,31 @@
+export default class Key
+{
+ constructor(name)
+ {
+ this.name = name;
+ this.pressed = false;
+
+ window.addEventListener(
+ 'keydown',
+ (event) => {
+ if (event.code === this.name) {
+ this.pressed = true;
+ }
+ }
+ );
+
+ window.addEventListener(
+ 'keyup',
+ (event) => {
+ if (event.code === this.name) {
+ this.pressed = false;
+ }
+ }
+ );
+ }
+
+ isPressed()
+ {
+ return this.pressed;
+ }
+}
diff --git a/js/MediaImage.js b/js/MediaImage.js
new file mode 100644
index 0000000..b7dd2da
--- /dev/null
+++ b/js/MediaImage.js
@@ -0,0 +1,14 @@
+import InterfaceEvent from "./events/InterfaceEvent.js";
+import ImageLoadedEvent from "./events/ImageLoadedEvent";
+
+export default class MediaImage
+{
+ constructor(filename)
+ {
+ this.image = new Image();
+ this.image.src = filename;
+ this.image.onload = function () {
+ window.dispatchEvent(new ImageLoadedEvent(filename));
+ }
+ }
+}
\ No newline at end of file
diff --git a/js/MediaImageCollection.js b/js/MediaImageCollection.js
new file mode 100644
index 0000000..792e326
--- /dev/null
+++ b/js/MediaImageCollection.js
@@ -0,0 +1,28 @@
+import InterfaceEvent from "./events/InterfaceEvent.js";
+import ImageLoadedEvent from "./events/ImageLoadedEvent";
+
+export default class MediaImageCollection
+{
+ constructor()
+ {
+ this.mediaImages = [];
+ this.numberImagesLoaded = 0;
+
+ window.addEventListener(
+ ImageLoadedEvent.NAME,
+ () => {
+ this.numberImagesLoaded++;
+
+ if (this.numberImagesLoaded === this.mediaImages.length) {
+ window.dispatchEvent(new InterfaceEvent.MEDIA_COLLECTION_LOADED);
+ }
+ }
+ )
+ }
+
+ addMediaImage(mediaImage)
+ {
+ this.mediaImages.push(mediaImage);
+
+ }
+}
\ No newline at end of file
diff --git a/js/Movable.js b/js/Movable.js
new file mode 100644
index 0000000..558da18
--- /dev/null
+++ b/js/Movable.js
@@ -0,0 +1,41 @@
+import GeometryPoint from "./geometry/GeometryPoint.js";
+
+export default class Movable
+{
+ constructor(defaultAnimation, speed = 1)
+ {
+ this.currentAnimation = 'DEFAULT';
+ this.animations = {
+ DEFAULT: defaultAnimation,
+ };
+ this.position = new GeometryPoint();
+ this.speed = speed;
+ }
+
+ playAnimation(animation, timestamp)
+ {
+ this.currentAnimation = animation;
+ this.animations[animation].play(timestamp);
+ }
+
+ addAnimation(name, animation)
+ {
+ this.animations[name] = animation;
+ }
+
+ moveLeft(delta = 1)
+ {
+ this.position.x -= this.speed * delta;
+ }
+
+ moveRight(delta = 1)
+ {
+ this.position.x += this.speed * delta;
+ }
+
+ draw(context)
+ {
+ this.animations[this.currentAnimation].setFootPosition(this.position.x, this.position.y);
+ this.animations[this.currentAnimation].draw(context);
+ }
+}
\ No newline at end of file
diff --git a/js/MrCroc.js b/js/MrCroc.js
new file mode 100644
index 0000000..341fd81
--- /dev/null
+++ b/js/MrCroc.js
@@ -0,0 +1,24 @@
+import Movable from "./Movable.js";
+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));
+ }
+
+ moveRight(timestamp, delta = 1)
+ {
+ this.playAnimation('WALK_RIGHT', timestamp);
+ super.moveRight(delta);
+ }
+
+ moveLeft(timestamp, delta = 1)
+ {
+ this.playAnimation('WALK_LEFT', timestamp);
+ super.moveLeft(delta);
+ }
+}
\ No newline at end of file
diff --git a/js/events/AudioLoadedEvent.js b/js/events/AudioLoadedEvent.js
new file mode 100644
index 0000000..8696007
--- /dev/null
+++ b/js/events/AudioLoadedEvent.js
@@ -0,0 +1,11 @@
+export default class AudioLoadedEvent extends CustomEvent
+{
+ constructor(filename) {
+ super('audioloaded', {detail: filename});
+ }
+
+ getFilename()
+ {
+ this.detail;
+ }
+}
\ No newline at end of file
diff --git a/js/events/BaseEvent.js b/js/events/BaseEvent.js
new file mode 100644
index 0000000..b4ed64c
--- /dev/null
+++ b/js/events/BaseEvent.js
@@ -0,0 +1,7 @@
+export default class BaseEvent extends CustomEvent
+{
+ constructor(name, detail) {
+ super(name, {detail: filename});
+ this.NAME = name;
+ }
+}
\ No newline at end of file
diff --git a/js/events/ImageCollectionLoadedEvent.js b/js/events/ImageCollectionLoadedEvent.js
new file mode 100644
index 0000000..e69de29
diff --git a/js/events/ImageLoadedEvent.js b/js/events/ImageLoadedEvent.js
new file mode 100644
index 0000000..60d1096
--- /dev/null
+++ b/js/events/ImageLoadedEvent.js
@@ -0,0 +1,13 @@
+import BaseEvent from "./BaseEvent.js";
+
+export default class ImageLoadedEvent extends BaseEvent
+{
+ constructor(filename) {
+ super('imgloaded', filename);
+ }
+
+ getFilename()
+ {
+ this.detail;
+ }
+}
\ No newline at end of file
diff --git a/js/events/InterfaceEvent.js b/js/events/InterfaceEvent.js
new file mode 100644
index 0000000..cbe1d37
--- /dev/null
+++ b/js/events/InterfaceEvent.js
@@ -0,0 +1,6 @@
+export default InterfaceEvent;
+
+const InterfaceEvent = {
+ IMAGE_LOADED: 'imgloaded',
+ MEDIA_COLLECTION_LOADED: 'mediacollectionloaded',
+};
\ No newline at end of file
diff --git a/js/geometry/GeometryPoint.js b/js/geometry/GeometryPoint.js
index bcbb722..5ede9b8 100644
--- a/js/geometry/GeometryPoint.js
+++ b/js/geometry/GeometryPoint.js
@@ -1,6 +1,6 @@
export default class GeometryPoint
{
- constructor(x, y)
+ constructor(x = 0, y = 0)
{
this.x = x;
this.y = y;
diff --git a/js/geometry/GeometryRect.js b/js/geometry/GeometryRect.js
index 7f73865..b987f23 100644
--- a/js/geometry/GeometryRect.js
+++ b/js/geometry/GeometryRect.js
@@ -1,6 +1,7 @@
import GeometryPoint from "./GeometryPoint.js";
import GeometryStroke from "./GeometryStroke.js";
import GeometryPointCollection from "./GeometryPointCollection.js";
+import GeometryLine from "./GeometryLine.js";
export default class GeometryRect
{
@@ -31,6 +32,11 @@ export default class GeometryRect
return containsHorizontally && containsVertically;
}
+ hasIntersectionWithRect(rect)
+ {
+ return this.getBorderIntersectonsWithRect(rect).getLength() > 0;
+ }
+
getBorderTop()
{
return new GeometryStroke(
@@ -92,6 +98,12 @@ export default class GeometryRect
return intersections;
}
+ /**
+ * Returns the intersection points between the border lines and the given stroke.
+ *
+ * @param stroke
+ * @returns {GeometryPointCollection}
+ */
getBorderIntersectionsWithStroke(stroke)
{
let borders = [
@@ -172,6 +184,12 @@ export default class GeometryRect
*/
getRectIntersection(rect)
{
+ let distanceToOrigin = this.getCenter().getDistanceToPoint(rect.getCenter());
+
+ if (distanceToOrigin > this.getDiagonal() * 0.5 + rect.getDiagonal() * 0.5) {
+ return null;
+ }
+
let intersectionPoints = new GeometryPointCollection();
this.getRectanglePoints().forEach(
@@ -211,6 +229,22 @@ export default class GeometryRect
return null;
}
+ getCenter()
+ {
+ return new GeometryPoint(
+ this.position.x + this.width * 0.5,
+ this.position.y + this.height * 0.5
+ );
+ }
+
+ getDiagonal()
+ {
+ return new GeometryStroke(
+ this.position,
+ new GeometryPoint(this.position.x + this.width, this.position.y + this.height)
+ );
+ }
+
/**
* Visualizes the rect.
*
diff --git a/js/geometry/GeometryStroke.js b/js/geometry/GeometryStroke.js
index 3316715..57e1fde 100644
--- a/js/geometry/GeometryStroke.js
+++ b/js/geometry/GeometryStroke.js
@@ -21,6 +21,11 @@ export default class GeometryStroke extends GeometryLine
return new GeometryLine(this.pointA, this.pointB);
}
+ getLength()
+ {
+ return this.pointA.getDistanceToPoint(this.pointB);
+ }
+
getIntersectionWithStroke(stroke)
{
let intersection = super.getIntersectionWithLine(stroke);
diff --git a/js/module.js b/js/module.js
index be35390..6643323 100644
--- a/js/module.js
+++ b/js/module.js
@@ -1,17 +1,117 @@
import RetroSprite from "./retro/RetroSprite.js";
+import Key from "./Key.js";
+import MrCroc from "./MrCroc.js";
+
+const MEDIA_READY_EVENT = 'mediaready';
+const IMAGE_READY_EVENT = 'imgready';
+
+class ImageLoader
+{
+ images = [];
+ numberImagesLoaded = 0;
+
+ update()
+ {
+ this.numberImagesLoaded++;
+
+ if (this.numberImagesLoaded === this.images.length) {
+ window.dispatchEvent(new Event('imagesloaded'));
+ }
+ }
+
+ getCurrentProgress()
+ {
+ return this.numberImagesLoaded / this.images.length;
+ }
+
+ addImage(image)
+ {
+ image.addEventListener(
+ 'load', () => {
+ this.update();1
+ }
+ );
+
+ this.images.push(image);
+ }
+}
+
+function MainLoop(timestamp)
+{
+ if (lastRendered === undefined && lastTimestamp === undefined) {
+ lastRendered = timestamp;
+ lastTimestamp = timestamp;
+ }
+
+ let delta = (timestamp - lastTimestamp) / (10 / GAME_SPEED);
+
+ if (KeyLeft.isPressed()) {
+ mrCroc.moveLeft(timestamp, delta);
+ } else if (KeyRight.isPressed()) {
+ mrCroc.moveRight(timestamp, delta);
+ }
+
+ if (lastRendered >= FRAME_DURATION) {
+ context.clearRect(0, 0, window.innerWidth, window.innerHeight);
+
+ ground.draw(context);
+ mrCroc.draw(context);
+ lastRendered = timestamp;
+ }
+
+ lastTimestamp = timestamp;
+
+ window.requestAnimationFrame(MainLoop);
+}
+
+const FPS = 60;
+const FRAME_DURATION = 1000 / FPS;
+const GAME_SPEED = 1;
+
+let lastRendered = undefined;
+let lastTimestamp = undefined;
+let context;
+let ground, mrCroc;
+
+let KeyLeft = new Key('ArrowLeft');
+let KeyRight = new Key('ArrowRight');
+
+let loader = new ImageLoader();
let image = new Image();
-image.src = 'graphics/mario.png';
+image.src = 'graphics/mr-croc-stand.png';
+loader.addImage(image);
-image.onload = function () {
- let sprite = new RetroSprite(image, 10);
- let canvas = document.getElementById('canvas');
- canvas.width = window.innerWidth;
- canvas.height = window.innerHeight;
+let imgAnimation = new Image();
+imgAnimation.src = 'graphics/mr-croc-walk-right.png';
+loader.addImage(imgAnimation);
- let context = canvas.getContext('2d');
+let imgAnimationB = new Image();
+imgAnimationB.src = 'graphics/mr-croc-walk-left.png';
+loader.addImage(imgAnimationB);
- console.log(context);
+let imgBackground = new Image();
+imgBackground.src = 'graphics/ground.jpg';
+loader.addImage(imgBackground);
- sprite.draw(context);
-};
\ No newline at end of file
+window.addEventListener(
+ 'imagesloaded',
+ () => {
+ console.log('Loaded');
+ 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;
+
+ context = canvas.getContext('2d');
+
+ mrCroc = new MrCroc();
+ mrCroc.position.x = 100;
+ mrCroc.position.y = window.innerHeight - ground.getHeight();
+ ground.draw(context);
+
+ window.requestAnimationFrame(MainLoop);
+ }
+);
diff --git a/js/retro/RetroAnimation.js b/js/retro/RetroAnimation.js
new file mode 100644
index 0000000..5ecadd7
--- /dev/null
+++ b/js/retro/RetroAnimation.js
@@ -0,0 +1,60 @@
+import RetroSprite from "./RetroSprite.js";
+
+export default class RetroAnimation extends RetroSprite
+{
+ constructor(image, frames, scale = 1, fps = 6)
+ {
+ super(image, scale);
+ this.frames = frames;
+ this.fps = fps;
+ this.frameDuration = 1000 / this.fps;
+ this.currentFrame = 0;
+ this.lastUpdate = undefined;
+ this.frameWidth = this.canvas.width / this.frames;
+ this.isPlaying = false;
+ }
+
+ getWidth() {
+ return this.frameWidth;
+ }
+
+ setFootPosition(x, y) {
+ this.position.x = x - this.frameWidth * 0.5;
+ this.position.y = y - this.canvas.height;
+ }
+
+ play(timestamp)
+ {
+ this.isPlaying = true;
+
+ if (this.lastUpdate === undefined) {
+ this.lastUpdate = timestamp;
+ }
+
+ if (timestamp - this.lastUpdate >= this.frameDuration) {
+ this.currentFrame = (this.currentFrame + 1) % this.frames;
+ this.lastUpdate = timestamp;
+ }
+ }
+
+ draw(context)
+ {
+ if (!this.isPlaying) {
+ this.currentFrame = 0;
+ }
+
+ context.drawImage(
+ this.canvas,
+ this.frameWidth * this.currentFrame,
+ 0,
+ this.frameWidth,
+ this.canvas.height,
+ this.position.x,
+ this.position.y,
+ this.frameWidth,
+ this.canvas.height
+ );
+
+ this.isPlaying = false;
+ }
+}
\ No newline at end of file
diff --git a/js/retro/RetroSprite.js b/js/retro/RetroSprite.js
index f953ec3..0553290 100644
--- a/js/retro/RetroSprite.js
+++ b/js/retro/RetroSprite.js
@@ -1,10 +1,12 @@
import GeometryPoint from "../geometry/GeometryPoint.js";
+import GeometryRect from "../geometry/GeometryRect.js";
export default class RetroSprite
{
constructor(image, scale = 1)
{
- this.image = image;
+ this.image = new Image();
+ this.image.src = image;
this.canvas = document.createElement('canvas');
this.context = this.canvas.getContext('2d');
this.position = new GeometryPoint();
@@ -13,6 +15,37 @@ export default class RetroSprite
this.render();
}
+ /**
+ * Returns the position of the sprite's center
+ *
+ * @returns {GeometryPoint}
+ */
+ getCenter()
+ {
+ this.getRect().getCenter();
+ }
+
+ setFootPosition(x, y)
+ {
+ this.position.x = x - this.canvas.width * 0.5;
+ this.position.y = y - this.canvas.height;
+ }
+
+ getRect()
+ {
+ return new GeometryRect(this.position.x, this.position.y, this.canvas.width, this.canvas.height);
+ }
+
+ getWidth()
+ {
+ return this.canvas.width;
+ }
+
+ getHeight()
+ {
+ return this.canvas.height;
+ }
+
draw(context)
{
context.drawImage(this.canvas, this.position.x, this.position.y);
@@ -20,7 +53,7 @@ export default class RetroSprite
hasRectCollisionWith(sprite)
{
-
+ return this.getRect().getRectIntersection(sprite.getRect()) !== null;
}
hasCollisionWith(sprite)
@@ -31,6 +64,9 @@ export default class RetroSprite
render()
{
let canvasTemp = document.createElement('canvas');
+ canvasTemp.width = this.image.width * this.scale;
+ canvasTemp.height = this.image.height * this.scale;
+
let contextTemp = canvasTemp.getContext('2d');
contextTemp.drawImage(this.image, 0, 0);