UNPKG

duckengine

Version:
1,510 lines (1,486 loc) 287 kB
/*! * * DuckEngine 2.1.0 * * https://github.com/ksplatdev/DuckEngine * * MIT License * * Copyright (c) 2021 Bleart Emini * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ /******/ // The require scope /******/ var __webpack_require__ = {}; /******/ /************************************************************************/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; // EXPORTS __webpack_require__.d(__webpack_exports__, { "_": () => (/* binding */ Duck), "Z": () => (/* binding */ lib) }); ;// CONCATENATED MODULE: ./lib/core/debug/debug.js const __debugStack_duckengine = []; class Error { constructor(message) { console.error('DuckEngine Error : ' + message); __debugStack_duckengine.push(message); } } class Warn { constructor(message) { console.warn('DuckEngine Warning : ' + message); __debugStack_duckengine.push(message); } } class Log { constructor(message) { console.log('DuckEngine : ' + message); __debugStack_duckengine.push(message); } } const Debug = { stack: () => { return __debugStack_duckengine; }, Error: Error, Warn: Warn, Log: Log, }; /* harmony default export */ const debug = (Debug); ;// CONCATENATED MODULE: ./lib/core/math/clamp.js /** * @function * @description Clamps a value based on min and max * @param {number} x Number * @param {number} min Minimum * @param {number} max Maximum * @since 1.0.0-beta */ function clamp(x, min, max) { if (x < min) x = min; else if (x > max) x = max; return x; } ;// CONCATENATED MODULE: ./lib/utils/degToRadians.js function degToRadians(degrees) { return (degrees * Math.PI) / 180; } ;// CONCATENATED MODULE: ./lib/core/math/vector2.js /** * @class Vector2 * @classdesc Creates a Vector2 * @description The Vector2 Class. Represents a point * @since 2.0.0 */ class Vector2 { /** * @constructor Vector2 * @description Creates a Vector2 instance * @param {number} [x=0] X position, optional -> defaults: 0 * @param {number} [y=0] Y position, optional -> defaults: 0 * @since 2.0.0 */ constructor(x = 0, y = 0) { this.x = x; this.y = y; } /** * @memberof Vector2 * @description Sets the values of the Vector2 * @param {number} x X position to setValue of * @param {number} y Y position to setValue of * @returns {Vector2} * @since 2.0.0 */ setValues(x, y) { this.x = x; this.y = y; return this; } /** * @memberof Vector2 * @description Sets the values of the Vector2 based on another Vector2 * @param {number} vector Vector2 to use to set the position * @returns {Vector2} * @since 2.0.0 */ setValuesVec(vector) { this.x = vector.x; this.y = vector.y; return this; } /** * @memberof Vector2 * @description Adds a Vector2 * @param {Vector2} vector Vector2 to be added to * @returns {Vector2} * @since 2.0.0 */ add(vector) { this.x += vector.x; this.y += vector.y; return this; } /** * @memberof Vector2 * @description Adds a number to the x and y properties * @param {number} number Number to add to x and y properties * @returns {Vector2} * @since 2.0.0 */ addNumber(number) { this.x += number; this.y += number; return this; } /** * @memberof Vector2 * @description Subtracts a Vector2 * @param {Vector2} vector Vector2 to be subtracted to * @returns {Vector2} * @since 2.0.0 */ subtract(vector) { this.x -= vector.x; this.y -= vector.y; return this; } /** * @memberof Vector2 * @description Subtracts a number from the x and y properties * @param {number} number Number to subtract from x and y properties * @returns {Vector2} * @since 2.0.0 */ subtractNumber(number) { this.x -= number; this.y -= number; return this; } /** * @memberof Vector2 * @description Multiplies a Vector2 * @param {Vector2} vector Vector2 to be multiplied to * @returns {Vector2} * @since 2.0.0 */ multiply(vector) { this.x *= vector.x; this.y *= vector.y; return this; } /** * @memberof Vector2 * @description Multiplies a number to the x and y properties * @param {number} number Number to multiply to x and y properties * @returns {Vector2} * @since 2.0.0 */ multiplyNumber(number) { this.x *= number; this.y *= number; return this; } /** * @memberof Vector2 * @description Divides a Vector2 * @param {Vector2} vector Vector2 to be divided to * @returns {Vector2} * @since 2.0.0 */ divide(vector) { this.x /= vector.x; this.y /= vector.y; return this; } /** * @memberof Vector2 * @description Divides a number to the x and y properties * @param {number} number Number to divide to x and y properties * @returns {Vector2} * @since 2.0.0 */ divideNumber(number) { this.x /= number; this.y /= number; return this; } /** * @memberof Vector2 * @description Rounds the Vector2 * @returns {Vector2} * @since 2.0.0 */ round() { this.x = Math.round(this.x); this.y = Math.round(this.y); return this; } /** * @memberof Vector2 * @description Gets the angle between two Vector2s * @param {Vector2} vector A Vector2 to get the angle between from * @returns {number} * @since 2.0.0 */ angleBetween(vector) { return Math.atan2(this.x * vector.y - this.y * vector.x, this.x * vector.x + this.y * vector.y); } /** * @memberof Vector2 * @description Gets the angle to two Vector2s * @param {Vector2} vector A Vector2 to get the angle to from * @returns {number} * @since 2.0.0 */ angleTo(vector) { return Math.atan2(vector.y - this.y, vector.x - this.x); } /** * @memberof Vector2 * @description Clones the current Vector2 * @returns {Vector2} * @since 2.0.0 */ clone() { return new Vector2(this.x, this.y); } /** * @memberof Vector2 * @description Gets the distance from another Vector2 * @param {Vector2} vector A Vector2 to get the distance from * @returns {number} * @since 2.0.0 */ distance(vector) { return Math.sqrt((vector.x - this.x) * (vector.x - this.x) + (vector.y - this.y) * (vector.y - this.y)); } /** * @memberof Vector2 * @description Gets the distance squared from another Vector2 * @param {Vector2} vector A Vector2 to get the distance from * @returns {number} * @since 2.0.0 */ distanceSqr(vector) { return ((vector.x - this.x) * (vector.x - this.x) + (vector.y - this.y) * (vector.y - this.y)); } /** * @memberof Vector2 * @description Gets the dot product with another Vector2 * @param {Vector2} vector A Vector2 to get the dot product from * @returns {number} * @since 2.0.0 */ dot(vector) { return this.x * vector.x + this.y * vector.y; } /** * @memberof Vector2 * @description Gets the cross dot product with another Vector2 * @param {Vector2} vector A Vector2 to get the cross dot product from * @returns {number} * @since 2.0.0 */ crossProduct(vector) { return this.x * vector.y - this.y * vector.x; } /** * @memberof Vector2 * @description Checks if another Vector2 is equal on both axises * @param {Vector2} vector A Vector2 to compare with * @returns {boolean} * @since 2.0.0 */ equals(vector) { return this.x === vector.x && this.y === vector.y; } /** * @memberof Vector2 * @description Gets the perpendicular values of the Vector2 * @param {Vector2} [resultVector] The new Vector2 to save the value to, optional -> defaults: new Vector2 * @returns {Vector2} * @since 2.0.0 */ perpendicular(resultVector) { resultVector = resultVector || new Vector2(); return resultVector.setValues(-this.y, this.x); } /** * @memberof Vector2 * @description Gradually interpolates the Vector2 towards another Vector2 by an amount * @param {Vector2} current The Current Vector * @param {Vector2} target The Target Vector2 * @param {number} maxDistanceDelta The amount to increase by * @returns {Vector2} * @since 2.0.0 */ moveTowards(current, target, maxDistanceDelta) { const toVector_x = target.x - current.x; const toVector_y = target.y - current.y; const sqDist = toVector_x * toVector_x + toVector_y * toVector_y; if (sqDist === 0 || (maxDistanceDelta >= 0 && sqDist <= maxDistanceDelta * maxDistanceDelta)) { return target; } const dist = Math.sqrt(sqDist); const newX = current.x + (toVector_x / dist) * maxDistanceDelta; const newY = current.y + (toVector_y / dist) * maxDistanceDelta; return new Vector2(newX, newY); } /** * @memberof Vector2 * @description Normalizes the Vector2 * @returns {Vector2} * @since 2.0.0 */ normalize() { const mag = this.magnitude(); if (mag > 0) { this.divideNumber(mag); } return this; } /** * @memberof Vector2 * @description Gets the normal value of the Vector2 and another Vector2 * @param * @param {Vector2} [resultVector] The new Vector2 to save the value to, optional -> defaults: new Vector2 * @returns {Vector2} * @since 2.0.0 */ getNormal(vector, resultVector) { resultVector = resultVector || new Vector2(); return resultVector .setValues(vector.y - this.y, this.x - vector.x) .normalize(); } /** * @memberof Vector2 * @description Determines if Vector2.x and Vector2.y are both equal to 0 * @returns {boolean} * @since 2.0.0 */ isZero() { return this.x === 0 && this.y === 0; } /** * @memberof Vector * @description Scales the Vector2 by a scalar Vector2 * @param {Vector2} scalar A Vector2 that is used to scale the current Vector2 * @returns {Vector2} * @since 2.0.0 */ scale(scalar) { this.x *= scalar.x; this.y *= scalar.y; return this; } /** * @memberof Vector2 * @description Sets Vector2.x and Vector2.y to their negative values * @returns {Vector2} * @since 2.0.0 */ negate() { this.x = -this.x; this.y = -this.y; return this; } /** * @memberof Vector2 * @description Returns the magnitude or length of the Vector2 * @returns {number} * @since 2.0.0 */ magnitude() { return Math.sqrt(this.x * this.x + this.y * this.y); } /** * @memberof Vector2 * @description Returns the magnitude/lenth squared of the Vector2 * @returns {number} * @since 2.0.0 */ magnitudeSqr() { return this.x * this.x + this.y * this.y; } /** * @memberof Vector2 * @description Scales the Vector2 by the magnitude or length * @param magnitude The magnitude or length of the Vector2 * @returns {Vector2} * @since 2.0.0 */ scaleToMagnitude(magnitude) { const k = magnitude / this.magnitude(); this.x *= k; this.y *= k; return this; } /** * @memberof Vector2 * @description Returns the string version of the Vector2 * @example console.log(new Vector2(0, 0).toString()) // Vector2(0, 0) * @returns {string} * @since 2.0.0 */ toString() { return `Vector2(${this.x}, ${this.y})`; } /** * @memberof Vector2 * @description Sets the values to be precise by using Number.toPrecision * @param {number} precision The precision * @returns {Vector2} * @since 2.0.0 */ toPrecision(precision) { this.x = Number(this.x.toPrecision(precision)); this.y = Number(this.y.toPrecision(precision)); return this; } /** * @memberof Vector2 * @description Adds to the Vector2 by an amount * @param {number} dx Delta x, the amount to increase the x value by * @param {number} dy Delta y, the amount to increase the y value by * @returns {Vector2} * @since 2.0.0 */ translate(dx, dy) { this.x += dx; this.y += dy; return this; } /** * @memberof Vector2 * @description Adds to the Vector2.x by an amount * @param {number} dx Delta x, the amount to increase the x value by * @returns {Vector2} * @since 2.0.0 */ translateX(dx) { this.x += dx; return this; } /** * @memberof Vector2 * @description Adds to the Vector2.y by an amount * @param {number} dy Delta y, the amount to increase the y value by * @returns {Vector2} * @since 2.0.0 */ translateY(dy) { this.x += dy; return this; } /** * @memberof Vector2 * @description Gets the dot product using three different Vector2s * @param {Vector2} a A Vector2 * @param {Vector2} b A Vector2 * @param {Vector2} c A Vector2 * @param {Vector2} [resultVector=Vector2] A Vector2 that the result is saved in, optional -> defaults: new Vector2 * @returns {Vector2} * @since 2.0.0 */ tripleProduct(a, b, c, resultVector) { resultVector = resultVector || new Vector2(); const ac = a.dot(c); const bc = b.dot(c); return resultVector.setValues(b.x * ac - a.x * bc, b.y * ac - a.y * bc); } /** * @memberof Vector2 * @description Clamps the values to a min and max * @param {number} min The min value * @param {number} max The max value * @returns {Vector2} * @since 2.0.0 */ clamp(min, max) { this.x = clamp(this.x, min, max); this.y = clamp(this.y, min, max); return this; } /** * @memberof Vector2 * @description Clamps the values to a min * @param {number} min The min value * @returns {Vector2} * @since 2.0.0 */ clampMin(min) { if (this.x < min) { this.x = min; } if (this.y < min) { this.y = min; } return this; } /** * @memberof Vector2 * @description Clamps the values to a max * @param {number} max The max value * @returns {Vector2} * @since 2.0.0 */ clampMax(max) { if (this.x > max) { this.x = max; } if (this.y > max) { this.y = max; } return this; } /** * @memberof Vector2 * @description Rotates the Vector2 based on degrees * @param {number} degrees The angle in degrees * @param {Vector2} [center] The center Vector relative to the Vector, optional -> defaults: Vector2.ZERO * @returns {Vector2} * @since 2.0.0 */ rotate(degrees, center = Vector2.ZERO) { const radians = degToRadians(degrees); const cx = center.x || 0; const cy = center.y || 0; const c = Math.cos(radians), s = Math.sin(radians); const x = this.x - cx; const y = this.y - cy; this.x = x * c - y * s + cx; this.y = x * s + y * c + cy; return this; } /** * @memberof Vector2 * @description Reflects the Vector2, returns the opposite value on a number line * * @example new Vector2(100, 50).reflect() // Vector2(-100, -50) * * @returns {Vector2} * @since 2.0.0 */ reflect() { this.x *= -1; this.y *= -1; return this; } /** * @memberof Vector2 * @description Gets the absolute value of the vector * @returns {Vector2} * @since 2.0.0 */ abs() { this.x = Math.abs(this.x); this.y = Math.abs(this.y); return this; } // STATIC /** * @memberof Vector2 * @static * @description Returns a Vector2 with 0 set as x and y * @returns {Vector2} * @since 2.0.0 */ static get ZERO() { return new Vector2(0, 0); } /** * @memberof Vector2 * @static * @description Returns a Vector2 with 0 set as x and -1 set as y * @returns {Vector2} * @since 2.0.0 */ static get UP() { return new Vector2(0, -1); } /** * @memberof Vector2 * @static * @description Returns a Vector2 with 0 set as x and 1 set as y * @returns {Vector2} * @since 2.0.0 */ static get DOWN() { return new Vector2(0, 1); } /** * @memberof Vector2 * @static * @description Returns a Vector2 with -1 set as x and 0 set as y * @returns {Vector2} * @since 2.0.0 */ static get LEFT() { return new Vector2(-1, 0); } /** * @memberof Vector2 * @static * @description Returns a Vector2 with 1 set as x and 0 set as y * @returns {Vector2} * @since 2.0.0 */ static get RIGHT() { return new Vector2(1, 0); } /** * @memberof Vector2 * @static * @description Returns a Vector2 with passed parameters, if no parameters are passed, a Vector2.ZERO is returned * @param {number} [x] X position, optional -> defaults: 0 * @param {number} [y] Y position, optional -> defaults: 0 * @returns {Vector2} * @since 2.0.0 */ static CREATE(x, y) { if (!x && !y) return this.ZERO; // none if (x && !y) return new Vector2(x); // only x if (!x && y) return new Vector2(0, y); // only y return new Vector2(x, y); // both } /** * @memberof Vector2 * @static * @description Returns a Vector2 with passed vector2Like object * @param {Duck.Types.Math.Vector2Like} vector2Like An object with x and y properties * @returns {Vector2} * @since 2.0.0 */ static fromVector2Like(vector2Like) { return new Vector2(vector2Like.x, vector2Like.y); } /** * @memberof Vector2 * @static * @description Returns a Vector2Like object with passed Vector2 * @param {Vector2} vector2 A Vector2 to convert to Vector2Like object * @returns {Vector2} * @since 2.0.0 */ static toVector2Like(vector2) { return { x: vector2.x, y: vector2.y, }; } /** * @memberof Vector2 * @static * @description Returns a Vector2 with values from a passed Vector2 * @param {Vector2} vector Vector2 to create a Vector2 from * @returns {Vector2} * @since 2.0.0 */ static fromVec(vector) { return new Vector2(vector.x, vector.y); } } ;// CONCATENATED MODULE: ./lib/core/physics/models/collider.js // circle collision physics from https://github.com/pothonprogramming/pothonprogramming.github.io/blob/master/content/circle-collision-response/circle-collision-response.html // most of rect physics also from PothOnProgramming /** * @class Collider * @classdesc Creates a DuckEngine Collider * @description The Collider Class. Collision Handler and Resolver * @since 1.0.0-beta */ class Collider { /** * @constructor Collider * @description Creates a Collider instance * @param {Hitbox} hitbox Hitbox to append the collider to * @param {GameObject<Duck.Types.Texture.Type>[] | Group<GameObject<Duck.Types.Texture.Type>>} collidesWith What the PhysicsBody collides with * @param {Game} game Game instance * @since 1.0.0-beta */ constructor(hitbox, collidesWith, game) { this.hitbox = hitbox; this.collidesWith = collidesWith; this.game = game; } /** * @memberof Collider * @description Updates the collider and checks for collisions with the updated version of the object, and collides with. * * DO NOT CALL MANUALLY! CALLED IN PHYSICS SERVER! * * @param {Hitbox} hitbox The updated hitbox that the collider is attached * @param {GameObject<Duck.Types.Texture.Type>[] | Group<GameObject<Duck.Types.Texture.Type>>} updatedCollidesWith Updated version of what the object collides with * @since 2.0.0 */ _update(hitbox, updatedCollidesWith) { this.hitbox = hitbox; this.collidesWith = updatedCollidesWith; if (Array.isArray(this.collidesWith)) { this.collidesWith.forEach((otherObject) => { if (otherObject.hitbox) { this.collideHitboxes(otherObject.hitbox); } }); } else { this.collidesWith.each((otherObject) => { if (otherObject.hitbox) { this.collideHitboxes(otherObject.hitbox); } }); } } collideHitboxes(hitbox2) { const rectCX = hitbox2.position.x + hitbox2.w * 0.5; const rectCY = hitbox2.position.y + hitbox2.h * 0.5; const thisCX = this.hitbox.position.x + this.hitbox.w * 0.5; const thisCY = this.hitbox.position.y + this.hitbox.h * 0.5; const dx = rectCX - thisCX; // x difference between centers const dy = rectCY - thisCY; // y difference between centers const aw = (hitbox2.w + this.hitbox.w) * 0.5; // average width const ah = (hitbox2.h + this.hitbox.h) * 0.5; // average height /* If either distance is greater than the average dimension there is no collision. */ if (Math.abs(dx) > aw || Math.abs(dy) > ah) { return 'none'; } /* To determine which region of this rectangle the rect's center point is in, we have to account for the scale of the this rectangle. To do that, we divide dx and dy by it's width and height respectively. */ if (Math.abs(dx / this.hitbox.w) > Math.abs(dy / this.hitbox.h)) { if (dx < 0) { // left this.hitbox.position.x = hitbox2.position.x + hitbox2.w; return 'left'; } else { // right this.hitbox.position.x = hitbox2.position.x - this.hitbox.w; return 'right'; } } else { if (dy < 0) { // top this.hitbox.position.y = hitbox2.position.y + hitbox2.h; return 'top'; } else { // bottom this.hitbox.position.y = hitbox2.position.y - this.hitbox.h; return 'bottom'; } } } } ;// CONCATENATED MODULE: ./lib/utils/uniqueID.js // https://gist.github.com/gordonbrander/2230317#gistcomment-1618310 function uniqueID() { function chr4() { return Math.random().toString(16).slice(-4); } return (chr4() + chr4() + '-' + chr4() + '-' + chr4() + '-' + chr4() + '-' + chr4() + chr4() + chr4()); } ;// CONCATENATED MODULE: ./lib/core/physics/utils/hitboxFaceIntersect.js function hitboxFaceIntersect(hitbox, hitbox2) { const rectCX = hitbox2.position.x + hitbox2.w * 0.5; const rectCY = hitbox2.position.y + hitbox2.h * 0.5; const thisCX = hitbox.position.x + hitbox.w * 0.5; const thisCY = hitbox.position.y + hitbox.h * 0.5; const dx = rectCX - thisCX; // x difference between centers const dy = rectCY - thisCY; // y difference between centers const aw = (hitbox2.w + hitbox.w) * 0.5; // average width const ah = (hitbox2.h + hitbox.h) * 0.5; // average height /* If either distance is greater than the average dimension there is no collision. */ if (Math.abs(dx) > aw || Math.abs(dy) > ah) { return 'none'; } /* To determine which region of this rectangle the rect's center point is in, we have to account for the scale of the this rectangle. To do that, we divide dx and dy by it's width and height respectively. */ if (Math.abs(dx / hitbox.w) > Math.abs(dy / hitbox.h)) { if (dx < 0) { // left hitbox.position.x = hitbox2.position.x + hitbox2.w; return 'left'; } else { // right hitbox.position.x = hitbox2.position.x - hitbox.w; return 'right'; } } else { if (dy < 0) { // top hitbox.position.y = hitbox2.position.y + hitbox2.h; return 'top'; } else { // bottom hitbox.position.y = hitbox2.position.y - hitbox.h; return 'bottom'; } } } ;// CONCATENATED MODULE: ./lib/core/physics/utils/rectToRectIntersect.js /** * @function * @description Returns a boolean based on if a rectangle is intersecting with a rectangle * @param {Rect |Sprite | RoundRect | { position: { x:number; y:number }; w:number; h:number }} rect * @param {Rect |Sprite| RoundRect | { position: { x:number; y:number }; w:number; h:number }} rect2 * @returns {boolean} * @since 1.1.0-beta */ function rectToRectIntersect(rect, rect2) { const rectCX = rect2.position.x + rect2.w * 0.5; const rectCY = rect2.position.y + rect2.h * 0.5; const thisCX = rect.position.x + rect.w * 0.5; const thisCY = rect.position.y + rect.h * 0.5; const dx = rectCX - thisCX; // x difference between centers const dy = rectCY - thisCY; // y difference between centers const aw = (rect2.w + rect.w) * 0.5; // average width const ah = (rect2.h + rect.h) * 0.5; // average height /* If either distance is greater than the average dimension there is no collision. */ if (Math.abs(dx) > aw || Math.abs(dy) > ah) return false; else return true; } ;// CONCATENATED MODULE: ./lib/core/physics/models/hitbox.js /** * @class Hitbox * @classdesc Creates a DuckEngine Hitbox * @description The Hitbox Class. A AABB Hitbox used for Colliders * @since 2.0.0 */ class Hitbox { /** * @constructor Hitbox * @param {number} id The PhysicsBody ID * @param {Vector2} position The position of the Hitbox * @param {number} w The width of the Hitbox * @param {number} h The height of the Hitbox * @param {Vector2} offset The offset position of the Hitbox * @param {PhysicsBody<Duck.Types.Texture.Type>} physicsObject The PhysicsBody that the Hitbox is attached to * @param {Game} game Game instance * @param {Scene} scene Scene instance * @param {string|undefined} [debugColor=undefined] The debugColor of the Hitbox */ constructor(position, w, h, offset, physicsObject, game, scene, debugColor) { this.id = uniqueID(); this.position = position; this.offset = offset; this.w = w; this.h = h; this.game = game; this.scene = scene; this.physicsObject = physicsObject; this.debugColor = debugColor; this.visible = debugColor ? true : false; this.zIndex = Duck.Layers.Rendering.zIndex.graphicDebug; this.culled = debugColor ? true : false; this.collisionState = 'none'; } /** * @memberof Hitbox * @description Draws the hitbox if a debugColor is passed in the constructor. * * DO NOT CALL MANUALLY, CALLED IN GAME LOOP USING SCENE.displayList * * @since 2.0.0 */ _draw() { if (this.game.renderer.ctx) { if (this.debugColor) { this.game.renderer.drawRect(this.position.x, this.position.y, this.w, this.h, this.debugColor); } } else { new debug.Error('CanvasRenderingContext2D is undefined. HTMLCanvasElement is undefined.'); } } /** * @memberof Hitbox * @description Sets the Hitboxes position to the PhysicsBodies position plus the passed offset if one was set * * DO NOT CALL MANUALLY, CALLED IN SCENE.__tick * * @since 2.0.0 */ _update(physicsObject) { this.physicsObject = physicsObject; // resolve this.position = this.physicsObject.position.add(this.offset); } /** * @memberof Hitbox * @description Sets the debugColor and visibility of the Hitbox as a debug aid * @param {string} debugColor Color * @param {boolean} [visible=true] What to set the visible property as, optional -> defaults: true * @since 2.0.0 */ setDebugColor(debugColor, visible = true) { this.debugColor = debugColor; this.visible = visible; } /** * @memberof Hitbox * @description Sets the width and height of the Hitbox * @param {Vector2} scale The new scale of the Hitbox * @since 2.0.0 */ scale(scale) { this.w = scale.x; this.h = scale.y; } /** * @memberof Hitbox * @description Sets position of the Hitbox * @param {Vector2} newPosition The new position of the Hitbox * @param {Vector2} [offset] The new offset position of the Hitbox, optional -> defaults: Hitbox.offset * @since 2.0.0 */ setPosition(newPosition, offset = this.offset) { this.offset = offset; this.position = newPosition.add(this.offset); } /** * @memberof Hitbox * @description Auto scales, positions, and offsets the Hitbox based on the shape of the PhysicsBody * @param {Vector2} [offset] The new offset position of the Hitbox, optional * @since 2.0.0 */ auto(offset) { if (this.physicsObject.shape === 'circle') { // top left corner const topLeft = new Vector2(this.physicsObject.position.x - this.physicsObject.r, this.physicsObject.position.y - this.physicsObject.r); if (offset) { topLeft.add(offset); } this.position = topLeft; this.scale(new Vector2(this.physicsObject.r * 2, this.physicsObject.r * 2)); } else { this.position = this.physicsObject.position; if (offset) { this.position.add(offset); } this.scale(new Vector2(this.physicsObject.w, this.physicsObject.h)); } } /** * @memberof Hitbox * @description Checks if the Hitbox is intersecting with another Hitbox * @param {Hitbox} Hitbox Hitbox to use to test the intersection * @returns {boolean} * @since 2.0.0 */ intersectsWith(hitbox) { return rectToRectIntersect({ position: { x: this.position.x, y: this.position.y, }, w: this.w, h: this.h, }, { position: { x: hitbox.position.x, y: hitbox.position.y, }, w: hitbox.w, h: hitbox.h, }); } /** * @memberof Hitbox * @description Checks if the Hitbox is intersecting with another Hitbox and returns the face that is colliding * @param {Hitbox} hitbox Hitbox to use to test the intersection * @returns {Duck.Types.Collider.CollisionResponseType} * @since 2.0.0 */ intersectsFaceWith(hitbox) { this.collisionState = hitboxFaceIntersect(this, hitbox); return this.collisionState; } /** * @memberof Hitbox * @description Checks if the Hitbox is intersecting with other Hitboxes and returns the face that is colliding * @param {Group<Hitbox> | Hitbox[]} hitboxes Hitboxes to use to test the intersection * @returns {Duck.Types.Collider.CollisionResponseType[]} * @since 2.0.0 */ groupIntersectsFaceWith(hitboxes) { const collisionStates = []; if (Array.isArray(hitboxes)) { for (let i = 0; i < hitboxes.length; i++) { const hitbox = hitboxes[i]; collisionStates.push(hitboxFaceIntersect(this, hitbox)); } } else { for (let i = 0; i < hitboxes.group.length; i++) { const hitbox = hitboxes.group[i]; collisionStates.push(hitboxFaceIntersect(this, hitbox)); } } return collisionStates; } /** * @memberof Hitbox * @description Gets the top most coordinate of the Hitbox * @returns {number} * @since 2.0.0 */ getTop() { return this.position.y; } /** * @memberof Hitbox * @description Gets the bottom most coordinate of the Hitbox * @returns {number} * @since 2.0.0 */ getBottom() { return this.position.y + this.h; } /** * @memberof Hitbox * @description Gets the left most coordinate of the Hitbox * @returns {number} * @since 2.0.0 */ getLeft() { return this.position.x; } /** * @memberof Hitbox * @description Gets the right most coordinate of the Hitbox * @returns {number} * @since 2.0.0 */ getRight() { return this.position.x + this.w; } /** * @memberof Hitbox * @description Gets the center coordinates of the Hitbox * @returns {Vector2} * @since 2.0.0 */ getCenter() { return new Vector2(this.position.x + this.w / 2, this.position.y + this.h / 2); } /** * @memberof Hitbox * @description Gets the centerY coordinate of the Hitbox * @returns {number} * @since 2.0.0 */ getCenterY() { return this.position.y + this.h / 2; } /** * @memberof Hitbox * @description Gets the centerX coordinate of the Hitbox * @returns {number} * @since 2.0.0 */ getCenterX() { return this.position.x + this.w / 2; } } ;// CONCATENATED MODULE: ./lib/core/physics/physicsBody.js /** * @class PhysicsBody * @classdesc Creates a DuckEngine PhysicsBody * @description The PhysicsBody Class. The GameObject class extends this class * @since 2.0.0 */ class PhysicsBody { /** * @constructor PhysicsBody * @description Creates a PhysicsBody instance. Extended by GameObject * @param {Duck.Types.Collider.ShapeString} shape Shape of PhysicsBody * @param {number} id ID from GameObject ID * @param {number} x X position * @param {number} y Y position * @param {number} w Width * @param {number} h Height * @param {number} r Radius * @param {Game} game Game instance * @param {Scene} scene Scene instance * @since 2.0.0 */ constructor(shape, id, x, y, w, h, r, game, scene) { this.shape = shape; this.id = id; this.position = new Vector2(x, y); this.w = w; this.h = h; this.r = r; this.options = { type: 'KinematicBody', }; this.game = game; this.scene = scene; this.velocity = Vector2.ZERO; this.collider = undefined; this.collidesWith = []; this.enabled = true; this.isAttached = false; this.attachedChildren = []; this.attachOffset = Vector2.ZERO; this.bounds = { x: -Infinity, y: -Infinity, w: Infinity, h: Infinity, }; // methods this.physics = { addCollider: (collidesWith) => { var _a; if (!this.hitbox) { new debug.Error('Cannot add collider to PhysicsObject. No hitbox exists. Create a hitbox first using PhysicsObject.physics.addHitbox'); return undefined; } if (!((_a = this.game.config.physics) === null || _a === void 0 ? void 0 : _a.enabled)) { new debug.Error('Cannot add collider to PhysicsObject. Game Config.physics.enabled must be truthy!'); } this.collidesWith = collidesWith; this.collider = new Collider(this.hitbox, collidesWith, this.game); return this.collider; }, addHitbox: (w, h, offset = Vector2.ZERO, debugColor) => { var _a; if (!((_a = this.game.config.physics) === null || _a === void 0 ? void 0 : _a.enabled)) { new debug.Error('Cannot add hitbox to PhysicsObject. Game Config.physics.enabled must be truthy!'); } this.hitbox = new Hitbox(this.position, w || 0, h || 0, offset, this, this.game, this.scene, debugColor); if (!w && !h) { this.hitbox.auto(offset); } this.scene.displayList.add(this.hitbox); return this.hitbox; }, setBounds: (x, y, w, h) => { this.bounds.x = x; this.bounds.y = y; this.bounds.w = w; this.bounds.h = h; }, }; } setEnabled(enabled) { this.enabled = enabled; return this.enabled; } /** * @memberof PhysicsBody * @description Updates the PhysicsBody's position by the velocity. Sets velocity to 0 on every tick. * Clamps position to bounds if exists. Rounds pixels if roundPixels game config is set to true. * Updates hitbox.collisionState if hitbox exists. * * DO NOT CALL MANUALLY, CALLED IN SCENE.__tick * * @since 2.0.0 */ _update() { var _a; this.position.x += this.velocity.x * this.game.smoothDeltaTime; this.position.y += this.velocity.y * this.game.smoothDeltaTime; // clamp to bounds this.position.x = clamp(this.position.x, this.bounds.x, this.bounds.w); this.position.y = clamp(this.position.y, this.bounds.y, this.bounds.h); // set to none this.velocity.x = 0; this.velocity.y = 0; // roundPixels if (this.game.config.roundPixels) { this.position.round(); } // apply gravity if ((_a = this.game.config.physics) === null || _a === void 0 ? void 0 : _a.gravity) { if (this.options.type === 'KinematicBody' || this.options.type === 'RigidBody') { this.applyGravity(Vector2.fromVector2Like(this.game.config.physics.gravity)); } } // update attached children position this.attachedChildren.forEach((object) => { const pos = this.position.clone(); pos.subtract(object.attachOffset); object.position = pos; if (object.hitbox) { object.hitbox.position = object.position .clone() .add(object.hitbox.offset); } }); } /** * @memberof PhysicsBody * @description Sets the PhysicsBody type, PhysicsBody type determines what can be applied and what effects the body * @param {Duck.Types.PhysicsBody.Type} type The type of the PhysicsBody, 'KinematicBody' | 'RigidBody' | 'StaticBody' * @since 2.0.0 */ setType(type) { this.options.type = type; return this.options; } /** * @memberof PhysicsBody * @description Attaches self to another PhysicsBody, makes self unmovable and follows the PhysicsBody accordingly * @param {PhysicsBody<Duck.Types.Texture.Type>} object PhysicsBody to attach to * @param {Vector2} [diffOffset=Vector2] A different offset, optional -> defaults: Difference in positions from PhysicsBody to self * @since 2.0.0 */ attachTo(object, diffOffset) { const offset = diffOffset || Vector2.fromVec(object.position).subtract(this.position); this.isAttached = true; this.attachOffset = offset; object.attachedChildren.push(this); } /** * @memberof PhysicsBody * @description Attaches a child PhysicsBody to self, makes child unmovable and follows the self accordingly * @param {PhysicsBody<Duck.Types.Texture.Type>} object PhysicsBody to attach * @param {Vector2} [diffOffset=Vector2] A different offset, optional -> defaults: Difference in positions from self to PhysicsBody * @since 2.0.0 */ attachChild(object, diffOffset) { const offset = diffOffset || Vector2.fromVec(this.position).subtract(object.position); object.isAttached = true; object.attachOffset = offset; this.attachedChildren.push(object); } /** * @memberof PhysicsBody * @description Detaches self from another PhysicsBody * @param {PhysicsBody<Duck.Types.Texture.Type>} object PhysicsBody to detach from * @since 2.0.0 */ detachFrom(object) { const f = object.attachedChildren.find((o) => o.id === this.id); if (f) { this.isAttached = false; this.attachOffset = Vector2.ZERO; object.attachedChildren.splice(object.attachedChildren.findIndex((o) => o.id === this.id), 1); } else { new debug.Error('Cannot detachFrom from object, PhysicsBody is not attached to anything.'); } } /** * @memberof PhysicsBody * @description Detaches PhysicsBody from self * @param {PhysicsBody<Duck.Types.Texture.Type>} object PhysicsBody to detach * @since 2.0.0 */ detachChild(object) { const f = this.attachedChildren.find((o) => o.id === object.id); if (f) { object.isAttached = false; object.attachOffset = Vector2.ZERO; this.attachedChildren.splice(this.attachedChildren.findIndex((o) => o.id === object.id), 1); } else { new debug.Error('Cannot detachChild from PhysicsBody, object is not attached to anything.'); } } /** * @memberof PhysicsBody * @description Sets the velocity based on an axis, PhysicsBody.options.type must be KinematicBody * @param {'x'|'y'} axis The axis to set the velocity of * @param {number} pxPerSecond The value to set the velocity axis as, in pixels per second * @since 2.0.0 */ setVelocity(axis, pxPerSecond) { if (this.options.type !== 'KinematicBody') { new debug.Error(`Cannot set velocity as PhysicsBody.options.type is ${this.options.type} instead of KinematicBody.`); return; } if (this.isAttached) { new debug.Error('Cannot set velocity as PhysicsBody is attached to another PhysicsBody.'); return; } if (axis === 'x') { this.velocity.x = pxPerSecond; } if (axis === 'y') { this.velocity.y = pxPerSecond; } } /** * @memberof PhysicsBody * @description Sets the velocity.x, PhysicsBody.options.type must be KinematicBody * @param {number} pxPerSecond The value to set the velocity axis as, in pixels per second * @since 2.0.0 */ setVelocityX(pxPerSecond) { if (this.options.type !== 'KinematicBody') { new debug.Error(`Cannot set velocity X as PhysicsBody.options.type is ${this.options.type} instead of KinematicBody.`); return; } if (this.isAttached) { new debug.Error('Cannot set velocity X as PhysicsBody is attached to another PhysicsBody.'); return; } this.velocity.x = pxPerSecond; } /** * @memberof PhysicsBody * @description Sets the velocity.y, PhysicsBody.options.type must be KinematicBody * @param {number} pxPerSecond The value to set the velocity.y as, in pixels per second * @since 2.0.0 */ setVelocityY(pxPerSecond) { if (this.options.type !== 'KinematicBody') { new debug.Error(`Cannot set velocity Y as PhysicsBody.options.type is ${this.options.type} instead of KinematicBody.`); return; } if (this.isAttached) { new debug.Error('Cannot set velocity Y as PhysicsBody is attached to another PhysicsBody.'); return; } this.velocity.y = pxPerSecond; } /** * @memberof PhysicsBody * @description Accelerates the velocity by an amount, PhysicsBody.options.type must be KinematicBody * @param {Vector2} target The target velocity * @param {number} amount The value to increase the velocity by * @since 2.0.0 */ accelerateVelocity(target, amount) { if (this.options.type !== 'KinematicBody') { new debug.Error(`Cannot accelerate velocity as PhysicsBody.options.type is ${this.options.type} instead of KinematicBody.`); return; } if (this.isAttached) { new debug.Error('Cannot accelerate velocity as PhysicsBody is attached to another PhysicsBody.'); return; } this.velocity.moveTowards(this.velocity, target, amount); } /** * @memberof PhysicsBody * @description Applies friction to the velocity by an amount, PhysicsBody.options.type must be KinematicBody or RigidBody * @param {number} frictionAmount The value to decrease the velocity by * @since 2.0.0 */ applyFriction(frictionAmount) { if (this.options.type !== 'KinematicBody' && this.options.type !== 'RigidBody') { new debug.Error(`Cannot apply friction as PhysicsBody.options.type is ${this.options.type} instead of KinematicBody or RigidBody.`); return; } if (this.isAttached) { new debug.Error('Cannot apply friction as PhysicsBody is attached to another PhysicsBody.'); return; } this.velocity.subtract(frictionAmount).clampMin(0); } /** * @memberof PhysicsBody * @description Applies gravity to the velocity by a Vector2, PhysicsBody.options.type must be KinematicBody or RigidBody * @param {Vector2} gravity The Vector2 to add to the velocity by * @since 2.0.0 */ applyGravity(gravity) { if (this.options.type !== 'KinematicBody' && this.options.type !== 'RigidBody') { new debug.Error(`Cannot apply gravity as PhysicsBody.options.type is ${this.options.type} instead of KinematicBody or RigidBody.`); return; } if (this.isAttached) { new debug.Error('Cannot apply gravity as PhysicsBody is attached to another PhysicsBody.'); return; } if (gravity.x !== 0) { this.velocity.x += gravity.x; } if (gravity.y !== 0) { this.velocity.y += gravity.y; } } /** * @memberof PhysicsBody * @description Applies gravity to the velocity by a Vector2, PhysicsBody.options.type must be KinematicBody or RigidBody * @param {Duck.Types.Math.BoundsLike} [bounds=PhysicsBody.bounds] The bounds of the PhysicsBody, optional -> defaults: PhysicsBody.bounds, if none * are set, it is infinite * @param {number} [restitution=1] How much energy is lost when bouncing, a number between 0-1 to loose energy, * 1-any to increase energy, 1 = none, must be a positive number * @since 2.0.0 */ bounceVelocityBounds(bounds = this.bounds, restitution = 1) { if (this.options.type !== 'KinematicBody' && this.options.type !== 'RigidBody') { new debug.Error(`Cannot bounce velocity as PhysicsBody.options.type is ${this.options.type} instead of KinematicBody or RigidBody.`); return; } if (this.isAttached) { new debug.Error('Cannot bounce velocity as PhysicsBody is attached to anot