From d7fef0c9bccec6ec9670d6dfb8667ff0ecfaf698 Mon Sep 17 00:00:00 2001 From: straker Date: Mon, 18 Feb 2019 00:18:40 -0700 Subject: [PATCH 01/10] initial plugin spec --- dist/core.js | 2 +- dist/gameLoop.js | 2 +- dist/pointer.js | 2 +- dist/sprite.js | 2 +- docs/js/kontra.js | 2 +- examples/plugins/advancedCollision.html | 231 ++++++++++++++++++++++++ examples/plugins/advancedVector.html | 65 +++++++ kontra.js | 116 ++++++------ kontra.min.js | 2 +- src/core.js | 105 ++++++----- src/gameLoop.js | 2 +- src/pointer.js | 8 +- src/sprite.js | 15 +- 13 files changed, 431 insertions(+), 123 deletions(-) create mode 100644 examples/plugins/advancedCollision.html create mode 100644 examples/plugins/advancedVector.html diff --git a/dist/core.js b/dist/core.js index e45a4be5..45a26784 100644 --- a/dist/core.js +++ b/dist/core.js @@ -1 +1 @@ -kontra={init(t){var n=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=n.getContext("2d"),this.context.imageSmoothingEnabled=!1,this._init()},_noop:new Function,_tick:new Function,_init:new Function}; \ No newline at end of file +!function(){let t={};window.kontra={init(t){let n=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=n.getContext("2d"),this.context.imageSmoothingEnabled=!1,this.emit("init",this)},on(n,e){t[n]=t[n]||[],t[n].push(e)},emit(n,...e){t[n]&&t[n].forEach(t=>t(...e))},_noop:new Function}}(); \ No newline at end of file diff --git a/dist/gameLoop.js b/dist/gameLoop.js index 10aaeff2..05c3f3ab 100644 --- a/dist/gameLoop.js +++ b/dist/gameLoop.js @@ -1 +1 @@ -kontra.gameLoop=function(e){let t,n,a,r,o=(e=e||{}).fps||60,i=0,p=1e3/o,c=1/o,s=!1===e.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(n=requestAnimationFrame(d),a=performance.now(),r=a-t,t=a,!(r>1e3)){for(kontra._tick(),i+=r;i>=p;)m.update(c),i-=p;s(),m.render()}}let m={update:e.update,render:e.render,isStopped:!0,start(){t=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(n)}};return m}; \ No newline at end of file +kontra.gameLoop=function(e){let t,n,a,r,o=(e=e||{}).fps||60,i=0,p=1e3/o,c=1/o,s=!1===e.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(n=requestAnimationFrame(d),a=performance.now(),r=a-t,t=a,!(r>1e3)){for(kontra.emit("tick"),i+=r;i>=p;)m.update(c),i-=p;s(),m.render()}}let m={update:e.update,render:e.render,isStopped:!0,start(){t=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(n)}};return m}; \ No newline at end of file diff --git a/dist/pointer.js b/dist/pointer.js index 3d9c7e28..c0802d82 100644 --- a/dist/pointer.js +++ b/dist/pointer.js @@ -1 +1 @@ -!function(){let t,n=[],e=[],o={},a=[],i={},r={0:"left",1:"middle",2:"right"};function c(n){let e=n.x,o=n.y;n.anchor&&(e-=n.width*n.anchor.x,o-=n.height*n.anchor.y);let a=t.x-Math.max(e,Math.min(t.x,e+n.width)),i=t.y-Math.max(o,Math.min(t.y,o+n.height));return a*a+i*i=0;n--)if(a=(o=i[n]).collidesWithPointer?o.collidesWithPointer(t):c(o))return o}function s(t){let n=void 0!==t.button?r[t.button]:"left";i[n]=!0,f(t,"onDown")}function h(t){let n=void 0!==t.button?r[t.button]:"left";i[n]=!1,f(t,"onUp")}function d(t){f(t,"onOver")}function l(t){i={}}function f(n,e){if(!kontra.canvas)return;let a,i;-1!==["touchstart","touchmove","touchend"].indexOf(n.type)?(a=(n.touches[0]||n.changedTouches[0]).clientX,i=(n.touches[0]||n.changedTouches[0]).clientY):(a=n.clientX,i=n.clientY);let r,c=kontra.canvas.height/kontra.canvas.offsetHeight,s=kontra.canvas.getBoundingClientRect(),h=(a-s.left)*c,d=(i-s.top)*c;t.x=h,t.y=d,n.target===kontra.canvas&&(n.preventDefault(),(r=u())&&r[e]&&r[e](n)),o[e]&&o[e](n,r)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){n.push(this),this._r()},a.push(t))})},untrack(t,n){[].concat(t).map(function(t){t.render=t._r,t._r=n;let e=a.indexOf(t);-1!==e&&a.splice(e,1)})},over:t=>-1!==a.indexOf(t)&&u()===t,onDown(t){o.onDown=t},onUp(t){o.onUp=t},pressed:t=>!!i[t]},kontra._tick=function(){e.length=0,n.map(function(t){e.push(t)}),n.length=0},kontra._init=function(){kontra.canvas.addEventListener("mousedown",s),kontra.canvas.addEventListener("touchstart",s),kontra.canvas.addEventListener("mouseup",h),kontra.canvas.addEventListener("touchend",h),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",d),kontra.canvas.addEventListener("touchmove",d)}}(); \ No newline at end of file +!function(){let t,n=[],e=[],o={},a=[],r={},i={0:"left",1:"middle",2:"right"};function c(n){let e=n.x,o=n.y;n.anchor&&(e-=n.width*n.anchor.x,o-=n.height*n.anchor.y);let a=t.x-Math.max(e,Math.min(t.x,e+n.width)),r=t.y-Math.max(o,Math.min(t.y,o+n.height));return a*a+r*r=0;n--)if(a=(o=r[n]).collidesWithPointer?o.collidesWithPointer(t):c(o))return o}function s(t){let n=void 0!==t.button?i[t.button]:"left";r[n]=!0,v(t,"onDown")}function h(t){let n=void 0!==t.button?i[t.button]:"left";r[n]=!1,v(t,"onUp")}function d(t){v(t,"onOver")}function l(t){r={}}function v(n,e){if(!kontra.canvas)return;let a,r;-1!==["touchstart","touchmove","touchend"].indexOf(n.type)?(a=(n.touches[0]||n.changedTouches[0]).clientX,r=(n.touches[0]||n.changedTouches[0]).clientY):(a=n.clientX,r=n.clientY);let i,c=kontra.canvas.height/kontra.canvas.offsetHeight,s=kontra.canvas.getBoundingClientRect(),h=(a-s.left)*c,d=(r-s.top)*c;t.x=h,t.y=d,n.target===kontra.canvas&&(n.preventDefault(),(i=u())&&i[e]&&i[e](n)),o[e]&&o[e](n,i)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){n.push(this),this._r()},a.push(t))})},untrack(t,n){[].concat(t).map(function(t){t.render=t._r,t._r=n;let e=a.indexOf(t);-1!==e&&a.splice(e,1)})},over:t=>-1!==a.indexOf(t)&&u()===t,onDown(t){o.onDown=t},onUp(t){o.onUp=t},pressed:t=>!!r[t]},kontra.on("tick",()=>{e.length=0,n.map(function(t){e.push(t)}),n.length=0}),kontra.on("init",()=>{kontra.canvas.addEventListener("mousedown",s),kontra.canvas.addEventListener("touchstart",s),kontra.canvas.addEventListener("mouseup",h),kontra.canvas.addEventListener("touchend",h),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",d),kontra.canvas.addEventListener("touchmove",d)})}(); \ No newline at end of file diff --git a/dist/sprite.js b/dist/sprite.js index f6c39d3a..270f4149 100644 --- a/dist/sprite.js +++ b/dist/sprite.js @@ -1 +1 @@ -!function(){class t{constructor(t,i){this._x=t||0,this._y=i||0}add(t,i){this.x+=(t.x||0)*(i||1),this.y+=(t.y||0)*(i||1)}clamp(t,i,h,s){this._c=!0,this._a=t,this._b=i,this._d=h,this._e=s}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((i,h)=>new t(i,h)),kontra.vector.prototype=t.prototype;class i{init(t,i,h,s){for(i in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[i]=t[i];if(h=t.image)this.image=h,this.width=h.width,this.height=h.height;else if(h=t.animations){for(i in h)this.animations[i]=h[i].clone(),s=s||h[i];this._ca=s,this.width=s.width,this.height=s.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let i=this.x-this.width*this.anchor.x,h=this.y-this.height*this.anchor.y,s=t.x,e=t.y;return t.anchor&&(s-=t.width*t.anchor.x,e-=t.height*t.anchor.y),is&&he}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity.add(this.acceleration,t),this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,i=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,t,i):this._ca?this._ca.render({x:t,y:i,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,i,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new i).init(t)),kontra.sprite.prototype=i.prototype}(); \ No newline at end of file +!function(){class t{constructor(t,i){this._x=t||0,this._y=i||0,kontra.emit("vector.init",this)}add(t,i){return kontra.vector(this.x+(t.x||0)*(i||1),this.y+(t.y||0)*(i||1))}clamp(t,i,h,s){this._c=!0,this._a=t,this._b=i,this._d=h,this._e=s}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((i,h)=>new t(i,h)),kontra.vector.prototype=t.prototype;class i{init(t,i,h,s){for(i in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[i]=t[i];if(h=t.image)this.image=h,this.width=void 0!==t.width?t.width:h.width,this.height=void 0!==t.height?t.height:h.height;else if(h=t.animations){for(i in h)this.animations[i]=h[i].clone(),s=s||h[i];this._ca=s,this.width=this.width||s.width,this.height=this.height||s.height}return kontra.emit("sprite.init",this),this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let i=this.x-this.width*this.anchor.x,h=this.y-this.height*this.anchor.y,s=t.x,e=t.y;return t.anchor&&(s-=t.width*t.anchor.x,e-=t.height*t.anchor.y),is&&he}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity=this.velocity.add(this.acceleration,t),this.position=this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,i=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,i,this.width,this.height):this._ca?this._ca.render({x:t,y:i,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,i,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new i).init(t)),kontra.sprite.prototype=i.prototype}(); \ No newline at end of file diff --git a/docs/js/kontra.js b/docs/js/kontra.js index 04cc5cc2..c4423fdc 100644 --- a/docs/js/kontra.js +++ b/docs/js/kontra.js @@ -1 +1 @@ -kontra={init(t){var e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this._init()},_noop:new Function,_tick:new Function,_init:new Function},function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function u(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function l(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function g(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?u(s):n.match(i)?l(s):g(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra._tick(),a+=s;a>=o;)u.update(r),a-=o;c(),u.render()}}let u={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return u},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,g(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,g(t,"onUp")}function u(t){g(t,"onOver")}function l(t){h={}}function g(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,u=(h-c.top)*o;t.x=d,t.y=u,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra._tick=function(){i.length=0,e.map(function(t){i.push(t)}),e.length=0},kontra._init=function(){kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",u),kontra.canvas.addEventListener("touchmove",u)}}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0}add(t,e){this.x+=(t.x||0)*(e||1),this.y+=(t.y||0)*(e||1)}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity.add(this.acceleration,t),this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,u=i%o.width*s,l=(i/o.width|0)*h,g=c%d*(s+a),f=(c/d|0)*(h+a);e.drawImage(r,g,f,s,h,u,l,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file +!function(){let t={};window.kontra={init(t){let e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this.emit("init",this)},on(e,i){t[e]=t[e]||[],t[e].push(i)},emit(e,...i){t[e]&&t[e].forEach(t=>t(...i))},_noop:new Function}}(),function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function l(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function u(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function g(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?l(s):n.match(i)?u(s):g(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra.emit("tick"),a+=s;a>=o;)l.update(r),a-=o;c(),l.render()}}let l={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return l},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,g(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,g(t,"onUp")}function l(t){g(t,"onOver")}function u(t){h={}}function g(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,l=(h-c.top)*o;t.x=d,t.y=l,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra.on("tick",()=>{i.length=0,e.map(function(t){i.push(t)}),e.length=0}),kontra.on("init",()=>{kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",u),kontra.canvas.addEventListener("mousemove",l),kontra.canvas.addEventListener("touchmove",l)})}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0,kontra.emit("vector.init",this)}add(t,e){return kontra.vector(this.x+(t.x||0)*(e||1),this.y+(t.y||0)*(e||1))}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return kontra.emit("sprite.init",this),this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity=this.velocity.add(this.acceleration,t),this.position=this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,l=i%o.width*s,u=(i/o.width|0)*h,g=c%d*(s+a),f=(c/d|0)*(h+a);e.drawImage(r,g,f,s,h,l,u,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file diff --git a/examples/plugins/advancedCollision.html b/examples/plugins/advancedCollision.html new file mode 100644 index 00000000..225e9a47 --- /dev/null +++ b/examples/plugins/advancedCollision.html @@ -0,0 +1,231 @@ + + + + Advanced Vector Plugin + + + + + + + + \ No newline at end of file diff --git a/examples/plugins/advancedVector.html b/examples/plugins/advancedVector.html new file mode 100644 index 00000000..68ec355d --- /dev/null +++ b/examples/plugins/advancedVector.html @@ -0,0 +1,65 @@ + + + + Advanced Vector Plugin + + + + + + + + \ No newline at end of file diff --git a/kontra.js b/kontra.js index dc50bf74..1f698e1a 100644 --- a/kontra.js +++ b/kontra.js @@ -1,56 +1,55 @@ -kontra = { +(function() { + let callbacks = {}; - /** - * Initialize the canvas. - * @memberof kontra - * - * @param {string|HTMLCanvasElement} canvas - Main canvas ID or Element for the game. - */ - init(canvas) { + window.kontra = { - // check if canvas is a string first, an element next, or default to getting - // first canvas on page - var canvasEl = this.canvas = document.getElementById(canvas) || - canvas || - document.querySelector('canvas'); + /** + * Initialize the canvas. + * @memberof kontra + * + * @param {string|HTMLCanvasElement} canvas - Main canvas ID or Element for the game. + */ + init(canvas) { - // @if DEBUG - if (!canvasEl) { - throw Error('You must provide a canvas element for the game'); - } - // @endif + // check if canvas is a string first, an element next, or default to getting + // first canvas on page + let canvasEl = this.canvas = document.getElementById(canvas) || + canvas || + document.querySelector('canvas'); - this.context = canvasEl.getContext('2d'); - this.context.imageSmoothingEnabled = false; - this._init(); - }, + // @if DEBUG + if (!canvasEl) { + throw Error('You must provide a canvas element for the game'); + } + // @endif - /** - * Noop function. - * @see https://stackoverflow.com/questions/21634886/what-is-the-javascript-convention-for-no-operation#comment61796464_33458430 - * @memberof kontra - * @private - * - * The new operator is required when using sinon.stub to replace with the noop. - */ - _noop: new Function, + this.context = canvasEl.getContext('2d'); + this.context.imageSmoothingEnabled = false; - /** - * Dispatch event to any part of the code that needs to know when - * a new frame has started. Will be filled out in pointer events. - * @memberOf kontra - * @private - */ - _tick: new Function, + this.emit('init', this); + }, - /** - * Dispatch event to any part of the code that needs to know when - * kontra has initialized. Will be filled out in pointer events. - * @memberOf kontra - * @private - */ - _init: new Function -}; + on(event, fn) { + callbacks[event] = callbacks[event] || []; + callbacks[event].push(fn); + }, + + emit(event, ...args) { + if (!callbacks[event]) return; + callbacks[event].forEach(fn => fn(...args)); + }, + + /** + * Noop function. + * @see https://stackoverflow.com/questions/21634886/what-is-the-javascript-convention-for-no-operation#comment61796464_33458430 + * @memberof kontra + * @private + * + * The new operator is required when using sinon.stub to replace with the noop. + */ + _noop: new Function + }; +})(); (function() { let imageRegex = /(jpeg|jpg|gif|png)$/; let audioRegex = /(wav|mp3|ogg|aac)$/; @@ -329,7 +328,7 @@ kontra = { return; } - kontra._tick(); + kontra.emit('tick'); accumulator += dt; while (accumulator >= delta) { @@ -745,7 +744,7 @@ kontra = { }; // reset object render order on every new frame - kontra._tick = function() { + kontra.on('tick', () => { lastFrameRenderOrder.length = 0; thisFrameRenderOrder.map(function(object) { @@ -753,10 +752,10 @@ kontra = { }); thisFrameRenderOrder.length = 0; - }; + }); // After the canvas is chosen, add events to it - kontra._init = function() { + kontra.on('init', () => { kontra.canvas.addEventListener('mousedown', pointerDownHandler); kontra.canvas.addEventListener('touchstart', pointerDownHandler); kontra.canvas.addEventListener('mouseup', pointerUpHandler); @@ -764,7 +763,7 @@ kontra = { kontra.canvas.addEventListener('blur', blurEventHandler); kontra.canvas.addEventListener('mousemove', mouseMoveHandler); kontra.canvas.addEventListener('touchmove', mouseMoveHandler); - } + }); })(); (function() { @@ -1183,6 +1182,8 @@ kontra = { constructor(x, y) { this._x = x || 0; this._y = y || 0; + + kontra.emit('vector.init', this); } /** @@ -1191,10 +1192,14 @@ kontra = { * * @param {vector} vector - Vector to add. * @param {number} dt=1 - Time since last update. + * + * @returns {vector} */ add(vector, dt) { - this.x += (vector.x || 0) * (dt || 1); - this.y += (vector.y || 0) * (dt || 1); + return kontra.vector( + this.x + (vector.x || 0) * (dt || 1), + this.y + (vector.y || 0) * (dt || 1) + ); } /** @@ -1333,6 +1338,7 @@ kontra = { this.height = this.height || firstAnimation.height; } + kontra.emit('sprite.init', this); return this; } @@ -1527,8 +1533,8 @@ kontra = { * @param {number} dt - Time since last update. */ advance(dt) { - this.velocity.add(this.acceleration, dt); - this.position.add(this.velocity, dt); + this.velocity = this.velocity.add(this.acceleration, dt); + this.position = this.position.add(this.velocity, dt); this.ttl--; diff --git a/kontra.min.js b/kontra.min.js index 04cc5cc2..c4423fdc 100644 --- a/kontra.min.js +++ b/kontra.min.js @@ -1 +1 @@ -kontra={init(t){var e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this._init()},_noop:new Function,_tick:new Function,_init:new Function},function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function u(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function l(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function g(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?u(s):n.match(i)?l(s):g(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra._tick(),a+=s;a>=o;)u.update(r),a-=o;c(),u.render()}}let u={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return u},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,g(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,g(t,"onUp")}function u(t){g(t,"onOver")}function l(t){h={}}function g(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,u=(h-c.top)*o;t.x=d,t.y=u,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra._tick=function(){i.length=0,e.map(function(t){i.push(t)}),e.length=0},kontra._init=function(){kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",u),kontra.canvas.addEventListener("touchmove",u)}}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0}add(t,e){this.x+=(t.x||0)*(e||1),this.y+=(t.y||0)*(e||1)}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity.add(this.acceleration,t),this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,u=i%o.width*s,l=(i/o.width|0)*h,g=c%d*(s+a),f=(c/d|0)*(h+a);e.drawImage(r,g,f,s,h,u,l,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file +!function(){let t={};window.kontra={init(t){let e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this.emit("init",this)},on(e,i){t[e]=t[e]||[],t[e].push(i)},emit(e,...i){t[e]&&t[e].forEach(t=>t(...i))},_noop:new Function}}(),function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function l(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function u(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function g(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?l(s):n.match(i)?u(s):g(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra.emit("tick"),a+=s;a>=o;)l.update(r),a-=o;c(),l.render()}}let l={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return l},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,g(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,g(t,"onUp")}function l(t){g(t,"onOver")}function u(t){h={}}function g(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,l=(h-c.top)*o;t.x=d,t.y=l,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra.on("tick",()=>{i.length=0,e.map(function(t){i.push(t)}),e.length=0}),kontra.on("init",()=>{kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",u),kontra.canvas.addEventListener("mousemove",l),kontra.canvas.addEventListener("touchmove",l)})}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0,kontra.emit("vector.init",this)}add(t,e){return kontra.vector(this.x+(t.x||0)*(e||1),this.y+(t.y||0)*(e||1))}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return kontra.emit("sprite.init",this),this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity=this.velocity.add(this.acceleration,t),this.position=this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,l=i%o.width*s,u=(i/o.width|0)*h,g=c%d*(s+a),f=(c/d|0)*(h+a);e.drawImage(r,g,f,s,h,l,u,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file diff --git a/src/core.js b/src/core.js index e979a2db..c6be2efe 100644 --- a/src/core.js +++ b/src/core.js @@ -1,53 +1,52 @@ -kontra = { - - /** - * Initialize the canvas. - * @memberof kontra - * - * @param {string|HTMLCanvasElement} canvas - Main canvas ID or Element for the game. - */ - init(canvas) { - - // check if canvas is a string first, an element next, or default to getting - // first canvas on page - var canvasEl = this.canvas = document.getElementById(canvas) || - canvas || - document.querySelector('canvas'); - - // @if DEBUG - if (!canvasEl) { - throw Error('You must provide a canvas element for the game'); - } - // @endif - - this.context = canvasEl.getContext('2d'); - this.context.imageSmoothingEnabled = false; - this._init(); - }, - - /** - * Noop function. - * @see https://stackoverflow.com/questions/21634886/what-is-the-javascript-convention-for-no-operation#comment61796464_33458430 - * @memberof kontra - * @private - * - * The new operator is required when using sinon.stub to replace with the noop. - */ - _noop: new Function, - - /** - * Dispatch event to any part of the code that needs to know when - * a new frame has started. Will be filled out in pointer events. - * @memberOf kontra - * @private - */ - _tick: new Function, - - /** - * Dispatch event to any part of the code that needs to know when - * kontra has initialized. Will be filled out in pointer events. - * @memberOf kontra - * @private - */ - _init: new Function -}; \ No newline at end of file +(function() { + let callbacks = {}; + + window.kontra = { + + /** + * Initialize the canvas. + * @memberof kontra + * + * @param {string|HTMLCanvasElement} canvas - Main canvas ID or Element for the game. + */ + init(canvas) { + + // check if canvas is a string first, an element next, or default to getting + // first canvas on page + let canvasEl = this.canvas = document.getElementById(canvas) || + canvas || + document.querySelector('canvas'); + + // @if DEBUG + if (!canvasEl) { + throw Error('You must provide a canvas element for the game'); + } + // @endif + + this.context = canvasEl.getContext('2d'); + this.context.imageSmoothingEnabled = false; + + this.emit('init', this); + }, + + on(event, fn) { + callbacks[event] = callbacks[event] || []; + callbacks[event].push(fn); + }, + + emit(event, ...args) { + if (!callbacks[event]) return; + callbacks[event].forEach(fn => fn(...args)); + }, + + /** + * Noop function. + * @see https://stackoverflow.com/questions/21634886/what-is-the-javascript-convention-for-no-operation#comment61796464_33458430 + * @memberof kontra + * @private + * + * The new operator is required when using sinon.stub to replace with the noop. + */ + _noop: new Function + }; +})(); \ No newline at end of file diff --git a/src/gameLoop.js b/src/gameLoop.js index 5f7ea0a1..24b936c0 100644 --- a/src/gameLoop.js +++ b/src/gameLoop.js @@ -49,7 +49,7 @@ return; } - kontra._tick(); + kontra.emit('tick'); accumulator += dt; while (accumulator >= delta) { diff --git a/src/pointer.js b/src/pointer.js index 96334cfd..37bda079 100644 --- a/src/pointer.js +++ b/src/pointer.js @@ -257,7 +257,7 @@ }; // reset object render order on every new frame - kontra._tick = function() { + kontra.on('tick', () => { lastFrameRenderOrder.length = 0; thisFrameRenderOrder.map(function(object) { @@ -265,10 +265,10 @@ }); thisFrameRenderOrder.length = 0; - }; + }); // After the canvas is chosen, add events to it - kontra._init = function() { + kontra.on('init', () => { kontra.canvas.addEventListener('mousedown', pointerDownHandler); kontra.canvas.addEventListener('touchstart', pointerDownHandler); kontra.canvas.addEventListener('mouseup', pointerUpHandler); @@ -276,5 +276,5 @@ kontra.canvas.addEventListener('blur', blurEventHandler); kontra.canvas.addEventListener('mousemove', mouseMoveHandler); kontra.canvas.addEventListener('touchmove', mouseMoveHandler); - } + }); })(); diff --git a/src/sprite.js b/src/sprite.js index dbec1809..847fbd18 100644 --- a/src/sprite.js +++ b/src/sprite.js @@ -14,6 +14,8 @@ constructor(x, y) { this._x = x || 0; this._y = y || 0; + + kontra.emit('vector.init', this); } /** @@ -22,10 +24,14 @@ * * @param {vector} vector - Vector to add. * @param {number} dt=1 - Time since last update. + * + * @returns {vector} */ add(vector, dt) { - this.x += (vector.x || 0) * (dt || 1); - this.y += (vector.y || 0) * (dt || 1); + return kontra.vector( + this.x + (vector.x || 0) * (dt || 1), + this.y + (vector.y || 0) * (dt || 1) + ); } /** @@ -164,6 +170,7 @@ this.height = this.height || firstAnimation.height; } + kontra.emit('sprite.init', this); return this; } @@ -358,8 +365,8 @@ * @param {number} dt - Time since last update. */ advance(dt) { - this.velocity.add(this.acceleration, dt); - this.position.add(this.velocity, dt); + this.velocity = this.velocity.add(this.acceleration, dt); + this.position = this.position.add(this.velocity, dt); this.ttl--; From 60e8768784919497d72cd113cd858f869ad78337 Mon Sep 17 00:00:00 2001 From: straker Date: Mon, 18 Feb 2019 00:25:59 -0700 Subject: [PATCH 02/10] revert commit to built files --- dist/core.js | 2 +- dist/gameLoop.js | 2 +- dist/pointer.js | 2 +- dist/sprite.js | 2 +- docs/js/kontra.js | 2 +- kontra.js | 116 ++++++++++++++++++++++------------------------ kontra.min.js | 2 +- 7 files changed, 61 insertions(+), 67 deletions(-) diff --git a/dist/core.js b/dist/core.js index 45a26784..e45a4be5 100644 --- a/dist/core.js +++ b/dist/core.js @@ -1 +1 @@ -!function(){let t={};window.kontra={init(t){let n=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=n.getContext("2d"),this.context.imageSmoothingEnabled=!1,this.emit("init",this)},on(n,e){t[n]=t[n]||[],t[n].push(e)},emit(n,...e){t[n]&&t[n].forEach(t=>t(...e))},_noop:new Function}}(); \ No newline at end of file +kontra={init(t){var n=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=n.getContext("2d"),this.context.imageSmoothingEnabled=!1,this._init()},_noop:new Function,_tick:new Function,_init:new Function}; \ No newline at end of file diff --git a/dist/gameLoop.js b/dist/gameLoop.js index 05c3f3ab..10aaeff2 100644 --- a/dist/gameLoop.js +++ b/dist/gameLoop.js @@ -1 +1 @@ -kontra.gameLoop=function(e){let t,n,a,r,o=(e=e||{}).fps||60,i=0,p=1e3/o,c=1/o,s=!1===e.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(n=requestAnimationFrame(d),a=performance.now(),r=a-t,t=a,!(r>1e3)){for(kontra.emit("tick"),i+=r;i>=p;)m.update(c),i-=p;s(),m.render()}}let m={update:e.update,render:e.render,isStopped:!0,start(){t=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(n)}};return m}; \ No newline at end of file +kontra.gameLoop=function(e){let t,n,a,r,o=(e=e||{}).fps||60,i=0,p=1e3/o,c=1/o,s=!1===e.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(n=requestAnimationFrame(d),a=performance.now(),r=a-t,t=a,!(r>1e3)){for(kontra._tick(),i+=r;i>=p;)m.update(c),i-=p;s(),m.render()}}let m={update:e.update,render:e.render,isStopped:!0,start(){t=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(n)}};return m}; \ No newline at end of file diff --git a/dist/pointer.js b/dist/pointer.js index c0802d82..3d9c7e28 100644 --- a/dist/pointer.js +++ b/dist/pointer.js @@ -1 +1 @@ -!function(){let t,n=[],e=[],o={},a=[],r={},i={0:"left",1:"middle",2:"right"};function c(n){let e=n.x,o=n.y;n.anchor&&(e-=n.width*n.anchor.x,o-=n.height*n.anchor.y);let a=t.x-Math.max(e,Math.min(t.x,e+n.width)),r=t.y-Math.max(o,Math.min(t.y,o+n.height));return a*a+r*r=0;n--)if(a=(o=r[n]).collidesWithPointer?o.collidesWithPointer(t):c(o))return o}function s(t){let n=void 0!==t.button?i[t.button]:"left";r[n]=!0,v(t,"onDown")}function h(t){let n=void 0!==t.button?i[t.button]:"left";r[n]=!1,v(t,"onUp")}function d(t){v(t,"onOver")}function l(t){r={}}function v(n,e){if(!kontra.canvas)return;let a,r;-1!==["touchstart","touchmove","touchend"].indexOf(n.type)?(a=(n.touches[0]||n.changedTouches[0]).clientX,r=(n.touches[0]||n.changedTouches[0]).clientY):(a=n.clientX,r=n.clientY);let i,c=kontra.canvas.height/kontra.canvas.offsetHeight,s=kontra.canvas.getBoundingClientRect(),h=(a-s.left)*c,d=(r-s.top)*c;t.x=h,t.y=d,n.target===kontra.canvas&&(n.preventDefault(),(i=u())&&i[e]&&i[e](n)),o[e]&&o[e](n,i)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){n.push(this),this._r()},a.push(t))})},untrack(t,n){[].concat(t).map(function(t){t.render=t._r,t._r=n;let e=a.indexOf(t);-1!==e&&a.splice(e,1)})},over:t=>-1!==a.indexOf(t)&&u()===t,onDown(t){o.onDown=t},onUp(t){o.onUp=t},pressed:t=>!!r[t]},kontra.on("tick",()=>{e.length=0,n.map(function(t){e.push(t)}),n.length=0}),kontra.on("init",()=>{kontra.canvas.addEventListener("mousedown",s),kontra.canvas.addEventListener("touchstart",s),kontra.canvas.addEventListener("mouseup",h),kontra.canvas.addEventListener("touchend",h),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",d),kontra.canvas.addEventListener("touchmove",d)})}(); \ No newline at end of file +!function(){let t,n=[],e=[],o={},a=[],i={},r={0:"left",1:"middle",2:"right"};function c(n){let e=n.x,o=n.y;n.anchor&&(e-=n.width*n.anchor.x,o-=n.height*n.anchor.y);let a=t.x-Math.max(e,Math.min(t.x,e+n.width)),i=t.y-Math.max(o,Math.min(t.y,o+n.height));return a*a+i*i=0;n--)if(a=(o=i[n]).collidesWithPointer?o.collidesWithPointer(t):c(o))return o}function s(t){let n=void 0!==t.button?r[t.button]:"left";i[n]=!0,f(t,"onDown")}function h(t){let n=void 0!==t.button?r[t.button]:"left";i[n]=!1,f(t,"onUp")}function d(t){f(t,"onOver")}function l(t){i={}}function f(n,e){if(!kontra.canvas)return;let a,i;-1!==["touchstart","touchmove","touchend"].indexOf(n.type)?(a=(n.touches[0]||n.changedTouches[0]).clientX,i=(n.touches[0]||n.changedTouches[0]).clientY):(a=n.clientX,i=n.clientY);let r,c=kontra.canvas.height/kontra.canvas.offsetHeight,s=kontra.canvas.getBoundingClientRect(),h=(a-s.left)*c,d=(i-s.top)*c;t.x=h,t.y=d,n.target===kontra.canvas&&(n.preventDefault(),(r=u())&&r[e]&&r[e](n)),o[e]&&o[e](n,r)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){n.push(this),this._r()},a.push(t))})},untrack(t,n){[].concat(t).map(function(t){t.render=t._r,t._r=n;let e=a.indexOf(t);-1!==e&&a.splice(e,1)})},over:t=>-1!==a.indexOf(t)&&u()===t,onDown(t){o.onDown=t},onUp(t){o.onUp=t},pressed:t=>!!i[t]},kontra._tick=function(){e.length=0,n.map(function(t){e.push(t)}),n.length=0},kontra._init=function(){kontra.canvas.addEventListener("mousedown",s),kontra.canvas.addEventListener("touchstart",s),kontra.canvas.addEventListener("mouseup",h),kontra.canvas.addEventListener("touchend",h),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",d),kontra.canvas.addEventListener("touchmove",d)}}(); \ No newline at end of file diff --git a/dist/sprite.js b/dist/sprite.js index 270f4149..f6c39d3a 100644 --- a/dist/sprite.js +++ b/dist/sprite.js @@ -1 +1 @@ -!function(){class t{constructor(t,i){this._x=t||0,this._y=i||0,kontra.emit("vector.init",this)}add(t,i){return kontra.vector(this.x+(t.x||0)*(i||1),this.y+(t.y||0)*(i||1))}clamp(t,i,h,s){this._c=!0,this._a=t,this._b=i,this._d=h,this._e=s}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((i,h)=>new t(i,h)),kontra.vector.prototype=t.prototype;class i{init(t,i,h,s){for(i in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[i]=t[i];if(h=t.image)this.image=h,this.width=void 0!==t.width?t.width:h.width,this.height=void 0!==t.height?t.height:h.height;else if(h=t.animations){for(i in h)this.animations[i]=h[i].clone(),s=s||h[i];this._ca=s,this.width=this.width||s.width,this.height=this.height||s.height}return kontra.emit("sprite.init",this),this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let i=this.x-this.width*this.anchor.x,h=this.y-this.height*this.anchor.y,s=t.x,e=t.y;return t.anchor&&(s-=t.width*t.anchor.x,e-=t.height*t.anchor.y),is&&he}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity=this.velocity.add(this.acceleration,t),this.position=this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,i=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,i,this.width,this.height):this._ca?this._ca.render({x:t,y:i,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,i,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new i).init(t)),kontra.sprite.prototype=i.prototype}(); \ No newline at end of file +!function(){class t{constructor(t,i){this._x=t||0,this._y=i||0}add(t,i){this.x+=(t.x||0)*(i||1),this.y+=(t.y||0)*(i||1)}clamp(t,i,h,s){this._c=!0,this._a=t,this._b=i,this._d=h,this._e=s}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((i,h)=>new t(i,h)),kontra.vector.prototype=t.prototype;class i{init(t,i,h,s){for(i in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[i]=t[i];if(h=t.image)this.image=h,this.width=h.width,this.height=h.height;else if(h=t.animations){for(i in h)this.animations[i]=h[i].clone(),s=s||h[i];this._ca=s,this.width=s.width,this.height=s.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let i=this.x-this.width*this.anchor.x,h=this.y-this.height*this.anchor.y,s=t.x,e=t.y;return t.anchor&&(s-=t.width*t.anchor.x,e-=t.height*t.anchor.y),is&&he}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity.add(this.acceleration,t),this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,i=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,t,i):this._ca?this._ca.render({x:t,y:i,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,i,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new i).init(t)),kontra.sprite.prototype=i.prototype}(); \ No newline at end of file diff --git a/docs/js/kontra.js b/docs/js/kontra.js index c4423fdc..04cc5cc2 100644 --- a/docs/js/kontra.js +++ b/docs/js/kontra.js @@ -1 +1 @@ -!function(){let t={};window.kontra={init(t){let e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this.emit("init",this)},on(e,i){t[e]=t[e]||[],t[e].push(i)},emit(e,...i){t[e]&&t[e].forEach(t=>t(...i))},_noop:new Function}}(),function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function l(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function u(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function g(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?l(s):n.match(i)?u(s):g(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra.emit("tick"),a+=s;a>=o;)l.update(r),a-=o;c(),l.render()}}let l={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return l},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,g(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,g(t,"onUp")}function l(t){g(t,"onOver")}function u(t){h={}}function g(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,l=(h-c.top)*o;t.x=d,t.y=l,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra.on("tick",()=>{i.length=0,e.map(function(t){i.push(t)}),e.length=0}),kontra.on("init",()=>{kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",u),kontra.canvas.addEventListener("mousemove",l),kontra.canvas.addEventListener("touchmove",l)})}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0,kontra.emit("vector.init",this)}add(t,e){return kontra.vector(this.x+(t.x||0)*(e||1),this.y+(t.y||0)*(e||1))}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return kontra.emit("sprite.init",this),this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity=this.velocity.add(this.acceleration,t),this.position=this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,l=i%o.width*s,u=(i/o.width|0)*h,g=c%d*(s+a),f=(c/d|0)*(h+a);e.drawImage(r,g,f,s,h,l,u,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file +kontra={init(t){var e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this._init()},_noop:new Function,_tick:new Function,_init:new Function},function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function u(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function l(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function g(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?u(s):n.match(i)?l(s):g(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra._tick(),a+=s;a>=o;)u.update(r),a-=o;c(),u.render()}}let u={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return u},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,g(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,g(t,"onUp")}function u(t){g(t,"onOver")}function l(t){h={}}function g(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,u=(h-c.top)*o;t.x=d,t.y=u,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra._tick=function(){i.length=0,e.map(function(t){i.push(t)}),e.length=0},kontra._init=function(){kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",u),kontra.canvas.addEventListener("touchmove",u)}}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0}add(t,e){this.x+=(t.x||0)*(e||1),this.y+=(t.y||0)*(e||1)}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity.add(this.acceleration,t),this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,u=i%o.width*s,l=(i/o.width|0)*h,g=c%d*(s+a),f=(c/d|0)*(h+a);e.drawImage(r,g,f,s,h,u,l,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file diff --git a/kontra.js b/kontra.js index 1f698e1a..dc50bf74 100644 --- a/kontra.js +++ b/kontra.js @@ -1,55 +1,56 @@ -(function() { - let callbacks = {}; - - window.kontra = { - - /** - * Initialize the canvas. - * @memberof kontra - * - * @param {string|HTMLCanvasElement} canvas - Main canvas ID or Element for the game. - */ - init(canvas) { +kontra = { - // check if canvas is a string first, an element next, or default to getting - // first canvas on page - let canvasEl = this.canvas = document.getElementById(canvas) || - canvas || - document.querySelector('canvas'); + /** + * Initialize the canvas. + * @memberof kontra + * + * @param {string|HTMLCanvasElement} canvas - Main canvas ID or Element for the game. + */ + init(canvas) { - // @if DEBUG - if (!canvasEl) { - throw Error('You must provide a canvas element for the game'); - } - // @endif + // check if canvas is a string first, an element next, or default to getting + // first canvas on page + var canvasEl = this.canvas = document.getElementById(canvas) || + canvas || + document.querySelector('canvas'); - this.context = canvasEl.getContext('2d'); - this.context.imageSmoothingEnabled = false; + // @if DEBUG + if (!canvasEl) { + throw Error('You must provide a canvas element for the game'); + } + // @endif - this.emit('init', this); - }, + this.context = canvasEl.getContext('2d'); + this.context.imageSmoothingEnabled = false; + this._init(); + }, - on(event, fn) { - callbacks[event] = callbacks[event] || []; - callbacks[event].push(fn); - }, + /** + * Noop function. + * @see https://stackoverflow.com/questions/21634886/what-is-the-javascript-convention-for-no-operation#comment61796464_33458430 + * @memberof kontra + * @private + * + * The new operator is required when using sinon.stub to replace with the noop. + */ + _noop: new Function, - emit(event, ...args) { - if (!callbacks[event]) return; - callbacks[event].forEach(fn => fn(...args)); - }, + /** + * Dispatch event to any part of the code that needs to know when + * a new frame has started. Will be filled out in pointer events. + * @memberOf kontra + * @private + */ + _tick: new Function, - /** - * Noop function. - * @see https://stackoverflow.com/questions/21634886/what-is-the-javascript-convention-for-no-operation#comment61796464_33458430 - * @memberof kontra - * @private - * - * The new operator is required when using sinon.stub to replace with the noop. - */ - _noop: new Function - }; -})(); + /** + * Dispatch event to any part of the code that needs to know when + * kontra has initialized. Will be filled out in pointer events. + * @memberOf kontra + * @private + */ + _init: new Function +}; (function() { let imageRegex = /(jpeg|jpg|gif|png)$/; let audioRegex = /(wav|mp3|ogg|aac)$/; @@ -328,7 +329,7 @@ return; } - kontra.emit('tick'); + kontra._tick(); accumulator += dt; while (accumulator >= delta) { @@ -744,7 +745,7 @@ }; // reset object render order on every new frame - kontra.on('tick', () => { + kontra._tick = function() { lastFrameRenderOrder.length = 0; thisFrameRenderOrder.map(function(object) { @@ -752,10 +753,10 @@ }); thisFrameRenderOrder.length = 0; - }); + }; // After the canvas is chosen, add events to it - kontra.on('init', () => { + kontra._init = function() { kontra.canvas.addEventListener('mousedown', pointerDownHandler); kontra.canvas.addEventListener('touchstart', pointerDownHandler); kontra.canvas.addEventListener('mouseup', pointerUpHandler); @@ -763,7 +764,7 @@ kontra.canvas.addEventListener('blur', blurEventHandler); kontra.canvas.addEventListener('mousemove', mouseMoveHandler); kontra.canvas.addEventListener('touchmove', mouseMoveHandler); - }); + } })(); (function() { @@ -1182,8 +1183,6 @@ constructor(x, y) { this._x = x || 0; this._y = y || 0; - - kontra.emit('vector.init', this); } /** @@ -1192,14 +1191,10 @@ * * @param {vector} vector - Vector to add. * @param {number} dt=1 - Time since last update. - * - * @returns {vector} */ add(vector, dt) { - return kontra.vector( - this.x + (vector.x || 0) * (dt || 1), - this.y + (vector.y || 0) * (dt || 1) - ); + this.x += (vector.x || 0) * (dt || 1); + this.y += (vector.y || 0) * (dt || 1); } /** @@ -1338,7 +1333,6 @@ this.height = this.height || firstAnimation.height; } - kontra.emit('sprite.init', this); return this; } @@ -1533,8 +1527,8 @@ * @param {number} dt - Time since last update. */ advance(dt) { - this.velocity = this.velocity.add(this.acceleration, dt); - this.position = this.position.add(this.velocity, dt); + this.velocity.add(this.acceleration, dt); + this.position.add(this.velocity, dt); this.ttl--; diff --git a/kontra.min.js b/kontra.min.js index c4423fdc..04cc5cc2 100644 --- a/kontra.min.js +++ b/kontra.min.js @@ -1 +1 @@ -!function(){let t={};window.kontra={init(t){let e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this.emit("init",this)},on(e,i){t[e]=t[e]||[],t[e].push(i)},emit(e,...i){t[e]&&t[e].forEach(t=>t(...i))},_noop:new Function}}(),function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function l(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function u(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function g(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?l(s):n.match(i)?u(s):g(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra.emit("tick"),a+=s;a>=o;)l.update(r),a-=o;c(),l.render()}}let l={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return l},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,g(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,g(t,"onUp")}function l(t){g(t,"onOver")}function u(t){h={}}function g(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,l=(h-c.top)*o;t.x=d,t.y=l,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra.on("tick",()=>{i.length=0,e.map(function(t){i.push(t)}),e.length=0}),kontra.on("init",()=>{kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",u),kontra.canvas.addEventListener("mousemove",l),kontra.canvas.addEventListener("touchmove",l)})}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0,kontra.emit("vector.init",this)}add(t,e){return kontra.vector(this.x+(t.x||0)*(e||1),this.y+(t.y||0)*(e||1))}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return kontra.emit("sprite.init",this),this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity=this.velocity.add(this.acceleration,t),this.position=this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,l=i%o.width*s,u=(i/o.width|0)*h,g=c%d*(s+a),f=(c/d|0)*(h+a);e.drawImage(r,g,f,s,h,l,u,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file +kontra={init(t){var e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this._init()},_noop:new Function,_tick:new Function,_init:new Function},function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function u(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function l(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function g(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?u(s):n.match(i)?l(s):g(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra._tick(),a+=s;a>=o;)u.update(r),a-=o;c(),u.render()}}let u={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return u},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,g(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,g(t,"onUp")}function u(t){g(t,"onOver")}function l(t){h={}}function g(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,u=(h-c.top)*o;t.x=d,t.y=u,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra._tick=function(){i.length=0,e.map(function(t){i.push(t)}),e.length=0},kontra._init=function(){kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",u),kontra.canvas.addEventListener("touchmove",u)}}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0}add(t,e){this.x+=(t.x||0)*(e||1),this.y+=(t.y||0)*(e||1)}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity.add(this.acceleration,t),this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,u=i%o.width*s,l=(i/o.width|0)*h,g=c%d*(s+a),f=(c/d|0)*(h+a);e.drawImage(r,g,f,s,h,u,l,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file From 625f2390c678fef9f3113a80976bac4d5b87432a Mon Sep 17 00:00:00 2001 From: straker Date: Fri, 22 Feb 2019 01:26:39 -0700 Subject: [PATCH 03/10] add plugin interceptor architecture --- examples/plugins/advancedCollision.html | 38 ++-- src/plugin.js | 131 +++++++++++++ test/plugin.spec.js | 249 ++++++++++++++++++++++++ 3 files changed, 400 insertions(+), 18 deletions(-) create mode 100644 src/plugin.js create mode 100644 test/plugin.spec.js diff --git a/examples/plugins/advancedCollision.html b/examples/plugins/advancedCollision.html index 225e9a47..40e1b958 100644 --- a/examples/plugins/advancedCollision.html +++ b/examples/plugins/advancedCollision.html @@ -51,31 +51,29 @@ return Math.sqrt(dx * dx + dy * dy) <= circle1.radius + circle2.radius; } - let collidesWith = function(object) { - let obj1 = accountForAnchor(this); - let obj2 = accountForAnchor(object); - - if (obj1.shape === RECTANGLE) { - if (obj2.shape === RECTANGLE) { - return recVsRec(obj1, obj2); + advacedCollisionPlugin = { + afterCollidesWith(sprite, result, object) { + let obj1 = accountForAnchor(sprite); + let obj2 = accountForAnchor(object); + + if (obj1.shape === RECTANGLE) { + if (obj2.shape === RECTANGLE) { + return recVsRec(obj1, obj2); + } + else { + return recVsCircle(obj1, obj2); + } + } + else if (obj2.shape === RECTANGLE) { + return recVsCircle(obj2, obj1); } else { - return recVsCircle(obj1, obj2); + return circleVsCircle(obj1, obj2); } } - else if (obj2.shape === RECTANGLE) { - return recVsCircle(obj2, obj1); - } - else { - return circleVsCircle(obj1, obj2); - } } - kontra.on('sprite.init', sprite => { - sprite.collidesWith = collidesWith; - }); - @@ -83,6 +81,10 @@ // code // initialize the game and setup the canvas kontra.init(); + + // register the plugin + kontra.plugin.register('sprite', advacedCollisionPlugin); + let sprites = []; // rec sprite diff --git a/src/plugin.js b/src/plugin.js new file mode 100644 index 00000000..9d465069 --- /dev/null +++ b/src/plugin.js @@ -0,0 +1,131 @@ +(function() { + + /** + * Get the kontra object method name from the plugin. + * @private + * + * @param {string} methodName - Before/After function name + * + * @returns {string} + */ + function getMethod(methodName) { + let methodTitle = methodName.substr( methodName.search(/[A-Z]/) ); + return methodTitle[0].toLowerCase() + methodTitle.substr(1); + } + + /** + * Remove an interceptor. + * @private + * + * @param {function[]} interceptors - Before/After interceptor list + * @param {function} fn - Interceptor function + */ + function removeInterceptor(interceptors, fn) { + let index = interceptors.indexOf(fn); + if (index !== -1) { + interceptors.splice(index, 1); + } + } + + /** + * Object for registering plugins + */ + kontra.plugin = { + + /** + * Register a plugin to run before or after methods. + * @memberof kontra.plugin + * + * @param {string} object - Kontra object to override + * @param {object} plugin - Plugin object + * + * @example + * kontra.plugin.register('sprite', myPluginObject) + */ + register(object, plugin) { + const kontraObjectProto = kontra[object].prototype || kontra[object]; + + // create interceptor list and functions + if (!kontraObjectProto._inc) { + kontraObjectProto._inc = {}; + kontraObjectProto._bInc = function beforePlugins(context, method, ...args) { + this._inc[method].before.forEach(fn => { + fn(context, ...args); + }); + }; + kontraObjectProto._aInc = function afterPlugins(context, method, result, ...args) { + return this._inc[method].after.reduce((acc, fn) => { + let newResult = fn(context, acc, ...args); + return newResult ? newResult : acc; + }, result); + }; + } + + // add plugin to interceptors + Object.getOwnPropertyNames(plugin).forEach(methodName => { + let method = getMethod(methodName); + + if (!kontraObjectProto[method]) return; + + // override original method + if (!kontraObjectProto['_o' + method]) { + kontraObjectProto['_o' + method] = kontraObjectProto[method]; + + kontraObjectProto[method] = function interceptedFn(...args) { + + // call before interceptors + this._bInc(this, method, ...args); + + let result = kontraObjectProto['_o' + method].call(this, ...args); + + // call after interceptors + return this._aInc(this, method, result, ...args); + }; + } + + // create interceptors for the method + if (!kontraObjectProto._inc[method]) { + kontraObjectProto._inc[method] = { + before: [], + after: [] + }; + } + + if (methodName.startsWith('before')) { + kontraObjectProto._inc[method].before.push(plugin[methodName]); + } + else if (methodName.startsWith('after')) { + kontraObjectProto._inc[method].after.push(plugin[methodName]); + } + }); + }, + + /** + * Unregister a plugin + * @memberof kontra.plugin + * + * @param {string} object - Kontra object to override + * @param {object} plugin - Plugin object + * + * @example + * kontra.plugin.unregister('sprite', myPluginObject) + */ + unregister(object, plugin) { + const kontraObjectProto = kontra[object].prototype || kontra[object]; + + if (!kontraObjectProto._inc) return; + + // remove plugin from interceptors + Object.getOwnPropertyNames(plugin).forEach(methodName => { + let method = getMethod(methodName); + + if (methodName.startsWith('before')) { + removeInterceptor(kontraObjectProto._inc[method].before, plugin[methodName]); + } + else if (methodName.startsWith('after')) { + removeInterceptor(kontraObjectProto._inc[method].after, plugin[methodName]); + } + }); + } + }; +})(); \ No newline at end of file diff --git a/test/plugin.spec.js b/test/plugin.spec.js new file mode 100644 index 00000000..8ce32193 --- /dev/null +++ b/test/plugin.spec.js @@ -0,0 +1,249 @@ +// -------------------------------------------------- +// kontra.plugin +// -------------------------------------------------- +describe('kontra.plugin', function() { + + let add = (p1, p2) => p1 + p2; + let myPlugin = { + beforeAdd(foobar, p1, p2) { + return; + }, + afterAdd(foobar, result, p1, p2) { + return result * 2; + } + } + + beforeEach(() => { + kontra.foobar = { + add: add + }; + }); + + + + + + // -------------------------------------------------- + // kontra.plugin.register + // -------------------------------------------------- + describe('register', function() { + + beforeEach(() => { + kontra.plugin.register('foobar', myPlugin); + }); + + it('should create an interceptor list', () => { + expect(kontra.foobar._inc).to.be.an('object'); + }); + + it('should create before and after interceptor functions', () => { + expect(kontra.foobar._bInc).to.be.an('function'); + expect(kontra.foobar._aInc).to.be.an('function'); + }); + + it('should save the original method', () => { + expect(kontra.foobar._oadd).to.be.an('function'); + }); + + it('should override the original method', () => { + expect(kontra.foobar.add).to.not.equal(add); + }); + + it('should create interceptors for the method', () => { + expect(kontra.foobar._inc.add).to.be.an('object'); + expect(kontra.foobar._inc.add.before).to.be.an('array'); + expect(kontra.foobar._inc.add.after).to.be.an('array'); + }); + + it('should add before method to interceptor list', () => { + expect(kontra.foobar._inc.add.before.length).to.equal(1); + expect(kontra.foobar._inc.add.before[0]).to.equal(myPlugin.beforeAdd); + }); + + it('should add the after method to interceptor list', () => { + expect(kontra.foobar._inc.add.after.length).to.equal(1); + expect(kontra.foobar._inc.add.after[0]).to.equal(myPlugin.afterAdd); + }); + + it('should not override interceptors if object is already intercepted', () => { + kontra.plugin.register('foobar', {}); + + expect(kontra.foobar._inc.add).to.be.ok; + expect(kontra.foobar._inc.add.before.length).to.equal(1); + expect(kontra.foobar._inc.add.after.length).to.equal(1); + }); + + it('should ignore functions that don\'t match the before/after syntax', () => { + kontra.plugin.register('foobar', { + doAdd() {} + }); + + expect(kontra.foobar._inc.add.before.length).to.equal(1); + expect(kontra.foobar._inc.add.after.length).to.equal(1); + }); + + it('should do nothing if original method does not exist', () => { + kontra.plugin.register('foobar', { + afterBaz() {}, + beforeBaz() {} + }); + + expect(kontra.foobar.baz).to.not.be.ok; + expect(kontra.foobar._inc.baz).to.not.be.ok; + }); + + it('should allow multiple plugins to be registered for the same method', () => { + kontra.plugin.register('foobar', myPlugin); + + expect(kontra.foobar._inc.add.before.length).to.equal(2); + expect(kontra.foobar._inc.add.after.length).to.equal(2); + }); + + + + + + describe('intercepted method', () => { + it('should call the original method', () => { + let spy = sinon.spy(kontra.foobar, '_oadd'); + kontra.foobar.add(1, 2); + + expect(spy.called).to.be.ok; + expect(spy.calledWith(1, 2)).to.be.ok; + }); + + it('should call any before methods', () => { + let stub = sinon.stub(); + kontra.foobar._inc.add.before[0] = stub; + kontra.foobar.add(1, 2); + + expect(stub.called).to.be.ok; + expect(stub.calledWith(kontra.foobar, 1, 2)).to.be.ok; + }); + + it('should call any after methods', () => { + let stub = sinon.stub(); + kontra.foobar._inc.add.after[0] = stub; + kontra.foobar.add(1, 2); + + expect(stub.called).to.be.ok; + expect(stub.calledWith(kontra.foobar, 3, 1, 2)).to.be.ok; + }); + + it('should return the result of all the after methods', () => { + let result = kontra.foobar.add(1, 2); + + expect(result).to.equal(6); + }); + + it('should pass the result from one after plugin to the next', () => { + let stub = sinon.stub().callsFake(function fakeFn(context, result, p1, p2) { + return result + p1 * p2; + }); + kontra.plugin.register('foobar', { + afterAdd: stub + }); + + let result = kontra.foobar.add(1, 2); + expect(stub.calledWith(kontra.foobar, 6, 1, 2)).to.be.ok; + expect(result).to.equal(8); + }); + + it('should pass the previous result if after plugin returns null', () => { + let stub1 = sinon.stub().callsFake(function fakeFn(context, result, p1, p2) { + return null; + }); + let stub2 = sinon.stub().callsFake(function fakeFn(context, result, p1, p2) { + return result + p1 * p2; + }); + kontra.plugin.register('foobar', { + afterAdd: stub1 + }); + kontra.plugin.register('foobar', { + afterAdd: stub2 + }); + + let result = kontra.foobar.add(1, 2); + + expect(stub2.calledWith(kontra.foobar, 6, 1, 2)).to.be.ok; + expect(result).to.equal(8); + }); + + it('should call plugins in the ordered they were registered', () => { + let stub = sinon.stub(); + let stub1 = sinon.stub().callsFake(function fakeFn(context, result, p1, p2) { + return null; + }); + let stub2 = sinon.stub().callsFake(function fakeFn(context, result, p1, p2) { + return result + p1 * p2; + }); + kontra.plugin.register('foobar', { + afterAdd: stub1 + }); + kontra.plugin.register('foobar', { + afterAdd: stub2 + }); + kontra.foobar._inc.add.before[0] = stub; + + kontra.foobar.add(1, 2); + + sinon.assert.callOrder(stub, stub1, stub2); + }); + }); + }); + + + + + + // -------------------------------------------------- + // kontra.plugin.unregister + // -------------------------------------------------- + describe('unregister', function() { + + beforeEach(() => { + kontra.plugin.register('foobar', myPlugin); + kontra.plugin.unregister('foobar', myPlugin); + }); + + it('should remove the before method from the interceptor list', () => { + expect(kontra.foobar._inc.add.before.length).to.equal(0); + }); + + it('should remove the after method from the interceptor list', () => { + expect(kontra.foobar._inc.add.after.length).to.equal(0); + }); + + it('should do nothing if object has not been overridden', () => { + let fn = () => { + kontra.plugin.unregister('sprite', myPlugin); + } + + expect(fn).to.not.throw(); + }); + + it('should ignore functions that don\'t match the before/after syntax', () => { + let fn = () => { + kontra.plugin.unregister('foobar', { + doAdd() {} + }); + } + + expect(fn).to.not.throw(); + }); + + it('should not remove methods from other plugins', () => { + let fn = () => { + kontra.plugin.unregister('foobar', { + afterAdd() {}, + beforeAdd() {} + }); + } + + kontra.plugin.register('foobar', myPlugin); + expect(fn).to.not.throw(); + expect(kontra.foobar._inc.add.before.length).to.equal(1); + expect(kontra.foobar._inc.add.after.length).to.equal(1); + }); + }); +}); \ No newline at end of file From 51270c100bfcf9979a9bc912e17b8405b2eed6ae Mon Sep 17 00:00:00 2001 From: straker Date: Fri, 22 Feb 2019 14:55:03 -0700 Subject: [PATCH 04/10] finalize and test plugin architecture --- dist/core.js | 2 +- dist/gameLoop.js | 2 +- dist/plugin.js | 1 + dist/sprite.js | 2 +- docs/api/gameLoop.html | 20 ++ docs/api/kontra.html | 77 +++++++ docs/api/plugin.html | 237 ++++++++++++++++++++++ docs/components/main-nav.html | 1 + docs/js/kontra.js | 2 +- examples/plugins/advancedVector.html | 28 ++- kontra.js | 293 ++++++++++++++++++++++----- kontra.min.js | 2 +- src/core.js | 36 +++- src/plugin.js | 35 +++- src/sprite.js | 3 - test/core.spec.js | 148 ++++++++++++++ test/gameLoop.spec.js | 16 ++ test/plugin.spec.js | 84 +++++++- test/pointer.spec.js | 8 +- test/sprite.spec.js | 20 +- 20 files changed, 921 insertions(+), 96 deletions(-) create mode 100644 dist/plugin.js create mode 100644 docs/api/plugin.html diff --git a/dist/core.js b/dist/core.js index e45a4be5..d7989ef1 100644 --- a/dist/core.js +++ b/dist/core.js @@ -1 +1 @@ -kontra={init(t){var n=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=n.getContext("2d"),this.context.imageSmoothingEnabled=!1,this._init()},_noop:new Function,_tick:new Function,_init:new Function}; \ No newline at end of file +!function(){let t={};window.kontra={init(t){let n=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=n.getContext("2d"),this.context.imageSmoothingEnabled=!1,this.emit("init",this)},on(n,e){t[n]=t[n]||[],t[n].push(e)},off(n,e,i){!t[n]||(i=t[n].indexOf(e))<0||t[n].splice(i,1)},emit(n,...e){t[n]&&t[n].forEach(t=>t(...e))},_noop:new Function,_callbacks:t}}(); \ No newline at end of file diff --git a/dist/gameLoop.js b/dist/gameLoop.js index 10aaeff2..05c3f3ab 100644 --- a/dist/gameLoop.js +++ b/dist/gameLoop.js @@ -1 +1 @@ -kontra.gameLoop=function(e){let t,n,a,r,o=(e=e||{}).fps||60,i=0,p=1e3/o,c=1/o,s=!1===e.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(n=requestAnimationFrame(d),a=performance.now(),r=a-t,t=a,!(r>1e3)){for(kontra._tick(),i+=r;i>=p;)m.update(c),i-=p;s(),m.render()}}let m={update:e.update,render:e.render,isStopped:!0,start(){t=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(n)}};return m}; \ No newline at end of file +kontra.gameLoop=function(e){let t,n,a,r,o=(e=e||{}).fps||60,i=0,p=1e3/o,c=1/o,s=!1===e.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(n=requestAnimationFrame(d),a=performance.now(),r=a-t,t=a,!(r>1e3)){for(kontra.emit("tick"),i+=r;i>=p;)m.update(c),i-=p;s(),m.render()}}let m={update:e.update,render:e.render,isStopped:!0,start(){t=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(n)}};return m}; \ No newline at end of file diff --git a/dist/plugin.js b/dist/plugin.js new file mode 100644 index 00000000..fab9b0a8 --- /dev/null +++ b/dist/plugin.js @@ -0,0 +1 @@ +!function(){function t(t){let e=t.substr(t.search(/[A-Z]/));return e[0].toLowerCase()+e.substr(1)}function e(t,e){let r=t.indexOf(e);-1!==r&&t.splice(r,1)}kontra.plugin={register(e,r){const n=kontra[e].prototype||kontra[e];n._inc||(n._inc={},n._bInc=function(t,e,...r){return this._inc[e].before.reduce((e,r)=>{let n=r(t,...e);return n||e},r)},n._aInc=function(t,e,r,...n){return this._inc[e].after.reduce((e,r)=>{let o=r(t,e,...n);return o||e},r)}),Object.getOwnPropertyNames(r).forEach(e=>{let o=t(e);n[o]&&(n["_o"+o]||(n["_o"+o]=n[o],n[o]=function(...t){let e=this._bInc(this,o,...t),r=n["_o"+o].call(this,...e);return this._aInc(this,o,r,...t)}),n._inc[o]||(n._inc[o]={before:[],after:[]}),e.startsWith("before")?n._inc[o].before.push(r[e]):e.startsWith("after")&&n._inc[o].after.push(r[e]))})},unregister(r,n){const o=kontra[r].prototype||kontra[r];o._inc&&Object.getOwnPropertyNames(n).forEach(r=>{let c=t(r);r.startsWith("before")?e(o._inc[c].before,n[r]):r.startsWith("after")&&e(o._inc[c].after,n[r])})},extend(t,e){const r=kontra[t].prototype||kontra[t];Object.getOwnPropertyNames(e).forEach(t=>{r[t]||(r[t]=e[t])})}}}(); \ No newline at end of file diff --git a/dist/sprite.js b/dist/sprite.js index f6c39d3a..ac6742a9 100644 --- a/dist/sprite.js +++ b/dist/sprite.js @@ -1 +1 @@ -!function(){class t{constructor(t,i){this._x=t||0,this._y=i||0}add(t,i){this.x+=(t.x||0)*(i||1),this.y+=(t.y||0)*(i||1)}clamp(t,i,h,s){this._c=!0,this._a=t,this._b=i,this._d=h,this._e=s}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((i,h)=>new t(i,h)),kontra.vector.prototype=t.prototype;class i{init(t,i,h,s){for(i in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[i]=t[i];if(h=t.image)this.image=h,this.width=h.width,this.height=h.height;else if(h=t.animations){for(i in h)this.animations[i]=h[i].clone(),s=s||h[i];this._ca=s,this.width=s.width,this.height=s.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let i=this.x-this.width*this.anchor.x,h=this.y-this.height*this.anchor.y,s=t.x,e=t.y;return t.anchor&&(s-=t.width*t.anchor.x,e-=t.height*t.anchor.y),is&&he}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity.add(this.acceleration,t),this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,i=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,t,i):this._ca?this._ca.render({x:t,y:i,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,i,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new i).init(t)),kontra.sprite.prototype=i.prototype}(); \ No newline at end of file +!function(){class t{constructor(t,i){this._x=t||0,this._y=i||0}add(t,i){return kontra.vector(this.x+(t.x||0)*(i||1),this.y+(t.y||0)*(i||1))}clamp(t,i,h,s){this._c=!0,this._a=t,this._b=i,this._d=h,this._e=s}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((i,h)=>new t(i,h)),kontra.vector.prototype=t.prototype;class i{init(t,i,h,s){for(i in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[i]=t[i];if(h=t.image)this.image=h,this.width=void 0!==t.width?t.width:h.width,this.height=void 0!==t.height?t.height:h.height;else if(h=t.animations){for(i in h)this.animations[i]=h[i].clone(),s=s||h[i];this._ca=s,this.width=this.width||s.width,this.height=this.height||s.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let i=this.x-this.width*this.anchor.x,h=this.y-this.height*this.anchor.y,s=t.x,e=t.y;return t.anchor&&(s-=t.width*t.anchor.x,e-=t.height*t.anchor.y),is&&he}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity=this.velocity.add(this.acceleration,t),this.position=this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,i=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,i,this.width,this.height):this._ca?this._ca.render({x:t,y:i,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,i,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new i).init(t)),kontra.sprite.prototype=i.prototype}(); \ No newline at end of file diff --git a/docs/api/gameLoop.html b/docs/api/gameLoop.html index 00db06c3..0d1dffa7 100644 --- a/docs/api/gameLoop.html +++ b/docs/api/gameLoop.html @@ -69,6 +69,12 @@

Kontra​.gameLoop(​properties)

Table of Contents

    +
  • + +
  • +
  • Properties

      @@ -92,6 +98,20 @@

      Methods

      +
      +

      Lifecycle Events

      + +

      Every frame it will emit a tick event.

      + +
      kontra.on('tick', () => {
      +  // run code every frame
      +});
      +
      + + + + +

      kontra.gameLoop​.isStopped

      diff --git a/docs/api/kontra.html b/docs/api/kontra.html index 6a7c0f2a..16e2738a 100644 --- a/docs/api/kontra.html +++ b/docs/api/kontra.html @@ -36,6 +36,12 @@

      Kontra

      Table of Contents

        +
      • + +
      • +
      • Properties

        @@ -51,7 +58,10 @@

        Properties

      • Methods

          +
        • kontra​.emit(event[, ...args])
        • kontra​.init([canvas])
        • +
        • kontra​.off(event, callback)
        • +
        • kontra​.on(event, callback)
        • kontra​.animation(properties)
        • kontra​.gameLoop(properties)
        • kontra​.pool(properties)
        • @@ -69,6 +79,20 @@

          Methods

          +
          +

          Lifecycle Events

          + +

          When Kontra is initialized, it will emit an init event.

          + +
          kontra.on('init', (canvas) => {
          +  // run code when Kontra is initialized
          +});
          +
          + + + + +

          kontra.canvas

          @@ -91,6 +115,24 @@

          kontra.context +

          kontra​.emit(event[, ...args])

          + +
          +
          event {string}
          +
          The name of the event to emit.
          +
          args {...*}
          +
          Arguments passed to all callbacks
          +
          + +

          Emit an event and run all its callbacks, passing it any arguments.

          + +

          + + + + +

          kontra.init([canvas])

          @@ -103,6 +145,41 @@

          kontra.init([canvas]) + + + + +
          +

          kontra​.off(event, callback)

          + +
          +
          event {string}
          +
          The name of the event to emit.
          +
          callback {function}
          +
          Function callback
          +
          + +

          Remove the callback from the event so it will not be run when the event is emitted.

          + +
          + + + + + +
          +

          kontra​.on(event, callback)

          + +
          +
          event {string}
          +
          The name of the event to emit.
          +
          callback {function}
          +
          Function callback
          +
          + +

          Add the callback to the event so it will be run when the event is emitted.

          + +
          diff --git a/docs/api/plugin.html b/docs/api/plugin.html new file mode 100644 index 00000000..fd725092 --- /dev/null +++ b/docs/api/plugin.html @@ -0,0 +1,237 @@ + + + + Kontra.js – Kontra.plugin + + + + + + + + + + + + + + + +
          + + +
          +
          +

          Kontra.plugin

          + +

          A minimal plugin architecture to enable sharing of common functionality. The plugin system is designed after the Interceptor Pattern and allows you to write functions that can be run before and after any Kontra object function.

          + +

          The plugin architecture also allows you to safely extend Kontra objects. This allows you to safely add additional properties without overriding ones that already may exist.

          + +
          let myPlugin = {
          +  beforeAdd(vector, vecToAdd) {
          +    console.log(`adding {${vecToAdd.x}, ${vecToAdd.y}} vector to {${vector.x}, ${vector.y}}`);
          +  },
          +  afterAdd(vector, result, vecToAdd) {
          +    console.log(`result of add was: {${result.x}, ${result.y}}`);
          +  }
          +};
          +
          +kontra.plugin.register('vector', myPlugin);
          + + + + + +
          +

          Table of Contents

          + + +
          + + + + + +
          +

          Plugin Best Practices

          + +
            +
          • Export your plugin object for the consumer to use.
          • +
          • You should not register the plugin yourself. Instead, you should expect the consumer of your plugin to register it. This way the consumer can determine the order in which plugins should run.
          • +
          • In your plugin docs, remind the user to register the plugin themselves.
          • +
          + +
          (function() {
          +  let myPlugin = {
          +    beforeAdd() {
          +      // ...
          +    },
          +    afterAdd() {
          +      // ...
          +    }
          +  }
          +
          +  window.myPlugin = myPlugin;
          +  // or
          +  module.export = myPlugin;
          +  // or
          +  export default myPlugin;
          +})();
          + +
          + + + + + +
          +

          kontra.plugin​.extend(object, properties)

          + +
          +
          object {string}
          +
          Kontra object to add extend.
          +
          properties {object}
          +
          Properties to add to the Kontra object.
          +
          + +

          Safely extend a Kontra object with the provided properties. If the property already exists on the object, your property will not be added.

          + +
          kontra.plugin.extend('vector', {
          +  subtract: function(vec) {
          +    return kontra.vector(this.x - vec.x, this.y - vec.y);
          +  }
          +};  // all kontra.vectors will now have a subtract function
          + +
          + + + + + +
          +

          kontra.plugin​.register(object, plugin)

          + +
          +
          object {string}
          +
          Kontra object to attach plugin to.
          +
          plugin {object}
          +
          Plugin object with before and after functions.
          +
          + +

          Register a plugin to run before and/or after any function defined on the Kontra object.

          + +
          // Intercept kontra.vector.add function and run functions before and after it
          +let myPlugin = {
          +  beforeAdd(vector, vecToAdd) {
          +    console.log(`adding {${vecToAdd.x}, ${vecToAdd.y}} vector to {${vector.x}, ${vector.y}}`);
          +  },
          +  afterAdd(vector, result, vecToAdd) {
          +    console.log(`result of add was: {${result.x}, ${result.y}}`);
          +  }
          +};
          +
          +kontra.plugin.register('vector', myPlugin);
          + +

          Before functions

          + +

          Before functions run before the intercepted Kontra object function. They are defined by prefixing the function with before and the name of the intercepted function. The parameters passed to the function will be the context the function was called with followed by all parameters passed to the intercepted function.

          + +

          Before functions can modify the arguments passed to the intercepted function. To do this, you'll need to return an array that represents the new arguments. You can modify or pass through any argument in this way. If your function does not modify the arguments, you can return null.

          + +

          If multiple before functions are registered, they will be run in the order they were registered and will be passed the arguments from the previous before function.

          + +
          let logPlugin = {
          +  // `vector` is the vector the `add` function was called on
          +  // `vecToAdd` is the vector being passed to the `add` function
          +  beforeAdd(vector, vecToAdd) {
          +    console.log(`adding {${vecToAdd.x}, ${vecToAdd.y}} vector to {${vector.x}, ${vector.y}}`);
          +
          +    // don't modify the arguments so return nothing
          +  }
          +}
          +
          +let modifyArgsPlugin = {
          +  beforeAdd(vector, vecToAdd) {
          +    // modify the parameter being passed to the `add` function
          +    let newVector = kontra.vector(10, 20);
          +    return [newVector]
          +  }
          +}
          +
          +let passThroughArgsPlugin = {
          +  beforeAdd(vector, vecToAdd) {
          +    // pass through the original argument to the `add` function but also
          +    // pass an additional one
          +    let newVector = kontra.vector(10, 20);
          +    return [vecToAdd, newVector]
          +  }
          +}
          + +

          After functions

          + +

          After functions run after the intercepted Kontra object function. They are defined by prefixing the function with after and the name of the intercepted function. The parameters passed to the function will be the context the function was called with, the result of the intercepted function, followed by all parameters passed to the intercepted function.

          + +

          After functions can modify the result of the intercepted function. To do this, you return a new value as the result. If your function does not modify the result, you can return null.

          + +

          If multiple after functions are registered, they will be run in the order they were registered and will be passed the arguments from the previous after function.

          + +
          let logPlugin = {
          +  // `vector` is the vector the `add` function was called on
          +  // `result` is the result of the `add` function
          +  // `vecToAdd` is the vector being passed to the `add` function
          +  afterAdd(vector, result, vecToAdd) {
          +    console.log(`result of add was: {${result.x}, ${result.y}}`);
          +
          +    // don't modify the result so return nothing
          +  }
          +}
          +
          +let modifyResultPlugin = {
          +  afterAdd(vector, result, vecToAdd) {
          +    // modify the result of the `add` function
          +    return kontra.vector(result.x * 2, result.y * 2);
          +  }
          +}
          + +
          + + + + + +
          +

          kontra.plugin​.unregsiter(object, plugin)

          + +
          +
          object {string}
          +
          Kontra object to attach plugin to.
          +
          plugin {object}
          +
          Plugin object with before and after functions.
          +
          + +

          Unregister a plugin and remove all intercepted before and after functions.

          + +
          + +
          +
          +
          + + + \ No newline at end of file diff --git a/docs/components/main-nav.html b/docs/components/main-nav.html index cc6b3eb4..d6946a58 100644 --- a/docs/components/main-nav.html +++ b/docs/components/main-nav.html @@ -23,6 +23,7 @@

          API

        • Keyboard
        • Kontra
        • Object Pool
        • +
        • Plugin
        • Pointer
        • Quadtree
        • Sprite
        • diff --git a/docs/js/kontra.js b/docs/js/kontra.js index 04cc5cc2..abb3160d 100644 --- a/docs/js/kontra.js +++ b/docs/js/kontra.js @@ -1 +1 @@ -kontra={init(t){var e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this._init()},_noop:new Function,_tick:new Function,_init:new Function},function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function u(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function l(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function g(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?u(s):n.match(i)?l(s):g(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra._tick(),a+=s;a>=o;)u.update(r),a-=o;c(),u.render()}}let u={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return u},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,g(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,g(t,"onUp")}function u(t){g(t,"onOver")}function l(t){h={}}function g(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,u=(h-c.top)*o;t.x=d,t.y=u,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra._tick=function(){i.length=0,e.map(function(t){i.push(t)}),e.length=0},kontra._init=function(){kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",u),kontra.canvas.addEventListener("touchmove",u)}}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0}add(t,e){this.x+=(t.x||0)*(e||1),this.y+=(t.y||0)*(e||1)}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity.add(this.acceleration,t),this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,u=i%o.width*s,l=(i/o.width|0)*h,g=c%d*(s+a),f=(c/d|0)*(h+a);e.drawImage(r,g,f,s,h,u,l,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file +!function(){let t={};window.kontra={init(t){let e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this.emit("init",this)},on(e,i){t[e]=t[e]||[],t[e].push(i)},off(e,i,n){!t[e]||(n=t[e].indexOf(i))<0||t[e].splice(n,1)},emit(e,...i){t[e]&&t[e].forEach(t=>t(...i))},_noop:new Function,_callbacks:t}}(),function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function u(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function l(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function f(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?u(s):n.match(i)?l(s):f(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra.emit("tick"),a+=s;a>=o;)u.update(r),a-=o;c(),u.render()}}let u={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return u},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){function t(t){let e=t.substr(t.search(/[A-Z]/));return e[0].toLowerCase()+e.substr(1)}function e(t,e){let i=t.indexOf(e);-1!==i&&t.splice(i,1)}kontra.plugin={register(e,i){const n=kontra[e].prototype||kontra[e];n._inc||(n._inc={},n._bInc=function(t,e,...i){return this._inc[e].before.reduce((e,i)=>{let n=i(t,...e);return n||e},i)},n._aInc=function(t,e,i,...n){return this._inc[e].after.reduce((e,i)=>{let s=i(t,e,...n);return s||e},i)}),Object.getOwnPropertyNames(i).forEach(e=>{let s=t(e);n[s]&&(n["_o"+s]||(n["_o"+s]=n[s],n[s]=function(...t){let e=this._bInc(this,s,...t),i=n["_o"+s].call(this,...e);return this._aInc(this,s,i,...t)}),n._inc[s]||(n._inc[s]={before:[],after:[]}),e.startsWith("before")?n._inc[s].before.push(i[e]):e.startsWith("after")&&n._inc[s].after.push(i[e]))})},unregister(i,n){const s=kontra[i].prototype||kontra[i];s._inc&&Object.getOwnPropertyNames(n).forEach(i=>{let h=t(i);i.startsWith("before")?e(s._inc[h].before,n[i]):i.startsWith("after")&&e(s._inc[h].after,n[i])})},extend(t,e){const i=kontra[t].prototype||kontra[t];Object.getOwnPropertyNames(e).forEach(t=>{i[t]||(i[t]=e[t])})}}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,f(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,f(t,"onUp")}function u(t){f(t,"onOver")}function l(t){h={}}function f(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,u=(h-c.top)*o;t.x=d,t.y=u,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra.on("tick",()=>{i.length=0,e.map(function(t){i.push(t)}),e.length=0}),kontra.on("init",()=>{kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",u),kontra.canvas.addEventListener("touchmove",u)})}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0}add(t,e){return kontra.vector(this.x+(t.x||0)*(e||1),this.y+(t.y||0)*(e||1))}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity=this.velocity.add(this.acceleration,t),this.position=this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,u=i%o.width*s,l=(i/o.width|0)*h,f=c%d*(s+a),g=(c/d|0)*(h+a);e.drawImage(r,f,g,s,h,u,l,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file diff --git a/examples/plugins/advancedVector.html b/examples/plugins/advancedVector.html index 68ec355d..f02dbe92 100644 --- a/examples/plugins/advancedVector.html +++ b/examples/plugins/advancedVector.html @@ -30,15 +30,29 @@ return this.length(this.subtract(this, vec)); } - kontra.on('vector.init', vec => { - vec.subtract = subtract; - vec.dot = dot; - vec.length = length; - vec.scale = scale; - vec.normalize = normalize; - vec.distance = distance; + let advancedVectorPlugin = {} + + kontra.plugin.extend('vector', { + subtract, + dot, + cross, + length, + scale, + normalize, + distance }); + let myPlugin = { + beforeAdd(vector, param) { + console.log(`adding {${param.x}, ${param.y}} vector to {${vector.x}, ${vector.y}}`); + }, + afterAdd(vector, result, param) { + console.log(`result of add was: {${result.x}, ${result.y}}`); + } + } + + kontra.plugin.register('vector', myPlugin); + diff --git a/kontra.js b/kontra.js index dc50bf74..6313f3cb 100644 --- a/kontra.js +++ b/kontra.js @@ -1,56 +1,85 @@ -kontra = { +(function() { + let callbacks = {}; - /** - * Initialize the canvas. - * @memberof kontra - * - * @param {string|HTMLCanvasElement} canvas - Main canvas ID or Element for the game. - */ - init(canvas) { + window.kontra = { - // check if canvas is a string first, an element next, or default to getting - // first canvas on page - var canvasEl = this.canvas = document.getElementById(canvas) || - canvas || - document.querySelector('canvas'); + /** + * Initialize the canvas. + * @memberof kontra + * + * @param {string|HTMLCanvasElement} canvas - Main canvas ID or Element for the game. + */ + init(canvas) { - // @if DEBUG - if (!canvasEl) { - throw Error('You must provide a canvas element for the game'); - } - // @endif + // check if canvas is a string first, an element next, or default to getting + // first canvas on page + let canvasEl = this.canvas = document.getElementById(canvas) || + canvas || + document.querySelector('canvas'); - this.context = canvasEl.getContext('2d'); - this.context.imageSmoothingEnabled = false; - this._init(); - }, + // @if DEBUG + if (!canvasEl) { + throw Error('You must provide a canvas element for the game'); + } + // @endif - /** - * Noop function. - * @see https://stackoverflow.com/questions/21634886/what-is-the-javascript-convention-for-no-operation#comment61796464_33458430 - * @memberof kontra - * @private - * - * The new operator is required when using sinon.stub to replace with the noop. - */ - _noop: new Function, + this.context = canvasEl.getContext('2d'); + this.context.imageSmoothingEnabled = false; - /** - * Dispatch event to any part of the code that needs to know when - * a new frame has started. Will be filled out in pointer events. - * @memberOf kontra - * @private - */ - _tick: new Function, + this.emit('init', this); + }, - /** - * Dispatch event to any part of the code that needs to know when - * kontra has initialized. Will be filled out in pointer events. - * @memberOf kontra - * @private - */ - _init: new Function -}; + /** + * Register a callback for an event. + * @memberof kontra + * + * @param {string} event - Name of the event + * @param {function} callback - Function callback + */ + on(event, callback) { + callbacks[event] = callbacks[event] || []; + callbacks[event].push(callback); + }, + + /** + * Remove a callback for an event. + * @memberof kontra + * + * @param {string} event - Name of the event + * @param {function} callback - Function callback + */ + // @see https://github.com/jed/140bytes/wiki/Byte-saving-techniques#use-placeholder-arguments-instead-of-var + off(event, callback, index) { + if (!callbacks[event] || (index = callbacks[event].indexOf(callback)) < 0) return; + callbacks[event].splice(index, 1); + }, + + /** + * Call all callback functions for the event. + * @memberof kontra + * + * @param {string} event - Name of the event + * @param {...*} args - Arguments passed to all callbacks + */ + emit(event, ...args) { + if (!callbacks[event]) return; + callbacks[event].forEach(fn => fn(...args)); + }, + + /** + * Noop function. + * @see https://stackoverflow.com/questions/21634886/what-is-the-javascript-convention-for-no-operation#comment61796464_33458430 + * @memberof kontra + * @private + * + * The new operator is required when using sinon.stub to replace with the noop. + */ + _noop: new Function, + + // expose for testing + _callbacks: callbacks + }; +})(); (function() { let imageRegex = /(jpeg|jpg|gif|png)$/; let audioRegex = /(wav|mp3|ogg|aac)$/; @@ -329,7 +358,7 @@ kontra = { return; } - kontra._tick(); + kontra.emit('tick'); accumulator += dt; while (accumulator >= delta) { @@ -486,6 +515,156 @@ kontra = { } }; })(); +(function() { + + /** + * Get the kontra object method name from the plugin. + * @private + * + * @param {string} methodName - Before/After function name + * + * @returns {string} + */ + function getMethod(methodName) { + let methodTitle = methodName.substr( methodName.search(/[A-Z]/) ); + return methodTitle[0].toLowerCase() + methodTitle.substr(1); + } + + /** + * Remove an interceptor. + * @private + * + * @param {function[]} interceptors - Before/After interceptor list + * @param {function} fn - Interceptor function + */ + function removeInterceptor(interceptors, fn) { + let index = interceptors.indexOf(fn); + if (index !== -1) { + interceptors.splice(index, 1); + } + } + + /** + * Object for registering plugins. Based on interceptor pattern. + * @see https://blog.kiprosh.com/javascript-method-interceptors/ + */ + kontra.plugin = { + + /** + * Register a plugin to run before or after methods. + * @memberof kontra.plugin + * + * @param {string} object - Kontra object to attach plugin to + * @param {object} plugin - Plugin object + * + * @example + * kontra.plugin.register('sprite', myPluginObject) + */ + register(object, plugin) { + const kontraObjectProto = kontra[object].prototype || kontra[object]; + + // create interceptor list and functions + if (!kontraObjectProto._inc) { + kontraObjectProto._inc = {}; + kontraObjectProto._bInc = function beforePlugins(context, method, ...args) { + return this._inc[method].before.reduce((acc, fn) => { + let newArgs = fn(context, ...acc); + return newArgs ? newArgs : acc; + }, args); + }; + kontraObjectProto._aInc = function afterPlugins(context, method, result, ...args) { + return this._inc[method].after.reduce((acc, fn) => { + let newResult = fn(context, acc, ...args); + return newResult ? newResult : acc; + }, result); + }; + } + + // add plugin to interceptors + Object.getOwnPropertyNames(plugin).forEach(methodName => { + let method = getMethod(methodName); + + if (!kontraObjectProto[method]) return; + + // override original method + if (!kontraObjectProto['_o' + method]) { + kontraObjectProto['_o' + method] = kontraObjectProto[method]; + + kontraObjectProto[method] = function interceptedFn(...args) { + + // call before interceptors + let alteredArgs = this._bInc(this, method, ...args); + + let result = kontraObjectProto['_o' + method].call(this, ...alteredArgs); + + // call after interceptors + return this._aInc(this, method, result, ...args); + }; + } + + // create interceptors for the method + if (!kontraObjectProto._inc[method]) { + kontraObjectProto._inc[method] = { + before: [], + after: [] + }; + } + + if (methodName.startsWith('before')) { + kontraObjectProto._inc[method].before.push(plugin[methodName]); + } + else if (methodName.startsWith('after')) { + kontraObjectProto._inc[method].after.push(plugin[methodName]); + } + }); + }, + + /** + * Unregister a plugin + * @memberof kontra.plugin + * + * @param {string} object - Kontra object to attach plugin to + * @param {object} plugin - Plugin object + * + * @example + * kontra.plugin.unregister('sprite', myPluginObject) + */ + unregister(object, plugin) { + const kontraObjectProto = kontra[object].prototype || kontra[object]; + + if (!kontraObjectProto._inc) return; + + // remove plugin from interceptors + Object.getOwnPropertyNames(plugin).forEach(methodName => { + let method = getMethod(methodName); + + if (methodName.startsWith('before')) { + removeInterceptor(kontraObjectProto._inc[method].before, plugin[methodName]); + } + else if (methodName.startsWith('after')) { + removeInterceptor(kontraObjectProto._inc[method].after, plugin[methodName]); + } + }); + }, + + /** + * Safely extend functionality of a kontra object. + * @memberof kontra.plugin + * + * @param {string} object - Kontra object to extend + * @param {object} properties - Properties to add + */ + extend(object, properties) { + const kontraObjectProto = kontra[object].prototype || kontra[object]; + + Object.getOwnPropertyNames(properties).forEach(prop => { + if (!kontraObjectProto[prop]) { + kontraObjectProto[prop] = properties[prop]; + } + }); + } + }; +})(); (function() { let pointer; @@ -745,7 +924,7 @@ kontra = { }; // reset object render order on every new frame - kontra._tick = function() { + kontra.on('tick', () => { lastFrameRenderOrder.length = 0; thisFrameRenderOrder.map(function(object) { @@ -753,10 +932,10 @@ kontra = { }); thisFrameRenderOrder.length = 0; - }; + }); // After the canvas is chosen, add events to it - kontra._init = function() { + kontra.on('init', () => { kontra.canvas.addEventListener('mousedown', pointerDownHandler); kontra.canvas.addEventListener('touchstart', pointerDownHandler); kontra.canvas.addEventListener('mouseup', pointerUpHandler); @@ -764,7 +943,7 @@ kontra = { kontra.canvas.addEventListener('blur', blurEventHandler); kontra.canvas.addEventListener('mousemove', mouseMoveHandler); kontra.canvas.addEventListener('touchmove', mouseMoveHandler); - } + }); })(); (function() { @@ -1191,10 +1370,14 @@ kontra = { * * @param {vector} vector - Vector to add. * @param {number} dt=1 - Time since last update. + * + * @returns {vector} */ add(vector, dt) { - this.x += (vector.x || 0) * (dt || 1); - this.y += (vector.y || 0) * (dt || 1); + return kontra.vector( + this.x + (vector.x || 0) * (dt || 1), + this.y + (vector.y || 0) * (dt || 1) + ); } /** @@ -1527,8 +1710,8 @@ kontra = { * @param {number} dt - Time since last update. */ advance(dt) { - this.velocity.add(this.acceleration, dt); - this.position.add(this.velocity, dt); + this.velocity = this.velocity.add(this.acceleration, dt); + this.position = this.position.add(this.velocity, dt); this.ttl--; diff --git a/kontra.min.js b/kontra.min.js index 04cc5cc2..abb3160d 100644 --- a/kontra.min.js +++ b/kontra.min.js @@ -1 +1 @@ -kontra={init(t){var e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this._init()},_noop:new Function,_tick:new Function,_init:new Function},function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function u(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function l(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function g(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?u(s):n.match(i)?l(s):g(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra._tick(),a+=s;a>=o;)u.update(r),a-=o;c(),u.render()}}let u={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return u},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,g(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,g(t,"onUp")}function u(t){g(t,"onOver")}function l(t){h={}}function g(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,u=(h-c.top)*o;t.x=d,t.y=u,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra._tick=function(){i.length=0,e.map(function(t){i.push(t)}),e.length=0},kontra._init=function(){kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",u),kontra.canvas.addEventListener("touchmove",u)}}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0}add(t,e){this.x+=(t.x||0)*(e||1),this.y+=(t.y||0)*(e||1)}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity.add(this.acceleration,t),this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,u=i%o.width*s,l=(i/o.width|0)*h,g=c%d*(s+a),f=(c/d|0)*(h+a);e.drawImage(r,g,f,s,h,u,l,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file +!function(){let t={};window.kontra={init(t){let e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this.emit("init",this)},on(e,i){t[e]=t[e]||[],t[e].push(i)},off(e,i,n){!t[e]||(n=t[e].indexOf(i))<0||t[e].splice(n,1)},emit(e,...i){t[e]&&t[e].forEach(t=>t(...i))},_noop:new Function,_callbacks:t}}(),function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function u(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function l(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function f(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?u(s):n.match(i)?l(s):f(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra.emit("tick"),a+=s;a>=o;)u.update(r),a-=o;c(),u.render()}}let u={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return u},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){function t(t){let e=t.substr(t.search(/[A-Z]/));return e[0].toLowerCase()+e.substr(1)}function e(t,e){let i=t.indexOf(e);-1!==i&&t.splice(i,1)}kontra.plugin={register(e,i){const n=kontra[e].prototype||kontra[e];n._inc||(n._inc={},n._bInc=function(t,e,...i){return this._inc[e].before.reduce((e,i)=>{let n=i(t,...e);return n||e},i)},n._aInc=function(t,e,i,...n){return this._inc[e].after.reduce((e,i)=>{let s=i(t,e,...n);return s||e},i)}),Object.getOwnPropertyNames(i).forEach(e=>{let s=t(e);n[s]&&(n["_o"+s]||(n["_o"+s]=n[s],n[s]=function(...t){let e=this._bInc(this,s,...t),i=n["_o"+s].call(this,...e);return this._aInc(this,s,i,...t)}),n._inc[s]||(n._inc[s]={before:[],after:[]}),e.startsWith("before")?n._inc[s].before.push(i[e]):e.startsWith("after")&&n._inc[s].after.push(i[e]))})},unregister(i,n){const s=kontra[i].prototype||kontra[i];s._inc&&Object.getOwnPropertyNames(n).forEach(i=>{let h=t(i);i.startsWith("before")?e(s._inc[h].before,n[i]):i.startsWith("after")&&e(s._inc[h].after,n[i])})},extend(t,e){const i=kontra[t].prototype||kontra[t];Object.getOwnPropertyNames(e).forEach(t=>{i[t]||(i[t]=e[t])})}}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,f(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,f(t,"onUp")}function u(t){f(t,"onOver")}function l(t){h={}}function f(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,u=(h-c.top)*o;t.x=d,t.y=u,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra.on("tick",()=>{i.length=0,e.map(function(t){i.push(t)}),e.length=0}),kontra.on("init",()=>{kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",u),kontra.canvas.addEventListener("touchmove",u)})}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0}add(t,e){return kontra.vector(this.x+(t.x||0)*(e||1),this.y+(t.y||0)*(e||1))}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity=this.velocity.add(this.acceleration,t),this.position=this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,u=i%o.width*s,l=(i/o.width|0)*h,f=c%d*(s+a),g=(c/d|0)*(h+a);e.drawImage(r,f,g,s,h,u,l,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file diff --git a/src/core.js b/src/core.js index c6be2efe..13a42554 100644 --- a/src/core.js +++ b/src/core.js @@ -29,11 +29,38 @@ this.emit('init', this); }, - on(event, fn) { + /** + * Register a callback for an event. + * @memberof kontra + * + * @param {string} event - Name of the event + * @param {function} callback - Function callback + */ + on(event, callback) { callbacks[event] = callbacks[event] || []; - callbacks[event].push(fn); + callbacks[event].push(callback); }, + /** + * Remove a callback for an event. + * @memberof kontra + * + * @param {string} event - Name of the event + * @param {function} callback - Function callback + */ + // @see https://github.com/jed/140bytes/wiki/Byte-saving-techniques#use-placeholder-arguments-instead-of-var + off(event, callback, index) { + if (!callbacks[event] || (index = callbacks[event].indexOf(callback)) < 0) return; + callbacks[event].splice(index, 1); + }, + + /** + * Call all callback functions for the event. + * @memberof kontra + * + * @param {string} event - Name of the event + * @param {...*} args - Arguments passed to all callbacks + */ emit(event, ...args) { if (!callbacks[event]) return; callbacks[event].forEach(fn => fn(...args)); @@ -47,6 +74,9 @@ * * The new operator is required when using sinon.stub to replace with the noop. */ - _noop: new Function + _noop: new Function, + + // expose for testing + _callbacks: callbacks }; })(); \ No newline at end of file diff --git a/src/plugin.js b/src/plugin.js index 9d465069..b4f53d6d 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -28,7 +28,8 @@ } /** - * Object for registering plugins + * Object for registering plugins. Based on interceptor pattern. + * @see https://blog.kiprosh.com/javascript-method-interceptors/ */ kontra.plugin = { @@ -36,7 +37,7 @@ * Register a plugin to run before or after methods. * @memberof kontra.plugin * - * @param {string} object - Kontra object to override + * @param {string} object - Kontra object to attach plugin to * @param {object} plugin - Plugin object * * @example @@ -49,9 +50,10 @@ if (!kontraObjectProto._inc) { kontraObjectProto._inc = {}; kontraObjectProto._bInc = function beforePlugins(context, method, ...args) { - this._inc[method].before.forEach(fn => { - fn(context, ...args); - }); + return this._inc[method].before.reduce((acc, fn) => { + let newArgs = fn(context, ...acc); + return newArgs ? newArgs : acc; + }, args); }; kontraObjectProto._aInc = function afterPlugins(context, method, result, ...args) { return this._inc[method].after.reduce((acc, fn) => { @@ -74,9 +76,9 @@ kontraObjectProto[method] = function interceptedFn(...args) { // call before interceptors - this._bInc(this, method, ...args); + let alteredArgs = this._bInc(this, method, ...args); - let result = kontraObjectProto['_o' + method].call(this, ...args); + let result = kontraObjectProto['_o' + method].call(this, ...alteredArgs); // call after interceptors return this._aInc(this, method, result, ...args); @@ -104,7 +106,7 @@ * Unregister a plugin * @memberof kontra.plugin * - * @param {string} object - Kontra object to override + * @param {string} object - Kontra object to attach plugin to * @param {object} plugin - Plugin object * * @example @@ -126,6 +128,23 @@ removeInterceptor(kontraObjectProto._inc[method].after, plugin[methodName]); } }); + }, + + /** + * Safely extend functionality of a kontra object. + * @memberof kontra.plugin + * + * @param {string} object - Kontra object to extend + * @param {object} properties - Properties to add + */ + extend(object, properties) { + const kontraObjectProto = kontra[object].prototype || kontra[object]; + + Object.getOwnPropertyNames(properties).forEach(prop => { + if (!kontraObjectProto[prop]) { + kontraObjectProto[prop] = properties[prop]; + } + }); } }; })(); \ No newline at end of file diff --git a/src/sprite.js b/src/sprite.js index 847fbd18..6fb337f8 100644 --- a/src/sprite.js +++ b/src/sprite.js @@ -14,8 +14,6 @@ constructor(x, y) { this._x = x || 0; this._y = y || 0; - - kontra.emit('vector.init', this); } /** @@ -170,7 +168,6 @@ this.height = this.height || firstAnimation.height; } - kontra.emit('sprite.init', this); return this; } diff --git a/test/core.spec.js b/test/core.spec.js index 890ad533..bab892e1 100644 --- a/test/core.spec.js +++ b/test/core.spec.js @@ -3,6 +3,143 @@ // -------------------------------------------------- describe('kontra', function() { + // -------------------------------------------------- + // kontra.on + // -------------------------------------------------- + describe('on', () => { + afterEach(() => { + delete kontra._callbacks.foo; + }); + + it('should add the event to the callbacks object', () => { + function func() {} + kontra.on('foo', func); + + expect(kontra._callbacks.foo).to.be.an('array'); + expect(kontra._callbacks.foo[0]).to.equal(func); + }); + + it('should append the event if it already exists', () => { + function func1() {} + function func2() {} + kontra.on('foo', func1); + kontra.on('foo', func2); + + expect(kontra._callbacks.foo).to.be.an('array'); + expect(kontra._callbacks.foo[0]).to.equal(func1); + expect(kontra._callbacks.foo[1]).to.equal(func2); + }); + }); + + + + + + // -------------------------------------------------- + // kontra.off + // -------------------------------------------------- + describe('off', () => { + function func() {} + + beforeEach(() => { + kontra.on('foo', func); + }); + + afterEach(() => { + delete kontra._callbacks.foo; + }); + + it('should remove the callback from the event', () => { + kontra.off('foo', func); + + expect(kontra._callbacks.foo.length).to.equal(0); + }); + + it('should only remove the callback', () => { + function func1() {} + function func2() {} + kontra.on('foo', func1); + kontra.on('foo', func2); + + kontra.off('foo', func); + + expect(kontra._callbacks.foo.length).to.equal(2); + expect(kontra._callbacks.foo[0]).to.equal(func1); + expect(kontra._callbacks.foo[1]).to.equal(func2); + }); + + it('should not error if the callback was not added before', () => { + function fn() { + kontra.off('foo', () => {}); + } + + expect(fn).to.not.throw(); + }); + + it('should not error if the event was not added before', () => { + function fn() { + kontra.off('myEvent', () => {}); + } + + expect(fn).to.not.throw(); + }); + }); + + + + + + // -------------------------------------------------- + // kontra.emit + // -------------------------------------------------- + describe('emit', () => { + let func = sinon.spy(); + + beforeEach(() => { + func.resetHistory(); + kontra.on('foo', func); + }); + + afterEach(() => { + delete kontra._callbacks.foo; + }); + + it('should call the callback', () => { + kontra.emit('foo'); + + expect(func.called).to.equal(true); + }); + + it('should pass all parameters to the callback', () => { + kontra.emit('foo', 1, 2, 3); + + expect(func.calledWith(1,2,3)).to.equal(true); + }); + + it('should call the callbacks in order', () => { + let func1 = sinon.spy(); + let func2 = sinon.spy(); + kontra.on('foo', func1); + kontra.on('foo', func2); + + kontra.emit('foo'); + + sinon.assert.callOrder(func, func1, func2); + }); + + it('should not error if the event was not added before', () => { + function fn() { + kontra.emit('myEvent', () => {}); + } + + expect(fn).to.not.throw(); + }); + }); + + + + + // -------------------------------------------------- // kontra.init // -------------------------------------------------- @@ -56,6 +193,17 @@ describe('kontra', function() { expect(kontra.canvas).to.equal(c); }); + it('should emit the init event', () => { + let stub = sinon.stub(kontra, 'emit'); + + kontra.init(); + + expect(stub.called).to.equal(true); + expect(stub.calledWith('init', kontra)).to.equal(true); + + stub.restore(); + }); + }); }); \ No newline at end of file diff --git a/test/gameLoop.spec.js b/test/gameLoop.spec.js index e116d2f8..2bfd8501 100644 --- a/test/gameLoop.spec.js +++ b/test/gameLoop.spec.js @@ -143,6 +143,22 @@ describe('kontra.gameLoop', function() { kontra.context.clearRect.restore(); }); + it('should emit the tick event', () => { + let stub = sinon.stub(kontra, 'emit'); + + loop = kontra.gameLoop({ + update: kontra._noop, + render: kontra._noop + }); + loop._last = performance.now() - (1E3/60); + loop._frame(); + + expect(stub.called).to.equal(true); + expect(stub.calledWith('tick')).to.equal(true); + + stub.restore(); + }); + }); }); \ No newline at end of file diff --git a/test/plugin.spec.js b/test/plugin.spec.js index 8ce32193..d260b4f3 100644 --- a/test/plugin.spec.js +++ b/test/plugin.spec.js @@ -104,8 +104,13 @@ describe('kontra.plugin', function() { describe('intercepted method', () => { + let spy; + afterEach(() => { + spy.restore && spy.restore(); + }); + it('should call the original method', () => { - let spy = sinon.spy(kontra.foobar, '_oadd'); + spy = sinon.spy(kontra.foobar, '_oadd'); kontra.foobar.add(1, 2); expect(spy.called).to.be.ok; @@ -121,6 +126,39 @@ describe('kontra.plugin', function() { expect(stub.calledWith(kontra.foobar, 1, 2)).to.be.ok; }); + it('should pass the modified arguments from one before plugin to the next', () => { + spy = sinon.spy(kontra.foobar, '_oadd'); + let stub = sinon.stub().callsFake(function fakeFn(context, p1, p2) { + return [5, 6]; + }); + kontra.foobar._inc.add.before[0] = stub; + + kontra.foobar.add(1, 2); + expect(stub.calledWith(kontra.foobar, 1, 2)).to.be.ok; + expect(spy.calledWith(5, 6)).to.be.ok; + }); + + it('should pass the previous result if before plugin returns null', () => { + spy = sinon.spy(kontra.foobar, '_oadd'); + let stub1 = sinon.stub().callsFake(function fakeFn(context, p1, p2) { + return null; + }); + let stub2 = sinon.stub().callsFake(function fakeFn(context, p1, p2) { + return [5, 6]; + }); + kontra.plugin.register('foobar', { + beforeAdd: stub1 + }); + kontra.plugin.register('foobar', { + beforeAdd: stub2 + }); + + kontra.foobar.add(1, 2); + + expect(stub2.calledWith(kontra.foobar, 1, 2)).to.be.ok; + expect(spy.calledWith(5, 6)).to.be.ok; + }); + it('should call any after methods', () => { let stub = sinon.stub(); kontra.foobar._inc.add.after[0] = stub; @@ -246,4 +284,48 @@ describe('kontra.plugin', function() { expect(kontra.foobar._inc.add.after.length).to.equal(1); }); }); + + + + + + // -------------------------------------------------- + // kontra.plugin.extend + // -------------------------------------------------- + describe('extend', function() { + + it('should add properties onto the object', () => { + let properties = { + number: 1, + string: 'hello', + fn: function() {}, + object: {} + }; + + kontra.plugin.extend('foobar', properties); + + expect(kontra.foobar.number).to.equal(properties.number); + expect(kontra.foobar.string).to.equal(properties.string); + expect(kontra.foobar.fn).to.equal(properties.fn); + expect(kontra.foobar.object).to.equal(properties.object); + }); + + it('should not add properties onto the object that already exist', () => { + let properties = { + number: 1, + string: 'hello', + fn: function() {}, + object: {} + }; + + let override = { + number: 20 + }; + + kontra.plugin.extend('foobar', properties); + kontra.plugin.extend('foobar', override); + + expect(kontra.foobar.number).to.equal(properties.number); + }); + }); }); \ No newline at end of file diff --git a/test/pointer.spec.js b/test/pointer.spec.js index 5daa62f4..6c72953d 100644 --- a/test/pointer.spec.js +++ b/test/pointer.spec.js @@ -44,7 +44,7 @@ describe('kontra.pointer', function() { // set up and take down the canvas before each test so it doesn't leak // the canvas element for the kontra.core.init tests kontra.canvas = canvas; - kontra._init(); + kontra.emit('init'); document.body.appendChild(canvas); object = { @@ -56,7 +56,7 @@ describe('kontra.pointer', function() { }; kontra.pointer.track(object); object.render(); - kontra._tick(); + kontra.emit('tick'); }); afterEach(function() { @@ -220,11 +220,11 @@ describe('kontra.pointer', function() { render: sinon.spy() }; kontra.pointer.track(obj); - kontra._tick(); + kontra.emit('tick'); object.render(); obj.render(); - kontra._tick(); + kontra.emit('tick'); kontra.pointer.x = 100; kontra.pointer.y = 50; diff --git a/test/sprite.spec.js b/test/sprite.spec.js index e2f548fc..97d6c6bc 100644 --- a/test/sprite.spec.js +++ b/test/sprite.spec.js @@ -30,33 +30,33 @@ describe('kontra.vector', function() { var vec1 = kontra.vector(10, 20); var vec2 = kontra.vector(5, 10); - vec1.add(vec2) + let vec = vec1.add(vec2) - expect(vec1.x).to.eql(15); - expect(vec1.y).to.eql(30); + expect(vec.x).to.eql(15); + expect(vec.y).to.eql(30); }); it('should incorporate dt if passed', function() { var vec1 = kontra.vector(10, 20); var vec2 = kontra.vector(5, 10); - vec1.add(vec2, 2) + let vec = vec1.add(vec2, 2) - expect(vec1.x).to.eql(20); - expect(vec1.y).to.eql(40); + expect(vec.x).to.eql(20); + expect(vec.y).to.eql(40); }); it('should default vector to 0 with empty parameters', function() { var vec1 = kontra.vector(10, 20); - vec1.add({x: 10}); + let vec = vec1.add({x: 10}); - expect(vec1.y).to.eql(20); + expect(vec.y).to.eql(20); vec1 = kontra.vector(10, 20); - vec1.add({y: 10}); + vec = vec1.add({y: 10}); - expect(vec1.x).to.eql(10); + expect(vec.x).to.eql(10); }); }); From 35c40fbb18acd04a7a556b1a1461c4154836e9b3 Mon Sep 17 00:00:00 2001 From: straker Date: Fri, 22 Feb 2019 14:56:58 -0700 Subject: [PATCH 05/10] add periods in docs --- docs/api/kontra.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api/kontra.html b/docs/api/kontra.html index 16e2738a..38cc97be 100644 --- a/docs/api/kontra.html +++ b/docs/api/kontra.html @@ -122,7 +122,7 @@

          kontra​.emit(event[, ..
          event {string}
          The name of the event to emit.
          args {...*}
          -
          Arguments passed to all callbacks
          +
          Arguments passed to all callbacks.

          Emit an event and run all its callbacks, passing it any arguments.

          @@ -156,7 +156,7 @@

          kontra​.off(event, callba
          event {string}
          The name of the event to emit.
          callback {function}
          -
          Function callback
          +
          Function callback.

          Remove the callback from the event so it will not be run when the event is emitted.

          @@ -174,7 +174,7 @@

          kontra​.on(event, callback)
          event {string}
          The name of the event to emit.
          callback {function}
          -
          Function callback
          +
          Function callback.

          Add the callback to the event so it will be run when the event is emitted.

          From 43a68acb4fb15d24e0eff58fecdf7c9a290d7fc9 Mon Sep 17 00:00:00 2001 From: straker Date: Fri, 22 Feb 2019 15:04:26 -0700 Subject: [PATCH 06/10] better wording in docs --- docs/api/kontra.html | 4 ++-- docs/api/plugin.html | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/api/kontra.html b/docs/api/kontra.html index 38cc97be..ead54095 100644 --- a/docs/api/kontra.html +++ b/docs/api/kontra.html @@ -159,7 +159,7 @@

          kontra​.off(event, callba
          Function callback.
          -

          Remove the callback from the event so it will not be run when the event is emitted.

          +

          Remove the callback from the event so it will no longer be run when the event is emitted.

          @@ -177,7 +177,7 @@

          kontra​.on(event, callback)
          Function callback.
          -

          Add the callback to the event so it will be run when the event is emitted.

          +

          Add the callback to the event so it will run when the event is emitted.

      diff --git a/docs/api/plugin.html b/docs/api/plugin.html index fd725092..e106a850 100644 --- a/docs/api/plugin.html +++ b/docs/api/plugin.html @@ -26,7 +26,7 @@

      Kontra.plugin

      A minimal plugin architecture to enable sharing of common functionality. The plugin system is designed after the Interceptor Pattern and allows you to write functions that can be run before and after any Kontra object function.

      -

      The plugin architecture also allows you to safely extend Kontra objects. This allows you to safely add additional properties without overriding ones that already may exist.

      +

      The plugin architecture also allows you to safely extend Kontra objects. This allows you to safely add additional properties without overriding ones that may already exist.

      let myPlugin = {
         beforeAdd(vector, vecToAdd) {
      @@ -75,6 +75,7 @@ 

      Plugin Be
    • Export your plugin object for the consumer to use.
    • You should not register the plugin yourself. Instead, you should expect the consumer of your plugin to register it. This way the consumer can determine the order in which plugins should run.
    • In your plugin docs, remind the user to register the plugin themselves.
    • +
    • Gracefully handle errors in in your plugin code. As an idea, you could return null on an error so your plugin does not break the rest of the plugin chain.
    (function() {
    @@ -154,7 +155,7 @@ 

    Before functions

    Before functions can modify the arguments passed to the intercepted function. To do this, you'll need to return an array that represents the new arguments. You can modify or pass through any argument in this way. If your function does not modify the arguments, you can return null.

    -

    If multiple before functions are registered, they will be run in the order they were registered and will be passed the arguments from the previous before function.

    +

    If multiple before functions are registered for the same intercepted function, they will be run in the order they were registered and will be passed the arguments from the previous before function.

    let logPlugin = {
       // `vector` is the vector the `add` function was called on
    @@ -189,7 +190,7 @@ 

    After functions

    After functions can modify the result of the intercepted function. To do this, you return a new value as the result. If your function does not modify the result, you can return null.

    -

    If multiple after functions are registered, they will be run in the order they were registered and will be passed the arguments from the previous after function.

    +

    If multiple after functions are registered for the same intercepted function, they will be run in the order they were registered and will be passed the result from the previous after function.

    let logPlugin = {
       // `vector` is the vector the `add` function was called on
    @@ -225,7 +226,7 @@ 

    kontra.pluginR
    Plugin object with before and after functions.
    -

    Unregister a plugin and remove all intercepted before and after functions.

    +

    Unregister a plugin and remove all its before and after functions from the intercepted Kontra object.

    From 412a7c49222ee4778ba574bea26c182c2ee18ca4 Mon Sep 17 00:00:00 2001 From: straker Date: Fri, 22 Feb 2019 15:06:05 -0700 Subject: [PATCH 07/10] remove test code from example --- ...cedCollision.html => advancedCollisionPlugin.html} | 0 .../{advancedVector.html => extendingVecotr.html} | 11 ----------- 2 files changed, 11 deletions(-) rename examples/plugins/{advancedCollision.html => advancedCollisionPlugin.html} (100%) rename examples/plugins/{advancedVector.html => extendingVecotr.html} (82%) diff --git a/examples/plugins/advancedCollision.html b/examples/plugins/advancedCollisionPlugin.html similarity index 100% rename from examples/plugins/advancedCollision.html rename to examples/plugins/advancedCollisionPlugin.html diff --git a/examples/plugins/advancedVector.html b/examples/plugins/extendingVecotr.html similarity index 82% rename from examples/plugins/advancedVector.html rename to examples/plugins/extendingVecotr.html index f02dbe92..ad6c77d0 100644 --- a/examples/plugins/advancedVector.html +++ b/examples/plugins/extendingVecotr.html @@ -42,17 +42,6 @@ distance }); - let myPlugin = { - beforeAdd(vector, param) { - console.log(`adding {${param.x}, ${param.y}} vector to {${vector.x}, ${vector.y}}`); - }, - afterAdd(vector, result, param) { - console.log(`result of add was: {${result.x}, ${result.y}}`); - } - } - - kontra.plugin.register('vector', myPlugin); - From d5cc19c2c8f1301e4d8e194f216bfffcb05f83a9 Mon Sep 17 00:00:00 2001 From: straker Date: Fri, 22 Feb 2019 15:18:29 -0700 Subject: [PATCH 08/10] add plugin to download page --- dist/pointer.js | 2 +- docs/download.html | 5 +++++ docs/index.html | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/dist/pointer.js b/dist/pointer.js index 3d9c7e28..c0802d82 100644 --- a/dist/pointer.js +++ b/dist/pointer.js @@ -1 +1 @@ -!function(){let t,n=[],e=[],o={},a=[],i={},r={0:"left",1:"middle",2:"right"};function c(n){let e=n.x,o=n.y;n.anchor&&(e-=n.width*n.anchor.x,o-=n.height*n.anchor.y);let a=t.x-Math.max(e,Math.min(t.x,e+n.width)),i=t.y-Math.max(o,Math.min(t.y,o+n.height));return a*a+i*i=0;n--)if(a=(o=i[n]).collidesWithPointer?o.collidesWithPointer(t):c(o))return o}function s(t){let n=void 0!==t.button?r[t.button]:"left";i[n]=!0,f(t,"onDown")}function h(t){let n=void 0!==t.button?r[t.button]:"left";i[n]=!1,f(t,"onUp")}function d(t){f(t,"onOver")}function l(t){i={}}function f(n,e){if(!kontra.canvas)return;let a,i;-1!==["touchstart","touchmove","touchend"].indexOf(n.type)?(a=(n.touches[0]||n.changedTouches[0]).clientX,i=(n.touches[0]||n.changedTouches[0]).clientY):(a=n.clientX,i=n.clientY);let r,c=kontra.canvas.height/kontra.canvas.offsetHeight,s=kontra.canvas.getBoundingClientRect(),h=(a-s.left)*c,d=(i-s.top)*c;t.x=h,t.y=d,n.target===kontra.canvas&&(n.preventDefault(),(r=u())&&r[e]&&r[e](n)),o[e]&&o[e](n,r)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){n.push(this),this._r()},a.push(t))})},untrack(t,n){[].concat(t).map(function(t){t.render=t._r,t._r=n;let e=a.indexOf(t);-1!==e&&a.splice(e,1)})},over:t=>-1!==a.indexOf(t)&&u()===t,onDown(t){o.onDown=t},onUp(t){o.onUp=t},pressed:t=>!!i[t]},kontra._tick=function(){e.length=0,n.map(function(t){e.push(t)}),n.length=0},kontra._init=function(){kontra.canvas.addEventListener("mousedown",s),kontra.canvas.addEventListener("touchstart",s),kontra.canvas.addEventListener("mouseup",h),kontra.canvas.addEventListener("touchend",h),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",d),kontra.canvas.addEventListener("touchmove",d)}}(); \ No newline at end of file +!function(){let t,n=[],e=[],o={},a=[],r={},i={0:"left",1:"middle",2:"right"};function c(n){let e=n.x,o=n.y;n.anchor&&(e-=n.width*n.anchor.x,o-=n.height*n.anchor.y);let a=t.x-Math.max(e,Math.min(t.x,e+n.width)),r=t.y-Math.max(o,Math.min(t.y,o+n.height));return a*a+r*r=0;n--)if(a=(o=r[n]).collidesWithPointer?o.collidesWithPointer(t):c(o))return o}function s(t){let n=void 0!==t.button?i[t.button]:"left";r[n]=!0,v(t,"onDown")}function h(t){let n=void 0!==t.button?i[t.button]:"left";r[n]=!1,v(t,"onUp")}function d(t){v(t,"onOver")}function l(t){r={}}function v(n,e){if(!kontra.canvas)return;let a,r;-1!==["touchstart","touchmove","touchend"].indexOf(n.type)?(a=(n.touches[0]||n.changedTouches[0]).clientX,r=(n.touches[0]||n.changedTouches[0]).clientY):(a=n.clientX,r=n.clientY);let i,c=kontra.canvas.height/kontra.canvas.offsetHeight,s=kontra.canvas.getBoundingClientRect(),h=(a-s.left)*c,d=(r-s.top)*c;t.x=h,t.y=d,n.target===kontra.canvas&&(n.preventDefault(),(i=u())&&i[e]&&i[e](n)),o[e]&&o[e](n,i)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){n.push(this),this._r()},a.push(t))})},untrack(t,n){[].concat(t).map(function(t){t.render=t._r,t._r=n;let e=a.indexOf(t);-1!==e&&a.splice(e,1)})},over:t=>-1!==a.indexOf(t)&&u()===t,onDown(t){o.onDown=t},onUp(t){o.onUp=t},pressed:t=>!!r[t]},kontra.on("tick",()=>{e.length=0,n.map(function(t){e.push(t)}),n.length=0}),kontra.on("init",()=>{kontra.canvas.addEventListener("mousedown",s),kontra.canvas.addEventListener("touchstart",s),kontra.canvas.addEventListener("mouseup",h),kontra.canvas.addEventListener("touchend",h),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",d),kontra.canvas.addEventListener("touchmove",d)})}(); \ No newline at end of file diff --git a/docs/download.html b/docs/download.html index 88cdd91e..e64749a5 100644 --- a/docs/download.html +++ b/docs/download.html @@ -80,6 +80,11 @@

    Download

    Object pool + +
      -
    • Lightweight: 12.5 kB minified (4.6 kB gzipped) for the entire library. The basic bundle is 3 kB minified (1.3 kB gzipped).
    • +
    • Lightweight: 14.5 kB minified (5.22 kB gzipped) for the entire library. The basic bundle is 3 kB minified (1.3 kB gzipped).
    • Modular: pick and choose what modules you want when you download. No inter-dependencies.
    • Extensible: everything is customizable and can be extended.
    • Fast: all logic has been removed from the update and render cycles.
    • From 19784433e3cf44dae6ed339cc67ee312e82d220c Mon Sep 17 00:00:00 2001 From: straker Date: Fri, 22 Feb 2019 23:06:30 -0700 Subject: [PATCH 09/10] update docs, reduce filesize of plugin a bit --- dist/plugin.js | 2 +- docs/api/kontra.html | 10 +++++++++- docs/index.html | 2 +- docs/js/kontra.js | 2 +- kontra.js | 28 ++++++++++++++++++++-------- kontra.min.js | 2 +- src/plugin.js | 28 ++++++++++++++++++++-------- 7 files changed, 53 insertions(+), 21 deletions(-) diff --git a/dist/plugin.js b/dist/plugin.js index fab9b0a8..722fd293 100644 --- a/dist/plugin.js +++ b/dist/plugin.js @@ -1 +1 @@ -!function(){function t(t){let e=t.substr(t.search(/[A-Z]/));return e[0].toLowerCase()+e.substr(1)}function e(t,e){let r=t.indexOf(e);-1!==r&&t.splice(r,1)}kontra.plugin={register(e,r){const n=kontra[e].prototype||kontra[e];n._inc||(n._inc={},n._bInc=function(t,e,...r){return this._inc[e].before.reduce((e,r)=>{let n=r(t,...e);return n||e},r)},n._aInc=function(t,e,r,...n){return this._inc[e].after.reduce((e,r)=>{let o=r(t,e,...n);return o||e},r)}),Object.getOwnPropertyNames(r).forEach(e=>{let o=t(e);n[o]&&(n["_o"+o]||(n["_o"+o]=n[o],n[o]=function(...t){let e=this._bInc(this,o,...t),r=n["_o"+o].call(this,...e);return this._aInc(this,o,r,...t)}),n._inc[o]||(n._inc[o]={before:[],after:[]}),e.startsWith("before")?n._inc[o].before.push(r[e]):e.startsWith("after")&&n._inc[o].after.push(r[e]))})},unregister(r,n){const o=kontra[r].prototype||kontra[r];o._inc&&Object.getOwnPropertyNames(n).forEach(r=>{let c=t(r);r.startsWith("before")?e(o._inc[c].before,n[r]):r.startsWith("after")&&e(o._inc[c].after,n[r])})},extend(t,e){const r=kontra[t].prototype||kontra[t];Object.getOwnPropertyNames(e).forEach(t=>{r[t]||(r[t]=e[t])})}}}(); \ No newline at end of file +!function(t,e){function r(t){let e=t.substr(t.search(/[A-Z]/));return e[0].toLowerCase()+e.substr(1)}function n(t,e){let r=t.indexOf(e);-1!==r&&t.splice(r,1)}function c(e){return t[e].prototype||t[e]}t.plugin={register(t,n){const i=c(t);i._inc||(i._inc={},i._bInc=function(t,e,...r){return this._inc[e].before.reduce((e,r)=>{let n=r(t,...e);return n||e},r)},i._aInc=function(t,e,r,...n){return this._inc[e].after.reduce((e,r)=>{let c=r(t,e,...n);return c||e},r)}),e(n).forEach(t=>{let e=r(t);i[e]&&(i["_o"+e]||(i["_o"+e]=i[e],i[e]=function(...t){let r=this._bInc(this,e,...t),n=i["_o"+e].call(this,...r);return this._aInc(this,e,n,...t)}),i._inc[e]||(i._inc[e]={before:[],after:[]}),t.startsWith("before")?i._inc[e].before.push(n[t]):t.startsWith("after")&&i._inc[e].after.push(n[t]))})},unregister(t,i){const s=c(t);s._inc&&e(i).forEach(t=>{let e=r(t);t.startsWith("before")?n(s._inc[e].before,i[t]):t.startsWith("after")&&n(s._inc[e].after,i[t])})},extend(t,r){const n=c(t);e(r).forEach(t=>{n[t]||(n[t]=r[t])})}}}(kontra,Object.getOwnPropertyNames); \ No newline at end of file diff --git a/docs/api/kontra.html b/docs/api/kontra.html index ead54095..e9d9269c 100644 --- a/docs/api/kontra.html +++ b/docs/api/kontra.html @@ -127,6 +127,8 @@

      kontra​.emit(event[, ..

      Emit an event and run all its callbacks, passing it any arguments.

      +
      kontra.emit('myEvent', 1, 2, 'three');
      + @@ -177,7 +179,13 @@

      kontra​.on(event, callback)
      Function callback.
      -

      Add the callback to the event so it will run when the event is emitted.

      +

      Add the callback to the event so it will run when the event is emitted. The callback function will be passed all arguments passed to the emit function.

      + +
      kontra.on('myEvent', (param1, param2, param3) => {
      +  // ...
      + });
      +
      +kontra.emit('myEvent', 1, 2, 'three');
      diff --git a/docs/index.html b/docs/index.html index b5166d6b..872806ad 100644 --- a/docs/index.html +++ b/docs/index.html @@ -37,7 +37,7 @@

      What is Kontra.js

      Kontra.js prides itself in being:

        -
      • Lightweight: 14.5 kB minified (5.22 kB gzipped) for the entire library. The basic bundle is 3 kB minified (1.3 kB gzipped).
      • +
      • Lightweight: 14.4 kB minified (5.21 kB gzipped) for the entire library. The basic bundle is 3 kB minified (1.3 kB gzipped).
      • Modular: pick and choose what modules you want when you download. No inter-dependencies.
      • Extensible: everything is customizable and can be extended.
      • Fast: all logic has been removed from the update and render cycles.
      • diff --git a/docs/js/kontra.js b/docs/js/kontra.js index abb3160d..6e12e055 100644 --- a/docs/js/kontra.js +++ b/docs/js/kontra.js @@ -1 +1 @@ -!function(){let t={};window.kontra={init(t){let e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this.emit("init",this)},on(e,i){t[e]=t[e]||[],t[e].push(i)},off(e,i,n){!t[e]||(n=t[e].indexOf(i))<0||t[e].splice(n,1)},emit(e,...i){t[e]&&t[e].forEach(t=>t(...i))},_noop:new Function,_callbacks:t}}(),function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function u(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function l(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function f(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?u(s):n.match(i)?l(s):f(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra.emit("tick"),a+=s;a>=o;)u.update(r),a-=o;c(),u.render()}}let u={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return u},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){function t(t){let e=t.substr(t.search(/[A-Z]/));return e[0].toLowerCase()+e.substr(1)}function e(t,e){let i=t.indexOf(e);-1!==i&&t.splice(i,1)}kontra.plugin={register(e,i){const n=kontra[e].prototype||kontra[e];n._inc||(n._inc={},n._bInc=function(t,e,...i){return this._inc[e].before.reduce((e,i)=>{let n=i(t,...e);return n||e},i)},n._aInc=function(t,e,i,...n){return this._inc[e].after.reduce((e,i)=>{let s=i(t,e,...n);return s||e},i)}),Object.getOwnPropertyNames(i).forEach(e=>{let s=t(e);n[s]&&(n["_o"+s]||(n["_o"+s]=n[s],n[s]=function(...t){let e=this._bInc(this,s,...t),i=n["_o"+s].call(this,...e);return this._aInc(this,s,i,...t)}),n._inc[s]||(n._inc[s]={before:[],after:[]}),e.startsWith("before")?n._inc[s].before.push(i[e]):e.startsWith("after")&&n._inc[s].after.push(i[e]))})},unregister(i,n){const s=kontra[i].prototype||kontra[i];s._inc&&Object.getOwnPropertyNames(n).forEach(i=>{let h=t(i);i.startsWith("before")?e(s._inc[h].before,n[i]):i.startsWith("after")&&e(s._inc[h].after,n[i])})},extend(t,e){const i=kontra[t].prototype||kontra[t];Object.getOwnPropertyNames(e).forEach(t=>{i[t]||(i[t]=e[t])})}}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,f(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,f(t,"onUp")}function u(t){f(t,"onOver")}function l(t){h={}}function f(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,u=(h-c.top)*o;t.x=d,t.y=u,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra.on("tick",()=>{i.length=0,e.map(function(t){i.push(t)}),e.length=0}),kontra.on("init",()=>{kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",u),kontra.canvas.addEventListener("touchmove",u)})}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0}add(t,e){return kontra.vector(this.x+(t.x||0)*(e||1),this.y+(t.y||0)*(e||1))}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity=this.velocity.add(this.acceleration,t),this.position=this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,u=i%o.width*s,l=(i/o.width|0)*h,f=c%d*(s+a),g=(c/d|0)*(h+a);e.drawImage(r,f,g,s,h,u,l,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file +!function(){let t={};window.kontra={init(t){let e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this.emit("init",this)},on(e,i){t[e]=t[e]||[],t[e].push(i)},off(e,i,n){!t[e]||(n=t[e].indexOf(i))<0||t[e].splice(n,1)},emit(e,...i){t[e]&&t[e].forEach(t=>t(...i))},_noop:new Function,_callbacks:t}}(),function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function u(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function l(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function f(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?u(s):n.match(i)?l(s):f(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra.emit("tick"),a+=s;a>=o;)u.update(r),a-=o;c(),u.render()}}let u={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return u},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(t,e){function i(t){let e=t.substr(t.search(/[A-Z]/));return e[0].toLowerCase()+e.substr(1)}function n(t,e){let i=t.indexOf(e);-1!==i&&t.splice(i,1)}function s(e){return t[e].prototype||t[e]}t.plugin={register(t,n){const h=s(t);h._inc||(h._inc={},h._bInc=function(t,e,...i){return this._inc[e].before.reduce((e,i)=>{let n=i(t,...e);return n||e},i)},h._aInc=function(t,e,i,...n){return this._inc[e].after.reduce((e,i)=>{let s=i(t,e,...n);return s||e},i)}),e(n).forEach(t=>{let e=i(t);h[e]&&(h["_o"+e]||(h["_o"+e]=h[e],h[e]=function(...t){let i=this._bInc(this,e,...t),n=h["_o"+e].call(this,...i);return this._aInc(this,e,n,...t)}),h._inc[e]||(h._inc[e]={before:[],after:[]}),t.startsWith("before")?h._inc[e].before.push(n[t]):t.startsWith("after")&&h._inc[e].after.push(n[t]))})},unregister(t,h){const a=s(t);a._inc&&e(h).forEach(t=>{let e=i(t);t.startsWith("before")?n(a._inc[e].before,h[t]):t.startsWith("after")&&n(a._inc[e].after,h[t])})},extend(t,i){const n=s(t);e(i).forEach(t=>{n[t]||(n[t]=i[t])})}}}(kontra,Object.getOwnPropertyNames),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,f(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,f(t,"onUp")}function u(t){f(t,"onOver")}function l(t){h={}}function f(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,u=(h-c.top)*o;t.x=d,t.y=u,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra.on("tick",()=>{i.length=0,e.map(function(t){i.push(t)}),e.length=0}),kontra.on("init",()=>{kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",u),kontra.canvas.addEventListener("touchmove",u)})}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0}add(t,e){return kontra.vector(this.x+(t.x||0)*(e||1),this.y+(t.y||0)*(e||1))}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity=this.velocity.add(this.acceleration,t),this.position=this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,u=i%o.width*s,l=(i/o.width|0)*h,f=c%d*(s+a),g=(c/d|0)*(h+a);e.drawImage(r,f,g,s,h,u,l,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file diff --git a/kontra.js b/kontra.js index 6313f3cb..0228f870 100644 --- a/kontra.js +++ b/kontra.js @@ -515,7 +515,7 @@ } }; })(); -(function() { +(function(kontra, getOwnPropertyNames) { /** * Get the kontra object method name from the plugin. @@ -544,6 +544,18 @@ } } + /** + * Get the object or class prototype. + * @private + * + * @param {object} Kontra object + * + * @returns {object} + */ + function getObjectProto(object) { + return kontra[object].prototype || kontra[object]; + } + /** * Object for registering plugins. Based on interceptor pattern. * @see https://blog.kiprosh.com/javascript-method-interceptors/ @@ -561,7 +573,7 @@ * kontra.plugin.register('sprite', myPluginObject) */ register(object, plugin) { - const kontraObjectProto = kontra[object].prototype || kontra[object]; + const kontraObjectProto = getObjectProto(object); // create interceptor list and functions if (!kontraObjectProto._inc) { @@ -581,7 +593,7 @@ } // add plugin to interceptors - Object.getOwnPropertyNames(plugin).forEach(methodName => { + getOwnPropertyNames(plugin).forEach(methodName => { let method = getMethod(methodName); if (!kontraObjectProto[method]) return; @@ -630,12 +642,12 @@ * kontra.plugin.unregister('sprite', myPluginObject) */ unregister(object, plugin) { - const kontraObjectProto = kontra[object].prototype || kontra[object]; + const kontraObjectProto = getObjectProto(object); if (!kontraObjectProto._inc) return; // remove plugin from interceptors - Object.getOwnPropertyNames(plugin).forEach(methodName => { + getOwnPropertyNames(plugin).forEach(methodName => { let method = getMethod(methodName); if (methodName.startsWith('before')) { @@ -655,16 +667,16 @@ * @param {object} properties - Properties to add */ extend(object, properties) { - const kontraObjectProto = kontra[object].prototype || kontra[object]; + const kontraObjectProto = getObjectProto(object); - Object.getOwnPropertyNames(properties).forEach(prop => { + getOwnPropertyNames(properties).forEach(prop => { if (!kontraObjectProto[prop]) { kontraObjectProto[prop] = properties[prop]; } }); } }; -})(); +})(kontra, Object.getOwnPropertyNames); (function() { let pointer; diff --git a/kontra.min.js b/kontra.min.js index abb3160d..6e12e055 100644 --- a/kontra.min.js +++ b/kontra.min.js @@ -1 +1 @@ -!function(){let t={};window.kontra={init(t){let e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this.emit("init",this)},on(e,i){t[e]=t[e]||[],t[e].push(i)},off(e,i,n){!t[e]||(n=t[e].indexOf(i))<0||t[e].splice(n,1)},emit(e,...i){t[e]&&t[e].forEach(t=>t(...i))},_noop:new Function,_callbacks:t}}(),function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function u(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function l(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function f(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?u(s):n.match(i)?l(s):f(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra.emit("tick"),a+=s;a>=o;)u.update(r),a-=o;c(),u.render()}}let u={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return u},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(){function t(t){let e=t.substr(t.search(/[A-Z]/));return e[0].toLowerCase()+e.substr(1)}function e(t,e){let i=t.indexOf(e);-1!==i&&t.splice(i,1)}kontra.plugin={register(e,i){const n=kontra[e].prototype||kontra[e];n._inc||(n._inc={},n._bInc=function(t,e,...i){return this._inc[e].before.reduce((e,i)=>{let n=i(t,...e);return n||e},i)},n._aInc=function(t,e,i,...n){return this._inc[e].after.reduce((e,i)=>{let s=i(t,e,...n);return s||e},i)}),Object.getOwnPropertyNames(i).forEach(e=>{let s=t(e);n[s]&&(n["_o"+s]||(n["_o"+s]=n[s],n[s]=function(...t){let e=this._bInc(this,s,...t),i=n["_o"+s].call(this,...e);return this._aInc(this,s,i,...t)}),n._inc[s]||(n._inc[s]={before:[],after:[]}),e.startsWith("before")?n._inc[s].before.push(i[e]):e.startsWith("after")&&n._inc[s].after.push(i[e]))})},unregister(i,n){const s=kontra[i].prototype||kontra[i];s._inc&&Object.getOwnPropertyNames(n).forEach(i=>{let h=t(i);i.startsWith("before")?e(s._inc[h].before,n[i]):i.startsWith("after")&&e(s._inc[h].after,n[i])})},extend(t,e){const i=kontra[t].prototype||kontra[t];Object.getOwnPropertyNames(e).forEach(t=>{i[t]||(i[t]=e[t])})}}}(),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,f(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,f(t,"onUp")}function u(t){f(t,"onOver")}function l(t){h={}}function f(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,u=(h-c.top)*o;t.x=d,t.y=u,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra.on("tick",()=>{i.length=0,e.map(function(t){i.push(t)}),e.length=0}),kontra.on("init",()=>{kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",u),kontra.canvas.addEventListener("touchmove",u)})}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0}add(t,e){return kontra.vector(this.x+(t.x||0)*(e||1),this.y+(t.y||0)*(e||1))}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity=this.velocity.add(this.acceleration,t),this.position=this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,u=i%o.width*s,l=(i/o.width|0)*h,f=c%d*(s+a),g=(c/d|0)*(h+a);e.drawImage(r,f,g,s,h,u,l,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file +!function(){let t={};window.kontra={init(t){let e=this.canvas=document.getElementById(t)||t||document.querySelector("canvas");this.context=e.getContext("2d"),this.context.imageSmoothingEnabled=!1,this.emit("init",this)},on(e,i){t[e]=t[e]||[],t[e].push(i)},off(e,i,n){!t[e]||(n=t[e].indexOf(i))<0||t[e].splice(n,1)},emit(e,...i){t[e]&&t[e].forEach(t=>t(...i))},_noop:new Function,_callbacks:t}}(),function(){let t,e=/(jpeg|jpg|gif|png)$/,i=/(wav|mp3|ogg|aac)$/,n=/^no$/,s=/^\//,h=/\/$/,a=new Audio,o={wav:"",mp3:a.canPlayType("audio/mpeg;").replace(n,""),ogg:a.canPlayType('audio/ogg; codecs="vorbis"').replace(n,""),aac:a.canPlayType("audio/aac;").replace(n,"")};function r(t,e){return[t.replace(h,""),t?e.replace(s,""):e].filter(t=>t).join("/")}function c(t){return t.split(".").pop()}function d(t){let e=t.replace("."+c(t),"");return 2==e.split("/").length?e.replace(s,""):e}function u(e,i){return new Promise(function(n,s){let h=new Image;i=r(t.imagePath,e),h.onload=function(){let s=t._u(i,window.location.href);t.images[d(e)]=t.images[i]=t.images[s]=this,n(this)},h.onerror=function(){s(i)},h.src=i})}function l(e,i,n){return new Promise(function(s,h){if(e=[].concat(e).reduce(function(t,e){return o[c(e)]?e:t},n)){let n=new Audio;i=r(t.audioPath,e),n.addEventListener("canplay",function(){let n=t._u(i,window.location.href);t.audio[d(e)]=t.audio[i]=t.audio[n]=this,s(this)}),n.onerror=function(){h(i)},n.src=i,n.load()}else h(e)})}function f(e,i){return i=r(t.dataPath,e),fetch(i).then(function(t){if(!t.ok)throw t;return t.clone().json().catch(function(){return t.text()})}).then(function(n){let s=t._u(i,window.location.href);return"object"==typeof n&&t._d.set(n,s),t.data[d(e)]=t.data[i]=t.data[s]=n,n})}t=kontra.assets={images:{},audio:{},data:{},_d:new WeakMap,_u:(t,e)=>new URL(t,e).href,imagePath:"",audioPath:"",dataPath:"",load(){let t,n,s,h,a,o=[];for(h=0;s=arguments[h];h++)a=(n=c(t=[].concat(s)[0])).match(e)?u(s):n.match(i)?l(s):f(s),o.push(a);return Promise.all(o)}}}(),kontra.gameLoop=function(t){let e,i,n,s,h=(t=t||{}).fps||60,a=0,o=1e3/h,r=1/h,c=!1===t.clearCanvas?kontra._noop:function(){kontra.context.clearRect(0,0,kontra.canvas.width,kontra.canvas.height)};function d(){if(i=requestAnimationFrame(d),n=performance.now(),s=n-e,e=n,!(s>1e3)){for(kontra.emit("tick"),a+=s;a>=o;)u.update(r),a-=o;c(),u.render()}}let u={update:t.update,render:t.render,isStopped:!0,start(){e=performance.now(),this.isStopped=!1,requestAnimationFrame(d)},stop(){this.isStopped=!0,cancelAnimationFrame(i)}};return u},function(){let t={},e={},n={13:"enter",27:"esc",32:"space",37:"left",38:"up",39:"right",40:"down"};for(let t=0;t<26;t++)n[65+t]=(10+t).toString(36);for(i=0;i<10;i++)n[48+i]=""+i;addEventListener("keydown",function(i){let s=n[i.which];e[s]=!0,t[s]&&t[s](i)}),addEventListener("keyup",function(t){e[n[t.which]]=!1}),addEventListener("blur",function(t){e={}}),kontra.keys={map:n,bind(e,i){[].concat(e).map(function(e){t[e]=i})},unbind(e,i){[].concat(e).map(function(e){t[e]=i})},pressed:t=>!!e[t]}}(),function(t,e){function i(t){let e=t.substr(t.search(/[A-Z]/));return e[0].toLowerCase()+e.substr(1)}function n(t,e){let i=t.indexOf(e);-1!==i&&t.splice(i,1)}function s(e){return t[e].prototype||t[e]}t.plugin={register(t,n){const h=s(t);h._inc||(h._inc={},h._bInc=function(t,e,...i){return this._inc[e].before.reduce((e,i)=>{let n=i(t,...e);return n||e},i)},h._aInc=function(t,e,i,...n){return this._inc[e].after.reduce((e,i)=>{let s=i(t,e,...n);return s||e},i)}),e(n).forEach(t=>{let e=i(t);h[e]&&(h["_o"+e]||(h["_o"+e]=h[e],h[e]=function(...t){let i=this._bInc(this,e,...t),n=h["_o"+e].call(this,...i);return this._aInc(this,e,n,...t)}),h._inc[e]||(h._inc[e]={before:[],after:[]}),t.startsWith("before")?h._inc[e].before.push(n[t]):t.startsWith("after")&&h._inc[e].after.push(n[t]))})},unregister(t,h){const a=s(t);a._inc&&e(h).forEach(t=>{let e=i(t);t.startsWith("before")?n(a._inc[e].before,h[t]):t.startsWith("after")&&n(a._inc[e].after,h[t])})},extend(t,i){const n=s(t);e(i).forEach(t=>{n[t]||(n[t]=i[t])})}}}(kontra,Object.getOwnPropertyNames),function(){let t,e=[],i=[],n={},s=[],h={},a={0:"left",1:"middle",2:"right"};function o(e){let i=e.x,n=e.y;e.anchor&&(i-=e.width*e.anchor.x,n-=e.height*e.anchor.y);let s=t.x-Math.max(i,Math.min(t.x,i+e.width)),h=t.y-Math.max(n,Math.min(t.y,n+e.height));return s*s+h*h=0;e--)if(s=(n=h[e]).collidesWithPointer?n.collidesWithPointer(t):o(n))return n}function c(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!0,f(t,"onDown")}function d(t){let e=void 0!==t.button?a[t.button]:"left";h[e]=!1,f(t,"onUp")}function u(t){f(t,"onOver")}function l(t){h={}}function f(e,i){if(!kontra.canvas)return;let s,h;-1!==["touchstart","touchmove","touchend"].indexOf(e.type)?(s=(e.touches[0]||e.changedTouches[0]).clientX,h=(e.touches[0]||e.changedTouches[0]).clientY):(s=e.clientX,h=e.clientY);let a,o=kontra.canvas.height/kontra.canvas.offsetHeight,c=kontra.canvas.getBoundingClientRect(),d=(s-c.left)*o,u=(h-c.top)*o;t.x=d,t.y=u,e.target===kontra.canvas&&(e.preventDefault(),(a=r())&&a[i]&&a[i](e)),n[i]&&n[i](e,a)}t=kontra.pointer={x:0,y:0,radius:5,track(t){[].concat(t).map(function(t){t._r||(t._r=t.render,t.render=function(){e.push(this),this._r()},s.push(t))})},untrack(t,e){[].concat(t).map(function(t){t.render=t._r,t._r=e;let i=s.indexOf(t);-1!==i&&s.splice(i,1)})},over:t=>-1!==s.indexOf(t)&&r()===t,onDown(t){n.onDown=t},onUp(t){n.onUp=t},pressed:t=>!!h[t]},kontra.on("tick",()=>{i.length=0,e.map(function(t){i.push(t)}),e.length=0}),kontra.on("init",()=>{kontra.canvas.addEventListener("mousedown",c),kontra.canvas.addEventListener("touchstart",c),kontra.canvas.addEventListener("mouseup",d),kontra.canvas.addEventListener("touchend",d),kontra.canvas.addEventListener("blur",l),kontra.canvas.addEventListener("mousemove",u),kontra.canvas.addEventListener("touchmove",u)})}(),kontra.pool=function(t){let e=0;return{_c:(t=t||{}).create,objects:[t.create()],size:1,maxSize:t.maxSize||1024,get(t){if(t=t||{},this.objects.length==e){if(this.size===this.maxSize)return;for(let t=0;t=s;)(i=this.objects[n]).update(t),i.isAlive()?n--:(this.objects=this.objects.splice(n,1).concat(this.objects),e--,s++)},render(){let t=Math.max(this.objects.length-e,0);for(let e=this.size-1;e>=t;e--)this.objects[e].render()}}},kontra.quadtree=function(t){return{maxDepth:(t=t||{}).maxDepth||3,maxObjects:t.maxObjects||25,_b:!1,_d:t.depth||0,bounds:t.bounds||{x:0,y:0,width:kontra.canvas.width,height:kontra.canvas.height},objects:[],subnodes:[],clear(){this.subnodes.map(function(t){t.clear()}),this._b=!1,this.objects.length=0},get(t){let e,i,n=[];for(;this.subnodes.length&&this._b;){for(e=this._g(t),i=0;ithis.maxObjects&&this._d=this.bounds.y,h=t.y+t.height>=n&&t.y=this.bounds.x&&(s&&e.push(0),h&&e.push(2)),t.x+t.width>=i&&t.x=2?e:0),width:t,height:e},depth:this._d+1,maxDepth:this.maxDepth,maxObjects:this.maxObjects})}}},function(){class t{constructor(t,e){this._x=t||0,this._y=e||0}add(t,e){return kontra.vector(this.x+(t.x||0)*(e||1),this.y+(t.y||0)*(e||1))}clamp(t,e,i,n){this._c=!0,this._a=t,this._b=e,this._d=i,this._e=n}get x(){return this._x}get y(){return this._y}set x(t){this._x=this._c?Math.min(Math.max(this._a,t),this._d):t}set y(t){this._y=this._c?Math.min(Math.max(this._b,t),this._e):t}}kontra.vector=((e,i)=>new t(e,i)),kontra.vector.prototype=t.prototype;class e{init(t,e,i,n){for(e in t=t||{},this.position=kontra.vector(t.x,t.y),this.velocity=kontra.vector(t.dx,t.dy),this.acceleration=kontra.vector(t.ddx,t.ddy),this.width=this.height=this.rotation=0,this.ttl=1/0,this.anchor={x:0,y:0},this.context=kontra.context,t)this[e]=t[e];if(i=t.image)this.image=i,this.width=void 0!==t.width?t.width:i.width,this.height=void 0!==t.height?t.height:i.height;else if(i=t.animations){for(e in i)this.animations[e]=i[e].clone(),n=n||i[e];this._ca=n,this.width=this.width||n.width,this.height=this.height||n.height}return this}get x(){return this.position.x}get y(){return this.position.y}get dx(){return this.velocity.x}get dy(){return this.velocity.y}get ddx(){return this.acceleration.x}get ddy(){return this.acceleration.y}set x(t){this.position.x=t}set y(t){this.position.y=t}set dx(t){this.velocity.x=t}set dy(t){this.velocity.y=t}set ddx(t){this.acceleration.x=t}set ddy(t){this.acceleration.y=t}isAlive(){return this.ttl>0}collidesWith(t){if(this.rotation||t.rotation)return null;let e=this.x-this.width*this.anchor.x,i=this.y-this.height*this.anchor.y,n=t.x,s=t.y;return t.anchor&&(n-=t.width*t.anchor.x,s-=t.height*t.anchor.y),en&&is}update(t){this.advance(t)}render(){this.draw()}playAnimation(t){this._ca=this.animations[t],this._ca.loop||this._ca.reset()}advance(t){this.velocity=this.velocity.add(this.acceleration,t),this.position=this.position.add(this.velocity,t),this.ttl--,this._ca&&this._ca.update(t)}draw(){let t=-this.width*this.anchor.x,e=-this.height*this.anchor.y;this.context.save(),this.context.translate(this.x,this.y),this.rotation&&this.context.rotate(this.rotation),this.image?this.context.drawImage(this.image,0,0,this.image.width,this.image.height,t,e,this.width,this.height):this._ca?this._ca.render({x:t,y:e,width:this.width,height:this.height,context:this.context}):(this.context.fillStyle=this.color,this.context.fillRect(t,e,this.width,this.height)),this.context.restore()}}kontra.sprite=(t=>(new e).init(t)),kontra.sprite.prototype=e.prototype}(),function(){class t{constructor(t,e){t=t||{},this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.frameRate=t.frameRate,this.loop=void 0===t.loop||t.loop,e=t.spriteSheet.frame,this.width=e.width,this.height=e.height,this.margin=e.margin||0,this._f=0,this._a=0}clone(){return kontra.animation(this)}reset(){this._f=0,this._a=0}update(t){if(this.loop||this._f!=this.frames.length-1)for(t=t||1/60,this._a+=t;this._a*this.frameRate>=1;)this._f=++this._f%this.frames.length,this._a-=1/this.frameRate}render(t){t=t||{};let e=this.frames[this._f]/this.spriteSheet._f|0,i=this.frames[this._f]%this.spriteSheet._f|0,n=void 0!==t.width?t.width:this.width,s=void 0!==t.height?t.height:this.height;(t.context||kontra.context).drawImage(this.spriteSheet.image,i*this.width+(2*i+1)*this.margin,e*this.height+(2*e+1)*this.margin,this.width,this.height,t.x,t.y,n,s)}}kontra.animation=function(e){return new t(e)},kontra.animation.prototype=t.prototype;class e{constructor(t){t=t||{},this.animations={},this.image=t.image,this.frame={width:t.frameWidth,height:t.frameHeight,margin:t.frameMargin},this._f=t.image.width/t.frameWidth|0,this.createAnimations(t.animations)}createAnimations(t){let e,i,n,s;for(s in t)i=(e=t[s]).frames,n=[],[].concat(i).map(function(t){n=n.concat(this._p(t))},this),this.animations[s]=kontra.animation({spriteSheet:this,frames:n,frameRate:e.frameRate,loop:e.loop})}_p(t,e){if(+t===t)return t;let i=[],n=t.split(".."),s=e=+n[0],h=+n[1];if(s=h;e--)i.push(e);return i}}kontra.spriteSheet=function(t){return new e(t)},kontra.spriteSheet.prototype=e.prototype}(),kontra.store={set(t,e){void 0===e?localStorage.removeItem(t):localStorage.setItem(t,JSON.stringify(e))},get(t){let e=localStorage.getItem(t);try{e=JSON.parse(e)}catch(t){}return e}},kontra.tileEngine=function(t){let e=t.width*t.tilewidth,i=t.height*t.tileheight,n=document.createElement("canvas"),s=n.getContext("2d");n.width=e,n.height=i;let h={},a={},o=Object.assign({mapwidth:e,mapheight:i,_sx:0,_sy:0,get sx(){return this._sx},get sy(){return this._sy},set sx(t){this._sx=Math.min(Math.max(0,t),e-kontra.canvas.width)},set sy(t){this._sy=Math.min(Math.max(0,t),i-kontra.canvas.height)},render(){d(n)},renderLayer(t){let n=a[t],s=h[t];n||((n=document.createElement("canvas")).width=e,n.height=i,a[t]=n,o._r(s,n.getContext("2d"))),d(n)},layerCollidesWith(t,e){let i=r(e.y),n=c(e.x),s=r(e.y+e.height),a=c(e.x+e.width),o=h[t];for(let t=i;t<=s;t++)for(let e=n;e<=a;e++)if(o.data[e+t*this.width])return!0;return!1},tileAtLayer(t,e){let i=e.row||r(e.y),n=e.col||c(e.x);return h[t]?h[t].data[n+i*o.width]:-1},_r:function(t,e){e.save(),e.globalAlpha=t.opacity,t.data.forEach((t,i)=>{if(!t)return;let n;for(let e=o.tilesets.length-1;e>=0&&(n=o.tilesets[e],!(t/n.firstgid>=1));e--);let s=n.tilewidth||o.tilewidth,h=n.tileheight||o.tileheight,a=n.margin||0,r=n.image,c=t-n.firstgid,d=n.columns||r.width/(s+a)|0,u=i%o.width*s,l=(i/o.width|0)*h,f=c%d*(s+a),g=(c/d|0)*(h+a);e.drawImage(r,f,g,s,h,u,l,s,h)}),e.restore()}},t);function r(t){return(o.sy+t)/o.tileheight|0}function c(t){return(o.sx+t)/o.tilewidth|0}function d(t){(o.context||kontra.context).drawImage(t,o.sx,o.sy,kontra.canvas.width,kontra.canvas.height,0,0,kontra.canvas.width,kontra.canvas.height)}return o.tilesets.forEach(e=>{let i=(kontra.assets?kontra.assets._d.get(t):"")||window.location.href;if(e.source){let t=kontra.assets.data[kontra.assets._u(e.source,i)];Object.keys(t).forEach(i=>{e[i]=t[i]})}if(""+e.image===e.image){let t=kontra.assets.images[kontra.assets._u(e.image,i)];e.image=t}}),o.layers&&o.layers.forEach(t=>{h[t.name]=t,!1!==t.visible&&o._r(t,s)}),o}; \ No newline at end of file diff --git a/src/plugin.js b/src/plugin.js index b4f53d6d..72262fef 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -1,4 +1,4 @@ -(function() { +(function(kontra, getOwnPropertyNames) { /** * Get the kontra object method name from the plugin. @@ -27,6 +27,18 @@ } } + /** + * Get the object or class prototype. + * @private + * + * @param {object} Kontra object + * + * @returns {object} + */ + function getObjectProto(object) { + return kontra[object].prototype || kontra[object]; + } + /** * Object for registering plugins. Based on interceptor pattern. * @see https://blog.kiprosh.com/javascript-method-interceptors/ @@ -44,7 +56,7 @@ * kontra.plugin.register('sprite', myPluginObject) */ register(object, plugin) { - const kontraObjectProto = kontra[object].prototype || kontra[object]; + const kontraObjectProto = getObjectProto(object); // create interceptor list and functions if (!kontraObjectProto._inc) { @@ -64,7 +76,7 @@ } // add plugin to interceptors - Object.getOwnPropertyNames(plugin).forEach(methodName => { + getOwnPropertyNames(plugin).forEach(methodName => { let method = getMethod(methodName); if (!kontraObjectProto[method]) return; @@ -113,12 +125,12 @@ * kontra.plugin.unregister('sprite', myPluginObject) */ unregister(object, plugin) { - const kontraObjectProto = kontra[object].prototype || kontra[object]; + const kontraObjectProto = getObjectProto(object); if (!kontraObjectProto._inc) return; // remove plugin from interceptors - Object.getOwnPropertyNames(plugin).forEach(methodName => { + getOwnPropertyNames(plugin).forEach(methodName => { let method = getMethod(methodName); if (methodName.startsWith('before')) { @@ -138,13 +150,13 @@ * @param {object} properties - Properties to add */ extend(object, properties) { - const kontraObjectProto = kontra[object].prototype || kontra[object]; + const kontraObjectProto = getObjectProto(object); - Object.getOwnPropertyNames(properties).forEach(prop => { + getOwnPropertyNames(properties).forEach(prop => { if (!kontraObjectProto[prop]) { kontraObjectProto[prop] = properties[prop]; } }); } }; -})(); \ No newline at end of file +})(kontra, Object.getOwnPropertyNames); \ No newline at end of file From 5e7f75c623a05e4ddddfb21817d467384f6b2e08 Mon Sep 17 00:00:00 2001 From: straker Date: Sat, 23 Feb 2019 12:42:57 -0700 Subject: [PATCH 10/10] wrap plugins in IIFE --- examples/plugins/advancedCollisionPlugin.html | 133 +++++++++--------- examples/plugins/extendingVecotr.html | 64 +++++---- 2 files changed, 103 insertions(+), 94 deletions(-) diff --git a/examples/plugins/advancedCollisionPlugin.html b/examples/plugins/advancedCollisionPlugin.html index 40e1b958..58148bd5 100644 --- a/examples/plugins/advancedCollisionPlugin.html +++ b/examples/plugins/advancedCollisionPlugin.html @@ -8,70 +8,76 @@