From 7b00b11eae26feaa6a313d854afa3a9d99e961fb Mon Sep 17 00:00:00 2001
From: Luiz Bills
- Lightweight HTML5 canvas engine suitable for small games and animations
- for people who enjoy coding: there is no fancy interface, no visual
- helpers, no gui tools... just coding.
+ Litecanvas is a lightweight HTML5 canvas engine suitable for small web games, prototypes, game jams, animations, creative programming, learning game programming and game design, etc. Just install our NPM package or load/download the CDN by adding it as a script tag in your HTML.
- NPM:
-
+ This project is still under development. All feedback is appreciated! For bugs, suggestions or contribuitions open a issue in our Github Repository.
@@ -166,6 +163,11 @@ LITECANVAS
npm i litecanvas
+ LITECANVAS
>
+ NPM:
+ npm install litecanvas
+
@@ -173,21 +175,20 @@
litecanvas({
loop: {
- init, update, draw, resized, tap, tapping, untap, tapped
+ init, update, draw, resized,
+ tap, tapping, untap, tapped
}
})
@@ -206,7 +207,7 @@ Basic boilerplate
function resized() {
// called when the browser was resized
- // always called once before init()
+ // also called once before init()
}
function tap(x, y, tapId) {
@@ -273,11 +274,12 @@ Game Configuration
settings.width = null
settings.height = settings.width || null
-// Determines whether the game loop should be paused
-// when the "blur" event happens.
+// Determines whether the game loop should
+// be paused when the "blur" event happens.
settings.pauseOnBlur = true
-// scale the canvas
+// Determines whether the canvas should
+// scale to fill the canvas
settings.autoscale = true
// target FPS
@@ -286,10 +288,12 @@ Game Configuration
// enable smooth drawing
settings.antialias = false
-// disables antialias and force integer scales
+// disables antialias
+// and force the autoscale to use integers
settings.pixelart = false
-// export all functions to global scope
+// exposes all methods and properties (see below)
+// in the global scope
settings.global = true
// set to `false` to disable the default tap handler
@@ -300,6 +304,47 @@ Game Configuration
// useful to create your own keyboard handler
settings.keyboardEvents = true
+ // the game canvas
+CANVAS: HTMLCanvasElement
+
+// the game screen width
+WIDTH: number
+
+// the game screen height
+HEIGHT: number
+
+// the center X of game screen
+CENTERX: number
+
+// the center Y of game screen
+CENTERY: number
+
+// the FPS meter
+FPS: number
+
+// the amount of time since the game started
+ELAPSED: number
+
+// the current mouse's X-position
+// or -1 (if the mouse was not used or detected)
+MOUSEX: number
+
+// the current mouse's Y-position
+// or -1 (if the mouse was not used or detected)
+MOUSEY: number
+
+// the default sound played in `sfx()`
+DEFAULT_SFX: number[]
+
+// Math constants
+PI: number // approximately 3.14 radians (180º)
+
+TWO_PI: number // approximately 6.28 radians (360º)
+
+HALF_PI: number // approximately 1.57 radians (90º)
+
/**
@@ -338,16 +383,16 @@ Functions for Drawing
* TEXT DRAWING-RELATED FUNCTIONS
*/
-// draw a text
+// Draw a text
text(x, y, text, color? = 3): void
-// set the text alignment and baseline
+// Sets the text alignment and baseline
// default values: align = 'start', baseline = 'top'
// see: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign
// see: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline
textalign(align: string, baseline: string): void
-// set the font family for texts
+// Sets the font family for texts
textfont(fontName: string): void
// Sets the font size (default: 32)
@@ -403,30 +448,30 @@ Functions for Drawing
// Adds a rotation to the transformation matrix
rotate(radians: number): void
-// update the transformation matrix
+// Updates the transformation matrix
// when `resetFirst = true` uses `context.setTransform()`
// see: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setTransform
// when `resetFirst = false` uses `context.transform()`
// see: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform
transform(a, b, c, d, e, f, resetFirst = true): void
-// set the alpha (opacity) value to apply when drawing new shapes and images
+// Sets the alpha (opacity) value to apply when drawing new shapes and images
// see: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalAlpha
alpha(value: number): void
-// fills the current path
+// Fills the current path
// see: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fill
fill(color: number, path?: Path2D): void
-// outlines the current path
+// Outlines the current path
// see: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/stroke
stroke(color: number, path?: Path2D): void
-// create (or clone) a Path2D instance
+// Create (or clone) a Path2D instance
// see: https://developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D
path(arg?: Path2D | string): Path2D
-// create a clipping region
+// Create a clipping region
// see: https://github.com/litecanvas/game-engine/blob/main/samples/clip/clip.js
// note: before call `clip()` you must save the context using `push()` and `pop()`
clipcirc(path: Path2D)
@@ -564,66 +609,9 @@ // the game canvas
-CANVAS: HTMLCanvasElement
-
-// the game screen width
-WIDTH: number
-
-// the game screen height
-HEIGHT: number
-
-// the center X of game screen
-CENTERX: number
-
-// the center Y of game screen
-CENTERY: number
-
-// the FPS meter
-FPS: number
-
-// the amount of time since the game started
-ELAPSED: number
-
-// the current mouse's X-position
-// or -1 (if the mouse was not used or detected)
-MOUSEX: number
-
-// the current mouse's Y-position
-// or -1 (if the mouse was not used or detected)
-MOUSEY: number
-
-// the default sound played in `sfx()`
-DEFAULT_SFX: number[]
-
-// Math constants
-PI: number // approximately 3.14159 radians (180º)
-
-TWO_PI: number // approximately 6.28318 radians (360º)
-
-HALF_PI: number // approximately 1.57079 radians (90º)
-
- // Registers a game event callback and
-// returns a function that unregister this callback.
-listen(event: string, callback: Function): Function
-
-// Triggers a game event and call its registered callbacks.
-emit(event: string, arg1?, arg2?, arg3?, arg4?): void
-
- // Loads a plugin.
// see: https://github.com/litecanvas/game-engine/blob/main/samples/plugin-basics/plugin-basics.js
@@ -636,9 +624,26 @@ Plugin API
// example: getcolor(0) returns "#111"
getcolor(index: number): string
+// Registers a game event callback and
+// returns a function that unregister this callback.
+listen(event: string, callback: Function): Function
+
+// Triggers a game event and call its registered callbacks.
+emit(event: string, arg1?, arg2?, arg3?, arg4?): void
+
// Resizes the game canvas.
// Also, emits the "resized" (use `listen` to observe this event).
-resize(width: number, height: number): void
+resize(width: number, height: number): void
+
+// Sets the scale of the game's delta time (dt).
+// By default is equal to 1.
+// Values higher than 1 increase the speed of time,
+// while values smaller than 1 decrease it.
+// A value of 0 freezes time (equivalent to pausing).
+timescale(value: number): void
+
+// Sets the target FPS at runtime
+setfps(value: number): void
y.from>=h.from&&y.to<=h.to&&Math.abs(y.from-c)y.from =h.from||p<=h.to||u){let f=Math.max(h.from,o)-u,d=Math.min(h.to,p)-u;h=f>=d?null:new r(f,d,h.tree,h.offset+u,l>0,!!c)}if(h&&n.push(h),a.to>p)break;a=i e.from&&(this.fragments=nw(this.fragments,n,i),this.skipped.splice(s--,1))}return this.skipped.length>=t?!1:(this.reset(),!0)}reset(){this.parse&&(this.takeTree(),this.parse=null)}skipUntilInView(e,t){this.skipped.push({from:e,to:t})}static getSkippingParser(e){return new class extends ta{createParse(t,s,n){let i=n[0].from,a=n[n.length-1].to;return{parsedPos:i,advance(){let o=Po;if(o){for(let u of n)o.tempSkipped.push(u);e&&(o.scheduleOn=o.scheduleOn?Promise.all([o.scheduleOn,e]):e)}return this.parsedPos=a,new or(Ar.none,[],[],a-i)},stoppedAt:null,stopAt(){}}}}}isDone(e){e=Math.min(e,this.state.doc.length);let t=this.fragments;return this.treeLen>=e&&t.length&&t[0].from==0&&t[0].to>=e}static get(){return Po}};function nw(r,e,t){return fi.applyChanges(r,[{fromA:e,toA:t,fromB:e,toB:t}])}var To=class r{constructor(e){this.context=e,this.tree=e.tree}apply(e){if(!e.docChanged&&this.tree==this.context.tree)return this;let t=this.context.changes(e.changes,e.state),s=this.context.treeLen==e.startState.doc.length?void 0:Math.max(e.changes.mapPos(this.context.treeLen),t.viewport.to);return t.work(20,s)||t.takeTree(),new r(t)}static init(e){let t=Math.min(3e3,e.doc.length),s=sh.create(e.facet(sa).parser,e,{from:0,to:t});return s.work(20,t)||s.takeTree(),new r(s)}};Yr.state=Yt.define({create:To.init,update(r,e){for(let t of e.effects)if(t.is(Yr.setState))return t.value;return e.startState.facet(sa)!=e.state.facet(sa)?To.init(e.state):r.apply(e)}});var ow=r=>{let e=setTimeout(()=>r(),500);return()=>clearTimeout(e)};typeof requestIdleCallback<"u"&&(ow=r=>{let e=-1,t=setTimeout(()=>{e=requestIdleCallback(r,{timeout:400})},100);return()=>e<0?clearTimeout(t):cancelIdleCallback(e)});var eh=typeof navigator<"u"&&(!((Jp=navigator.scheduling)===null||Jp===void 0)&&Jp.isInputPending)?()=>navigator.scheduling.isInputPending():null,Jj=rr.fromClass(class{constructor(e){this.view=e,this.working=null,this.workScheduled=0,this.chunkEnd=-1,this.chunkBudget=-1,this.work=this.work.bind(this),this.scheduleWork()}update(e){let t=this.view.state.field(Yr.state).context;(t.updateViewport(e.view.viewport)||this.view.viewport.to>t.treeLen)&&this.scheduleWork(),(e.docChanged||e.selectionSet)&&(this.view.hasFocus&&(this.chunkBudget+=50),this.scheduleWork()),this.checkAsyncSchedule(t)}scheduleWork(){if(this.working)return;let{state:e}=this.view,t=e.field(Yr.state);(t.tree!=t.context.tree||!t.context.isDone(e.doc.length))&&(this.working=ow(this.work))}work(e){this.working=null;let t=Date.now();if(this.chunkEnd =h.from||p<=h.to||u){let f=Math.max(h.from,o)-u,d=Math.min(h.to,p)-u;h=f>=d?null:new r(f,d,h.tree,h.offset+u,l>0,!!c)}if(h&&n.push(h),a.to>p)break;a=i e.from&&(this.fragments=nw(this.fragments,n,i),this.skipped.splice(s--,1))}return this.skipped.length>=t?!1:(this.reset(),!0)}reset(){this.parse&&(this.takeTree(),this.parse=null)}skipUntilInView(e,t){this.skipped.push({from:e,to:t})}static getSkippingParser(e){return new class extends ta{createParse(t,s,n){let i=n[0].from,a=n[n.length-1].to;return{parsedPos:i,advance(){let o=Po;if(o){for(let u of n)o.tempSkipped.push(u);e&&(o.scheduleOn=o.scheduleOn?Promise.all([o.scheduleOn,e]):e)}return this.parsedPos=a,new or(Ar.none,[],[],a-i)},stoppedAt:null,stopAt(){}}}}}isDone(e){e=Math.min(e,this.state.doc.length);let t=this.fragments;return this.treeLen>=e&&t.length&&t[0].from==0&&t[0].to>=e}static get(){return Po}};function nw(r,e,t){return fi.applyChanges(r,[{fromA:e,toA:t,fromB:e,toB:t}])}var To=class r{constructor(e){this.context=e,this.tree=e.tree}apply(e){if(!e.docChanged&&this.tree==this.context.tree)return this;let t=this.context.changes(e.changes,e.state),s=this.context.treeLen==e.startState.doc.length?void 0:Math.max(e.changes.mapPos(this.context.treeLen),t.viewport.to);return t.work(20,s)||t.takeTree(),new r(t)}static init(e){let t=Math.min(3e3,e.doc.length),s=sh.create(e.facet(sa).parser,e,{from:0,to:t});return s.work(20,t)||s.takeTree(),new r(s)}};Yr.state=Yt.define({create:To.init,update(r,e){for(let t of e.effects)if(t.is(Yr.setState))return t.value;return e.startState.facet(sa)!=e.state.facet(sa)?To.init(e.state):r.apply(e)}});var ow=r=>{let e=setTimeout(()=>r(),500);return()=>clearTimeout(e)};typeof requestIdleCallback<"u"&&(ow=r=>{let e=-1,t=setTimeout(()=>{e=requestIdleCallback(r,{timeout:400})},100);return()=>e<0?clearTimeout(t):cancelIdleCallback(e)});var eh=typeof navigator<"u"&&(!((Jp=navigator.scheduling)===null||Jp===void 0)&&Jp.isInputPending)?()=>navigator.scheduling.isInputPending():null,Jj=rr.fromClass(class{constructor(e){this.view=e,this.working=null,this.workScheduled=0,this.chunkEnd=-1,this.chunkBudget=-1,this.work=this.work.bind(this),this.scheduleWork()}update(e){let t=this.view.state.field(Yr.state).context;(t.updateViewport(e.view.viewport)||this.view.viewport.to>t.treeLen)&&this.scheduleWork(),(e.docChanged||e.selectionSet)&&(this.view.hasFocus&&(this.chunkBudget+=50),this.scheduleWork()),this.checkAsyncSchedule(t)}scheduleWork(){if(this.working)return;let{state:e}=this.view,t=e.field(Yr.state);(t.tree!=t.context.tree||!t.context.isDone(e.doc.length))&&(this.working=ow(this.work))}work(e){this.working=null;let t=Date.now();if(this.chunkEndn||Math.min(a.bottom,l)=this.cursorPos?this.doc.sliceString(e,t):this.string.slice(e-s,t-s)}},Po=null,sh=class r{constructor(e,t,s=[],n,i,a,l,o){this.parser=e,this.state=t,this.fragments=s,this.tree=n,this.treeLen=i,this.viewport=a,this.skipped=l,this.scheduleOn=o,this.parse=null,this.tempSkipped=[]}static create(e,t,s){return new r(e,t,[],or.empty,0,s,[],null)}startParse(){return this.parser.startParse(new nh(this.state.doc),this.fragments)}work(e,t){return t!=null&&t>=this.state.doc.length&&(t=void 0),this.tree!=or.empty&&this.isDone(t??this.state.doc.length)?(this.takeTree(),!0):this.withContext(()=>{var s;if(typeof e=="number"){let n=Date.now()+e;e=()=>Date.now()>n}for(this.parse||(this.parse=this.startParse()),t!=null&&(this.parse.stoppedAt==null||this.parse.stoppedAt>t)&&t=this.to?this.to:this.text.lineAt(e).to}next(){for(;;){let e=this.re.lastIndex=this.matchPos-this.flat.from,t=this.re.exec(this.flat.text);if(t&&!t[0]&&t.index==e&&(this.re.lastIndex=e+1,t=this.re.exec(this.flat.text)),t){let s=this.flat.from+t.index,n=s+t[0].length;if((this.flat.to>=this.to||t.index+t[0].length<=this.flat.text.length-10)&&(!this.test||this.test(s,n,t)))return this.value={from:s,to:n,match:t},this.matchPos=Fu(this.text,n+(s==n?1:0)),this}if(this.flat.to==this.to)return this.done=!0,this;this.flat=Iu.get(this.text,this.flat.from,this.chunkEnd(this.flat.from+this.flat.text.length*2))}}};typeof Symbol<"u"&&(_u.prototype[Symbol.iterator]=Ru.prototype[Symbol.iterator]=function(){return this});function kQ(r){try{return new RegExp(r,Ah),!0}catch{return!1}}function Fu(r,e){if(e>=r.length)return e;let t=r.lineAt(e),s;for(;en||Math.min(a.bottom,l)=this.cursorPos?this.doc.sliceString(e,t):this.string.slice(e-s,t-s)}},Po=null,sh=class r{constructor(e,t,s=[],n,i,a,l,o){this.parser=e,this.state=t,this.fragments=s,this.tree=n,this.treeLen=i,this.viewport=a,this.skipped=l,this.scheduleOn=o,this.parse=null,this.tempSkipped=[]}static create(e,t,s){return new r(e,t,[],or.empty,0,s,[],null)}startParse(){return this.parser.startParse(new nh(this.state.doc),this.fragments)}work(e,t){return t!=null&&t>=this.state.doc.length&&(t=void 0),this.tree!=or.empty&&this.isDone(t??this.state.doc.length)?(this.takeTree(),!0):this.withContext(()=>{var s;if(typeof e=="number"){let n=Date.now()+e;e=()=>Date.now()>n}for(this.parse||(this.parse=this.startParse()),t!=null&&(this.parse.stoppedAt==null||this.parse.stoppedAt>t)&&t=this.to?this.to:this.text.lineAt(e).to}next(){for(;;){let e=this.re.lastIndex=this.matchPos-this.flat.from,t=this.re.exec(this.flat.text);if(t&&!t[0]&&t.index==e&&(this.re.lastIndex=e+1,t=this.re.exec(this.flat.text)),t){let s=this.flat.from+t.index,n=s+t[0].length;if((this.flat.to>=this.to||t.index+t[0].length<=this.flat.text.length-10)&&(!this.test||this.test(s,n,t)))return this.value={from:s,to:n,match:t},this.matchPos=Fu(this.text,n+(s==n?1:0)),this}if(this.flat.to==this.to)return this.done=!0,this;this.flat=Iu.get(this.text,this.flat.from,this.chunkEnd(this.flat.from+this.flat.text.length*2))}}};typeof Symbol<"u"&&(_u.prototype[Symbol.iterator]=Ru.prototype[Symbol.iterator]=function(){return this});function kQ(r){try{return new RegExp(r,Ah),!0}catch{return!1}}function Fu(r,e){if(e>=r.length)return e;let t=r.lineAt(e),s;for(;e=t)return null;n.push(s.value)}return n}highlight(e,t,s,n){let i=ua(this.spec,e,Math.max(0,t-this.spec.unquoted.length),Math.min(s+this.spec.unquoted.length,e.doc.length));for(;!i.next().done;)n(i.value.from,i.value.to)}};function ca(r,e,t,s){return new _u(e.doc,r.search,{ignoreCase:!r.caseSensitive,test:r.wholeWord?MQ(e.charCategorizer(e.selection.main.head)):void 0},t,s)}function $u(r,e){return r.slice(ur(r,e,!1),e)}function Mu(r,e){return r.slice(e,ur(r,e))}function MQ(r){return(e,t,s)=>!s[0].length||(r($u(s.input,s.index))!=zt.Word||r(Mu(s.input,s.index))!=zt.Word)&&(r(Mu(s.input,s.index+s[0].length))!=zt.Word||r($u(s.input,s.index+s[0].length))!=zt.Word)}var Ch=class extends Nu{nextMatch(e,t,s){let n=ca(this.spec,e,s,e.doc.length).next();return n.done&&(n=ca(this.spec,e,0,t).next()),n.done?null:n.value}prevMatchInRange(e,t,s){for(let n=1;;n++){let i=Math.max(t,s-n*1e4),a=ca(this.spec,e,i,s),l=null;for(;!a.next().done;)l=a.value;if(l&&(i==t||l.from>i+10))return l;if(i==t)return null}}prevMatch(e,t,s){return this.prevMatchInRange(e,0,t)||this.prevMatchInRange(e,s,e.doc.length)}getReplacement(e){return this.spec.unquote(this.spec.replace).replace(/\$([$&\d+])/g,(t,s)=>s=="$"?"$":s=="&"?e.match[0]:s!="0"&&+s