蒜末面包html5游戏在线玩,HTML5 Canvas可操作的面包机

HTML

导入代码模板:

var EPSILON = 0.000001;

let canvas = undefined;

let ctx = undefined;

let gl = undefined;

let isWebGl = false;

var gravity_multiplier = 0.5;

var gravity_base = -0.00982;

var gravity = gravity_base * gravity_multiplier;

var mouse = {

x: 0,

y: 0,

leftButton: false,

rightButton: false,

middleButton: false

};

var Time = {

timeScale: 1,

deltaTime: 0,

deltaTimeUnscaled: 0,

time: 0,

frameCount: 0

};

var onDraw = undefined;

var onUpdate = undefined;

let ctxScaleY = 1.0;

let ctxScaleX = 1.0;

function setGravityMultiplier(val) {

gravity_multiplier = val;

gravity = gravity_base * gravity_multiplier;

}

function setup3d(canvasSelector, onDrawCallback, onUpdateCallback, onResizeCallback, onInitCallback) {

isWebGl = true;

canvas = document.querySelector(canvasSelector);

ctx = canvas.getContext("experimental-webgl");

gl = ctx; // alias

onDraw = onDrawCallback;

onUpdate = onUpdateCallback;

window.addEventListener("resize", onResizeCallback, false);

canvas.addEventListener("mousemove", mouseMove, false);

canvas.addEventListener("touchmove", touchMove, false);

canvas.addEventListener("touchstart", e => {

e.preventDefault();

mouse.leftButton = true;

}, false);

canvas.addEventListener("touchend", e => {

e.preventDefault();

mouse.leftButton = false;

}, false);

canvas.addEventListener("mousedown", mouseDown, false);

canvas.addEventListener("mouseup", mouseUp, false);

if (onResizeCallback) onResizeCallback();

if (onInitCallback) onInitCallback();

run(0);

}

function setup(canvasSelector, onDrawCallback, onUpdateCallback, onResizeCallback) {

canvas = document.querySelector(canvasSelector);

ctx = canvas.getContext("2d");

onDraw = onDrawCallback;

onUpdate = onUpdateCallback;

window.addEventListener("resize", onResizeCallback, false);

canvas.addEventListener("mousemove", mouseMove, false);

canvas.addEventListener("touchmove", touchMove, false);

canvas.addEventListener("touchstart", e => {

e.preventDefault();

mouse.leftButton = true;

}, false);

canvas.addEventListener("touchend", e => {

e.preventDefault();

mouse.leftButton = false;

}, false);

canvas.addEventListener("mousedown", mouseDown, false);

canvas.addEventListener("mouseup", mouseUp, false);

if (onResizeCallback) onResizeCallback();

run(0);

}

function drawEllipse(cx, cy, w, h) {

ctx.beginPath();

let lx = cx - w / 2,

rx = cx + w / 2,

ty = cy - h / 2,

by = cy + h / 2;

// let kappa = 4 * ((Math.sqrt(2) - 1)/3)

let kappa = 0.551784;

var xkappa = kappa * w / 2;

var ykappa = h * kappa / 2;

ctx.moveTo(cx, ty);

ctx.bezierCurveTo(cx + xkappa, ty, rx, cy - ykappa, rx, cy);

ctx.bezierCurveTo(rx, cy + ykappa, cx + xkappa, by, cx, by);

ctx.bezierCurveTo(cx - xkappa, by, lx, cy + ykappa, lx, cy);

ctx.bezierCurveTo(lx, cy - ykappa, cx - xkappa, ty, cx, ty);

ctx.stroke();

ctx.fill();

}

function drawCircle(x, y, fillStyle, radius) {

radius = radius || 5;

ctx.beginPath();

ctx.fillStyle = fillStyle || "green";

ctx.arc(x, y, radius, 0, 2 * Math.PI, false);

ctx.stroke();

ctx.fill();

}

function resetScale() {

ctxScaleX = 1;

ctxScaleY = 1;

}

Array.prototype.remove = function(from, to) {

var rest = this.slice((to || from) + 1 || this.length);

this.length = from < 0 ? this.length + from : from;

return this.push.apply(this, rest);

};

function rgb(r, g, b) {

return new Color(r, g, b);

}

function rgba(r, g, b, a) {

return new Color(r, g, b, a);

}

class Color {

constructor(r, g, b, a) {

this.r = r;

this.g = g;

this.b = b;

if (a === undefined && a !== 0) a = 1.0;

this.a = a;

}

static getWhite() {

return new Color(255, 255, 255, 1);

}

static getBlack() {

return new Color(0, 0, 0, 1);

}

darker(amount) {

return this.shade(-amount);

}

lighter(amount) {

return this.shade(amount);

}

lerp(to, amount) {

const lerpNum = (start, end, a) => parseInt(start + ((end - start) * a));

return new Color(

lerpNum(this.r, to.r, amount),

lerpNum(this.g, to.g, amount),

lerpNum(this.b, to.b, amount),

this.a

);

}

shade(percent) {

let r = parseInt(this.r * (100 + percent) / 100);

let g = parseInt(this.g * (100 + percent) / 100);

let b = parseInt(this.b * (100 + percent) / 100);

let c = new Color((r < 255) ? r : 255, (g < 255) ? g : 255, (b < 255) ? b : 255, this.a);

return c;

}

rgba(alpha) {

alpha = alpha || this.a;

return `rgba(${this.r},${this.g},${this.b},${alpha})`;

}

rgb() {

return `rgb(${this.r},${this.g},${this.b})`;

}

}

class PixelUtilities {

static resizeNearestNeighbor(pixels, oldWidth, oldHeight, newWidth, newHeight) {

let tmp = new Array(newWidth * newHeight);

let xRatio = ((oldWidth << 16) / newWidth) + 1;

let yRatio = ((oldHeight << 16) / newHeight) + 1;

let x2, y2;

for (let y = 0; y < newHeight; ++y) {

for (let x = 0; x < newWidth; ++x) {

y2 = (y * yRatio) >> 16;

x2 = (x * xRatio) >> 16;

tmp[y * newWidth + x] = pixels[y2 * oldWidth + x2];

}

}

return tmp;

}

}

class Shape {

constructor(points) {

this.points = points;

}

}

class Polygon extends Shape {

constructor(points) {

super(points);

}

static create(x, y, radius, npoints) {

let TWO_PI = Math.PI * 2;

let angle = TWO_PI / npoints;

let pts = [];

let startPoint = undefined;

for (let a = 0; a < TWO_PI; a += angle) {

let sx = x + Math.cos(a) * radius;

let sy = y + Math.sin(a) * radius;

let pt = new Point(sx, sy);

if (startPoint == undefined)

startPoint = pt.copy();

pts.push(pt);

}

pts.push(startPoint);

return new Polygon(pts);

}

isPointInside(p) {

let isInside = false;

let polygon = this.points;

let minX = polygon[0].x,

maxX = polygon[0].x;

let minY = polygon[0].y,

maxY = polygon[0].y;

for (let n = 1; n < polygon.length; n++) {

var q = polygon[n];

minX = Math.min(q.x, minX);

maxX = Math.max(q.x, maxX);

minY = Math.min(q.y, minY);

maxY = Math.max(q.y, maxY);

}

if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY) {

return false;

}

var i = 0,

j = polygon.length - 1;

for (i, j; i < polygon.length; j = i++) {

if ((polygon[i].y > p.y) != (polygon[j].y > p.y) &&

p.x < (polygon[j].x - polygon[i].x) * (p.y - polygon[i].y) / (polygon[j].y - polygon[i].y) + polygon[i].x) {

isInside = !isInside;

}

}

return isInside;

}

draw(strokeStyle, fillStyle) {

ctx.save();

ctx.beginPath();

ctx.strokeStyle = strokeStyle || "red";

ctx.moveTo(this.points[0].x, this.points[0].y);

for (let i = 1; i < this.points.length; ++i) {

ctx.lineTo(this.points[i].x, this.points[i].y);

}

ctx.stroke();

if (fillStyle !== undefined) {

ctx.fillStyle = fillStyle;

ctx.fill();

}

ctx.closePath();

ctx.restore();

}

offset(xoffset, yoffset) {

for (let i = 0; i < this.points.length; i++) {

this.points[i] = new Point(this.points[i].x + xoffset, this.points[i].y + yoffset);

}

return this;

}

scale(scale) {

for (let i = 0; i < this.points.length; i++) {

this.points[i] = new Point(this.points[i].x * scale, this.points[i].y * scale);

}

}

moveTo(x, y, origin) {

// get bounds

// then move all points so the bounds LEFT,TOP is touching the x, y pos

origin = origin || new Point(0, 0);

let bb = this.getBoundingBox();

let dx = x - bb.min.x;

let dy = y - bb.min.y;

if (origin.x > 0) dx -= (bb.max.x - bb.min.x) * origin.x;

if (origin.y > 0) dy -= (bb.max.y - bb.min.y) * origin.y;

return this.offset(dx, dy);

}

getBoundingBox() {

let xMin = 999999,

xMax = 0;

let yMin = 999999,

yMax = 0;

for (let i = 0; i < this.points.length; ++i) {

xMin = Math.min(xMin, this.points[i].x);

yMin = Math.min(yMin, this.points[i].y);

xMax = Math.max(xMax, this.points[i].x);

yMax = Math.max(yMax, this.points[i].y);

}

let min = {

x: xMin,

y: yMin

};

let max = {

x: xMax,

y: yMax

};

return new BoundingBox(min, max);

}

getLines() {

let pts = [];

let lines = [];

let idx = 0;

this.points.forEach(x => pts.push(x));

for (let i = 1; i < pts.length; i++) {

let line = new Line(pts[i - 1], pts[i]);

line.index = idx++;

lines.push(line);

}

return lines;

}

}

class Rectangle {

constructor() {

this.left = 0;

this.right = 0;

this.top = 0;

this.bottom = 0;

}

}

class Line extends Shape {

constructor(p1, p2) {

super([p1, p2]);

this.start = p1;

this.stop = p2;

}

get normal() {

let dx = this.stop.x - this.start.x;

let dy = this.stop.y - this.start.y;

return new Line(new Point(-dy, dx), new Point(dy, -dx));

}

draw(strokeStyle, linedash, linewidth) {

this.drawLine(this, strokeStyle, linedash, linewidth);

}

drawNormal(strokeStyle) {

let bb = this.getBoundingBox();

let n = this.normal;

let posx = bb.min.x;

let posy = bb.min.y;

let x1 = posx + n.start.x;

let x2 = posx + n.stop.x;

let y1 = posy + n.start.y;

let y2 = posy + n.stop.y;

this.drawLine(

new Line(

new Point(x1, y1),

new Point(x2, y2)

),

strokeStyle);

}

drawLine(line, strokeStyle, linedash, linewidth) {

ctx.save();

ctx.beginPath();

if (linedash) ctx.setLineDash(linedash);

if (linewidth) ctx.lineWidth = linewidth;

ctx.strokeStyle = strokeStyle || "yellow";

ctx.moveTo(line.start.x, line.start.y);

ctx.lineTo(line.stop.x, line.stop.y);

ctx.stroke();

ctx.restore();

}

getBoundingBox() {

return new BoundingBox(

new Point(Math.min(this.start.x, this.stop.x), Math.min(this.start.y, this.stop.y)),

new Point(Math.max(this.start.x, this.stop.x), Math.max(this.start.y, this.stop.y))

);

}

getMidPoint() {

return new Point((this.start.x + this.stop.x) / 2, (this.start.y + this.stop.y) / 2);

}

}

class Point {

constructor(x, y, index, intersectionPoint) {

this.x = x;

this.y = y;

this.intersectionPoint = intersectionPoint || false;

this.index = index || -1;

}

mul(scale) {

this.x *= scale;

this.y *= scale;

return this;

}

copy() {

return new Point(this.x, this.y, this.index, false);

}

}

class Vector3 {

constructor(x, y, z) {

this.x = x || 0;

this.y = y || 0;

this.z = z || 0;

}

}

class Vector2 {

constructor(x, y) {

this.x = x || 0;

this.y = y || 0;

}

get length() {

return Math.sqrt(this.x * this.x + this.y * this.y);

}

get sqrLength() {

return this.x * this.x + this.y * this.y;

}

lerp(v2, a) {

return new Vector2(

this.x + (v2.x - this.x) * a,

this.y + (v2.y - this.y) * a,

);

}

moveTowards(v2, a) {

if (a > 1.0) a = 1.0;

if (a < 0.0) a = 0.0;

return this.lerp(v2, a);

}

dot(v2) {

let m = this.mul(v2);

return m.x + m.y;

}

cross(v2) {

return this.x * v2.y - v2.x * this.y;

}

angleBetween(v2) {

let sin = this.x * v2.y - v2.x * this.y;

let cos = this.x * v2.y + v2.x * this.y;

return Math.atan2(sin, cos) * (180 / Math.PI);

}

normalize() {

let len = this.length;

return new Vector2(this.x / len, this.y / len);

}

direction(v2) {

let heading = v2.sub(this);

var distance = heading.length;

return heading.div(distance);

}

distance(v2) {

let diff = v2.sub(this);

return Math.sqrt(diff.x * diff.x + diff.y * diff.y);

}

add(v2) {

if (v2 instanceof Vector2) return new Vector2(this.x + v2.x, this.y + v2.y);

else return new Vector2(this.x + v2, this.y + v2);

}

sub(v2) {

if (v2 instanceof Vector2) return new Vector2(this.x - v2.x, this.y - v2.y);

else return new Vector2(this.x - v2, this.y - v2);

}

mul(v2) {

if (v2 instanceof Vector2) return new Vector2(this.x * v2.x, this.y * v2.y);

else return new Vector2(this.x * v2, this.y * v2);

}

div(v2) {

if (v2 instanceof Vector2) return new Vector2(this.x / v2.x, this.y / v2.y);

else return new Vector2(this.x / v2, this.y / v2);

}

}

class Intersection {

/**

* Calculate the cross product of two points.

* @param a first point

* @param b second point

* @return the value of the cross product

*/

static crossProduct(a, b) {

return a.x * b.y - b.x * a.y;

}

static doBoundingBoxesIntersect(a, b) {

return a.min.x <= b.max.x && a.max.x >= b.min.x && a.min.y <= b.max.y && a.max.y >= b.min.y;

}

/**

* Checks if a Point is on a line

* @param a line (interpreted as line, although given as line

* segment)

* @param b point

* @return true if point is on line, otherwise

* false

*/

static isPointOnLine(a, b) {

// Move the image, so that a.first is on (0|0)

let aTmp = new Line(new Point(0, 0), new Point(a.stop.x - a.start.x, a.stop.y - a.start.y));

let bTmp = new Point(b.x - a.start.x, b.y - a.start.y);

let r = this.crossProduct(aTmp.stop, bTmp);

return Math.abs(r) < EPSILON;

}

/**

* Checks if a point is right of a line. If the point is on the

* line, it is not right of the line.

* @param a line segment interpreted as a line

* @param b the point

* @return true if the point is right of the line,

* false otherwise

*/

static isPointRightOfLine(a, b) {

// Move the image, so that a.first is on (0|0)

let aTmp = new Line(new Point(0, 0), new Point(

a.stop.x - a.start.x, a.stop.y - a.start.y));

let bTmp = new Point(b.x - a.start.x, b.y - a.start.y);

return this.crossProduct(aTmp.stop, bTmp) < 0;

}

/**

* Check if line segment first touches or crosses the line that is

* defined by line segment second.

*

* @param first line segment interpreted as line

* @param second line segment

* @return true if line segment first touches or

* crosses line second,

* false otherwise.

*/

static lineSegmentTouchesOrCrossesLine(a, b) {

return this.isPointOnLine(a, b.start) || this.isPointOnLine(a, b.stop) || (this.isPointRightOfLine(a, b.start) ^ this.isPointRightOfLine(a, b.stop));

}

/**

* Check if line segments intersect

* @param a first line segment

* @param b second line segment

* @return true if lines do intersect,

* false otherwise

*/

static doLinesIntersect(a, b) {

let box1 = a.getBoundingBox();

let box2 = b.getBoundingBox();

return this.doBoundingBoxesIntersect(box1, box2) && this.lineSegmentTouchesOrCrossesLine(a, b) && this.lineSegmentTouchesOrCrossesLine(b, a);

}

static getIntersection(a, b) {

/* the intersection [(x1,y1), (x2, y2)]

it might be a line or a single point. If it is a line,

then x1 = x2 and y1 = y2. */

var x1, y1, x2, y2;

if (a.start.x == a.stop.x) {

// Case (A)

// As a is a perfect vertical line, it cannot be represented

// nicely in a mathematical way. But we directly know that

//

x1 = a.start.x;

x2 = x1;

if (b.start.x == b.stop.x) {

// Case (AA): all x are the same!

// Normalize

if (a.start.y > a.stop.y) {

a = {

start: a.stop,

stop: a.start

};

}

if (b.start.y > b.stop.y) {

b = {

start: b.stop,

stop: b.start

};

}

if (a.start.y > b.start.y) {

var tmp = a;

a = b;

b = tmp;

}

// Now we know that the y-value of a.start is the

// lowest of all 4 y values

// this means, we are either in case (AAA):

// a: x--------------x

// b: x---------------x

// or in case (AAB)

// a: x--------------x

// b: x-------x

// in both cases:

// get the relavant y intervall

y1 = b.start.y;

y2 = Math.min(a.stop.y, b.stop.y);

} else {

// Case (AB)

// we can mathematically represent line b as

// y = m*x + t <=> t = y - m*x

// m = (y1-y2)/(x1-x2)

var m, t;

m = (b.start.y - b.stop.y) /

(b.start.x - b.stop.x);

t = b.start.y - m * b.start.x;

y1 = m * x1 + t;

y2 = y1

}

} else if (b.start.x == b.stop.x) {

// Case (B)

// essentially the same as Case (AB), but with

// a and b switched

x1 = b.start.x;

x2 = x1;

var tmp = a;

a = b;

b = tmp;

var m, t;

m = (b.start.y - b.stop.y) /

(b.start.x - b.stop.x);

t = b.start.y - m * b.start.x;

y1 = m * x1 + t;

y2 = y1

} else {

// Case (C)

// Both lines can be represented mathematically

var ma, mb, ta, tb;

ma = (a.start.y - a.stop.y) /

(a.start.x - a.stop.x);

mb = (b.start.y - b.stop.y) /

(b.start.x - b.stop.x);

ta = a.start.y - ma * a.start.x;

tb = b.start.y - mb * b.start.x;

if (ma == mb) {

// Case (CA)

// both lines are in parallel. As we know that they

// intersect, the intersection could be a line

// when we rotated this, it would be the same situation

// as in case (AA)

// Normalize

if (a.start.x > a.stop.x) {

a = {

start: a.stop,

stop: a.start

};

}

if (b.start.x > b.stop.x) {

b = {

start: b.stop,

stop: b.start

};

}

if (a.start.x > b.start.x) {

var tmp = a;

a = b;

b = tmp;

}

// get the relavant x intervall

x1 = b.start.x;

x2 = Math.min(a.stop.x, b.stop.x);

y1 = ma * x1 + ta;

y2 = ma * x2 + ta;

} else {

// Case (CB): only a point as intersection:

// y = ma*x+ta

// y = mb*x+tb

// ma*x + ta = mb*x + tb

// (ma-mb)*x = tb - ta

// x = (tb - ta)/(ma-mb)

x1 = (tb - ta) / (ma - mb);

y1 = ma * x1 + ta;

x2 = x1;

y2 = y1;

}

}

// return {start: {"x":x1, "y":y1}, stop: {"x":x2, "y":y2}};

let intersectionPoint = new Point(x1, y1);

intersectionPoint.intersectionPoint = true;

return intersectionPoint;

}

static anyLineStartsWith(pt, polyLines) {

return polyLines.filter(x => x.start.x === pt.x && x.start.y === pt.y).length > 0;

}

/**

* Slice a polygon by the provided intersection points

* @param poly the polygon to slice

* @param intersections the intersection points used for the slicing

* @return an array of polygons that is the result of the slice

*/

static slice(poly, intersections) {

// if (intersections.length <= 1) return [poly];

if (intersections.length == 2) return this.sliceSimplePolygonIntersection(poly, intersections);

if (intersections.length > 2) return this.sliceComplexPolygonIntersection(poly, intersections);

return [poly];

}

static sliceSimplePolygonIntersection(poly, intersections) {

let polyLines = poly.getLines();

let breakIndex = 0;

let polyPointsA = [];

let polyPointsB = [];

let polys = [];

let polyA = undefined;

let polyB = undefined;

let polyPoints = [];

// 1. insert cut points

// 2. then iterate all points to build new lines

let lidx = 0;

let ll = -1;

for (let x = 0; x < poly.points.length; ++x) {

polyPoints.push(poly.points[x]);

if (x !== 0 && this.anyLineStartsWith(poly.points[x], polyLines)) lidx++;

for (let int of intersections) {

if (int.index === lidx && lidx !== ll) {

polyPoints.push(int);

ll = lidx;

}

}

}

// rotate points until we start on an intersection

while (!polyPoints[0].intersectionPoint) {

let item = polyPoints.shift();

polyPoints.push(item);

}

let intersectionIndex = 0;

// iterate each poly point so we can build our points for polyA (top-piece)

// logic:

// start at first intersectionPoint, iterate until next intersectionPoint

// then add the start(first intersectionPoint) again. Presto, polygon done!

for (let x = 0; x < polyPoints.length; x++) {

if (polyPoints[x].intersectionPoint === true) {

if (intersectionIndex === 0) {

// our starting point! Add it and continue.

polyPointsA.push(polyPoints[x].copy());

} else {

// alright! We're at our second intersection. Add this point

polyPointsA.push(polyPoints[x].copy());

// and add the first point so we can close the polygon

polyPointsA.push(polyPointsA[0].copy());

// presto! Create the poly and be happy

polyA = new Polygon(polyPointsA);

polys.push(polyA);

break;

}

++intersectionIndex;

}

// while we still havnt found our second intersection, keep pushing those points

else if (intersectionIndex === 1) {

polyPointsA.push(polyPoints[x].copy());

}

if (polyA !== undefined) break; // we're done, so jump out!

}

// time to create the bottom-piece polygon

// logic:

// start at first intersectionPoint

// then skip all points until we find our next intersectionPoint, when we do. We take

// the rest of the points until we're at the starting point again. (basically, take the rest)

intersectionIndex = 0;

for (let x = 0; x < polyPoints.length; x++) {

if (polyPoints[x].intersectionPoint === true) {

if (intersectionIndex === 0) {

// our starting point! Add it and continue.

polyPointsB.push(polyPoints[x].copy());

} else {

// alright! We're at our second intersection. Add this point

polyPointsB.push(polyPoints[x].copy());

// this means that we can now add polys again :)

}

++intersectionIndex;

}

// after adding both intersection points, we are now ready to grab the rest of points!

else if (intersectionIndex === 2) {

polyPointsB.push(polyPoints[x].copy());

}

}

// finally, all points have been added to our bottom-piece.

// all we have to do now is close the poly by adding starting point

// and then lets create our polygon :)

polyPointsB.push(polyPointsB[0].copy());

polyB = new Polygon(polyPointsB);

polys.push(polyB);

return polys; // return the new set of polygons

}

static sliceComplexPolygonIntersection(poly, intersections) {

return [poly]; // not implemented, better just return same than breaking the shape.

// to solve this one we still want to add the intersection points into the polygon.

// .... todo

// possible solution for having more than 2 intersection points are to first do the slice on the first two points

// then do another slice between 2 and 3rd point.

// although its a bit slower than wanted. it is at least simple to follow

}

static check(a, b) {

let linesA = [];

let linesB = [];

let intersections = [];

if (a instanceof Polygon) linesA = this.getPolygonLines(a);

if (a instanceof Rectangle) linesA = this.getRectangleLines(a);

if (a instanceof Line) linesA = [a];

if (b instanceof Polygon) linesB = this.getPolygonLines(b);

if (b instanceof Rectangle) linesB = this.getRectangleLines(b);

if (b instanceof Line) linesB = [b];

for (let i = 0; i < linesA.length; i++) {

for (let j = 0; j < linesB.length; j++) {

if (this.doLinesIntersect(linesA[i], linesB[j])) {

let result = this.getIntersection(linesA[i], linesB[j]);

if (result !== undefined) {

result.index = linesA[i].index;

intersections.push(result);

}

}

}

}

return intersections;

}

static getPolygonLines(poly) {

return poly.getLines();

}

static getRectangleLines(rect) {

return [

new Line(new Point(rect.left, rect.top), new Point(rect.right, rect.top)),

new Line(new Point(rect.right, rect.top), new Point(rect.right, rect.bottom)),

new Line(new Point(rect.right, rect.bottom), new Point(rect.left, rect.bottom)),

new Line(new Point(rect.left, rect.bottom), new Point(rect.left, rect.top)),

];

}

}

class Physics {

static register(object) {

if (!this.objects) this.objects = [];

this.objects.push(object);

}

static unregister(object) {

// note: this would have been super bad if we had multiple threads..

if (!this.objects) this.objects = [];

var index = this.objects.indexOf(object);

if (index === -1) return;

this.objects.remove(index);

}

static update() {

if (!this.objects) this.objects = [];

for (let index = 0; index < this.objects.length; ++index) {

let obj = this.objects[index];

if (obj.rigidBody) this.updateRigidBody(obj.rigidBody);

else if (obj.collider) this.updateCollider(obj.collider);

}

}

static updateRigidBody(rigidBody) {

let isGrounded = false;

let velX = rigidBody.velocity.x * Time.deltaTime;

let velY = rigidBody.velocity.y * Time.deltaTime;

if (!isNaN(velX) && !rigidBody.constraints.freezePositionX) rigidBody.gameObject.position.x += velX;

if (!isNaN(velY) && !rigidBody.constraints.freezePositionY) rigidBody.gameObject.position.y += velY;

// let height = rigidBody.gameObject.collider.bounds.max.y;

let screenHeight = window.innerHeight / ctxScaleY;

for (let i = 0; i < this.objects.length; ++i) {

if (this.objects[i].rigidBody !== rigidBody) {

let obj = this.objects[i];

if (obj.collider && obj.collider.isTouching(rigidBody.gameObject.collider)) {

if (obj.isTrigger === true) {

rigidBody.gameObject.onTriggerEnter(obj.collider);

} else {

rigidBody.gameObject.onCollisionEnter(obj.collider);

if (rigidBody.ignoreCollisionPhysics) return;

// check which face of the boundingbox/collider that actually had a collision

// and then stop the velocity of that direction

// TODO: RayCast the sides to determine where the blockage is

// and stop the velocity in that direction

// NOTE: You can jump through objects that whould block your left or right if you jump towards it

if (obj.position.y + obj.offset.y >= (rigidBody.gameObject.position.y + rigidBody.gameObject.offset.y)) {

isGrounded = true;

rigidBody.velocity.y = 0;

rigidBody.gameObject.position.y = (obj.position.y - rigidBody.gameObject.collider.bounds.max.y) + 1;

} else if (obj.position.x + obj.offset.x >= rigidBody.gameObject.position.x) {

// collides to the right

// + rigidBody.gameObject.collider.bounds.max.x

rigidBody.gameObject.position.x -= velX;

rigidBody.velocity.x = 0;

} else if (obj.position.x + obj.offset.x + obj.collider.bounds.max.x >=

rigidBody.gameObject.position.x) {

// collides to the right

// + rigidBody.gameObject.collider.bounds.max.x

rigidBody.gameObject.position.x -= velX;

rigidBody.velocity.x = 0;

}

}

}

}

}

if (!isGrounded) {

let fall = gravity * Time.deltaTime;

if (!isNaN(fall)) {

rigidBody.velocity.y -= fall;

}

} else {

if (rigidBody.force.y != 0) {

rigidBody.velocity.y = rigidBody.force.y;

rigidBody.force.y = 0;

}

}

rigidBody.isGrounded = isGrounded;

}

static updateCollider(collider) {

// console.log("update collider");

}

}

class GameComponent {

constructor() {

this.gameObject = null;

this.isEnabled = true;

this.tag = '';

this.layer = '';

}

setGameObject(gameObject) {

this.gameObject = gameObject;

}

update() {}

draw() {}

}

class BoundingBox {

constructor(min = {

x: 0,

y: 0

}, max = {

x: 0,

y: 0

}) {

this.min = min;

this.max = max;

}

get delta() {

return {

x: this.max.x - this.min.x,

y: this.max.y - this.min.y

};

}

}

class Viewport {

constructor() {

this.x = 0;

this.y = 0;

this.width = window.innerWidth / ctxScaleX;

this.height = window.innerWidth / ctxScaleY;

}

move(xOffset, yOffset) {

this.x += xOffset;

this.y += yOffset;

}

reset() {

this.x = 0;

this.y = 0;

}

}

class Camera {

constructor() {

this.viewport = new Viewport();

}

static getMainCamera() {

if (!this.mainCamera) this.mainCamera = new Camera();

return this.mainCamera;

}

setViewport(x, y, w, h) {

this.viewport.x = x;

this.viewport.y = y;

if (w) this.viewport.width = w;

if (h) this.viewport.height = h;

}

}

class FollowTarget extends GameComponent {

constructor(target, xOffset, yOffset) {

super();

this.target = target;

this.xOffset = xOffset || 0;

this.yOffset = yOffset || 0;

}

draw() {

if (!this.isEnabled || !this.isVisible) return;

super.draw();

}

update() {

if (!this.isEnabled) return;

super.update();

if (this.target instanceof GameObject) {

this.gameObject.position.x = this.target.position.x + this.xOffset;

this.gameObject.position.y = this.target.position.y + this.yOffset;

}

}

}

class FadeOut extends GameComponent {

constructor() {

super();

this.isRunning = false;

this.value = 1.0;

this.duration = 5.0;

this.timer = this.duration;

this.destroyObjectOnCompletion = false;

}

start() {

this.value = this.gameObject.opacity;

this.timer = this.duration * this.gameObject.opacity;

this.isRunning = true;

}

stop() {

this.isRunning = false;

}

update() {

if (!this.isEnabled || !this.isRunning) return;

super.update();

this.timer -= Time.deltaTime / 1000;

this.value = 1 - ((this.duration - this.timer) / this.duration);

this.gameObject.opacity = this.value;

if (this.value <= 0.001 || this.timer <= 0) {

if (this.onComplete) this.onComplete(this.gameObject);

this.isRunning = false;

this.timer = 0;

this.gameObject.opacity = 0;

if (this.destroyObjectOnCompletion) {

this.gameObject.destroy();

}

}

}

draw() {

super.draw();

}

}

class Mask extends GameComponent {

constructor() {

super();

this.shape = undefined;

this.centerShape = false;

this.drawShape = false;

this.offset = {

x: 0,

y: 0

};

}

draw() {

if (this.shape === undefined) return;

if (this.isEnabled !== true) return;

if (this.drawShape) {

this.shape.draw();

}

let path = new Path2D();

path.moveTo(this.shape.points[0].x, this.shape.points[0].y);

for (let i = 1; i < this.shape.points.length; ++i) {

path.lineTo(this.shape.points[i].x, this.shape.points[i].y);

}

path.closePath();

ctx.clip(path, "nonzero");

}

update() {

if (!this.isEnabled || !this.centerShape || !this.shape) return;

// console.log(this.gameObject.image.src);

let goPos = this.gameObject.position;

// TODO: move path to center of sprite

//let bb = this.shape.getBoundingBox();

this.shape.moveTo(goPos.x + this.offset.x, goPos.y + this.offset.y, new Point(0, 0));

}

}

class Collider extends GameComponent {

constructor() {

super();

this.isTrigger = false;

this.bounds = new BoundingBox();

this.layerMask = undefined;

}

isTouching(otherCollider) {

return false;

}

}

class PolygonCollider extends Collider {

constructor(shape) {

super();

this.isTrigger = false;

this.shape = shape;

this.bounds = new BoundingBox();

if (this.shape) {

this.bounds = shape.getBoundingBox();

}

}

isTouching(otherCollider) {

if (!this.shape) return;

if (this.gameObject && otherCollider && otherCollider.gameObject) {

if (this.layerMask && otherCollider.layer !== this.layerMask) {

return false;

}

if (this.bounds.max.x == 0) this.bounds = this.shape.getBoundingBox();

// first check if we are inside eachother's bounding box. otherwise theres no point of checking whether they touch

let aSize = {

width: this.bounds.max.x,

height: this.bounds.max.y

};

let aPos = {

x: this.gameObject.position.x + this.gameObject.offset.x,

y: this.gameObject.position.y + this.gameObject.offset.y

};

let bSize = {

width: otherCollider.bounds.max.x,

height: otherCollider.bounds.max.y

};

let bPos = {

x: otherCollider.gameObject.position.x + otherCollider.gameObject.offset.x,

y: otherCollider.gameObject.position.y + otherCollider.gameObject.offset.y

};

if (aPos.x + aSize.width >= bPos.x && aPos.x <= bPos.x + bSize.width && aPos.y + aSize.height >= bPos.y && aPos.y <= bPos.y + bSize.height) {

let pts = otherCollider.getPoints();

for (let pt of pts) {

if (this.shape.isPointInside(pt)) return true;

}

}

}

return false;

}

getPoints() {

return this.shape.points;

}

}

class BoxCollider extends Collider {

constructor() {

super();

this.isTrigger = false;

this.bounds = new BoundingBox();

}

isTouching(otherCollider) {

if (this.gameObject && otherCollider && otherCollider.gameObject) {

if (this.layerMask && otherCollider.layer !== this.layerMask) {

return false;

}

let aSize = {

width: this.bounds.max.x,

height: this.bounds.max.y

};

let aPos = {

x: this.gameObject.position.x + this.gameObject.offset.x,

y: this.gameObject.position.y + this.gameObject.offset.y

};

let bSize = {

width: otherCollider.bounds.max.x,

height: otherCollider.bounds.max.y

};

let bPos = {

x: otherCollider.gameObject.position.x + otherCollider.gameObject.offset.x,

y: otherCollider.gameObject.position.y + otherCollider.gameObject.offset.y

};

return aPos.x + aSize.width >= bPos.x && aPos.x <= bPos.x + bSize.width && aPos.y + aSize.height >= bPos.y && aPos.y <= bPos.y + bSize.height;

}

return false;

}

getPoints() {

return [

new Point(this.bounds.min.x, this.bounds.min.y), new Point(this.bounds.max.x, this.bounds.min.y),

new Point(this.bounds.max.x, this.bounds.min.y), new Point(this.bounds.max.x, this.bounds.max.y),

new Point(this.bounds.max.x, this.bounds.max.y), new Point(this.bounds.min.x, this.bounds.max.y),

new Point(this.bounds.min.x, this.bounds.max.y), new Point(this.bounds.min.x, this.bounds.min.y),

];

}

}

class BoxRenderer extends GameComponent {

constructor() {

super();

this.size = {

width: 0,

height: 0

};

}

draw() {

if (this.isEnabled !== true) return;

let camera = Camera.getMainCamera();

ctx.save();

ctx.beginPath();

ctx.rect(

camera.viewport.x + this.gameObject.position.x,

camera.viewport.y + this.gameObject.position.y,

this.size.width,

this.size.height);

ctx.strokeStyle = 'Red';

ctx.stroke();

ctx.closePath();

ctx.restore();

}

}

class RigidBody extends GameComponent {

constructor() {

super();

this.velocity = {

x: 0,

y: 0

};

this.force = {

x: 0,

y: 0

};

this.isStatic = false;

this.isGrounded = false;

this.constraints = {

freezePositionX: false,

freezePositionY: false

};

this.ignoreCollisionPhysics = false;

}

}

class GameObject {

constructor() {

this.position = {

x: 0,

y: 0

};

this.rotation = 0;

this.opacity = 1;

this.isEnabled = true;

this.isVisible = true;

this.rigidBody = null;

this.collider = null;

this.renderer = null;

this.parent = null;

this.isDestroyed = false;

this.components = [];

this.children = [];

this.name = '';

this.tag = '';

this.layer = '';

this.offset = {

x: 0,

y: 0

};

Physics.register(this);

}

// get localPosition() {

getLocalPosition() {

if (this.parent !== null) {

return {

x: this.position.x - this.parent.position.x,

y: this.position.y - this.parent.position.y

};

}

return {

x: this.position.x,

y: this.position.y

};

}

//set localPosition(value) {

setLocalPosition(x, y) {

if (this.parent !== null) {

this.position = {

x: this.parent.position.x + x,

y: this.parent.position.y + y

};

return;

}

this.position = {

x: value.x,

y: value.y

};

}

destroy() {

if (this.isDestroyed) return;

this.isDestroyed = true;

Physics.unregister(this);

if (this.parent) {

this.parent.removeChild(this);

}

}

addComponent(component) {

this.components.push(component);

component.setGameObject(this);

return component;

}

addChild(gameObject) {

this.children.push(gameObject);

gameObject.setParent(this);

return gameObject;

}

removeChild(gameObject) {

let index = this.children.indexOf(gameObject);

gameObject.setParent(null);

this.children.remove(index);

}

getComponent(name) {

for (let child of this.components) {

if (child.constructor.name == name) {

return child;

}

}

return undefined;

}

setParent(gameObject) {

this.parent = gameObject;

}

setRenderer(renderer) {

if (this.renderer) {

var index = this.components.indexOf(this.renderer);

this.components.remove(index);

}

this.renderer = renderer;

this.addComponent(this.renderer);

}

setRigidBody(rigidBody) {

if (this.rigidBody) {

var index = this.components.indexOf(this.rigidBody);

this.components.remove(index);

}

this.rigidBody = rigidBody;

this.addComponent(this.rigidBody);

}

setCollider(collider) {

if (this.collider) {

var index = this.components.indexOf(this.collider);

this.components.remove(index);

}

this.collider = collider;

this.addComponent(this.collider);

}

update() {

for (let i = 0; i < this.components.length; ++i) {

this.components[i].update();

}

for (let i = 0; i < this.children.length; ++i) {

this.children[i].update();

}

}

draw() {

for (let i = 0; i < this.components.length; ++i) {

this.components[i].draw();

}

for (let i = 0; i < this.children.length; ++i) {

this.children[i].draw();

}

}

onCollisionEnter(collider) {}

onTriggerEnter(collider) {}

}

class ParticleSystem extends GameObject {

constructor() {

super();

this.isLooping = false;

this.isEmitting = false;

this.startSize = 1;

this.startDelay = 0;

this.startSpeed = 5;

this.startColor = "red";

this.startLifetime = 1;

this.startTime = 0;

this.duration = 1.0;

this.timer = this.duration;

this.maxParticleCount = 10;

}

update() {

if (!this.isEnabled) return;

super.update();

// pass-1 update whether we should continue to emit particles

if (this.isEmitting) {

this.timer -= Time.deltaTime / 1000;

if (this.timer <= 0) {

this.timer = this.isLooping ? this.duration : 0;

// if its still 0, then stop the emitting

this.isEmitting = this.timer > 0;

}

}

// if we are still emitting, keep adding those particles!

if (this.isEmitting) {

this.addParticle();

this.updateParticles();

}

}

addParticle() {

if (this.children.length >= this.maxParticleCount) return;

let velocity = new Point((Math.random() * 0.5) - 0.25, Math.random() * 0.2);

let particle = new Particle(this.position.x, this.position.y, this.startLifetime, velocity, this.startColor, (Math.random() * 3) + 1);

this.addChild(particle);

}

updateParticles() {

let toRemove = this.children.filter(x => x.lifetime <= 0 || x.position.y >= canvas.height);

toRemove.forEach(x => x.destroy());

}

destroyParticles() {

let toRemove = [];

this.children.forEach(x => toRemove.push(particle));

toRemove.forEach(x => x.destroy());

}

draw() {

if (!this.isEnabled) return;

super.draw();

}

start() {

this.destroyParticles();

this.isEmitting = true;

this.timer = this.duration;

this.startTime = Time.time;

}

stop() {

this.isEmitting = false;

}

get particleCount() {

return children.length;

}

}

class Particle extends GameObject {

constructor(startX, startY, life, velocity, color, size) {

super();

const rigidBody = new RigidBody();

rigidBody.velocity.x = velocity.x;

rigidBody.velocity.y = velocity.y;

this.size = size;

this.color = color;

this.lifetime = life;

this.setRigidBody(rigidBody);

this.position.x = startX;

this.position.y = startY;

}

update() {

if (!this.isEnabled) return;

super.update();

this.lifetime -= Time.deltaTime / 1000;

}

draw() {

if (!this.isEnabled || !this.isVisible) return;

super.draw();

ctx.save();

// console.log("draw particle at: " + this.position.x + "," + this.position.y);

drawCircle(this.position.x, this.position.y, this.color, this.size);

ctx.restore();

}

}

class Animation {

constructor(name, interval = 150.0, animationFrames = [], playOnce = false, canInterrupt = true) {

this.name = name;

this.updateInterval = interval;

this.playOnce = playOnce;

this.interruptable = canInterrupt;

this.isPlaying = false;

this.updateTimer = 0.0;

this.frameIndex = 0;

this.frames = animationFrames;

}

update() {

if (!this.isPlaying) return;

if (this.frameIndex + 1 >= this.frames.length && this.playOnce) {

this.isPlaying = false;

this.frameIndex = 0;

return;

}

this.updateTimer += Time.deltaTime;

if (this.updateTimer >= this.updateInterval) {

this.updateTimer = 0.0;

let targetFrameIndex = (this.frameIndex + 1) % this.frames.length;

let frame = this.getCurrentFrame();

if (frame) {

if (!frame.continueWhen) {

this.frameIndex = targetFrameIndex;

return;

}

if (frame.continueWhen() === true) {

this.frameIndex = targetFrameIndex;

}

}

}

}

play() {

this.isPlaying = true;

}

stop() {

this.isPlaying = false;

}

addFrame(frame) {

this.frames.push(frame);

}

addFrames(framesToAdd) {

for (let i = 0; i < framesToAdd.length; ++i) {

this.addFrame(framesToAdd[i]);

}

}

getCurrentFrame() {

if (this.frames.length === 0) {

return null;

}

return this.frames[this.frameIndex];

}

getFrameAt(index) {

if (this.frames.length === 0 || index >= this.frames.length) {

return null;

}

return this.frames[index];

}

}

class AnimationFrame {

constructor(x, y, width, height, continueWhen) {

this.position = {

x: x,

y: y

};

this.size = {

width: width,

height: height

};

this.continueWhen = continueWhen;

}

}

class Button extends GameObject {

constructor() {

super();

this.states = [];

this.states["default"] = {

callbacks: []

};

this.states["hover"] = {

callbacks: []

};

this.states["active"] = {

callbacks: []

};

this.states["click"] = {

callbacks: []

};

this.state = "default";

this.borderOnInside = false;

this.width = 150;

this.height = 50;

this.text = "Button 1";

this.fontSize = 16;

this.fontColor = Color.getWhite();

this.font = "arial";

this.background = new Color(255, 0, 0);

this.border = new Color(0, 255, 0);

this.borderWidth = 1;

this.doubleBorder = false;

this.doubleBorderDistance = 5;

this.content = undefined; // appoint a gameobject and it will draw it as content :-)

this.contentScale = 1.0;

this.contentMargin = {

top: 0,

left: 0,

right: 0,

bottom: 0

};

}

draw() {

if (!this.isEnabled || !this.isVisible) return

super.draw();

this.drawButtonBase();

this.drawContent();

this.drawText();

}

drawButtonBase() {

let fill = this.background;

let stroke = this.border;

switch (this.state) {

case "hover":

fill = fill.darker(22);

stroke = stroke.darker(22);

break;

case "active":

fill = fill.darker(44);

stroke = stroke.darker(44);

break;

}

const bw = ctx.lineWidth;

const fs = ctx.fillStyle;

const ss = ctx.strokeStyle;

ctx.save();

ctx.beginPath();

ctx.lineWidth = this.borderWidth;

ctx.fillStyle = fill.rgba();

ctx.strokeStyle = stroke.rgba();

ctx.rect(this.position.x, this.position.y, this.width, this.height);

ctx.fill();

if (this.borderOnInside === true) {

ctx.beginPath();

ctx.rect(this.position.x + this.borderWidth / 2,

this.position.y + this.borderWidth / 2,

this.width - (this.borderWidth),

this.height - (this.borderWidth));

}

ctx.stroke();

if (this.doubleBorder === true) {

const bd = this.doubleBorderDistance;

ctx.beginPath();

ctx.lineWidth = this.borderWidth;

ctx.fillStyle = fill.rgba();

ctx.strokeStyle = stroke.rgba();

ctx.rect(this.position.x + bd, this.position.y + bd, this.width - bd * 2, this.height - bd * 2);

ctx.fill();

ctx.stroke();

}

ctx.restore();

ctx.lineWidth = bw;

ctx.fillStyle = fs;

ctx.strokeStyle = ss;

}

drawContent() {

if (!this.content) return;

ctx.save();

let x = this.position.x + this.contentMargin.left;

let y = this.position.y + this.contentMargin.top;

ctx.translate(x, y);

ctx.scale(this.contentScale, this.contentScale);

this.content.draw();

ctx.restore();

}

drawText() {

if (!this.text || this.text.length === 0) return;

ctx.save();

ctx.font = this.fontSize + "px " + this.font;

const size = ctx.measureText(this.text);

ctx.fillStyle = this.fontColor.rgba();

ctx.fillText(this.text, this.position.x + (this.width / 2 - size.width / 2), (this.position.y + this.fontSize) + (this.height / 2 - this.fontSize / 2));

ctx.restore();

}

update() {

if (!this.isEnabled) return;

super.update();

let oldState = this.state;

let click = false;

if (mouse.x >= this.position.x && mouse.x <= this.position.x + this.width &&

mouse.y >= this.position.y && mouse.y <= this.position.y + this.height) {

if (mouse.leftButton) {

if (this.state !== "active") {

this.state = "active";

}

} else {

if (this.state !== "hover") {

this.state = "hover";

click = oldState === "active";

}

}

} else if (this.state !== "default") {

this.state = "default"

}

if (oldState !== this.state) {

this.states[this.state].callbacks.forEach(x => x());

if (click) this.states["click"].callbacks.forEach(x => x());

}

}

on(state, callback) {

this.states[state].callbacks.push(callback);

}

}

class Sprite extends GameObject {

constructor(img) {

super();

this.image = img;

this.width = -1;

this.height = -1;

this.imageOffset = {

x: 0,

y: 0

};

this.scale = {

x: 1,

y: 1

};

this.origin = {

x: 0,

y: 0

};

this.isTiledRepeat = false;

}

static fromUrl(src) {

const spriteImg = new Image();

spriteImg.src = src;

return new Sprite(spriteImg);

}

update() {

if (!this.isEnabled) return;

super.update();

}

draw() {

if (!ctx || !this.isVisible || !this.isEnabled || !this.image) return;

ctx.save();

super.draw();

// ctx.restore();

const w = this.image.width;

const h = this.image.height;

const cols = Math.floor(this.width / w) + 1;

const rows = Math.floor(this.height / h) + 1;

if (this.isTiledRepeat && !isNaN(cols) && !isNaN(rows) && cols > 0 && rows > 0 && cols < 1920 && rows < 1080) {

for (let x = 0; x < cols; x++) {

for (let y = 0; y < rows; y++) {

ctx.drawImage(this.image, x * w, y * h);

}

}

} else {

let camera = Camera.getMainCamera();

if (this.width <= 0) {

this.width = this.image.width;

this.height = this.image.height;

}

const scaledWidth = this.width * this.scale.x;

const scaledHeight = this.height * this.scale.y;

const bb = this.getBoundingBox();

const dx = this.origin.x > 0 ? -((bb.max.x - bb.min.x) * this.origin.x) : 0;

const dy = this.origin.y > 0 ? -((bb.max.y - bb.min.y) * this.origin.y) : 0;

const renderX = camera.viewport.x + this.position.x + this.offset.x + this.imageOffset.x + dx;

const renderY = camera.viewport.y + this.position.y + this.offset.y + this.imageOffset.y + dy;

// ctx.save();

ctx.globalAlpha = this.opacity;

if (this.rotation !== 0) {

ctx.translate(renderX - dx, renderY - dy);

ctx.rotate(this.rotation * Math.PI / 180.0);

ctx.drawImage(this.image, 0, 0, this.image.width, this.image.height, dx, dy, scaledWidth, scaledHeight);

} else {

ctx.drawImage(

this.image,

0,

0,

this.image.width,

this.image.height,

renderX,

renderY,

scaledWidth,

scaledHeight);

}

}

ctx.restore(); // do restore again as we saved our context the first thing we did.

}

getBoundingBox() {

return new BoundingBox(

new Point(this.position.x, this.position.y),

new Point(this.position.x + (this.width * this.scale.x), this.position.y + (this.height * this.scale.y))

);

}

}

class AnimatedSprite extends GameObject {

constructor(spritesheet) {

super();

this.spritesheet = spritesheet;

this.animations = [];

this.currentAnimation = null;

this.flipHorizontal = false;

this.flipVertical = false;

}

addAnimation(animation) {

this.animations.push(animation);

}

playAnimation(key) {

if (this.currentAnimation) {

if (!this.currentAnimation.interruptable && this.currentAnimation.isPlaying) {

return;

}

this.currentAnimation.stop();

}

let targetAnimation = this.animations.find(x => x.name == key);

this.currentAnimation = targetAnimation;

this.currentAnimation.play();

}

update() {

if (!this.isEnabled) {

return;

}

if (this.currentAnimation) {

this.currentAnimation.update();

}

super.update();

}

draw() {

if (!ctx || !this.isVisible || !this.isEnabled || !this.currentAnimation)

return;

let frame = this.currentAnimation.getCurrentFrame();

if (!frame) return;

ctx.save();

super.draw();

if (this.collider) this.offset.x = (this.collider.bounds.max.x / 2);

let camera = Camera.getMainCamera();

if (this.flipHorizontal) {

ctx.translate(camera.viewport.x + (this.position.x + this.offset.x + this.collider.bounds.max.x), this.position.y);

ctx.scale(-1, 1);

} else {

ctx.translate(this.position.x + ((-this.offset.x) + this.collider.bounds.max.x), this.position.y);

}

ctx.globalAlpha = this.opacity;

ctx.drawImage(

this.spritesheet,

frame.position.x,

frame.position.y,

frame.size.width,

frame.size.height,

this.flipHorizontal ? 0 : camera.viewport.x,

camera.viewport.y,

frame.size.width,

frame.size.height);

ctx.restore();

}

}

class Scene extends GameObject {

constructor() {

super();

}

update() {

super.update();

}

draw() {

super.draw();

}

}

function getMousePos(element, evt) {

var rect = element.getBoundingClientRect();

return {

x: evt.clientX - rect.left,

y: evt.clientY - rect.top

};

}

function getTouchPos(canvas, evt) {

var rect = element.getBoundingClientRect();

return {

x: evt.touches[0].clientX - rect.left,

y: evt.touches[0].clientY - rect.top

};

}

function run(time) {

Time.deltaTime = (time - Time.time) * Time.timeScale;

Time.deltaTimeUnscaled = time - Time.time;

Time.time = time;

Physics.update();

if (onUpdate) onUpdate();

if (onDraw) onDraw();

Time.frameCount++;

window.requestAnimationFrame(run);

}

function clear(clearStyle) {

if (isWebGl) {

ctx.clearColor(0, 0, 0.8, 1);

ctx.clear(gl.COLOR_BUFFER_BIT);

return;

}

if (clearStyle) {

ctx.fillStyle = clearStyle;

ctx.fillRect(0, 0, canvas.width, canvas.height);

} else {

ctx.clearRect(0, 0, canvas.width, canvas.height);

}

}

function mouseMove(evt) {

let pos = getMousePos(canvas, evt);

mouse.x = pos.x;

mouse.y = pos.y;

}

function touchMove(evt) {

evt.preventDefault();

let pos = getTouchPos(canvas, evt);

mouse.x = pos.x;

mouse.y = pos.y;

}

function mouseDown(evt) {

if (evt.button === 0) mouse.leftButton = true;

if (evt.button === 1) mouse.rightButton = true;

if (evt.button === 3) mouse.middleButton = true;

}

function mouseUp(evt) {

if (evt.button === 0) mouse.leftButton = false;

if (evt.button === 1) mouse.rightButton = false;

if (evt.button === 3) mouse.middleButton = false;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值