/* * GDevelop JS Platform * Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved. * This project is released under the MIT License. */ /** * A frame used by a SpriteAnimation in a {@link gdjs.SpriteRuntimeObject}. * * It contains the texture displayed as well as information like the points position * or the collision mask. * * @memberof gdjs * @class SpriteAnimationFrame * @param {gdjs.ImageManager} imageManager The game image manager * @param {Object} frameData the data to be used to create the frame. */ gdjs.SpriteAnimationFrame = function(imageManager, frameData) { this.image = frameData ? frameData.image : ""; //TODO: Rename in imageName, and do not store it in the object? this.texture = gdjs.SpriteRuntimeObjectRenderer.getAnimationFrame(imageManager, this.image); if ( this.center === undefined ) this.center = { x:0, y:0 }; if ( this.origin === undefined ) this.origin = { x:0, y:0 }; this.hasCustomHitBoxes = false; if ( this.customHitBoxes === undefined ) this.customHitBoxes = []; if ( this.points === undefined ) this.points = new Hashtable(); else this.points.clear(); //Initialize points: for(var i = 0, len = frameData.points.length;i= this.customHitBoxes.length ) this.customHitBoxes.push(new gdjs.Polygon()); for(var j = 0, len2 = polygonData.length;j= this.customHitBoxes[i].vertices.length ) this.customHitBoxes[i].vertices.push([0,0]); this.customHitBoxes[i].vertices[j][0] = parseFloat(pointData.x, 10); this.customHitBoxes[i].vertices[j][1] = parseFloat(pointData.y, 10); } this.customHitBoxes[i].vertices.length = j; } this.customHitBoxes.length = i; } else { this.customHitBoxes.length = 0; } }; /** * Get a point of the frame.
* If the point does not exist, the origin is returned. * * @return The requested point. */ gdjs.SpriteAnimationFrame.prototype.getPoint = function(name) { if ( name == "Centre" ) return this.center; else if ( name == "Origin" ) return this.origin; return this.points.containsKey(name) ? this.points.get(name) : this.origin; }; /** * Represents a direction of an animation of a {@link gdjs.SpriteRuntimeObject}. * * @class SpriteAnimationDirection * @memberof gdjs * @param {gdjs.ImageManager} imageManager The game image manager * @param {Object} directionData the data to be used to create the direction. */ gdjs.SpriteAnimationDirection = function(imageManager, directionData) { this.timeBetweenFrames = directionData ? parseFloat(directionData.timeBetweenFrames) : 1.0; this.loop = !!directionData.looping; if ( this.frames === undefined ) this.frames = []; for(var i = 0, len = directionData.sprites.length;i= this._animations.length || this._currentDirection >= this._animations[this._currentAnimation].directions.length) { return; } var direction = this._animations[this._currentAnimation].directions[this._currentDirection]; var oldFrame = this._currentFrame; if (!direction.loop && this._currentFrame >= direction.frames.length) { //*Optimization*: Animation is finished, don't change the current frame //and compute nothing more. } else { var elapsedTime = this.getElapsedTime(runtimeScene) / 1000; this._frameElapsedTime += this._animationPaused ? 0 : elapsedTime * this._animationSpeedScale; if ( this._frameElapsedTime > direction.timeBetweenFrames ) { var count = Math.floor(this._frameElapsedTime / direction.timeBetweenFrames); this._currentFrame += count; this._frameElapsedTime = this._frameElapsedTime-count*direction.timeBetweenFrames; if ( this._frameElapsedTime < 0 ) this._frameElapsedTime = 0; } if ( this._currentFrame >= direction.frames.length ) { this._currentFrame = direction.loop ? this._currentFrame % direction.frames.length : direction.frames.length-1; } if ( this._currentFrame < 0 ) this._currentFrame = 0; //May happen if there is no frame. } if ( oldFrame !== this._currentFrame || this._frameDirty ) this._updateFrame(); if ( oldFrame !== this._currentFrame ) this.hitBoxesDirty = true; this._renderer.ensureUpToDate(); }; gdjs.SpriteRuntimeObject.prototype._updateFrame = function() { this._frameDirty = false; if ( this._currentAnimation < this._animations.length && this._currentDirection < this._animations[this._currentAnimation].directions.length) { var direction = this._animations[this._currentAnimation].directions[this._currentDirection]; if ( this._currentFrame < direction.frames.length ) { this._animationFrame = direction.frames[this._currentFrame]; if ( this._animationFrame !== null ) { this._renderer.updateFrame(this._animationFrame); } return; } } //Invalid animation/direction/frame: this._animationFrame = null; }; gdjs.SpriteRuntimeObject.prototype.getRendererObject = function() { return this._renderer.getRendererObject(); }; /** * Update the hit boxes for the object. * Fallback to the default implementation (rotated bounding box) if there is no custom * hitboxes defined for the current animation frame. */ gdjs.SpriteRuntimeObject.prototype.updateHitBoxes = function() { if ( this._frameDirty ) this._updateFrame(); //Beware, _animationFrame could be invalid if this._frameDirty === true. if ( this._animationFrame === null ) return; if ( !this._animationFrame.hasCustomHitBoxes ) return gdjs.RuntimeObject.prototype.updateHitBoxes.call(this); //console.log("Update for "+this.name); //Uncomment to track updates //(and in particular be sure that unnecessary update are avoided). //Update the current hitboxes with the frame custom hit boxes //and apply transformations. for (var i = 0;i= this.hitBoxes.length ) this.hitBoxes.push(new gdjs.Polygon()); for (var j = 0;j= this.hitBoxes[i].vertices.length ) this.hitBoxes[i].vertices.push([0,0]); this._transformToGlobal(this._animationFrame.customHitBoxes[i].vertices[j][0], this._animationFrame.customHitBoxes[i].vertices[j][1], this.hitBoxes[i].vertices[j]); } this.hitBoxes[i].vertices.length = this._animationFrame.customHitBoxes[i].vertices.length; } this.hitBoxes.length = this._animationFrame.customHitBoxes.length; //Rotate and scale and flipping have already been applied to the point by _transformToGlobal. }; //Animations : /** * Change the animation being played. * @param {number} newAnimation The index of the new animation to be played */ gdjs.SpriteRuntimeObject.prototype.setAnimation = function(newAnimation) { newAnimation = newAnimation | 0; if ( newAnimation < this._animations.length && this._currentAnimation !== newAnimation && newAnimation >= 0) { this._currentAnimation = newAnimation; this._currentFrame = 0; this._frameElapsedTime = 0; this._renderer.update(); //TODO: This may be unnecessary. this._frameDirty = true; this.hitBoxesDirty = true; } }; /** * Change the animation being played. * @param {string} newAnimationName The name of the new animation to be played */ gdjs.SpriteRuntimeObject.prototype.setAnimationName = function(newAnimationName) { if (!newAnimationName) return; for(var i = 0;i < this._animations.length;++i) { if (this._animations[i].name === newAnimationName) { return this.setAnimation(i); } } }; /** * Get the index of the animation being played. * @return {number} The index of the new animation being played */ gdjs.SpriteRuntimeObject.prototype.getAnimation = function() { return this._currentAnimation; }; /** * Get the name of the animation being played. * @return {string} The name of the new animation being played */ gdjs.SpriteRuntimeObject.prototype.getAnimationName = function() { if ( this._currentAnimation >= this._animations.length ) { return ''; } return this._animations[this._currentAnimation].name; }; gdjs.SpriteRuntimeObject.prototype.isCurrentAnimationName = function(name) { return this.getAnimationName() === name; }; /** * Change the angle (or direction index) of the object * @return {number} The new angle (or direction index) to be applied */ gdjs.SpriteRuntimeObject.prototype.setDirectionOrAngle = function(newValue) { if ( this._currentAnimation >= this._animations.length ) { return; } var anim = this._animations[this._currentAnimation]; if ( !anim.hasMultipleDirections ) { if ( this.angle === newValue ) return; this.angle = newValue; this.hitBoxesDirty = true; this._renderer.updateAngle(); } else { newValue = newValue | 0; if (newValue === this._currentDirection || newValue >= anim.directions.length || anim.directions[newValue].frames.length === 0) return; this._currentDirection = newValue; this._currentFrame = 0; this._frameElapsedTime = 0; this.angle = 0; this._renderer.update(); //TODO: This may be unnecessary. this._frameDirty = true; this.hitBoxesDirty = true; } }; gdjs.SpriteRuntimeObject.prototype.getDirectionOrAngle = function() { if ( this._currentAnimation >= this._animations.length ) { return 0; } if ( !this._animations[this._currentAnimation].hasMultipleDirections ) { return this.angle; } else { return this._currentDirection; } }; /** * Change the current frame displayed by the animation * @param {number} newFrame The index of the frame to be displayed */ gdjs.SpriteRuntimeObject.prototype.setAnimationFrame = function(newFrame) { if ( this._currentAnimation >= this._animations.length || this._currentDirection >= this._animations[this._currentAnimation].directions.length) { return; } var direction = this._animations[this._currentAnimation].directions[this._currentDirection]; if ( newFrame >= 0 && newFrame < direction.frames.length && newFrame !== this._currentFrame ) { this._currentFrame = newFrame; this._frameDirty = true; this.hitBoxesDirty = true; } }; /** * Get the index of the current frame displayed by the animation * @return {number} newFrame The index of the frame being displayed */ gdjs.SpriteRuntimeObject.prototype.getAnimationFrame = function() { return this._currentFrame; }; /** * Return true if animation has ended. */ gdjs.SpriteRuntimeObject.prototype.hasAnimationEnded = function() { if ( this._currentAnimation >= this._animations.length || this._currentDirection >= this._animations[this._currentAnimation].directions.length) { return true; } if ( this._animations[this._currentAnimation].loop ) return false; var direction = this._animations[this._currentAnimation].directions[this._currentDirection]; return this._currentFrame == direction.frames.length-1; }; gdjs.SpriteRuntimeObject.prototype.animationPaused = function() { return this._animationPaused; }; gdjs.SpriteRuntimeObject.prototype.pauseAnimation = function() { this._animationPaused = true; }; gdjs.SpriteRuntimeObject.prototype.playAnimation = function() { this._animationPaused = false; }; gdjs.SpriteRuntimeObject.prototype.getAnimationSpeedScale = function() { return this._animationSpeedScale; }; gdjs.SpriteRuntimeObject.prototype.setAnimationSpeedScale = function(ratio) { this._animationSpeedScale = ratio }; //Position : /** * Get the position on X axis on the scene of the given point. * @param {string} name The point name * @return {number} the position on X axis on the scene of the given point. */ gdjs.SpriteRuntimeObject.prototype.getPointX = function(name) { if ( name.length === 0 || this._animationFrame === null ) return this.getX(); var pt = this._animationFrame.getPoint(name); var pos = gdjs.staticArray(gdjs.SpriteRuntimeObject.prototype.getPointX); this._transformToGlobal(pt.x, pt.y, pos); return pos[0]; }; /** * Get the position on Y axis on the scene of the given point. * @param {string} name The point name * @return {number} the position on Y axis on the scene of the given point. */ gdjs.SpriteRuntimeObject.prototype.getPointY = function(name) { if ( name.length === 0 || this._animationFrame === null ) return this.getY(); var pt = this._animationFrame.getPoint(name); var pos = gdjs.staticArray(gdjs.SpriteRuntimeObject.prototype.getPointY); this._transformToGlobal(pt.x, pt.y, pos); return pos[1]; }; /** * Return an array containing the coordinates of the point passed as parameter * in world coordinates (as opposed to the object local coordinates). * * Beware: this._animationFrame and this._sprite must *not* be null! * * All transformations (flipping, scale, rotation) are supported. * * @param {number} pointX The X position of the point, in object coordinates. * @param {number} pointY The Y position of the point, in object coordinates. * @param {Array} result Array that will be updated with the result (x and y position of the point * in global coordinates) * @private */ gdjs.SpriteRuntimeObject.prototype._transformToGlobal = function(x, y, result) { var cx = this._animationFrame.center.x; var cy = this._animationFrame.center.y; //Flipping if ( this._flippedX ) { x = this._renderer.getUnscaledWidth() - x; cx = this._renderer.getUnscaledWidth() - cx; } if ( this._flippedY ) { y = this._renderer.getUnscaledHeight() - y; cy = this._renderer.getUnscaledHeight() - cy; } //Scale x *= Math.abs(this._scaleX); y *= Math.abs(this._scaleY); cx *= Math.abs(this._scaleX); cy *= Math.abs(this._scaleY); //Rotation var oldX = x; x = cx + Math.cos(this.angle/180*3.14159)*(x-cx) - Math.sin(this.angle/180*3.14159)*(y-cy); y = cy + Math.sin(this.angle/180*3.14159)*(oldX-cx) + Math.cos(this.angle/180*3.14159)*(y-cy); result.length = 2; result[0] = x + this.getDrawableX(); result[1] = y + this.getDrawableY(); }; /** * Get the X position, on the scene, of the origin of the texture of the object. * @return {number} the X position, on the scene, of the origin of the texture of the object. */ gdjs.SpriteRuntimeObject.prototype.getDrawableX = function() { if ( this._animationFrame === null ) return this.x; return this.x - this._animationFrame.origin.x*Math.abs(this._scaleX); }; /** * Get the Y position, on the scene, of the origin of the texture of the object. * @return {number} the Y position, on the scene, of the origin of the texture of the object. */ gdjs.SpriteRuntimeObject.prototype.getDrawableY = function() { if ( this._animationFrame === null ) return this.y; return this.y - this._animationFrame.origin.y*Math.abs(this._scaleY); }; /** * Get the X position of the center of the object, relative to top-left of the object. * @return {number} X position of the center of the object, relative to top-left of the object. */ gdjs.SpriteRuntimeObject.prototype.getCenterX = function() { if ( this._animationFrame === null ) return 0; //Just need to multiply by the scale as it is the center return this._animationFrame.center.x*Math.abs(this._scaleX); }; /** * Get the Y position of the center of the object, relative to top-left of the object. * @return {number} Y position of the center of the object, relative to top-left of the object. */ gdjs.SpriteRuntimeObject.prototype.getCenterY = function() { if ( this._animationFrame === null ) return 0; //Just need to multiply by the scale as it is the center return this._animationFrame.center.y*Math.abs(this._scaleY); }; /** * Set the X position of the (origin of the) object. * @param {number} x The new X position. */ gdjs.SpriteRuntimeObject.prototype.setX = function(x) { if ( x === this.x ) return; this.x = x; if (this._animationFrame !== null) { this.hitBoxesDirty = true; this._renderer.updateX(); } }; /** * Set the Y position of the (origin of the) object. * @param {number} y The new Y position. */ gdjs.SpriteRuntimeObject.prototype.setY = function(y) { if ( y === this.y ) return; this.y = y; if ( this._animationFrame !== null) { this.hitBoxesDirty = true; this._renderer.updateY(); } }; /** * Set the angle of the object. * @param {number} angle The new angle, in degrees. */ gdjs.SpriteRuntimeObject.prototype.setAngle = function(angle) { if ( this._currentAnimation >= this._animations.length ) { return; } if ( !this._animations[this._currentAnimation].hasMultipleDirections ) { if (this.angle === angle) return; this.angle = angle; this._renderer.updateAngle(); this.hitBoxesDirty = true; } else { angle = angle % 360; if ( angle < 0 ) angle += 360; this.setDirectionOrAngle(Math.round(angle/45) % 8); } }; /** * Get the angle of the object. * @return {number} The angle, in degrees. */ gdjs.SpriteRuntimeObject.prototype.getAngle = function(angle) { if ( this._currentAnimation >= this._animations.length ) { return 0; } if ( !this._animations[this._currentAnimation].hasMultipleDirections ) return this.angle; else return this._currentDirection * 45; }; //Visibility and display : gdjs.SpriteRuntimeObject.prototype.setBlendMode = function(newMode) { if (this._blendMode === newMode) return; this._blendMode = newMode; this._renderer.update(); }; gdjs.SpriteRuntimeObject.prototype.getBlendMode = function() { return this._blendMode; }; /** * Change the transparency of the object. * @param {number} opacity The new opacity, between 0 (transparent) and 255 (opaque). */ gdjs.SpriteRuntimeObject.prototype.setOpacity = function(opacity) { if ( opacity < 0 ) opacity = 0; if ( opacity > 255 ) opacity = 255; this.opacity = opacity; this._renderer.updateOpacity(); }; /** * Get the transparency of the object. * @return {number} The opacity, between 0 (transparent) and 255 (opaque). */ gdjs.SpriteRuntimeObject.prototype.getOpacity = function() { return this.opacity; }; /** * Hide (or show) the object * @param {boolean} enable true to hide the object, false to show it again. */ gdjs.SpriteRuntimeObject.prototype.hide = function(enable) { if ( enable === undefined ) enable = true; this.hidden = enable; this._renderer.updateVisibility(); }; /** * Change the tint of the sprite object. * * @param {string} rgbColor The color, in RGB format ("128;200;255"). */ gdjs.SpriteRuntimeObject.prototype.setColor = function(rgbColor) { this._renderer.setColor(rgbColor); }; gdjs.SpriteRuntimeObject.prototype.flipX = function(enable) { if ( enable !== this._flippedX ) { this._scaleX *= -1; this._flippedX = enable; this._renderer.update(); } }; gdjs.SpriteRuntimeObject.prototype.flipY = function(enable) { if ( enable !== this._flippedY ) { this._scaleY *= -1; this._flippedY = enable; this._renderer.update(); } }; gdjs.SpriteRuntimeObject.prototype.isFlippedX = function() { return this._flippedX; }; gdjs.SpriteRuntimeObject.prototype.isFlippedY = function() { return this._flippedY; }; //Scale and size : /** * Get the width of the object. * * @return {number} The width of the object, in pixels. */ gdjs.SpriteRuntimeObject.prototype.getWidth = function() { return this._renderer.getWidth(); }; /** * Get the height of the object. * * @return {number} The height of the object, in pixels. */ gdjs.SpriteRuntimeObject.prototype.getHeight = function() { return this._renderer.getHeight(); }; /** * Change the width of the object. This changes the scale on X axis of the object. * * @param {number} width The new width of the object, in pixels. */ gdjs.SpriteRuntimeObject.prototype.setWidth = function(newWidth) { if ( this._frameDirty ) this._updateFrame(); var unscaledWidth = this._renderer.getUnscaledWidth(); if (unscaledWidth !== 0) this.setScaleX(newWidth / unscaledWidth); }; /** * Change the height of the object. This changes the scale on Y axis of the object. * * @param {number} height The new height of the object, in pixels. */ gdjs.SpriteRuntimeObject.prototype.setHeight = function(newHeight) { if ( this._frameDirty ) this._updateFrame(); var unscaledHeight = this._renderer.getUnscaledHeight(); if (unscaledHeight !== 0) this.setScaleY(newHeight / unscaledHeight); }; /** * Change the scale on X and Y axis of the object. * * @param {number} newScale The new scale (must be greater than 0). */ gdjs.SpriteRuntimeObject.prototype.setScale = function(newScale) { if ( newScale < 0 ) newScale = 0; if ( newScale === Math.abs(this._scaleX) && newScale === Math.abs(this._scaleY) ) return; this._scaleX = newScale * (this._flippedX ? -1 : 1); this._scaleY = newScale * (this._flippedY ? -1 : 1); this._renderer.update(); this.hitBoxesDirty = true; }; /** * Change the scale on X axis of the object (changing its width). * * @param {number} newScale The new scale (must be greater than 0). */ gdjs.SpriteRuntimeObject.prototype.setScaleX = function(newScale) { if ( newScale < 0 ) newScale = 0; if ( newScale === Math.abs(this._scaleX) ) return; this._scaleX = newScale * (this._flippedX ? -1 : 1); this._renderer.update(); this.hitBoxesDirty = true; }; /** * Change the scale on Y axis of the object (changing its width). * * @param {number} newScale The new scale (must be greater than 0). */ gdjs.SpriteRuntimeObject.prototype.setScaleY = function(newScale) { if ( newScale < 0 ) newScale = 0; if ( newScale === Math.abs(this._scaleY) ) return; this._scaleY = newScale * (this._flippedY ? -1 : 1); this._renderer.update(); this.hitBoxesDirty = true; }; /** * Get the scale of the object (or the average of the X and Y scale in case they are different). * * @return {number} the scale of the object (or the average of the X and Y scale in case they are different). */ gdjs.SpriteRuntimeObject.prototype.getScale = function() { return (Math.abs(this._scaleX)+Math.abs(this._scaleY))/2.0; }; /** * Get the scale of the object on Y axis. * * @return {number} the scale of the object on Y axis */ gdjs.SpriteRuntimeObject.prototype.getScaleY = function() { return Math.abs(this._scaleY); }; /** * Get the scale of the object on X axis. * * @return {number} the scale of the object on X axis */ gdjs.SpriteRuntimeObject.prototype.getScaleX = function() { return Math.abs(this._scaleX); }; //Other : /** * @param obj The target object * @param scene The scene containing the object * @deprecated */ gdjs.SpriteRuntimeObject.prototype.turnTowardObject = function(obj, scene) { if ( obj === null ) return; this.rotateTowardPosition(obj.getDrawableX()+obj.getCenterX(), obj.getDrawableY()+obj.getCenterY(), 0, scene); };