kippy 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/bundle.min.js +1 -1
- package/dist/types/animation.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -327,7 +327,9 @@ import { SpriteSheet } from "kippy";
|
|
|
327
327
|
const spriteSheet = new SpriteSheet({
|
|
328
328
|
texture, // Similar to sprite texture
|
|
329
329
|
frameWidth, // Width of each frame, type number
|
|
330
|
-
frameHeight // Height of each frame, type number
|
|
330
|
+
frameHeight, // Height of each frame, type number
|
|
331
|
+
width, // Width when render, default is frameWidth
|
|
332
|
+
height // Height when render, default is frameHeight
|
|
331
333
|
});
|
|
332
334
|
```
|
|
333
335
|
|
package/dist/bundle.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class t{x;y;static ZERO=new t(0,0);static ONE=new t(1,1);static UP=new t(0,-1);static DOWN=new t(0,1);static LEFT=new t(-1,0);static RIGHT=new t(1,0);constructor(t,e){this.x=t,this.y=e}toString(){return`Vector2(${this.x}, ${this.y})`}add(e){return new t(this.x+e.x,this.y+e.y)}sub(e){return new t(this.x-e.x,this.y-e.y)}mul(e){return new t(this.x*e.x,this.y*e.y)}div(e){return new t(this.x/e.x,this.y/e.y)}neg(){return new t(-this.x,-this.y)}scale(e){return new t(this.x*e,this.y*e)}magnitude(){return Math.sqrt(this.x*this.x+this.y*this.y)}magnitudeSquared(){return this.x*this.x+this.y*this.y}normalize(){const e=this.magnitude();return e>0?new t(this.x/e,this.y/e):new t(0,0)}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}project(t){const e=this.dot(t)/t.magnitudeSquared();return t.scale(e)}min(e){return new t(Math.min(this.x,e.x),Math.min(this.y,e.y))}max(e){return new t(Math.max(this.x,e.x),Math.max(this.y,e.y))}floor(){return new t(Math.floor(this.x),Math.floor(this.y))}ceil(){return new t(Math.ceil(this.x),Math.ceil(this.y))}round(){return new t(Math.round(this.x),Math.round(this.y))}distance(t){return Math.sqrt((this.x-t.x)**2+(this.y-t.y)**2)}distanceSquared(t){return(this.x-t.x)**2+(this.y-t.y)**2}copy(){return new t(this.x,this.y)}lerp(t,e){return this.add(t.sub(this).scale(e))}clamp(t){const e=this.magnitude();return e>t?this.scale(t/e):this.copy()}rotate(e){const i=Math.cos(e),s=Math.sin(e);return new t(this.x*i-this.y*s,this.x*s+this.y*i)}orthogonal(){return new t(-this.y,this.x)}angle(){return Math.atan2(this.y,this.x)}angleTo(t){return Math.atan2(t.y-this.y,t.x-this.x)}reflect(t){const e=this.dot(t);return this.sub(t.scale(2*e))}equals(t){return this.x===t.x&&this.y===t.y}}class e{canvas;keys=new Set;keysPressed=new Set;keysReleased=new Set;pointer=new t(0,0);pointers=new Set;pointersPressed=new Set;pointersReleased=new Set;constructor(t){this.canvas=t.canvas,window.addEventListener("keydown",t=>{this.keys.has(t.key)||this.keysPressed.add(t.key),this.keys.add(t.key)}),window.addEventListener("keyup",t=>{this.keys.delete(t.key),this.keysReleased.add(t.key)}),this.canvas.addEventListener("mousemove",t=>{const e=this.canvas.getBoundingClientRect();this.pointer.x=t.clientX-e.left,this.pointer.y=t.clientY-e.top}),this.canvas.addEventListener("mousedown",t=>{this.pointers.has(t.button)||this.pointersPressed.add(t.button),this.pointers.add(t.button)}),this.canvas.addEventListener("mouseup",t=>{this.pointers.delete(t.button),this.pointersReleased.add(t.button)}),this.canvas.addEventListener("touchmove",t=>{t.preventDefault();const e=this.canvas.getBoundingClientRect(),i=t.touches[0];this.pointer.x=i.clientX-e.left,this.pointer.y=i.clientY-e.top}),this.canvas.addEventListener("touchstart",t=>{t.preventDefault(),this.pointers.has(2)||this.pointersPressed.add(2),this.pointers.add(2)}),this.canvas.addEventListener("touchend",t=>{t.preventDefault(),this.pointers.delete(2),this.pointersReleased.add(2)}),this.canvas.addEventListener("contextmenu",t=>{t.preventDefault()})}update(){this.keysPressed.clear(),this.keysReleased.clear(),this.pointersPressed.clear(),this.pointersReleased.clear()}isKeyDown(t){return this.keys.has(t)}isKeyPressed(t){return this.keysPressed.has(t)}isKeyReleased(t){return this.keysReleased.has(t)}isPointerDown(t=0){return this.pointers.has(t)}isPointerPressed(t=0){return this.pointersPressed.has(t)}isPointerReleased(t=0){return this.pointersReleased.has(t)}}class i{velocity;rotationVelocity;mass;inertia;force;torque;restitution;sleepThreshold;sleepTimeThreshold;isSleeping;sleepTimer;constructor(e={}){this.velocity=e.velocity??new t(0,0),this.rotationVelocity=e.rotationVelocity??0,this.mass=e.mass??1,this.inertia=e.inertia??1,this.force=e.force??new t(0,0),this.torque=e.torque??0,this.restitution=e.restitution??0,this.sleepThreshold=e.sleepThreshold??.1,this.sleepTimeThreshold=e.sleepTimeThreshold??.5,this.isSleeping=e.isSleeping??!1,this.sleepTimer=e.sleepTimer??0}wake(){this.isSleeping=!1,this.sleepTimer=0}}class s{radius;offset;isTrigger;layer;mask;constructor(e){this.radius=e.radius,this.offset=e.offset??new t(0,0),this.isTrigger=e.isTrigger??!1,this.layer=e.layer??1,this.mask=e.mask??4294967295}}class o{width;height;offset;isTrigger;layer;mask;constructor(e){this.width=e.width,this.height=e.height,this.offset=e.offset??new t(0,0),this.isTrigger=e.isTrigger??!1,this.layer=e.layer??1,this.mask=e.mask??4294967295}}class n{cellSize;grid;constructor(t={}){this.cellSize=t.cellSize||100,this.grid=t.grid||new Map}clear(){this.grid.clear()}adaptCellSize(t){const e=[];for(const i of t){const t=this.getEntityBounds(i),s=t.maxX-t.minX,o=t.maxY-t.minY,n=Math.max(s,o);e.push(n)}e.sort((t,e)=>t-e);const i=e[Math.floor(.75*e.length)];this.cellSize=2.5*i}insert(t){if(t.collider){const e=this.getEntityBounds(t),i=Math.floor(e.minX/this.cellSize),s=Math.floor(e.maxX/this.cellSize),o=Math.floor(e.minY/this.cellSize),n=Math.floor(e.maxY/this.cellSize);for(let e=i;e<=s;e++)for(let i=o;i<=n;i++){const s=`${e},${i}`;this.grid.has(s)||this.grid.set(s,new Set),this.grid.get(s).add(t)}}}getNearby(t){const e=Math.floor(t.position.x/this.cellSize),i=Math.floor(t.position.y/this.cellSize),s=new Set;for(let o=-1;o<=1;o++)for(let n=-1;n<=1;n++){const r=`${e+o},${i+n}`,a=this.grid.get(r);if(a)for(const e of a)e!==t&&s.add(e)}return Array.from(s)}getEntityBounds(t){if(t.collider instanceof s){const e=t.collider.radius;return{minX:t.position.x-e,maxX:t.position.x+e,minY:t.position.y-e,maxY:t.position.y+e}}if(t.collider instanceof o){const e=t.collider.width,i=t.collider.height;return{minX:t.position.x-e,maxX:t.position.x+e,minY:t.position.y-i,maxY:t.position.y+i}}throw new Error("Collider type not supported")}}class r{collisionPairs=new Map;spatialGrid=new n;entityCount=0;update(e,s){for(const t of e)if(t.body instanceof i){if((t.body.force.magnitudeSquared()>0||0!==t.body.torque||t.body.velocity.magnitudeSquared()>t.body.sleepThreshold**2||Math.abs(t.body.rotationVelocity)>t.body.sleepThreshold)&&t.body.wake(),t.body.isSleeping)continue;t.body.velocity.x+=t.body.force.x/t.body.mass*s,t.body.velocity.y+=t.body.force.y/t.body.mass*s,t.body.rotationVelocity+=t.body.torque/t.body.inertia*s,t.body.force.x=0,t.body.force.y=0,t.body.torque=0}const o=e.filter(t=>t.collider);this.spatialGrid.clear(),this.entityCount!==o.length&&(this.entityCount=o.length,this.spatialGrid.adaptCellSize(o));for(const t of o)this.spatialGrid.insert(t);const n=new Map,r=[];for(const e of o){const i=this.spatialGrid.getNearby(e);for(const s of i)if(s.collider){if(n.has(e)&&n.get(e)?.has(s)||n.has(s)&&n.get(s)?.has(e))continue;const i=this.checkCollision(e,s);if(i.info){e.body?.isSleeping&&s.body?.isSleeping||(e.body?.wake(),s.body?.wake()),n.has(e)||n.set(e,new Map),n.get(e)?.set(s,i.info);this.collisionPairs.get(e)?.has(s)||this.collisionPairs.get(s)?.has(e)?i.isTrigger?(e.onTriggerStay?.(s),s.onTriggerStay?.(e)):(e.onCollisionStay?.(s,i.info),s.onCollisionStay?.(e,{...i.info,normal:new t(-i.info.normal.x,-i.info.normal.y)})):i.isTrigger?(e.onTriggerEnter?.(s),s.onTriggerEnter?.(e)):(e.onCollisionEnter?.(s,i.info),s.onCollisionEnter?.(e,{...i.info,normal:new t(-i.info.normal.x,-i.info.normal.y)})),i.isTrigger||r.push({entityA:e,entityB:s,info:i.info})}}}for(let t=0;t<6;t++)for(const t of r)this.resolveCollision(t.entityA,t.entityB,t.info,s);for(const[e,i]of this.collisionPairs)for(const[s,o]of i){if(!(n.get(e)?.has(s)||n.get(s)?.has(e))){e.collider?.isTrigger||s.collider?.isTrigger?(e.onTriggerExit?.(s),s.onTriggerExit?.(e)):(e.onCollisionExit?.(s,o),s.onCollisionExit?.(e,{...o,normal:new t(-o.normal.x,-o.normal.y)}))}}this.collisionPairs=n;for(const t of e)if(t.body instanceof i){if(t.body.isSleeping)continue;t.position.x+=t.body.velocity.x*s,t.position.y+=t.body.velocity.y*s,t.rotation+=t.body.rotationVelocity*s;const e=t.body.velocity.magnitude(),i=Math.abs(t.body.rotationVelocity);e<t.body.sleepThreshold&&i<t.body.sleepThreshold?(t.body.sleepTimer+=s,t.body.sleepTimer>=t.body.sleepTimeThreshold&&(t.body.isSleeping=!0,t.body.velocity.x=0,t.body.velocity.y=0,t.body.rotationVelocity=0)):t.body.sleepTimer=0}}checkCollision(e,i){if(e.collider&&i.collider){if(0===(e.collider.mask&i.collider.layer)||0===(i.collider.mask&e.collider.layer))return{isTrigger:!1};const n=e.collider.isTrigger||i.collider.isTrigger,r=e.position.add(e.collider.offset),a=i.position.add(i.collider.offset);if(e.collider instanceof s&&i.collider instanceof s){const s=r.distance(a),o=e.collider.radius+i.collider.radius;if(s>=o)return{isTrigger:!1};const h=o-s,l=a.sub(r);let c=s>0?l.scale(1/s):new t(1,0),d=0;const u=this.collisionPairs.get(e)?.get(i)||this.collisionPairs.get(i)?.get(e);return u&&(d=u.accumulatedNormalImpulse),{isTrigger:n,info:{normal:c,penetration:h,contact:r.add(c.scale(e.collider.radius)),accumulatedNormalImpulse:d}}}if(e.collider instanceof o&&i.collider instanceof o){const s=e.collider.width/2,o=e.collider.height/2,h=i.collider.width/2,l=i.collider.height/2,c=a.sub(r),d=s+h-Math.abs(c.x),u=o+l-Math.abs(c.y);if(d<=0||u<=0)return{isTrigger:!1};let y,m;d<u?(m=d,y=new t(c.x<0?-1:1,0)):(m=u,y=new t(0,c.y<0?-1:1));const p=r.x+Math.sign(c.x)*(s-d/2),f=r.y+Math.sign(c.y)*(o-u/2);let g=0;const x=this.collisionPairs.get(e)?.get(i)||this.collisionPairs.get(i)?.get(e);return x&&(g=x.accumulatedNormalImpulse),{isTrigger:n,info:{normal:y,penetration:m,contact:new t(p,f),accumulatedNormalImpulse:g}}}if(e.collider instanceof s&&i.collider instanceof o||e.collider instanceof o&&i.collider instanceof s){const o=e.collider instanceof s,h=o?r:a,l=o?a:r,c=(o?e:i).collider,d=(o?i:e).collider,u=d.width/2,y=d.height/2,m=h.sub(l);let p,f,g;if(Math.abs(m.x)<u&&Math.abs(m.y)<y){const e=u-Math.abs(m.x),i=y-Math.abs(m.y);if(e<i){const i=m.x<0?-1:1,s=new t(i,0);p=o?s.neg():s,f=c.radius+e,g=new t(l.x+i*u,h.y)}else{const e=m.y<0?-1:1,s=new t(0,e);p=o?s.neg():s,f=c.radius+i,g=new t(h.x,l.y+e*y)}}else{const e=Math.max(-u,Math.min(u,m.x)),i=Math.max(-y,Math.min(y,m.y)),s=l.add(new t(e,i)),n=h.sub(s),r=n.magnitudeSquared();if(r>=c.radius*c.radius)return{isTrigger:!1};const a=Math.sqrt(r),d=a>0?n.scale(1/a):new t(1,0);p=o?d.neg():d,f=c.radius-a,g=s}let x=0;const w=this.collisionPairs.get(e)?.get(i)||this.collisionPairs.get(i)?.get(e);return w&&(x=w.accumulatedNormalImpulse),{isTrigger:n,info:{normal:p,penetration:f,contact:g,accumulatedNormalImpulse:x}}}return{isTrigger:!1}}return{isTrigger:!1}}resolveCollision(t,e,i,s){if(!t.body||!e.body)return;const o=t.body,n=e.body,r=isFinite(o.mass)?1/o.mass:0,a=isFinite(n.mass)?1/n.mass:0,h=r+a;if(0===h)return;const l=n.velocity.sub(o.velocity).dot(i.normal);if(l>0)return;let c=Math.max(o.restitution,n.restitution);Math.abs(l)<.5&&(c=0);const d=(-(1+c)*l+(0===c?.2*Math.max(0,i.penetration-.01)/s:0))/h,u=i.accumulatedNormalImpulse||0;i.accumulatedNormalImpulse=Math.max(0,u+d);const y=i.accumulatedNormalImpulse-u;o.velocity=o.velocity.sub(i.normal.scale(y*r)),n.velocity=n.velocity.add(i.normal.scale(y*a))}}class a{canvas;ctx;scene;lastTime=0;input;physics;paused=!1;constructor(t){this.canvas=t.canvas,this.ctx=this.canvas.getContext("2d"),this.input=t.input??new e({canvas:this.canvas}),this.physics=t.physics??new r}setCanvas(t){this.canvas=t,this.ctx=this.canvas.getContext("2d")}setScene(t){this.scene?.exit(),t.ctx=this.ctx,this.scene=t,this.scene.init()}start(){window.addEventListener("blur",()=>{this.paused=!0}),window.addEventListener("focus",()=>{this.paused=!1,this.lastTime=performance.now()}),requestAnimationFrame(this.loop.bind(this))}loop(t){if(this.paused)return void requestAnimationFrame(this.loop.bind(this));const e=(t-this.lastTime)/1e3;if(this.lastTime=t,!this.scene)throw new Error("Can not run game loop without a scene");this.scene.update(e),this.scene.animationUpdate(e),this.physics.update(this.scene.entities,e),this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),this.scene.render(),this.input.update(),requestAnimationFrame(this.loop.bind(this))}}class h{position;rotation;zoom;scene;constructor(e){this.position=e.position??new t(0,0),this.rotation=e.rotation??0,this.zoom=e.zoom??1,this.scene=e.scene}apply(){const t=this.scene.ctx;if(t){const e=t.canvas.width/2,i=t.canvas.height/2;t.translate(e,i),t.scale(this.zoom,this.zoom),t.rotate(this.rotation),t.translate(-this.position.x-e/this.zoom,-this.position.y-i/this.zoom)}}screenToWorld(e){const i=this.scene.ctx;if(i){const s=i.canvas.width/2,o=i.canvas.height/2;let n=e.x-s,r=e.y-o;const a=Math.cos(-this.rotation),h=Math.sin(-this.rotation),l=n*h+r*a,c=(n*a-r*h)/this.zoom,d=l/this.zoom;return new t(c+this.position.x+s/this.zoom,d+this.position.y+o/this.zoom)}return new t(0,0)}}class l{ctx;camera=new h({scene:this});entities=[];init(){}update(t){}exit(){}addEntity(t){this.entities.push(t)}removeEntity(t){this.entities=this.entities.filter(e=>e!==t)}animationUpdate(t){for(const e of this.entities)e.animator?.update(t)}render(){const t=this.ctx;if(t){t.save(),this.camera.apply();for(const e of this.entities)e.render(t);t.restore()}}}class c{animator;sprite;position;rotation;body;collider;constructor(e={}){this.animator=e.animator,this.sprite=e.sprite,this.position=e.position??new t(0,0),this.rotation=e.rotation??0,this.body=e.body,this.collider=e.collider}onCollisionEnter;onCollisionStay;onCollisionExit;onTriggerEnter;onTriggerStay;onTriggerExit;render(t){if(this.animator){const e=this.animator.currentAnimation.spriteSheet,i=this.animator.currentFrame,s=Math.floor(e.texture.width/e.frameWidth),o=i%s*e.frameWidth,n=Math.floor(i/s)*e.frameHeight;t.save(),t.translate(this.position.x,this.position.y),t.rotate(this.rotation),t.drawImage(e.texture,o,n,e.frameWidth,e.frameHeight,-e.frameWidth/2,-e.frameHeight/2,e.frameWidth,e.frameHeight),t.restore()}else this.sprite&&(t.save(),t.translate(this.position.x,this.position.y),t.rotate(this.rotation),t.drawImage(this.sprite.texture,-this.sprite.width/2,-this.sprite.height/2,this.sprite.width,this.sprite.height),t.restore())}}class d{texture;width;height;constructor(t){this.texture=t.texture,this.width=t.width??this.texture.width,this.height=t.height??this.texture.height}}class u{texture;frameWidth;frameHeight;constructor(t){this.texture=t.texture,this.frameWidth=t.frameWidth,this.frameHeight=t.frameHeight}}class y{spriteSheet;frames;fps;loop;constructor(t){this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.fps=t.fps,this.loop=t.loop??!1}}class m{animations;currentAnimation;currentAnimationName;frameIndex=0;stopped=!1;elapsed=0;onEnd;constructor(t){this.animations=t.animations,this.currentAnimation=this.animations[t.default],this.currentAnimationName=t.default}play(t){this.currentAnimation=this.animations[t],this.currentAnimationName=t,this.frameIndex=0,this.stopped=!1,this.elapsed=0}update(t){if(this.stopped)return;this.elapsed+=t;const e=1/this.currentAnimation.fps;this.elapsed>=e&&(this.elapsed-=e,this.frameIndex++,this.frameIndex>=this.currentAnimation.frames.length&&(this.currentAnimation.loop?this.frameIndex=0:(this.frameIndex=this.currentAnimation.frames.length-1,this.stopped=!0,this.onEnd?.(this.currentAnimationName))))}get currentFrame(){return this.currentAnimation.frames[this.frameIndex]}}export{y as Animation,m as Animator,o as BoxCollider,h as Camera,s as CircleCollider,c as Entity,a as Game,e as Input,r as Physics,i as RigidBody,l as Scene,n as SpatialGrid,d as Sprite,u as SpriteSheet,t as Vector2};
|
|
1
|
+
class t{x;y;static ZERO=new t(0,0);static ONE=new t(1,1);static UP=new t(0,-1);static DOWN=new t(0,1);static LEFT=new t(-1,0);static RIGHT=new t(1,0);constructor(t,e){this.x=t,this.y=e}toString(){return`Vector2(${this.x}, ${this.y})`}add(e){return new t(this.x+e.x,this.y+e.y)}sub(e){return new t(this.x-e.x,this.y-e.y)}mul(e){return new t(this.x*e.x,this.y*e.y)}div(e){return new t(this.x/e.x,this.y/e.y)}neg(){return new t(-this.x,-this.y)}scale(e){return new t(this.x*e,this.y*e)}magnitude(){return Math.sqrt(this.x*this.x+this.y*this.y)}magnitudeSquared(){return this.x*this.x+this.y*this.y}normalize(){const e=this.magnitude();return e>0?new t(this.x/e,this.y/e):new t(0,0)}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}project(t){const e=this.dot(t)/t.magnitudeSquared();return t.scale(e)}min(e){return new t(Math.min(this.x,e.x),Math.min(this.y,e.y))}max(e){return new t(Math.max(this.x,e.x),Math.max(this.y,e.y))}floor(){return new t(Math.floor(this.x),Math.floor(this.y))}ceil(){return new t(Math.ceil(this.x),Math.ceil(this.y))}round(){return new t(Math.round(this.x),Math.round(this.y))}distance(t){return Math.sqrt((this.x-t.x)**2+(this.y-t.y)**2)}distanceSquared(t){return(this.x-t.x)**2+(this.y-t.y)**2}copy(){return new t(this.x,this.y)}lerp(t,e){return this.add(t.sub(this).scale(e))}clamp(t){const e=this.magnitude();return e>t?this.scale(t/e):this.copy()}rotate(e){const i=Math.cos(e),s=Math.sin(e);return new t(this.x*i-this.y*s,this.x*s+this.y*i)}orthogonal(){return new t(-this.y,this.x)}angle(){return Math.atan2(this.y,this.x)}angleTo(t){return Math.atan2(t.y-this.y,t.x-this.x)}reflect(t){const e=this.dot(t);return this.sub(t.scale(2*e))}equals(t){return this.x===t.x&&this.y===t.y}}class e{canvas;keys=new Set;keysPressed=new Set;keysReleased=new Set;pointer=new t(0,0);pointers=new Set;pointersPressed=new Set;pointersReleased=new Set;constructor(t){this.canvas=t.canvas,window.addEventListener("keydown",t=>{this.keys.has(t.key)||this.keysPressed.add(t.key),this.keys.add(t.key)}),window.addEventListener("keyup",t=>{this.keys.delete(t.key),this.keysReleased.add(t.key)}),this.canvas.addEventListener("mousemove",t=>{const e=this.canvas.getBoundingClientRect();this.pointer.x=t.clientX-e.left,this.pointer.y=t.clientY-e.top}),this.canvas.addEventListener("mousedown",t=>{this.pointers.has(t.button)||this.pointersPressed.add(t.button),this.pointers.add(t.button)}),this.canvas.addEventListener("mouseup",t=>{this.pointers.delete(t.button),this.pointersReleased.add(t.button)}),this.canvas.addEventListener("touchmove",t=>{t.preventDefault();const e=this.canvas.getBoundingClientRect(),i=t.touches[0];this.pointer.x=i.clientX-e.left,this.pointer.y=i.clientY-e.top}),this.canvas.addEventListener("touchstart",t=>{t.preventDefault(),this.pointers.has(2)||this.pointersPressed.add(2),this.pointers.add(2)}),this.canvas.addEventListener("touchend",t=>{t.preventDefault(),this.pointers.delete(2),this.pointersReleased.add(2)}),this.canvas.addEventListener("contextmenu",t=>{t.preventDefault()})}update(){this.keysPressed.clear(),this.keysReleased.clear(),this.pointersPressed.clear(),this.pointersReleased.clear()}isKeyDown(t){return this.keys.has(t)}isKeyPressed(t){return this.keysPressed.has(t)}isKeyReleased(t){return this.keysReleased.has(t)}isPointerDown(t=0){return this.pointers.has(t)}isPointerPressed(t=0){return this.pointersPressed.has(t)}isPointerReleased(t=0){return this.pointersReleased.has(t)}}class i{velocity;rotationVelocity;mass;inertia;force;torque;restitution;sleepThreshold;sleepTimeThreshold;isSleeping;sleepTimer;constructor(e={}){this.velocity=e.velocity??new t(0,0),this.rotationVelocity=e.rotationVelocity??0,this.mass=e.mass??1,this.inertia=e.inertia??1,this.force=e.force??new t(0,0),this.torque=e.torque??0,this.restitution=e.restitution??0,this.sleepThreshold=e.sleepThreshold??.1,this.sleepTimeThreshold=e.sleepTimeThreshold??.5,this.isSleeping=e.isSleeping??!1,this.sleepTimer=e.sleepTimer??0}wake(){this.isSleeping=!1,this.sleepTimer=0}}class s{radius;offset;isTrigger;layer;mask;constructor(e){this.radius=e.radius,this.offset=e.offset??new t(0,0),this.isTrigger=e.isTrigger??!1,this.layer=e.layer??1,this.mask=e.mask??4294967295}}class o{width;height;offset;isTrigger;layer;mask;constructor(e){this.width=e.width,this.height=e.height,this.offset=e.offset??new t(0,0),this.isTrigger=e.isTrigger??!1,this.layer=e.layer??1,this.mask=e.mask??4294967295}}class n{cellSize;grid;constructor(t={}){this.cellSize=t.cellSize||100,this.grid=t.grid||new Map}clear(){this.grid.clear()}adaptCellSize(t){const e=[];for(const i of t){const t=this.getEntityBounds(i),s=t.maxX-t.minX,o=t.maxY-t.minY,n=Math.max(s,o);e.push(n)}e.sort((t,e)=>t-e);const i=e[Math.floor(.75*e.length)];this.cellSize=2.5*i}insert(t){if(t.collider){const e=this.getEntityBounds(t),i=Math.floor(e.minX/this.cellSize),s=Math.floor(e.maxX/this.cellSize),o=Math.floor(e.minY/this.cellSize),n=Math.floor(e.maxY/this.cellSize);for(let e=i;e<=s;e++)for(let i=o;i<=n;i++){const s=`${e},${i}`;this.grid.has(s)||this.grid.set(s,new Set),this.grid.get(s).add(t)}}}getNearby(t){const e=Math.floor(t.position.x/this.cellSize),i=Math.floor(t.position.y/this.cellSize),s=new Set;for(let o=-1;o<=1;o++)for(let n=-1;n<=1;n++){const r=`${e+o},${i+n}`,a=this.grid.get(r);if(a)for(const e of a)e!==t&&s.add(e)}return Array.from(s)}getEntityBounds(t){if(t.collider instanceof s){const e=t.collider.radius;return{minX:t.position.x-e,maxX:t.position.x+e,minY:t.position.y-e,maxY:t.position.y+e}}if(t.collider instanceof o){const e=t.collider.width,i=t.collider.height;return{minX:t.position.x-e,maxX:t.position.x+e,minY:t.position.y-i,maxY:t.position.y+i}}throw new Error("Collider type not supported")}}class r{collisionPairs=new Map;spatialGrid=new n;entityCount=0;update(e,s){for(const t of e)if(t.body instanceof i){if((t.body.force.magnitudeSquared()>0||0!==t.body.torque||t.body.velocity.magnitudeSquared()>t.body.sleepThreshold**2||Math.abs(t.body.rotationVelocity)>t.body.sleepThreshold)&&t.body.wake(),t.body.isSleeping)continue;t.body.velocity.x+=t.body.force.x/t.body.mass*s,t.body.velocity.y+=t.body.force.y/t.body.mass*s,t.body.rotationVelocity+=t.body.torque/t.body.inertia*s,t.body.force.x=0,t.body.force.y=0,t.body.torque=0}const o=e.filter(t=>t.collider);this.spatialGrid.clear(),this.entityCount!==o.length&&(this.entityCount=o.length,this.spatialGrid.adaptCellSize(o));for(const t of o)this.spatialGrid.insert(t);const n=new Map,r=[];for(const e of o){const i=this.spatialGrid.getNearby(e);for(const s of i)if(s.collider){if(n.has(e)&&n.get(e)?.has(s)||n.has(s)&&n.get(s)?.has(e))continue;const i=this.checkCollision(e,s);if(i.info){e.body?.isSleeping&&s.body?.isSleeping||(e.body?.wake(),s.body?.wake()),n.has(e)||n.set(e,new Map),n.get(e)?.set(s,i.info);this.collisionPairs.get(e)?.has(s)||this.collisionPairs.get(s)?.has(e)?i.isTrigger?(e.onTriggerStay?.(s),s.onTriggerStay?.(e)):(e.onCollisionStay?.(s,i.info),s.onCollisionStay?.(e,{...i.info,normal:new t(-i.info.normal.x,-i.info.normal.y)})):i.isTrigger?(e.onTriggerEnter?.(s),s.onTriggerEnter?.(e)):(e.onCollisionEnter?.(s,i.info),s.onCollisionEnter?.(e,{...i.info,normal:new t(-i.info.normal.x,-i.info.normal.y)})),i.isTrigger||r.push({entityA:e,entityB:s,info:i.info})}}}for(let t=0;t<6;t++)for(const t of r)this.resolveCollision(t.entityA,t.entityB,t.info,s);for(const[e,i]of this.collisionPairs)for(const[s,o]of i){if(!(n.get(e)?.has(s)||n.get(s)?.has(e))){e.collider?.isTrigger||s.collider?.isTrigger?(e.onTriggerExit?.(s),s.onTriggerExit?.(e)):(e.onCollisionExit?.(s,o),s.onCollisionExit?.(e,{...o,normal:new t(-o.normal.x,-o.normal.y)}))}}this.collisionPairs=n;for(const t of e)if(t.body instanceof i){if(t.body.isSleeping)continue;t.position.x+=t.body.velocity.x*s,t.position.y+=t.body.velocity.y*s,t.rotation+=t.body.rotationVelocity*s;const e=t.body.velocity.magnitude(),i=Math.abs(t.body.rotationVelocity);e<t.body.sleepThreshold&&i<t.body.sleepThreshold?(t.body.sleepTimer+=s,t.body.sleepTimer>=t.body.sleepTimeThreshold&&(t.body.isSleeping=!0,t.body.velocity.x=0,t.body.velocity.y=0,t.body.rotationVelocity=0)):t.body.sleepTimer=0}}checkCollision(e,i){if(e.collider&&i.collider){if(0===(e.collider.mask&i.collider.layer)||0===(i.collider.mask&e.collider.layer))return{isTrigger:!1};const n=e.collider.isTrigger||i.collider.isTrigger,r=e.position.add(e.collider.offset),a=i.position.add(i.collider.offset);if(e.collider instanceof s&&i.collider instanceof s){const s=r.distance(a),o=e.collider.radius+i.collider.radius;if(s>=o)return{isTrigger:!1};const h=o-s,l=a.sub(r);let c=s>0?l.scale(1/s):new t(1,0),d=0;const u=this.collisionPairs.get(e)?.get(i)||this.collisionPairs.get(i)?.get(e);return u&&(d=u.accumulatedNormalImpulse),{isTrigger:n,info:{normal:c,penetration:h,contact:r.add(c.scale(e.collider.radius)),accumulatedNormalImpulse:d}}}if(e.collider instanceof o&&i.collider instanceof o){const s=e.collider.width/2,o=e.collider.height/2,h=i.collider.width/2,l=i.collider.height/2,c=a.sub(r),d=s+h-Math.abs(c.x),u=o+l-Math.abs(c.y);if(d<=0||u<=0)return{isTrigger:!1};let y,m;d<u?(m=d,y=new t(c.x<0?-1:1,0)):(m=u,y=new t(0,c.y<0?-1:1));const p=r.x+Math.sign(c.x)*(s-d/2),f=r.y+Math.sign(c.y)*(o-u/2);let g=0;const x=this.collisionPairs.get(e)?.get(i)||this.collisionPairs.get(i)?.get(e);return x&&(g=x.accumulatedNormalImpulse),{isTrigger:n,info:{normal:y,penetration:m,contact:new t(p,f),accumulatedNormalImpulse:g}}}if(e.collider instanceof s&&i.collider instanceof o||e.collider instanceof o&&i.collider instanceof s){const o=e.collider instanceof s,h=o?r:a,l=o?a:r,c=(o?e:i).collider,d=(o?i:e).collider,u=d.width/2,y=d.height/2,m=h.sub(l);let p,f,g;if(Math.abs(m.x)<u&&Math.abs(m.y)<y){const e=u-Math.abs(m.x),i=y-Math.abs(m.y);if(e<i){const i=m.x<0?-1:1,s=new t(i,0);p=o?s.neg():s,f=c.radius+e,g=new t(l.x+i*u,h.y)}else{const e=m.y<0?-1:1,s=new t(0,e);p=o?s.neg():s,f=c.radius+i,g=new t(h.x,l.y+e*y)}}else{const e=Math.max(-u,Math.min(u,m.x)),i=Math.max(-y,Math.min(y,m.y)),s=l.add(new t(e,i)),n=h.sub(s),r=n.magnitudeSquared();if(r>=c.radius*c.radius)return{isTrigger:!1};const a=Math.sqrt(r),d=a>0?n.scale(1/a):new t(1,0);p=o?d.neg():d,f=c.radius-a,g=s}let x=0;const w=this.collisionPairs.get(e)?.get(i)||this.collisionPairs.get(i)?.get(e);return w&&(x=w.accumulatedNormalImpulse),{isTrigger:n,info:{normal:p,penetration:f,contact:g,accumulatedNormalImpulse:x}}}return{isTrigger:!1}}return{isTrigger:!1}}resolveCollision(t,e,i,s){if(!t.body||!e.body)return;const o=t.body,n=e.body,r=isFinite(o.mass)?1/o.mass:0,a=isFinite(n.mass)?1/n.mass:0,h=r+a;if(0===h)return;const l=n.velocity.sub(o.velocity).dot(i.normal);if(l>0)return;let c=Math.max(o.restitution,n.restitution);Math.abs(l)<.5&&(c=0);const d=(-(1+c)*l+(0===c?.2*Math.max(0,i.penetration-.01)/s:0))/h,u=i.accumulatedNormalImpulse||0;i.accumulatedNormalImpulse=Math.max(0,u+d);const y=i.accumulatedNormalImpulse-u;o.velocity=o.velocity.sub(i.normal.scale(y*r)),n.velocity=n.velocity.add(i.normal.scale(y*a))}}class a{canvas;ctx;scene;lastTime=0;input;physics;paused=!1;constructor(t){this.canvas=t.canvas,this.ctx=this.canvas.getContext("2d"),this.input=t.input??new e({canvas:this.canvas}),this.physics=t.physics??new r}setCanvas(t){this.canvas=t,this.ctx=this.canvas.getContext("2d")}setScene(t){this.scene?.exit(),t.ctx=this.ctx,this.scene=t,this.scene.init()}start(){window.addEventListener("blur",()=>{this.paused=!0}),window.addEventListener("focus",()=>{this.paused=!1,this.lastTime=performance.now()}),requestAnimationFrame(this.loop.bind(this))}loop(t){if(this.paused)return void requestAnimationFrame(this.loop.bind(this));const e=(t-this.lastTime)/1e3;if(this.lastTime=t,!this.scene)throw new Error("Can not run game loop without a scene");this.scene.update(e),this.scene.animationUpdate(e),this.physics.update(this.scene.entities,e),this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),this.scene.render(),this.input.update(),requestAnimationFrame(this.loop.bind(this))}}class h{position;rotation;zoom;scene;constructor(e){this.position=e.position??new t(0,0),this.rotation=e.rotation??0,this.zoom=e.zoom??1,this.scene=e.scene}apply(){const t=this.scene.ctx;if(t){const e=t.canvas.width/2,i=t.canvas.height/2;t.translate(e,i),t.scale(this.zoom,this.zoom),t.rotate(this.rotation),t.translate(-this.position.x-e/this.zoom,-this.position.y-i/this.zoom)}}screenToWorld(e){const i=this.scene.ctx;if(i){const s=i.canvas.width/2,o=i.canvas.height/2;let n=e.x-s,r=e.y-o;const a=Math.cos(-this.rotation),h=Math.sin(-this.rotation),l=n*h+r*a,c=(n*a-r*h)/this.zoom,d=l/this.zoom;return new t(c+this.position.x+s/this.zoom,d+this.position.y+o/this.zoom)}return new t(0,0)}}class l{ctx;camera=new h({scene:this});entities=[];init(){}update(t){}exit(){}addEntity(t){this.entities.push(t)}removeEntity(t){this.entities=this.entities.filter(e=>e!==t)}animationUpdate(t){for(const e of this.entities)e.animator?.update(t)}render(){const t=this.ctx;if(t){t.save(),this.camera.apply();for(const e of this.entities)e.render(t);t.restore()}}}class c{animator;sprite;position;rotation;body;collider;constructor(e={}){this.animator=e.animator,this.sprite=e.sprite,this.position=e.position??new t(0,0),this.rotation=e.rotation??0,this.body=e.body,this.collider=e.collider}onCollisionEnter;onCollisionStay;onCollisionExit;onTriggerEnter;onTriggerStay;onTriggerExit;render(t){if(this.animator){const e=this.animator.currentAnimation.spriteSheet,i=this.animator.currentFrame,s=Math.floor(e.texture.width/e.frameWidth),o=i%s*e.frameWidth,n=Math.floor(i/s)*e.frameHeight,r=e.width??e.frameWidth,a=e.height??e.frameHeight;t.save(),t.translate(this.position.x,this.position.y),t.rotate(this.rotation),t.drawImage(e.texture,o,n,e.frameWidth,e.frameHeight,-r/2,-a/2,r,a),t.restore()}else this.sprite&&(t.save(),t.translate(this.position.x,this.position.y),t.rotate(this.rotation),t.drawImage(this.sprite.texture,-this.sprite.width/2,-this.sprite.height/2,this.sprite.width,this.sprite.height),t.restore())}}class d{texture;width;height;constructor(t){this.texture=t.texture,this.width=t.width??this.texture.width,this.height=t.height??this.texture.height}}class u{texture;frameWidth;frameHeight;width;height;constructor(t){this.texture=t.texture,this.frameWidth=t.frameWidth,this.frameHeight=t.frameHeight,this.width=t.width??t.frameWidth,this.height=t.height??t.frameHeight}}class y{spriteSheet;frames;fps;loop;constructor(t){this.spriteSheet=t.spriteSheet,this.frames=t.frames,this.fps=t.fps,this.loop=t.loop??!1}}class m{animations;currentAnimation;currentAnimationName;frameIndex=0;stopped=!1;elapsed=0;onEnd;constructor(t){this.animations=t.animations,this.currentAnimation=this.animations[t.default],this.currentAnimationName=t.default}play(t){this.currentAnimation=this.animations[t],this.currentAnimationName=t,this.frameIndex=0,this.stopped=!1,this.elapsed=0}update(t){if(this.stopped)return;this.elapsed+=t;const e=1/this.currentAnimation.fps;this.elapsed>=e&&(this.elapsed-=e,this.frameIndex++,this.frameIndex>=this.currentAnimation.frames.length&&(this.currentAnimation.loop?this.frameIndex=0:(this.frameIndex=this.currentAnimation.frames.length-1,this.stopped=!0,this.onEnd?.(this.currentAnimationName))))}get currentFrame(){return this.currentAnimation.frames[this.frameIndex]}}export{y as Animation,m as Animator,o as BoxCollider,h as Camera,s as CircleCollider,c as Entity,a as Game,e as Input,r as Physics,i as RigidBody,l as Scene,n as SpatialGrid,d as Sprite,u as SpriteSheet,t as Vector2};
|
|
@@ -3,11 +3,15 @@ export interface SpriteSheetOptions {
|
|
|
3
3
|
texture: Texture;
|
|
4
4
|
frameWidth: number;
|
|
5
5
|
frameHeight: number;
|
|
6
|
+
width?: number;
|
|
7
|
+
height?: number;
|
|
6
8
|
}
|
|
7
9
|
export declare class SpriteSheet {
|
|
8
10
|
texture: Texture;
|
|
9
11
|
frameWidth: number;
|
|
10
12
|
frameHeight: number;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
11
15
|
constructor(options: SpriteSheetOptions);
|
|
12
16
|
}
|
|
13
17
|
export interface AnimationOptions {
|