/** A game object creates an object with a sprite at a certain position. Once the object is added to a SceneGraph it will be drawn on the canvas.
* @class GameObject
* @param {Number} name the name of the object
* @param {Number} sprite the sprite for the object (null if undrawable)
* @param {Number} x the x position of the object on the screen
* @param {Number} y the y position of the object on the screen
* @param {Number} [xOffset=0] the x offset of the top left corner of the sprite relative to the object position
* @param {Number} [yOffset=0] the y offset of the top left corner of the sprite relative to the object position
* @property {String} name the name of the object
* @property {Sprite} sprite the object's sprite
* @property {Vector} pos the current position of the object
* @property {Number} nextX the x position of the object after an update
* @property {Number} nextY the y position of the object after an update
* @property {Number} lastX the last x position of the object (before the last update)
* @property {Number} lastY the last y position of the object (before the last update)
* @property {Boolean} isClicked whether the object is currently being clicked
* @property {Boolean} isDraggable=false whether or not the object can be dragged via the mouse
* @property {Boolean} isClickable=true whether or not the object can be clicked
* @property {Boolean} isCollidable=true whether or not the object collides with anything
* @property {Boolean} isLooping=false whether the object should loop around the edges of the screen
* @property {Boolean} doUpdate=true whether or not the object should be updated
* @property {Vector} direction the velocity of the object
* @property {Number} angle the angle of the object, used for what angle to draw it at
*/
function GameObject(name, sprite, x, y, xOffset=0, yOffset=0) {
var self = this;
/** Creates the game object
* @memberof GameObject
* @function GameObject#constructor
* @param {Number} name the name of the object
* @param {Number} sprite the sprite for the object (null if undrawable)
* @param {Number} x the x position of the object on the screen
* @param {Number} y the y position of the object on the screen
* @param {Number} [xOffset=0] the x offset of the top left corner of the sprite relative to the object position
* @param {Number} [yOffset=0] the y offset of the top left corner of the sprite relative to the object position
*/
self.constructor = function(name, sprite, x, y, xOffset, yOffset) {
self.name = name;
self.sprite = sprite;
self.pos = new Vector(x, y);
self.nextX = x;
self.nextY = y;
self.lastX = x;
self.lastY = y;
self.xOffset = xOffset;
self.yOffset = yOffset;
self.isClicked = false;
self.isDraggable = false;
self.isClickable = true;
self.isCollidable = true;
self.isLooping = false;
self.doUpdate = true;
self.setSquareHitbox([0, 1], [0, 1]);
self.direction = new Vector(0,0);
self.velocity = new Vector(0,0);
self.angle = 0;
}
Object.defineProperties(self, {
/**
* @memberof GameObject
* @instance
* @property {Number} x x position of object (gets current position, but sets nextX)
*/
'x': {
get: function() {
return self.pos.x;
},
set: function(val) {
self.nextX = val;
}
},
/**
* @memberof GameObject
* @instance
* @property {Number} y y position of object (gets current position, but sets nextY)
*/
'y': {
get: function() {
return self.pos.y;
},
set: function(val) {
self.nextY = val;
}
},
/**
* @memberof GameObject
* @instance
* @property {Number} width width of image
* @readonly
*/
'width': { get: function() {if(self.sprite == null){return null} else{return self.sprite.width}}},
/**
* @memberof GameObject
* @instance
* @property {Number} height height of image
* @readonly
*/
'height': { get: function() {if(self.sprite == null){ return null} else{return self.sprite.height}}},
/**
* @memberof GameObject
* @instance
* @property {String} src source of the image
* @readonly
*/
'src': { get: function() {if(self.sprite == null){ return null} else{return self.sprite.src}}}
});
/** Use this function to set a Square Hitbox for the object
* @memberof GameObject
* @function GameObject#setSquareHitbox
* @param {Number[]} [xRange=[0,1]] - The x range of which you want the hitbox to be. The array should only have a length of 2, with each number being in the range [0, 1]
* @param {Number[]} [yRange=[0,1]] - The y range of which you want the hitbox to be. The array should only have a length of 2, with each number being in the range [0, 1]
*/
self.setSquareHitbox = function(xRange=[0,1], yRange=[0,1]) {
self.hitbox = {type: 'square', xRange: xRange, yRange: yRange, center: [(xRange[1]+xRange[0])/2, (yRange[1]+yRange[0])/2], halfWidth: (xRange[1]-xRange[0])/2, halfHeight: (yRange[1]-yRange[0])/2};
}
/** Use this function to set a Circular Hitbox for the object
* @memberof GameObject
* @function GameObject#setCircleHitbox
* @param {Vector} [center=halfway point of sprite] - The center of the circle
* @param {Number} [radius=Math.max(this.width, this.height)/2] - The radius of the circle
*/
self.setCircleHitbox = function(center=new Vector(self.width/2, self.height/2), radius=Math.max(self.width, self.height)/2) {
self.hitbox = {type: 'circle', radius: radius, center: center};
}
/** The mouseDown handler for the object. By default, just sets the isClicked variable to true.
* @memberof GameObject
* @function GameObject#mouseDown
* @param {Game} game - the game object
* @param {MouseEvent} event - the actual mouse event
*/
self.mouseDown = function(game, event) {
self.isClicked = true;
}
/** The mouseUp handler for the object. By default, just sets the isClicked variable to false.
* @memberof GameObject
* @function GameObject#mouseUp
* @param {Game} game - the game object
* @param {MouseEvent} event - the actual mouse event
* @returns {Boolean} True if the mouse is currently not clicking on the object
*/
self.mouseUp = function(game, event) {
self.isClicked = false;
}
/** Override this function to put in your custom pre-updates for this object. Called at start of update.
* @memberof GameObject
* @function GameObject#customUpdate
* @param {Game} game - the game object
*/
self.customUpdate = function(game){ }
/** Override this function to put in your custom pre-draw for this object. Called at start of pre-draw.
* @memberof GameObject
* @function GameObject#customPreDraw
* @param {Game} game - the game object
*/
self.customPreDraw = null;
/** The update function of the object. Do not override this function. Handles looping, velocity, and dragging
* @memberof GameObject
* @function GameObject#update
* @param {Game} game - The game object
*/
self.update = function(game) {
if (self.doUpdate) {
self.customUpdate(game);
if(self.isLooping && game.outOfBounds(self.nextX, self.nextY)){
if(self.nextX > game.canvas.width){self.nextX = 0}
else if(self.nextX < 0){self.nextX = game.canvas.width}
if(self.nextY > game.canvas.height){self.nextY = 0}
else if(self.nextY < 0){self.nextY = game.canvas.height}
}
if (self.direction.x != 0 || self.direction.y != 0) {
self.nextX += self.direction.x;
self.nextY += self.direction.y;
}
if (self.isDraggable && self.isClicked) {
self.nextX = game.mouseX;
self.nextY = game.mouseY;
}
self.postUpdate(game);
}
}
/** Override this function to put in your custom post updates for this object. Called after update.
* @memberof GameObject
* @function GameObject#postUpdate
* @param {Game} game - the game object
*/
self.postUpdate = function(game) {}
/**
* Call this function to update the object's position (moves position from nextX and nextY to current position)
* @memberof GameObject
* @function GameObject#updatePosition
*/
self.updatePosition = function() {
self.lastX = self.x;
self.lastY = self.y;
self.pos.x = self.nextX;
self.pos.y = self.nextY;
}
/** Changes the object angle based on the direction given
* @memberof GameObject
* @function GameObject#calculateAngleFromDirection
* @param {Number} dirX - the x direction
* @param {Number} dirY - the y direction
*/
self.calculateAngleFromDirection = function(dirX, dirY) {
self.angle = Math.atan2(dirY, dirX) / (Math.PI/180);
}
/** This function draws the object. Do not override this function. If you wish to add anything to this function, use customePreDraw.
* @memberof GameObject
* @function GameObject#draw
* @param {RenderingContext} context - the context of the game.
* @returns {Boolean} true if sprite is drawn
*/
self.draw = function(context) {
if (self.customPreDraw)
self.customPreDraw(context);
if (self.sprite) {
self.sprite.draw(context, self.x - self.xOffset, self.y - self.yOffset, self.angle);
return true;
}
return false;
}
/** Returns true if the object collides at a specific point.
* @memberof GameObject
* @function GameObject#pointCollide
* @param {Number} x - x position to check
* @param {Number} y - y position to check
* @returns {Boolean} true if the object collides with the specific position
*/
self.pointCollide = function(x, y) {
var pos = new Vector(x, y);
if (self.hitbox.type == 'square') {
var minX = self.x - self.xOffset + self.hitbox.xRange[0] * self.width;
var maxX = self.x - self.xOffset + self.hitbox.xRange[1] * self.width;
var minY = self.y - self.yOffset + self.hitbox.yRange[0] * self.height;
var maxY = self.y - self.yOffset + self.hitbox.yRange[1] * self.height;
return x >= minX && x <= maxX && y >= minY && y <= maxY;
} else if (self.hitbox.type == 'circle') {
return pos.subtract(self.hitbox.center).magnitude() < self.hitbox.radius;
}
return false;
}
/** Returns true if the object collides with another object
* @memberof GameObject
* @function GameObject#checkForObjectCollide
* @param {GameObject} other - the object to check collisions for.
* @returns {Boolean} true if the objects collide
*/
self.checkForObjectCollide = function(other) {
if (self.hitbox.type == 'square') {
var minX = self.nextX - self.xOffset + self.hitbox.xRange[0] * self.width;
var maxX = self.nextX - self.xOffset + self.hitbox.xRange[1] * self.width;
var minY = self.nextY - self.yOffset + self.hitbox.yRange[0] * self.height;
var maxY = self.nextY - self.yOffset + self.hitbox.yRange[1] * self.height;
if (other.hitbox.type == 'square') {
//TODO account for angle
var otherMinX = other.x - other.xOffset + other.hitbox.xRange[0] * other.width;
var otherMaxX = other.x - other.xOffset + other.hitbox.xRange[1] * other.width;
var otherMinY = other.y - other.yOffset + other.hitbox.yRange[0] * other.height;
var otherMaxY = other.y - other.yOffset + other.hitbox.yRange[1] * other.height;
return minX < otherMaxX && maxX > otherMinX && minY < otherMaxY && maxY > otherMinY
} else if (other.hitbox.type == 'circle') {
//TODO account for angle
var centerX = self.nextX - self.xOffset + self.hitbox.center[0] * self.width;
var centerY = self.nextY - self.yOffset + self.hitbox.center[1] * self.height;
var diffCentX = Math.abs(other.hitbox.center.x + other.x - other.xOffset - centerX);
var diffCentY = Math.abs(other.hitbox.center.y + other.y - other.yOffset - centerY);
if (diffCentX >= self.hitbox.halfWidth + other.hitbox.radius || diffCentY >= self.hitbox.halfHeight + other.hitbox.radius) {
return false;
}
if (diffCentX < self.hitbox.halfWidth || diffCentY < self.hitbox.halfHeight) {
return true;
}
return Math.hypot(diffCentX - self.hitbox.halfWidth, diffCentY - self.hitbox.halfHeight) < other.hitbox.radius;
}
} else if (self.hitbox.type == 'circle') {
if (other.hitbox.type == 'square') {
return other.checkForObjectCollide(self);
} else if (other.hitbox.type == 'circle') {
return other.hitbox.center.add(new Vector(other.x - other.xOffset, other.y-other.yOffset)).subtract(self.hitbox.center.add(new Vector(self.nextX - self.xOffset, self.nextY-self.yOffset))).magnitude() < self.hitbox.radius + other.hitbox.radius
}
}
return false;
}
/** Changes the the sprite sheet to a specific sheet number
* @memberof GameObject
* @function GameObject#changeSpriteSheetNumber
* @param {Number} number - the accessor that the sprite sheet will use
*/
self.changeSpriteSheetNumber = function(number) {
self.sprite.currentSprite = number;
}
/** Calculates velocity based on the angle and speed of the object
* @memberof GameObject
* @function GameObject#calculateVelocity
* @param {Number} speed - the speed of the object
* @param {Number} angle - the angle of the object
* @return {Vector} The new velocity based on the given angle and speed
*/
self.calculateVelocity = function(speed, angle){
return tempVel = new Vector(speed*Math.sin(angle * (Math.PI/180)), -speed*Math.cos(angle * (Math.PI/180)))
}
/** Returns a slowed velocity based on the deceleration amount.
* @memberof GameObject
* @memberof GameObject#slowVelocity
* @param {Number} velocity - the velocity of the object
* @param {Number} decelerationAmt - the amount to slow velocity by
* @returns {Vector} The new velocity based on the old velocity and the deceleration amount.
*/
self.slowVelocity = function(velocity, decelerationAmt){
var currentVelVector = velocity;
var velMagnitudeCurrent = currentVelVector.magnitude();
if(velMagnitudeCurrent != 0){
var velMagnitudeNext = velMagnitudeCurrent - decelerationAmt;
if(velMagnitudeNext < 0 )
velMagnitudeNext = 0;
var velUnitVector = currentVelVector.normalize();
var nextVelVector = velUnitVector.multiply(velMagnitudeNext);
return nextVelVector;
}
return velocity;
}
/** Adds an angle amount to the current angle of the object
* @memberof GameObject
* @function GameObject#changeAngle
* @param {Number} angle - the amount of degrees to add to the current angle
*/
self.changeAngle = function(angle){
self.angle += angle;
}
/**
* Override this function to determine what this object can and can't collide with.
* @memberof GameObject
* @function GameObject#canCollideWith
* @param {GameObject} other - the object that collided
* @return {Boolean} if the object can collide with other objects
*/
self.canCollideWith = function(other) {
return false;
}
/** Overide this function with the event you want to take place when the object collides with another
* @memberof GameObject
* @function GameObject#collideWith
* @param {GameObject} other - the object that collided
* @returns {Boolean} False if there is no event to take place when a collision has happened.
*/
self.collideWith = function(other) {
return false;
}
/** Tries to collide with another object. Fails if other object isn't collidable, if hitboxes aren't colliding, or if the objects don't have an applicable collision interaction.
* @memberof GameObject
* @function GameObject#tryCollide
* @param {GameObject} other - the object that collided
* @returns {Boolean} returns true if the object successfully collides with another object
*/
self.tryCollide = function(other) {
if (self !== other && other.isCollidable && self.checkForObjectCollide(other) && self.canCollideWith(other)) {
return self.collideWith(other);
}
return false;
}
/** Creates the GameObject */
self.constructor(name, sprite, x, y, xOffset, yOffset);
};