//I used this game engine to make, among others, my Geometry Dash Remake and my Fantasy Platformer.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var width = canvas.width;
var height = canvas.height;
//circle: this function draws a circle.
//inputs: x position of circle's center, y position of circle's center, radius of circle, boolean fillcircle weether to fill the circle.
//outputs: none
var circle = function(x, y, radius, fillCircle) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
if (fillCircle) {
ctx.fill();
} else {
ctx.stroke();
}
};
//gets rid of the blurred contours and gives a sharper look without changing the resolution.
//However, it is recommended to keep the option on if you plan to have rotating objects.
var getRidOfThoseBlurryContours = function() {
ctx.imageSmoothingEnabled = false;
}
//draws a rectangle with rounded corners
//inputs: x position of center of rectangle, y position of center of rectangle, width of rectangle, height of rectangle, rounded corner radius, color
//outputs: none
var drawRoundedSquare = function(x, y, xSize, ySize, radius, color) {
ctx.fillStyle = color;
xSize = xSize-2*radius;
ySize = ySize-2*radius;
ctx.fillRect((x-(xSize/2)), (y-(ySize/2)), xSize, ySize);
circle((x-(xSize/2))+1, (y-(ySize/2))+1, radius, true);
circle((x+(xSize/2))-1, (y-(ySize/2))+1, radius, true);
circle(x-(xSize/2)+1, y+(ySize/2)-1, radius, true);
circle(x+(xSize/2)-1, y+(ySize/2)-1, radius, true);
ctx.fillRect((x-(xSize/2))+1, (y-(ySize/2)-radius)+1, xSize, radius);
ctx.fillRect((x-(xSize/2)-radius)+1, (y-(ySize/2))+1, radius, ySize);
ctx.fillRect((x+(xSize/2)+radius)-1, (y-(ySize/2))+1, -radius, ySize);
ctx.fillRect((x+(xSize/2))-1, (y+(ySize/2)+radius)-1, -xSize, -radius);
}
//check if a rectangle is colliding with another rectangle
//inputs:
var checkSquareCollision = function(x1, y1, width1, height1, x2, y2, width2, height2) {
if (x1+width1 > x2 && x1 < x2 + width2) {//x
if (y1 + height1> y2 && y1 < y2 + height2) {//y
return true;
}
}
return false;
}
var checkPointCollision = function(px, py, x, y, width, height) {
if (px > x && px < x + width) {//x
if (py> y && py < y + height) {//y
return true;
}
}
return false;
}
var checkMultipleSquareCollisions = function(x, y, width, height, obstacles, complex=false) {
//console.log("2");
var occurences = [];
for (let i = 0; i<=obstacles.length; i+=4) {
// console.log(obstacles);
if (x+width > obstacles[i] && x < obstacles[i] + obstacles[i+2]) {//x
if (y + height> obstacles[i+1] && y < obstacles[i+1] + obstacles[i+3]) {//y
//console.log("h");
if (!complex) {
return true;
} else {
occurences.push(i/4);
}
}
}
}
if (complex) {
return occurences;
} else {
return false;
}
}
var checkMultiplePointCollisions = function(x, y, obstacles) {
for (let i = 0; i<=obstacles.length; i+=4) {
if (x > obstacles[i] && x < obstacles[i] + obstacles[i+2]) {//x
if (y > obstacles[i+1] && y < obstacles[i+1] + obstacles[i+3]) {//y
return true;
}
}
}
return false;
}
var elementsEqual = function (array) {
var equal = true;
for (let i = 0; i < array.length; i++) {
if (!(array[array.length - 1] == array[i])) {
equal = false;
}
}
return equal;
}
//I want a path finding function, that navigates an object around obstacles.
// step one: build a multiple obstacle collision check function. (Done)
// step two: build a method to glide to a target. (in progress)
var followTarget = function(x, y, tx, ty, speed, deadx = 0, deady=0) {
//difference
var dx = Math.abs(tx - x);
var dy = Math.abs(ty - y);
//has arrived?
if(checkPointCollision(x, y, tx-deadx/2, ty-deady/2, deadx, deady)) { //dead means a dead space where the object stops.
return [x, y];
}
//ratio
if (dx != 0) {
var ratio = dy/dx;
} else {
var ratio = 0;
}
//direction
var sx = 1;
if (tx - x < 0) {
sx = -1;
}
var sy = 1;
if (ty - y < 0) {
sy = -1;
}
//adjust speed
var hyp = Math.sqrt((speed*sx)**2+(ratio*speed*sy)**2);
var aspeed = speed/hyp; //aspeed stands for actual speed
//change of position
var step = [(aspeed*sx)*speed, (ratio*aspeed*sy)*speed]; //x, y
//new position
var newPos = [x+step[0], y+step[1]];
//return new position
return newPos;
}
//camera function (camera has an x, y width and height; it updates all other object's xs, ys, widths and heights)
//camera follow function: an (invisible) object follows a target; camera follows object.
//...
// watch https://www.youtube.com/watch?v=eX7W-FWpx2Y to learn more about this type of camera.
var Camera = function() {
this.x = 0;
this.y = 0;
this.width = width;
this.height = height;
this.followX = this.x;
this.followY = this.y;
this.zoom = 1;
this.camerax = width/2;
this.cameray = height/2;
this.update = function() {};
this.motionBlur = 0;
}
Camera.prototype.follow = function(targetx, targety, speed, x=true, y=true, deadx=0, deady=0, tDXPos= width/2, tDYPos = height/2) {
var newPosition = followTarget(this.camerax, this.cameray, targetx, targety, speed, deadx, deady);
if (x) {
this.camerax = newPosition[0];
}
if (y) {
this.cameray = newPosition[1];
}
}
Camera.prototype.glideZoom = function(desiredZoom, speed) {
var dZoom = desiredZoom - this.zoom;//zoom difference
var change = dZoom / speed;
this.zoom = this.zoom+change;
}
camera = new Camera();
var Obstacle = function(x, y, Owidth, Oheight, colour, name = "Obstacle", Obstacle_set = null, collisionDetection = false, camEnabled = true) {
this.x = x;
this.y = y;
this.drawx = this.x;
this.drawy = this.y;
this.relx = width/2 - this.x;
this.rely = height/2 - this.y;
this.width = Owidth;
this.height = Oheight;
this.drawWidth = this.width * (camera.width/width);
this.drawHeight = this.height * (camera.height/height);
this.colour = colour;
this.name = name;
this.collider = [this.x, this.y, this.width, this.height];
this.collisionDetection = collisionDetection;
this.remove = function() {
var index = Obstacle_set.obstacles.indexOf(this);
var new_obstacles = Obstacle_set.obstacles;
new_obstacles.splice(index, 1);
Obstacle_set.obstacles = new_obstacles;
var new_objects = GOS.gameObjects;
index = GOS.gameObjects.indexOf(this);
new_objects.splice(index, 1);
console.log(new_objects);
GOS.gameObjects = new_objects;
Obstacle_set.colliders.splice(index*4, 4);
}
this.draw = function() {
ctx.fillStyle = this.colour;
ctx.fillRect(this.drawx, this.drawy, this.drawWidth, this.drawHeight);
};
this.update = function() {
if (camEnabled) {
this.relx = (camera.camerax - this.x)*camera.zoom;
this.rely = (camera.cameray - this.y)*camera.zoom;
this.drawx = width/2 - this.relx;
this.drawy = height/2 - this.rely;
this.drawWidth = this.width*camera.zoom;
this.drawHeight = this.height*camera.zoom;
} else {
this.drawx = this.x;
this.drawy = this.y;
this.drawWidth = this.width;
this.drawHeight = this.height;
}
}
if (this.collisionDetection) {
if (Obstacle_set != null ) {
Obstacle_set.addObstacle(this);
} else {
console.warn("Obstacle set is null. Declaration dropped. Specific obstacle reporting this error: "+this.name+".");
}
}
}
var ObstacleSet = function() {
this.colliders = [];
this.obstacles = [];
this.addObstacle = function(inputObstacle) {
this.colliders.push(inputObstacle.x);
this.colliders.push(inputObstacle.y);
this.colliders.push(inputObstacle.width);
this.colliders.push(inputObstacle.height);
this.obstacles.push(inputObstacle);
GOS.addGameObject(inputObstacle);
}
this.drawAllObstacles = function() {
for (var current of this.obstacles) {
current.draw();
}
}
this.updateAllObstacles = function() {
for (var current of this.obstacles) {
current.update();
}
}
}
obset = new ObstacleSet();
var strokedRectObstacle = function(x, y, swidth, sheight, thickness, colour, name = "strokedRectObstacle", sides = [true, true, true, true], Obstacle_set = null, collisionDetection = false, cam = true) {
//this.top = new Obstacle(x, y, swidth, thickness, colour, name+".top", Obstacle_set, collisionDetection, cam);
if (sides[0]) {
this.top = new Obstacle(x, y, swidth, thickness, colour, name+".top", Obstacle_set, collisionDetection, cam);
}
if (sides[1]) {
this.right = new Obstacle(x+swidth-thickness, y, thickness, sheight, colour, name+".right", Obstacle_set, collisionDetection, cam);
}
if (sides[2]) {
this.bottom = new Obstacle(x, y+sheight-thickness, swidth, thickness, colour, name+".bottom", Obstacle_set, collisionDetection, cam);
}
if (sides[3]) {
this.left = new Obstacle(x, y, thickness, sheight, colour, name+".left", Obstacle_set, collisionDetection, cam);
}
this.draw = function() {
if (sides[0]) {
this.top.draw();
}
if (sides[1]) {
this.right.draw();
}
if (sides[2]) {
this.bottom.draw();
}
if (sides[3]) {
this.left.draw();
}
}
this.update = function() {
if (sides[0]) {
this.top.update();
}
if (sides[1]) {
this.right.update();
}
if (sides[2]) {
this.bottom.update();
}
if (sides[3]) {
this.left.update();
}
}
this.remove = function() {
this.top.remove();
this.right.remove();
this.bottom.remove();
this.left.remove();
}
}
var gameObject = function(x, y, Owidth, Oheight, colour, name = "gameObject", Obstacle_set = null, isCollider = false, camEnabled = true) {
this.x = x;
this.y = y;
this.drawx = this.x;
this.drawy = this.y;
this.relx = width/2 - this.x;
this.rely = height/2 - this.y;
this.width = Owidth;
this.height = Oheight;
this.drawWidth = this.width * (camera.width/width);
this.drawHeight = this.height * (camera.height/height);
this.colour = colour;
this.name = name;
this.isCollider = isCollider;
if (this.isCollider) {
Obstacle_set.addObstacle(this);
}
GOS.addGameObject(this);
this.remove = function() {
var index = Obstacle_set.obstacles.indexOf(this);
var new_obstacles = Obstacle_set.obstacles;
new_obstacles.splice(index, 1);
Obstacle_set.obstacles = new_obstacles;
GOS.gameObjects.splice(index, 2);
Obstacle_set.colliders.splice(index*4, 4);
}
this.draw = function() {
ctx.fillStyle = this.colour;
ctx.fillRect(this.drawx, this.drawy, this.drawWidth, this.drawHeight);
};
this.update = function() {
if (camEnabled) {
this.relx = (camera.camerax - this.x)*camera.zoom;
this.rely = (camera.cameray - this.y)*camera.zoom;
this.drawx = width/2 - this.relx;
this.drawy = height/2 - this.rely;
this.drawWidth = this.width*camera.zoom;
this.drawHeight = this.height*camera.zoom;
} else {
this.drawx = this.x;
this.drawy = this.y;
this.drawWidth = this.width;
this.drawHeight = this.height;
}
if (this.isCollider) {
var index = Obstacle_set.obstacles.indexOf(this);
Obstacle_set.colliders[index*4] = this.x;
Obstacle_set.colliders[index*4+1] = this.y;
Obstacle_set.colliders[index*4+2] = this.width;
Obstacle_set.colliders[index*4+3] = this.height;
}
}
this.checkCollision = function() {
if (!this.isCollider) {
return checkMultipleSquareCollisions(this.x, this.y, this.width, this.height, obset.colliders);
} else {
if (checkMultipleSquareCollisions(this.x, this.y, this.width, this.height, obset.colliders, true).length > 1) {
return true;
} else {
return false;
}
}
}
}
var rayCast = function(originx, originy, dx, dy, obstacle_set, max_runs = 10000) {
var x = originx;
var y = originy;
var run = 0;
do {
x += dx;
y += dy;
run++;
if (run > max_runs) {
return [originx, originy];
}
} while (!checkMultiplePointCollisions(x, y, obstacle_set.colliders));
return [x, y];
}
var screenToWorld = function(screenx, screeny) {
return [camera.camerax + ((screenx - width/2)/camera.zoom), camera.cameray + ((screeny - height/2)/camera.zoom)];
}
var AllGameObjects = function() {
this.gameObjects = [];
this.addGameObject = function(GAME_OBJECT) {
this.gameObjects.push(GAME_OBJECT);
}
this.drawAllObjects = function() {
for (var current of this.gameObjects) {
current.draw();
}
obset.drawAllObstacles();
}
this.updateAllObjects = function() {
for (var current of this.gameObjects) {
current.update();
}
obset.updateAllObstacles();
}
}
GOS = new AllGameObjects(); //stands for "Game Object Set"
var degToRad = function(angle) {
return angle * (Math.PI / 180);
}
var radToDeg = function(radians) {
return (radians / Math.PI) * 180;
}
var rotate_start = function(pivotx, pivoty, angle) {
var radians = angle * (Math.PI / 180);
ctx.save();
ctx.translate(pivotx, pivoty);
ctx.rotate(radians);
}
var rotate_end = function() {
ctx.restore();
}
var drawRotatedRect = function(x, y, width, height, angle, colour, filled=true) {
rotate_start(x+width/2, y+height/2, angle);
//rotate_start(x, y, angle);
if (filled) {
ctx.fillStyle = colour;
ctx.fillRect(-width/2, -height/2, width, height);
} else {
ctx.strokeStyle = colour;
ctx.strokeRect(-width/2, -height/2, width, height);
}
rotate_end();
}
var initialize_frame = function(border = true, mblur = 0) {
if (mblur == 0) {
ctx.clearRect(0, 0, width, height);
} else {
ctx.fillStyle = "rgba(255, 255, 255, "+mblur.toString()+")";
ctx.fillRect(0, 0, width, height);
}
if (border) {
ctx.strokeStyle = "Black";
ctx.strokeRect(0, 0, width, height);
}
camera.update();
GOS.updateAllObjects();
GOS.drawAllObjects();
var clicked = false;
}
var wasClicked = false;
var clickHandler = function() {
var clicked = false;
$("#canvas").click(function (event) {
var xClick = event.offsetX;
var yClick = event.offsetY;
if (!wasClicked) {
UI.checkClick(xClick, yClick);
console.log(xClick + ", " + yClick);
}
clicked = true;
wasClicked = clicked;
});
if (clicked) {
wasClicked = true;
} else {
wasClicked = false;
}
}
var UIComponent = function(X, Y, Owidth, Oheight, color, name="UIComponent") {
this.x = X;
this.y = Y;
this.width = Owidth;
this.height = Oheight;
this.color = color;
this.name = name;
this.enabled = true;
UI.addComponent(this);
this.draw = function() {
if (this.enabled) {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
this.disable = function() {
this.enabled = false;
}
this.enable = function() {
this.enabled = true;
}
this.update = function() {
//do nothing
}
this.onClick = function() {
console.log(this.name + " clicked.");
}
}
var UserInterface = function() {
this.clickColliders = [];
this.UIComponents = [];
this.addComponent = function(component) {
GOS.addGameObject(component);
this.clickColliders.push(component.x);
this.clickColliders.push(component.y);
this.clickColliders.push(component.width);
this.clickColliders.push(component.height);
this.UIComponents.push(component);
}
this.checkClick = function(x, y) {
/*if (checkMultiplePointCollisions(x, y, this.clickColliders)) {
this.onClick();
}*/
for (let i = 0; i<=this.clickColliders.length; i+=4) {
if (x > this.clickColliders[i] && x < this.clickColliders[i] + this.clickColliders[i+2]) {//x
if (y > this.clickColliders[i+1] && y < this.clickColliders[i+1] + this.clickColliders[i+3]) {//y
this.UIComponents[i/4].onClick();
}
}
}
}
}
UI = new UserInterface();
var Particle = function(initx, inity, initxvelocity, inityvelocity, forces, camEnabled) {
this.x = initx;
this.y = inity;
this.xvelocity = initxvelocity;
this.yvelocity = inityvelocity;
this.drawx = this.x;
this.drawy = this.y;
this.cam = camEnabled;
this.relx = 0;
this.rely = 0;
this.forces = [0, 0.0098];
this.width = 6;
this.height = 6;
this.elasticity = 0.8;
GOS.addGameObject(this);
obset.addObstacle(this);
this.draw = function() {
ctx.fillStyle = "Black";
circle(this.drawx, this.drawy, 3, true);
}
this.update = function() {
if (this.cam) {
this.relx = (camera.camerax - this.x)*camera.zoom;
this.rely = (camera.cameray - this.y)*camera.zoom;
this.drawx = width/2 - this.relx;
this.drawy = height/2 - this.rely;
} else {
this.drawx = this.x;
this.drawy = this.y;
}
this.move();
var index = Obstacle_set.obstacles.indexOf(this);
Obstacle_set.colliders[index*4] = this.x;
Obstacle_set.colliders[index*4+1] = this.y;
Obstacle_set.colliders[index*4+2] = this.width;
Obstacle_set.colliders[index*4+3] = this.height;
}
this.move = function() {
this.xvelocity += this.forces[0];
this.yvelocity += this.forces[1];
if (!checkMultiplePointCollisions(this.x, this.y, obset.colliders)) {
this.x += this.xvelocity;
this.y += this.yvelocity;
} else {
this.xvelocity = -this.xvelocity * this.elasticity;
this.yvelocity = -this.yvelocity * this.elasticity;
this.x += this.xvelocity;
this.y += this.yvelocity;
}
}
}
var ParticleSystem = function(x, y, flowRate, modelParticle, camEnabled = true) {
this.x = x;
this.y = y;
this.relx = 0;
this.rely = 0;
this.drawx = this.x;
this.drawy = this.y;
this.flowrate = flowRate;
this.particle = modelParticle;
this.time = 0;
this.forces = [0, 0];
this.randomness = 0.1;
this.enabled = true;
GOS.addGameObject(this);
//new Particle(this.x, this.y, this.particle.xvelocity, this.particle.yvelocity, camEnabled);
this.draw = function() {
ctx.fillStyle = "Red";
//circle(this.drawx, this.drawy, 15, true);
}
console.log(this.particle);
this.update = function() {
this.time ++;
if (this.time == this.flowrate) {
this.time = 0;
if (this.enabled) {
new Particle(this.x, this.y, this.particle.xvelocity, this.particle.yvelocity, this.forces, true);
}
}
this.relx = (camera.camerax - this.x)*camera.zoom;
this.rely = (camera.cameray - this.y)*camera.zoom;
this.drawx = width/2 - this.relx;
this.drawy = height/2 - this.rely;
}
}
var drawImg = function(img, x, y, width, height) {
//img.onload = () => {
ctx.drawImage(img, x, y, width, height);
//};
}
var Img = function(source) {
this.sourceName = source;
this.img = new Image();
this.src = source;
this.img.src = this.src;
return this.img;
}
var TileMap = function(src, x, y, cutwidth, cutheight, blockwidth, blockheight, map) {
this.src = src;
this.cutwidth = cutwidth;
this.cutheight = cutheight;
this.blockw = blockwidth;
this.blockh = blockheight;
this.srcImg = Img(this.src);
this.x = x;
this.y = y;
this.drawx = this.x;
this.drawy = this.y;
this.relx = 0;
this.rely = 0;
this.width = map[0].length * this.blockw;
this.height = map.length * this.blockh;
this.dbw = this.blockw;
this.dbh = this.blockh;
this.map = map;
this.aWidth = 0;
this.aHeight = 0;
this.tiles = [];
for (var y = 0; y