platjs
Version:
Simple engine for platformer physics and rendering
2 lines (1 loc) • 9.53 kB
JavaScript
(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))s(i);new MutationObserver(i=>{for(const r of i)if(r.type==="childList")for(const h of r.addedNodes)h.tagName==="LINK"&&h.rel==="modulepreload"&&s(h)}).observe(document,{childList:!0,subtree:!0});function t(i){const r={};return i.integrity&&(r.integrity=i.integrity),i.referrerpolicy&&(r.referrerPolicy=i.referrerpolicy),i.crossorigin==="use-credentials"?r.credentials="include":i.crossorigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function s(i){if(i.ep)return;i.ep=!0;const r=t(i);fetch(i.href,r)}})();const O=(a,e)=>a.x-a.width/2<e.x+e.width/2&&a.x+a.width/2>e.x-e.width/2&&a.y-a.height/2<e.y+e.height/2&&a.y+a.height/2>e.y-e.height/2;class p{x;y;rotation;width;height;image;render;update;color;layer;_randomId;constructor({x:e=0,y:t=0,rotation:s=0,width:i=0,height:r=0,image:h=null,color:d=null,layer:l=0,render:y=null,update:n=()=>{}}){this.x=e,this.y=t,this.rotation=s,this.width=i,this.height=r,this.image=h,this.color=d,this.render=y,this.update=n,this.layer=l,this._randomId=Math.random()}_render(e){if(e.save(),e.translate(this.x,this.y),e.rotate(this.rotation),this.render)try{this.render.apply(this,[e,this])}catch{this.render(e,this)}else this.image?e.drawImage(this.image,-this.width/2,-this.height/2,this.width,this.height):this.color&&(e.fillStyle=this.color,e.fillRect(-this.width/2,-this.height/2,this.width,this.height));e.restore()}collides(e){return O(this,e)}}class w extends p{v;isOnBody;friction;constructor({x:e=0,y:t=0,rotation:s=0,width:i=0,height:r=0,image:h=null,color:d=null,layer:l=0,render:y=null,update:n=()=>{}}){super({x:e,y:t,rotation:s,width:i,height:r,image:h,color:d,layer:l,render:y,update:u=>{n(u,this),this.applyFriction(u)}}),this.v={x:0,y:0},this.friction=.3,this.isOnBody=!1}applyFriction(e){!this.isOnBody||this.keys&&this.getPreventFriction()||(this.friction*e>Math.abs(this.v.x)?this.v.x=0:this.v.x>0?this.v.x-=this.friction*e:this.v.x+=this.friction*e)}}class c extends w{maxXSpeed;jumpVel;keys;xAcceleration;jumps;maxJumps;wallJumps;wallPushOffSpeed;wallSide;constructor({x:e=0,y:t=0,rotation:s=0,width:i=0,height:r=0,image:h=null,color:d=null,layer:l=0,render:y=null,update:n=()=>{},maxXSpeed:u=5,jumpVel:f=13,maxJumps:g=1,wallJump:v=!1,wallPushOffSpeed:k=3}){super({x:e,y:t,rotation:s,width:i,height:r,image:h,color:d,layer:l,render:y,update:L=>{n(this),this.updateHorizontalMovement(L)}}),this.maxXSpeed=u,this.jumpVel=f,this.maxJumps=g,this.jumps=0,this.wallJumps=v,this.wallPushOffSpeed=k,this.wallSide=1,this.keys={w:!1,a:!1,s:!1,d:!1,top:!1,bottom:!1,right:!1,left:!1},this.xAcceleration=.3}getPreventFriction(){let e=0;return this.keys.right&&e++,this.keys.left&&e--,this.keys.d&&e++,this.keys.a&&e--,e!==0}jump(){this.jumps<this.maxJumps&&(this.v.y=-this.jumpVel,this.jumps++,this.wallSide===0?this.v.x=-this.wallPushOffSpeed:this.wallSide===2&&(this.v.x=this.wallPushOffSpeed))}bindKeyboardControls({wasd:e=!0,arrowKeys:t=!0,spaceJump:s=!0}){e&&(window.addEventListener("keydown",this.wasdKeyListener.bind(this),!0),window.addEventListener("keyup",this.wasdKeyListener.bind(this),!0)),t&&(window.addEventListener("keydown",this.arrowKeyListener.bind(this),!0),window.addEventListener("keyup",this.arrowKeyListener.bind(this),!0)),s&&(window.addEventListener("keydown",this.spaceKeyListener.bind(this),!0),window.addEventListener("keyup",this.spaceKeyListener.bind(this),!0))}wasdKeyListener({key:e,repeat:t,type:s}){if(t)return;const i=s==="keydown";i&&e.toLowerCase()==="w"&&this.jump(),e.toLowerCase()==="d"&&(this.keys.d=i),e.toLowerCase()==="s"&&(this.keys.left=i),e.toLowerCase()==="a"&&(this.keys.a=i)}arrowKeyListener({key:e,type:t,repeat:s}){if(s)return;const i=t==="keydown";i&&e==="ArrowUp"&&this.jump(),e==="ArrowRight"&&(this.keys.right=i),e==="ArrowDown"&&(this.keys.bottom=i),e==="ArrowLeft"&&(this.keys.left=i)}spaceKeyListener({key:e,repeat:t,type:s}){const i=s==="keydown";t||i&&e===" "&&this.jump()}updateHorizontalMovement(e){(this.keys.a||this.keys.left)&&(this.v.x-=this.xAcceleration*e),(this.keys.d||this.keys.right)&&(this.v.x+=this.xAcceleration*e),this.v.x=Math.max(-this.maxXSpeed,Math.min(this.maxXSpeed,this.v.x))}}class b{renderer;lockedObject;defaultPos;pos;minimums;constructor(e,t){this.renderer=e,this.lockedObject=null,this.defaultPos=t||{x:e.width/2,y:e.height/2},this.pos=this.defaultPos,this.minimums={x:1/0,y:1/0}}lock(e,{minXSpace:t=1/0,minYSpace:s=1/0}={}){this.lockedObject=e,this.minimums={x:t,y:s}}unlock(){this.lockedObject=null,this.pos=this.defaultPos}update(){return this.lockedObject&&(this.minimums.x>=this.renderer.width/2?this.pos.x=this.lockedObject.x:this.lockedObject.x-this.pos.x+this.renderer.width/2<this.minimums.x?this.pos.x=this.lockedObject.x-this.minimums.x+this.renderer.width/2:this.lockedObject.x-this.pos.x>this.renderer.width/2-this.minimums.x&&(this.pos.x=this.lockedObject.x+this.minimums.x-this.renderer.width/2),this.minimums.y>=this.renderer.height/2&&(this.pos.y=this.lockedObject.y)),this.pos}}class m extends p{constructor({x:e=0,y:t=0,rotation:s=0,width:i=0,height:r=0,image:h=null,color:d=null,layer:l=0,render:y=null,update:n=()=>{}}){super({x:e,y:t,rotation:s,width:i,height:r,image:h,color:d,layer:l,render:y,update:n})}}class x extends HTMLCanvasElement{ctx;objects;physics;camera;constructor(){super(),this.resize(),window.addEventListener("resize",()=>this.resize()),this.ctx=this.getContext("2d"),this.objects=[],this.render=this.render.bind(this),this.camera=new b(this)}enablePixelated(){return this.ctx.webkitImageSmoothingEnabled=!1,this.ctx.mozImageSmoothingEnabled=!1,this.ctx.imageSmoothingEnabled=!1,this}enablePhysics({gravity:e=.7}){this.physics={gravity:e}}enableFixedPosition(){return this.style.position="fixed",this.style.top="0px",this.style.left="0px",this}resize(){this.width=window.innerWidth,this.height=window.innerHeight}add(e){return this.objects.push(e),e}destroy(e){typeof e=="number"?this.objects.filter(t=>t._randomId===e):this.objects=this.objects.filter(t=>t!==e)}mount(e){return e.appendChild(this),this}update(e=1){this.objects.forEach(t=>{if(this.physics&&t instanceof w){t.v.y+=this.physics.gravity,t.update(e);const{x:s,y:i}=t;t.x+=t.v.x,t.y+=t.v.y;const r=(s+t.x)/2,h=(i+t.y)/2,d=Math.abs(s-t.x)+t.width,l=Math.abs(i-t.y)+t.height,y={x:r,y:h,width:d,height:l};t.isOnBody=!1,t instanceof c&&(t.wallSide=1);for(const n of this.objects)n instanceof m&&n.collides(y)&&(i+t.height/2<=n.y-n.height/2?(t.y=n.y-n.height/2-t.height/2,t.v.y=0,t.isOnBody=!0,t instanceof c&&(t.jumps=0)):i-t.height/2>=n.y+n.height/2?(t.y=n.y+n.height/2+t.height/2,t.v.y=0):s+t.width/2<=n.x-n.width/2?(t.x=n.x-n.width/2-t.width/2,t.v.x=0,t instanceof c&&t.wallJumps&&(t.jumps=0,t.wallSide=0)):s-t.width/2>=n.x+n.width/2&&(t.x=n.x+n.width/2+t.width/2,t instanceof c&&t.wallJumps&&(t.jumps=0,t.wallSide=2),t.v.x=0))}})}render(){const{x:e,y:t}=this.camera.update();this.ctx.clearRect(0,0,this.width,this.height),this.ctx.translate(this.width/2-e,this.height/2-t),this.objects.sort((s,i)=>s.layer-i.layer).forEach(s=>s._render(this.ctx)),this.ctx.translate(-this.width/2+e,-this.height/2+t)}}customElements.define("game-renderer",x,{extends:"canvas"});function E(a,e,t=()=>{}){return new Promise((s,i)=>{const r=new Image,h=new XMLHttpRequest;h.open("GET",e,!0),h.responseType="arraybuffer",h.addEventListener("load",()=>{if(h.status===200){const d=new Blob([h.response]);r.src=URL.createObjectURL(d),s({name:a,img:r})}}),h.addEventListener("progress",d=>{d.lengthComputable?t(d.loaded,d.total):t(0,8.1*1e3)}),h.addEventListener("error",()=>{i("Network error or blocked by cors")}),h.send()})}async function S(a,e=()=>{}){console.group("loading progress");const t=Object.keys(a).map(()=>0),s=Object.keys(a).map(()=>1),i=Object.keys(a).map((d,l)=>E(d,a[d],(y,n)=>{t[l]=y,s[l]=n,e(t.reduce((u,f)=>u+f),s.reduce((u,f)=>u+f))})),r=await Promise.all(i);console.groupEnd();const h={};return r.forEach(({name:d,img:l})=>{h[d]=l}),h}const P="/assets/dragon.40077cb6.png",I="/assets/dragon-flipped.b62c758b.png",o=new x;o.mount(document.body).enableFixedPosition().enablePixelated().enablePhysics({});S({playerLeft:P,playerRight:I},(a,e)=>console.log("Image progress:",a/e*100,"%")).then(a=>{const e=new c({x:30,y:30,width:30,height:30,image:a.playerLeft,layer:1,wallJump:!0,update:h=>{(h.keys.a||h.keys.left)&&!(h.keys.d||h.keys.right)?h.image=a.playerLeft:!(h.keys.a||h.keys.left)&&(h.keys.d||h.keys.right)&&(h.image=a.playerRight)}});e.bindKeyboardControls({}),o.add(e),o.camera.lock(e,{minXSpace:300,minYSpace:250}),o.add(new m({x:0,y:500,width:300,height:100,color:"black"}));let t=!1,s={x:0,y:0},i={x:0,y:0};o.addEventListener("mousedown",({clientX:h,clientY:d})=>{t=!0,s=i={x:h,y:d}}),window.addEventListener("mousemove",({clientX:h,clientY:d})=>{t&&(i={x:h,y:d})}),window.addEventListener("mouseup",({clientX:h,clientY:d})=>{if(t){i={x:h,y:d},t=!1,t=!1,s.x+=-o.width/2+o.camera.pos.x,s.y+=-o.height/2+o.camera.pos.y,i.x+=-o.width/2+o.camera.pos.x,i.y+=-o.height/2+o.camera.pos.y;const l=Math.min(s.x,i.x)+Math.abs(i.x-s.x)/2,y=Math.min(s.y,i.y)+Math.abs(i.y-s.y)/2,n=Math.abs(i.x-s.x),u=Math.abs(i.y-s.y);o.add(new m({x:l,y,width:n,height:u,color:"black"}))}});const r=()=>{o.update(),e.y-e.height/2>o.height&&(e.v.y=0,e.v.x=0,e.x=30,e.y=30),o.render(),t&&(o.ctx.fillStyle="blue",o.ctx.fillRect(s.x,s.y,i.x-s.x,i.y-s.y)),requestAnimationFrame(r)};requestAnimationFrame(r)});