initial commit

This commit is contained in:
Norman Bos
2023-02-09 23:05:28 +01:00
parent a8fd89660d
commit d6c47fb6bf
105 changed files with 4914 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
.DS_Store
+6
View File
@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true
}
+47
View File
@@ -0,0 +1,47 @@
Copyright (c) 2013, 2014, 2015, 2016 Jake Gordon and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
# Javascript Tiny-Platformer
A very minimal javascript platform game
- [play the game](http://codeincomplete.com/projects/tiny-platformer/index.html)
- read the [original article](http://codeincomplete.com/posts/2013/5/27/tiny_platformer/)
- read a [follow up article](http://codeincomplete.com/posts/2013/6/2/tiny_platformer_revisited/) about adding monsters and treasure
- view the [source](https://github.com/jakesgordon/javascript-tiny-platformer)
Just a sinple example of how to have a tiny rectangle run around some rectangle platforms,
collecting rectangular gold and avoiding rectangular monsters.
# SUPPORTED BROWSERS
Should work in any modern browser with canvas support
# DEVELOPMENT
The game is 100% client side javascript, html and css. It should run when served up by any web server.
# License
[MIT](http://en.wikipedia.org/wiki/MIT_License) license.
---
+103
View File
@@ -0,0 +1,103 @@
# Readme
Simple JS platformer to modify and extend.
## Welche VS Code Extensions sollten installiert sein
- Better Comments
- Color Highlight
- Atom One Dark Theme
- Material Icon Theme
## Grundideen
- Camera Setup kann ich erstmal auslassen. Dann mache ich stattdessen single rooms (vgl. Lykia).
- Overworld und Levels?
- Character selection?
- Inventory System mit durchsuchbaren Kisten, Content bleibt erhalten.
- Auflösung von Gameboy, GBC, Game Gear ist 160 × 144 pixel.
- Sprite Animation kann ich genauso machen wie bei Pico-8 (<https://nerdyteachers.com/Explain/AnimateSprite/>, <https://www.youtube.com/watch?v=rtWGZ1uiBLY>)!
- Setting ist ein Space Adventure. Mit einem Raumschiff kann man viele verschiedene Umgebungen erreichen (Dschungel, Techno, Unterwasser, Höhlen,...)
- Commander Keen trifft Metroidvania (mit ein bisschen Subnautica?)
## Todo
- [x] Refactor code. Alles sortieren (Helper, Utils, Functions, Main Function, Objects usw.)
- [x] Robuste collision checks und collision maps bauen
- [x] Irgendeine Art Dev Function bauen, die mir übersichtliche console logs ausgibt.
- [x] "Doors" System entwickeln.
- [x] Die Camera funktioniert in Level2 noch nicht mit Begrenzung nach Rechts und Unten. Liegt das am Level selbst?
- [x] Pause Function
- [x] Sprite Animation (<https://codehs.com/tutorial/andy/Programming_Sprites_in_JavaScript>)
- [x] Maps umrüsten auf 16x16 px tiles. Ist in den Variablen schon vorbereitet, muss nur die Maps noch mal aus Tiled exportieren
- [x] Aus Tiled werden die Custom Properties nicht richtig exportiert
- [x] "type" muss ich ändern in "class" in der setup Funktion
- [x] Die properties werden als Array exportiert. Da brauche ich in der setup Funktion noch eine Function um die zu Konviertieren.
- [x] Globales Objekt bauen mit einem einzigen Wert ("life = 3" z.B.), den ich immer Anzeige und der über die Level hinaus bestehen bleibt.
- [ ] Gravity Variable für verschiedene Umgebungen (Land, Wasser, Space)
- [ ] Verschiedene Biomes
- [ ] Ice World
- [ ] Desert World
- [ ] Lava World
- [ ] Underwater
- [ ] Forest World
- [ ] Techno World
- [ ] Fix Scaling bei verschiedenen Level-Größen (protrait ist broken)
### Welche Assets nutze ich
- "Grotto Escape Pack" by Ansimuz
- "8-Bit Cave Loop" by Wolfgang
## Welche Tools nutze ich
- Beepbox für Musik <https://www.beepbox.co/>
- Tiled für Sprites und Maps <https://www.mapeditor.org/>
### Tiled
Map Setup:
- Orthogonal
- CSV
- Left Down (! das ist wichtig)
Layer in folgender Reihenfolge:
- foreground
- Object Layer 1
- tileLayer
- background
Export als ".tmj"
## Feature Roadmap
- [x] zweite Map
- [x] Variablen für alle Props, die die Map betreffen (MAP.tw/.th, Scaling, HUD-Scaling, TileAtlas)
- [x] Alphabet TileMap (oder font!)
- [x] Map Layers (CollisionLayer, Background, Foreground, Object-Layer) inkl. Anleitung für Tiled
- [x] Map Transitions (mit einem Doors Object)
- [x] Map Layers
- [x] Global Player Object
- [x] Shooting
- [x] Swimming
- [x] Ammo
- [ ] mehr Weapons
- [ ] animated Tiles
- [ ] Destructibles (haben eigenes "destructibles = {}" Objekt wie monsters oder treasure und haben hitpoints, eigener Layer oder gehören die zu Objects?)
- [ ] Upgrades/Powerups
- [ ] Ladders/Poles
- [ ] mehr Enemy Types (flying, wallcrawler, shooting)
- [ ] Game Start/Game Over Screen
- [ ] Inventory System
- [ ] CameraBox
- [ ] Sprites
- [ ] Player
- [ ] Enemies
- [ ] Items
- [ ] Map
## Possible Story
Biff Bolton, sidekick to intergalactic space adventurer Ace Hunter, is stranded on a strange planet after several parts of his ship fail and he has to make an emergency landing. To repair his ship he must explore the planet and delve deep into its mysterious caves and catacombs.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+683
View File
@@ -0,0 +1,683 @@
{
"format": "BeepBox",
"version": 9,
"scale": "easy :)",
"key": "C",
"introBars": 0,
"loopBars": 4,
"beatsPerBar": 8,
"ticksPerBeat": 4,
"beatsPerMinute": 150,
"layeredInstruments": false,
"patternInstruments": false,
"channels": [
{
"type": "pitch",
"instruments": [
{
"type": "FM",
"volume": 80,
"eqFilter": [],
"preset": 325,
"effects": [
"chord type",
"note filter",
"reverb"
],
"chord": "strum",
"noteFilter": [
{
"type": "low-pass",
"cutoffHz": 19027.31,
"linearGain": 0.3536
}
],
"reverb": 33,
"fadeInSeconds": 0,
"fadeOutTicks": -3,
"algorithm": "1←2←3←4",
"feedbackType": "3⟲",
"feedbackAmplitude": 4,
"operators": [
{
"frequency": "3×",
"amplitude": 13
},
{
"frequency": "1×",
"amplitude": 7
},
{
"frequency": "13×",
"amplitude": 3
},
{
"frequency": "1×",
"amplitude": 11
}
],
"envelopes": [
{
"target": "noteFilterAllFreqs",
"envelope": "twang 1"
}
]
}
],
"patterns": [
{
"notes": [
{
"pitches": [
57
],
"points": [
{
"tick": 4,
"pitchBend": 0,
"volume": 100
},
{
"tick": 6,
"pitchBend": 0,
"volume": 100
}
]
}
]
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
}
],
"sequence": [
1,
1,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"octaveScrollBar": 3
},
{
"type": "pitch",
"instruments": [
{
"type": "FM",
"volume": 80,
"eqFilter": [],
"preset": 649,
"effects": [
"transition type",
"vibrato",
"reverb"
],
"transition": "slide",
"vibrato": "delayed",
"reverb": 33,
"fadeInSeconds": 0.0263,
"fadeOutTicks": -3,
"algorithm": "1←(234)",
"feedbackType": "1⟲",
"feedbackAmplitude": 2,
"operators": [
{
"frequency": "2×",
"amplitude": 15
},
{
"frequency": "1×",
"amplitude": 7
},
{
"frequency": "~1×",
"amplitude": 7
},
{
"frequency": "1×",
"amplitude": 0
}
],
"envelopes": [
{
"target": "operatorAmplitude",
"envelope": "punch",
"index": 1
},
{
"target": "feedbackAmplitude",
"envelope": "twang 3"
}
]
}
],
"patterns": [
{
"notes": [
{
"pitches": [
38
],
"points": [
{
"tick": 4,
"pitchBend": 0,
"volume": 100
},
{
"tick": 6,
"pitchBend": 0,
"volume": 100
}
]
}
]
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
}
],
"sequence": [
1,
1,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"octaveScrollBar": 2
},
{
"type": "pitch",
"instruments": [
{
"type": "FM",
"volume": 80,
"eqFilter": [],
"preset": 322,
"effects": [
"chord type",
"note filter",
"reverb"
],
"chord": "strum",
"noteFilter": [
{
"type": "low-pass",
"cutoffHz": 13454.34,
"linearGain": 0.25
}
],
"reverb": 33,
"fadeInSeconds": 0,
"fadeOutTicks": 48,
"algorithm": "1←(23←4)",
"feedbackType": "3⟲",
"feedbackAmplitude": 4,
"operators": [
{
"frequency": "1×",
"amplitude": 15
},
{
"frequency": "1×",
"amplitude": 5
},
{
"frequency": "11×",
"amplitude": 1
},
{
"frequency": "1×",
"amplitude": 9
}
],
"envelopes": [
{
"target": "noteFilterAllFreqs",
"envelope": "twang 1"
},
{
"target": "operatorAmplitude",
"envelope": "twang 3",
"index": 2
},
{
"target": "feedbackAmplitude",
"envelope": "twang 1"
}
]
}
],
"patterns": [
{
"notes": [
{
"pitches": [
19
],
"points": [
{
"tick": 4,
"pitchBend": 0,
"volume": 100
},
{
"tick": 6,
"pitchBend": 0,
"volume": 100
}
]
},
{
"pitches": [
19
],
"points": [
{
"tick": 8,
"pitchBend": 0,
"volume": 100
},
{
"tick": 10,
"pitchBend": 0,
"volume": 100
}
]
},
{
"pitches": [
28
],
"points": [
{
"tick": 12,
"pitchBend": 0,
"volume": 100
},
{
"tick": 14,
"pitchBend": 0,
"volume": 100
}
]
},
{
"pitches": [
19
],
"points": [
{
"tick": 20,
"pitchBend": 0,
"volume": 100
},
{
"tick": 22,
"pitchBend": 0,
"volume": 100
}
]
},
{
"pitches": [
19
],
"points": [
{
"tick": 24,
"pitchBend": 0,
"volume": 100
},
{
"tick": 26,
"pitchBend": 0,
"volume": 100
}
]
},
{
"pitches": [
28
],
"points": [
{
"tick": 28,
"pitchBend": 0,
"volume": 100
},
{
"tick": 30,
"pitchBend": 0,
"volume": 100
}
]
}
]
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
}
],
"sequence": [
1,
1,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"octaveScrollBar": 1
},
{
"type": "drum",
"instruments": [
{
"type": "noise",
"volume": 80,
"eqFilter": [
{
"type": "low-pass",
"cutoffHz": 16000,
"linearGain": 0.3536
}
],
"preset": 69,
"effects": [
"chord type"
],
"chord": "arpeggio",
"fadeInSeconds": 0,
"fadeOutTicks": -3,
"wave": "retro",
"envelopes": []
}
],
"patterns": [
{
"notes": [
{
"pitches": [
9
],
"points": [
{
"tick": 0,
"pitchBend": 0,
"volume": 100
},
{
"tick": 2,
"pitchBend": 0,
"volume": 0
}
],
"continuesLastPattern": false
},
{
"pitches": [
6
],
"points": [
{
"tick": 4,
"pitchBend": 0,
"volume": 100
},
{
"tick": 6,
"pitchBend": 0,
"volume": 0
}
]
},
{
"pitches": [
6
],
"points": [
{
"tick": 6,
"pitchBend": 0,
"volume": 100
},
{
"tick": 8,
"pitchBend": 0,
"volume": 0
}
]
},
{
"pitches": [
9
],
"points": [
{
"tick": 8,
"pitchBend": 0,
"volume": 100
},
{
"tick": 10,
"pitchBend": 0,
"volume": 0
}
]
},
{
"pitches": [
2
],
"points": [
{
"tick": 10,
"pitchBend": 0,
"volume": 100
},
{
"tick": 12,
"pitchBend": 0,
"volume": 0
}
]
},
{
"pitches": [
2
],
"points": [
{
"tick": 12,
"pitchBend": 0,
"volume": 100
},
{
"tick": 14,
"pitchBend": 0,
"volume": 0
}
]
},
{
"pitches": [
9
],
"points": [
{
"tick": 16,
"pitchBend": 0,
"volume": 100
},
{
"tick": 18,
"pitchBend": 0,
"volume": 0
}
]
},
{
"pitches": [
2
],
"points": [
{
"tick": 20,
"pitchBend": 0,
"volume": 100
},
{
"tick": 23,
"pitchBend": 0,
"volume": 0
}
]
},
{
"pitches": [
9
],
"points": [
{
"tick": 24,
"pitchBend": 0,
"volume": 100
},
{
"tick": 26,
"pitchBend": 0,
"volume": 0
}
]
}
]
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
},
{
"notes": []
}
],
"sequence": [
1,
1,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
}
]
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+15
View File
@@ -0,0 +1,15 @@
/*! FPSMeter 0.3.1 - 9th May 2013 | https://github.com/Darsain/fpsmeter */
(function(m,j){function s(a,e){for(var g in e)try{a.style[g]=e[g]}catch(j){}return a}function H(a){return null==a?String(a):"object"===typeof a||"function"===typeof a?Object.prototype.toString.call(a).match(/\s([a-z]+)/i)[1].toLowerCase()||"object":typeof a}function R(a,e){if("array"!==H(e))return-1;if(e.indexOf)return e.indexOf(a);for(var g=0,j=e.length;g<j;g++)if(e[g]===a)return g;return-1}function I(){var a=arguments,e;for(e in a[1])if(a[1].hasOwnProperty(e))switch(H(a[1][e])){case "object":a[0][e]=
I({},a[0][e],a[1][e]);break;case "array":a[0][e]=a[1][e].slice(0);break;default:a[0][e]=a[1][e]}return 2<a.length?I.apply(null,[a[0]].concat(Array.prototype.slice.call(a,2))):a[0]}function N(a){a=Math.round(255*a).toString(16);return 1===a.length?"0"+a:a}function S(a,e,g,j){if(a.addEventListener)a[j?"removeEventListener":"addEventListener"](e,g,!1);else if(a.attachEvent)a[j?"detachEvent":"attachEvent"]("on"+e,g)}function D(a,e){function g(a,b,d,c){return y[0|a][Math.round(Math.min((b-d)/(c-d)*J,J))]}
function r(){f.legend.fps!==q&&(f.legend.fps=q,f.legend[T]=q?"FPS":"ms");K=q?b.fps:b.duration;f.count[T]=999<K?"999+":K.toFixed(99<K?0:d.decimals)}function m(){z=A();L<z-d.threshold&&(b.fps-=b.fps/Math.max(1,60*d.smoothing/d.interval),b.duration=1E3/b.fps);for(c=d.history;c--;)E[c]=0===c?b.fps:E[c-1],F[c]=0===c?b.duration:F[c-1];r();if(d.heat){if(w.length)for(c=w.length;c--;)w[c].el.style[h[w[c].name].heatOn]=q?g(h[w[c].name].heatmap,b.fps,0,d.maxFps):g(h[w[c].name].heatmap,b.duration,d.threshold,
0);if(f.graph&&h.column.heatOn)for(c=u.length;c--;)u[c].style[h.column.heatOn]=q?g(h.column.heatmap,E[c],0,d.maxFps):g(h.column.heatmap,F[c],d.threshold,0)}if(f.graph)for(p=0;p<d.history;p++)u[p].style.height=(q?E[p]?Math.round(O/d.maxFps*Math.min(E[p],d.maxFps)):0:F[p]?Math.round(O/d.threshold*Math.min(F[p],d.threshold)):0)+"px"}function k(){20>d.interval?(x=M(k),m()):(x=setTimeout(k,d.interval),P=M(m))}function G(a){a=a||window.event;a.preventDefault?(a.preventDefault(),a.stopPropagation()):(a.returnValue=
!1,a.cancelBubble=!0);b.toggle()}function U(){d.toggleOn&&S(f.container,d.toggleOn,G,1);a.removeChild(f.container)}function V(){f.container&&U();h=D.theme[d.theme];y=h.compiledHeatmaps||[];if(!y.length&&h.heatmaps.length){for(p=0;p<h.heatmaps.length;p++){y[p]=[];for(c=0;c<=J;c++){var b=y[p],e=c,g;g=0.33/J*c;var j=h.heatmaps[p].saturation,m=h.heatmaps[p].lightness,n=void 0,k=void 0,l=void 0,t=l=void 0,v=n=k=void 0,v=void 0,l=0.5>=m?m*(1+j):m+j-m*j;0===l?g="#000":(t=2*m-l,k=(l-t)/l,g*=6,n=Math.floor(g),
v=g-n,v*=l*k,0===n||6===n?(n=l,k=t+v,l=t):1===n?(n=l-v,k=l,l=t):2===n?(n=t,k=l,l=t+v):3===n?(n=t,k=l-v):4===n?(n=t+v,k=t):(n=l,k=t,l-=v),g="#"+N(n)+N(k)+N(l));b[e]=g}}h.compiledHeatmaps=y}f.container=s(document.createElement("div"),h.container);f.count=f.container.appendChild(s(document.createElement("div"),h.count));f.legend=f.container.appendChild(s(document.createElement("div"),h.legend));f.graph=d.graph?f.container.appendChild(s(document.createElement("div"),h.graph)):0;w.length=0;for(var q in f)f[q]&&
h[q].heatOn&&w.push({name:q,el:f[q]});u.length=0;if(f.graph){f.graph.style.width=d.history*h.column.width+(d.history-1)*h.column.spacing+"px";for(c=0;c<d.history;c++)u[c]=f.graph.appendChild(s(document.createElement("div"),h.column)),u[c].style.position="absolute",u[c].style.bottom=0,u[c].style.right=c*h.column.width+c*h.column.spacing+"px",u[c].style.width=h.column.width+"px",u[c].style.height="0px"}s(f.container,d);r();a.appendChild(f.container);f.graph&&(O=f.graph.clientHeight);d.toggleOn&&("click"===
d.toggleOn&&(f.container.style.cursor="pointer"),S(f.container,d.toggleOn,G))}"object"===H(a)&&a.nodeType===j&&(e=a,a=document.body);a||(a=document.body);var b=this,d=I({},D.defaults,e||{}),f={},u=[],h,y,J=100,w=[],W=0,B=d.threshold,Q=0,L=A()-B,z,E=[],F=[],x,P,q="fps"===d.show,O,K,c,p;b.options=d;b.fps=0;b.duration=0;b.isPaused=0;b.tickStart=function(){Q=A()};b.tick=function(){z=A();W=z-L;B+=(W-B)/d.smoothing;b.fps=1E3/B;b.duration=Q<L?B:z-Q;L=z};b.pause=function(){x&&(b.isPaused=1,clearTimeout(x),
C(x),C(P),x=P=0);return b};b.resume=function(){x||(b.isPaused=0,k());return b};b.set=function(a,c){d[a]=c;q="fps"===d.show;-1!==R(a,X)&&V();-1!==R(a,Y)&&s(f.container,d);return b};b.showDuration=function(){b.set("show","ms");return b};b.showFps=function(){b.set("show","fps");return b};b.toggle=function(){b.set("show",q?"ms":"fps");return b};b.hide=function(){b.pause();f.container.style.display="none";return b};b.show=function(){b.resume();f.container.style.display="block";return b};b.destroy=function(){b.pause();
U();b.tick=b.tickStart=function(){}};V();k()}var A,r=m.performance;A=r&&(r.now||r.webkitNow)?r[r.now?"now":"webkitNow"].bind(r):function(){return+new Date};for(var C=m.cancelAnimationFrame||m.cancelRequestAnimationFrame,M=m.requestAnimationFrame,r=["moz","webkit","o"],G=0,k=0,Z=r.length;k<Z&&!C;++k)M=(C=m[r[k]+"CancelAnimationFrame"]||m[r[k]+"CancelRequestAnimationFrame"])&&m[r[k]+"RequestAnimationFrame"];C||(M=function(a){var e=A(),g=Math.max(0,16-(e-G));G=e+g;return m.setTimeout(function(){a(e+
g)},g)},C=function(a){clearTimeout(a)});var T="string"===H(document.createElement("div").textContent)?"textContent":"innerText";D.extend=I;window.FPSMeter=D;D.defaults={interval:100,smoothing:10,show:"fps",toggleOn:"click",decimals:1,maxFps:60,threshold:100,position:"absolute",zIndex:10,left:"5px",top:"5px",right:"auto",bottom:"auto",margin:"0 0 0 0",theme:"dark",heat:0,graph:0,history:20};var X=["toggleOn","theme","heat","graph","history"],Y="position zIndex left top right bottom margin".split(" ")})(window);(function(m,j){j.theme={};var s=j.theme.base={heatmaps:[],container:{heatOn:null,heatmap:null,padding:"5px",minWidth:"95px",height:"30px",lineHeight:"30px",textAlign:"right",textShadow:"none"},count:{heatOn:null,heatmap:null,position:"absolute",top:0,right:0,padding:"5px 10px",height:"30px",fontSize:"24px",fontFamily:"Consolas, Andale Mono, monospace",zIndex:2},legend:{heatOn:null,heatmap:null,position:"absolute",top:0,left:0,padding:"5px 10px",height:"30px",fontSize:"12px",lineHeight:"32px",fontFamily:"sans-serif",
textAlign:"left",zIndex:2},graph:{heatOn:null,heatmap:null,position:"relative",boxSizing:"padding-box",MozBoxSizing:"padding-box",height:"100%",zIndex:1},column:{width:4,spacing:1,heatOn:null,heatmap:null}};j.theme.dark=j.extend({},s,{heatmaps:[{saturation:0.8,lightness:0.8}],container:{background:"#222",color:"#fff",border:"1px solid #1a1a1a",textShadow:"1px 1px 0 #222"},count:{heatOn:"color"},column:{background:"#3f3f3f"}});j.theme.light=j.extend({},s,{heatmaps:[{saturation:0.5,lightness:0.5}],
container:{color:"#666",background:"#fff",textShadow:"1px 1px 0 rgba(255,255,255,.5), -1px -1px 0 rgba(255,255,255,.5)",boxShadow:"0 0 0 1px rgba(0,0,0,.1)"},count:{heatOn:"color"},column:{background:"#eaeaea"}});j.theme.colorful=j.extend({},s,{heatmaps:[{saturation:0.5,lightness:0.6}],container:{heatOn:"backgroundColor",background:"#888",color:"#fff",textShadow:"1px 1px 0 rgba(0,0,0,.2)",boxShadow:"0 0 0 1px rgba(0,0,0,.1)"},column:{background:"#777",backgroundColor:"rgba(0,0,0,.2)"}});j.theme.transparent=
j.extend({},s,{heatmaps:[{saturation:0.8,lightness:0.5}],container:{padding:0,color:"#fff",textShadow:"1px 1px 0 rgba(0,0,0,.5)"},count:{padding:"0 5px",height:"40px",lineHeight:"40px"},legend:{padding:"0 5px",height:"40px",lineHeight:"42px"},graph:{height:"40px"},column:{width:5,background:"#999",heatOn:"backgroundColor",opacity:0.5}})})(window,FPSMeter);
Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

@@ -0,0 +1,10 @@
Artwork created by Luis Zuno (@ansimuz)
License for Everyone. This piece is under a CC-BY-3.0 License http://creativecommons.org/licenses/by/3.0/
You can copy, modify, distribute and perform the work, even for commercial purposes, as long as you give appropiate credit.
You are not require to credit this work if you are or had been a Patreon Supporter at https://www.patreon.com/ansimuz
Thanks get more like these at: https://www.patreon.com/ansimuz
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

+66
View File
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Javascript Platformer</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="platformer">
<canvas id="canvas">
<div class="unsupported">
Sorry, this example cannot be run because your browser does not
support the &lt;canvas&gt; element
</div>
</canvas>
<div id="instructions">
<b>LEFT/RIGHT</b> to move, <b>SPACE</b> to jump, collect gold and jump
on monsters.
</div>
<div class="controls-wrapper">
<div class="dpad-wrapper">
<div class="dpad-top-bottom">
<button class="dpad-button up"></button>
</div>
<div class="dpad-middle">
<button class="dpad-button left"></button>
<button class="dpad-button right"></button>
</div>
<div class="dpad-top-bottom">
<button class="dpad-button down"></button>
</div>
</div
<div id="buttons">
<div class="buttons-wrapper">
<button class="button">A</button>
<button class="button">B</button>
</div>
</div>
</div>
</div>
<script src="fpsmeter.min.js"></script>
<script src="js/utils.js"></script>
<script src="levels/levelObjects.js"></script>
<script src="levels/level1.js"></script>
<script src="levels/level2.js"></script>
<script src="levels/level3.js"></script>
<script src="js/variables.js"></script>
<script src="js/input.js"></script>
<script src="js/render.js"></script>
<script src="js/update.js"></script>
<script src="js/camera.js"></script>
<script src="js/setup.js"></script>
<script src="js/main.js"></script>
</body>
</html>
+118
View File
@@ -0,0 +1,118 @@
//-------------------------------------------------------------------------
// 3.2 Simple CAMERA
//-------------------------------------------------------------------------
function renderCamera() {
// ! Ich muss diese Funktion gegen eine mit CameraBox austauschen. Diese kann ich aber für TopDown/Overworld Level benutzen.
const cameraWidth = mapWidth * TILE
const cameraHeight = mapHeight * TILE
const camera = {
get x() {
if (player.x < scaledCanvas.width) return 0
if (player.x > cameraWidth - scaledCanvas.width)
return -(cameraWidth - scaledCanvas.width * 2)
else return -(player.x - scaledCanvas.width)
},
get y() {
if (player.y < scaledCanvas.height) return 0
if (player.y > cameraHeight - scaledCanvas.height)
return -(cameraHeight - scaledCanvas.height * 2)
else return -(player.y - scaledCanvas.height)
},
}
ctx.translate(camera.x, camera.y)
}
//-------------------------------------------------------------------------
// Box CAMERA
//-------------------------------------------------------------------------
let cameraWithBox = {
position: {
x: 0,
y: 0,
},
}
function renderCameraWithBox() {
//console.log('log renderCameraWithBox position.x: ', cameraWithBox.position.x)
ctx.translate(cameraWithBox.position.x, cameraWithBox.position.y)
}
const cameraBox = {
// ! Die Werte stimme nur ungefähr, geben mir aber eine grobe CameraBox zurück.
get x() {
return -(canvas.width / 2 / 2 - 100 - player.x)
},
get y() {
return -(canvas.height / 2 / 2 - 150 - player.y)
},
width: canvas.width / 2 - 200,
height: canvas.height / 2 - 300,
}
// ! "camera" muss ich dann in der renderCamera() in der render Funktion anpassen.
// ? Woher kommt this.velocity ?
function shouldPanCameraToTheLeft({ canvas, camera }) {
const cameraboxRightSide = cameraBox.x + cameraBox.width
const scaledDownCanvasWidth = canvas.width / 2
// console.log('log cameraboxRightSide: ', cameraboxRightSide)
//console.log('log cameraboxRightSide >= 2048: ', cameraboxRightSide >= 2048)
// ! Woher kommt der Wert hier?
if (cameraboxRightSide >= 2048) return
//console.log('log player.x: ', player.x)
//console.log('log scaledDownCanvasWidth: ', scaledDownCanvasWidth)
/* console.log(
'log shouldPanCameraToTheLeft: ',
cameraboxRightSide >= scaledDownCanvasWidth
) */
if (
cameraboxRightSide >=
scaledDownCanvasWidth + Math.abs(cameraWithBox.position.x)
) {
cameraWithBox.position.x -= player.x - canvas.width / 2 / 2
}
}
function shouldPanCameraToTheRight({ canvas, cameraprop }) {
//console.log('log shouldPanCameraToTheRight')
//console.log('log entity x update: ', (player.x + step * player.dx).toFixed(2))
//console.log('log cameraBox.x <= 0: ', cameraBox.x <= 0)
if (cameraBox.x <= 0) return
if (cameraBox.x <= Math.abs(cameraWithBox.position.x)) {
cameraWithBox.position.x = cameraWithBox.position.x--
}
}
function shouldPanCameraDown({ canvas, camera }) {
//console.log('log shouldPanCameraDown')
/* if (cameraBox.position.y + this.velocity.y <= 0) return
if (cameraBox.position.y <= Math.abs(camera.position.y)) {
camera.position.y -= this.velocity.y
} */
}
function shouldPanCameraUp({ canvas, camera }) {
// console.log('log shouldPanCameraUp')
/* if (cameraBox.position.y + cameraBox.height + this.velocity.y >= 432) return
const scaledCanvasHeight = canvas.height / 4
if (
cameraBox.position.y + cameraBox.height >=
Math.abs(camera.position.y) + scaledCanvasHeight
) {
camera.position.y -= this.velocity.y
} */
}
+50
View File
@@ -0,0 +1,50 @@
//-------------------------------------------------------------------------
// 3.1 INPUT
//-------------------------------------------------------------------------
function onkey(ev, key, down) {
switch (key) {
case KEY.LEFT:
player.left = down
shouldPanCameraToTheRight({ canvas, cameraWithBox })
ev.preventDefault()
return false
case KEY.RIGHT:
player.right = down
shouldPanCameraToTheLeft({ canvas, cameraWithBox })
ev.preventDefault()
return false
case KEY.X:
player.jump = down
// console.log('log Key X')
shouldPanCameraDown({ canvas, cameraWithBox })
ev.preventDefault()
return false
case KEY.Y:
console.log('log shoot')
// player.shooting = down
shoot(player)
ev.preventDefault()
return false
case KEY.UP:
console.log('log Key Up')
player.up = down
shouldPanCameraDown({ canvas, cameraWithBox })
ev.preventDefault()
return false
case KEY.DOWN:
console.log('log Key Down')
player.down = down
ev.preventDefault()
return false
case KEY.ENTER:
console.log('log Key Enter')
player.interact = down
ev.preventDefault()
return false
case KEY.SPACE:
console.log('log Key Space')
ev.preventDefault()
return false
}
}
+97
View File
@@ -0,0 +1,97 @@
function main() {
//-------------------------------------------------------------------------
// 6. THE GAME LOOP
//-------------------------------------------------------------------------
var counter = 0,
dt = 0,
now,
last = timestamp(),
fpsmeter = new FPSMeter({
decimals: 0,
graph: true,
theme: 'dark',
left: '5px',
})
function frame() {
fpsmeter.tickStart()
now = timestamp()
dt = dt + Math.min(1, (now - last) / 1000)
while (dt > step) {
dt = dt - step
// * Update wird pausiert bei Toggle 'p'.
if (!paused) {
update(step)
}
}
render(ctx, counter, dt, currentLevel)
last = now
counter++
fpsmeter.tick()
requestAnimationFrame(frame, canvas)
}
document.addEventListener(
'keydown',
function (ev) {
return onkey(ev, ev.key, true)
},
false
)
document.addEventListener(
'keyup',
function (ev) {
return onkey(ev, ev.key, false)
},
false
)
// * EventListener für Pause-Taste
window.addEventListener('keydown', function (e) {
var key = e.key
if (key === 'p') {
// p key
togglePause()
}
})
// * EventListener für DevInfos Toggle
window.addEventListener('keydown', function (e) {
var key = e.key
if (key === 'o') {
// p key
toggleDevInfos()
}
})
// ! Vielleicht kann ich statt des setup() an dieser Stelle auch eine gameStart() Funktion erstellen, die beim erstmaligen Start alles erledigt. Später wird dann ja immer die _initLevel() aufgerufen.
setup(currentLevel.levelData) // ! Was brauche ich hier, damit das variabel alle Level laden kann?
console.log('log monsters ', monsters)
frame()
}
// * window onload function
;(function () {
//-------------------------------------------------------------------------
// POLYFILLS
//-------------------------------------------------------------------------
if (!window.requestAnimationFrame) {
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimationFrame =
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback, element) {
window.setTimeout(callback, 1000 / 60)
}
}
main()
})()
+375
View File
@@ -0,0 +1,375 @@
//-------------------------------------------------------------------------
// 4. RENDERING
//-------------------------------------------------------------------------
function render(ctx, frame, dt, thiscurrentLevel) {
// ? Kann ich mich eigentlich mit scale und translate noch weiter an Pico-8 orientieren und in den variables sowas wie scale = ctx.scale oder so erstellen?
// * Disable Image Smoothing
ctx.mozImageSmoothingEnabled = false
ctx.webkitImageSmoothingEnabled = false
ctx.msImageSmoothingEnabled = false
ctx.imageSmoothingEnabled = false
ctx.save()
ctx.scale(
Math.round(thiscurrentLevel.scalingFactor / 2),
Math.round(thiscurrentLevel.scalingFactor / 2)
)
ctx.clearRect(0, 0, width, height)
renderCamera()
//renderCameraWithBox()
renderBackground(ctx, thiscurrentLevel.backgroundAtlas, bgCells)
renderMap(ctx, thiscurrentLevel.levelAtlas, cells)
renderPlayer(ctx, playerAtlas, dt, frame)
renderBullets(ctx, itemsAtlas, dt, frame)
renderMonsters(ctx, enemyAtlas, dt, frame)
renderTreasure(ctx, itemsAtlas, dt, frame)
renderPickups(ctx, itemsAtlas, dt, frame)
renderForeground(ctx, thiscurrentLevel.foregroundAtlas, fgCells)
renderLiquids(ctx, thiscurrentLevel.foregroundAtlas, fgCells)
// * Draw CameraBox
//renderCameraBox()
ctx.restore()
renderPause()
renderHud(ctx, meterAtlas, dt, frame)
renderHudSprites()
//fadeScreen(frame, dt)
renderDevInfos()
}
// Map Limit Variables
// * Das hier ist die allgemeine Funktion um Tiles aus dem TileAtlas auf den Canvas zu malen.
function drawTile(levelAtlas, cell, dx, dy, opacity) {
levelAtlas.sourceY =
Math.floor(cell / levelAtlas.atlasCol) * levelAtlas.tileSize
levelAtlas.sourceX = (cell % levelAtlas.atlasCol) * levelAtlas.tileSize
// ? Wofür stehen die Werte sWidth, sHeight, dWidth, dHeight
ctx.globalAlpha = opacity
// ! Kann ich die ganzen festen Zahlen als Variablen aus der Map holen?
ctx.drawImage(
levelAtlas.tileAtlas,
levelAtlas.sourceX,
levelAtlas.sourceY,
16,
16,
dx * 16,
dy * 16,
16,
16
)
}
function renderBackground(ctx, levelAtlas) {
var x, y, cell
for (y = 0; y < mapHeight; y++) {
for (x = 0; x < mapWidth; x++) {
cell = bgTcell(x, y, mapWidth)
if (cell) {
drawTile(levelAtlas, cell - levelAtlas.offset, x, y)
}
}
}
}
function renderForeground(ctx, levelAtlas) {
var x, y, cell
for (y = 0; y < mapHeight; y++) {
for (x = 0; x < mapWidth; x++) {
cell = fgTcell(x, y, mapWidth)
if (cell) {
drawTile(levelAtlas, cell - levelAtlas.offset, x, y)
}
}
}
}
function renderLiquids(ctx, levelAtlas) {
var x, y, cell
for (y = 0; y < mapHeight; y++) {
for (x = 0; x < mapWidth; x++) {
cell = lqTcell(x, y, mapWidth)
if (cell) {
drawTile(levelAtlas, cell - levelAtlas.offset, x, y, 0.5)
}
}
}
}
function renderMap(ctx, levelAtlas) {
var x, y, cell
for (y = 0; y < mapHeight; y++) {
for (x = 0; x < mapWidth; x++) {
cell = tcell(x, y, mapWidth)
if (cell) {
drawTile(levelAtlas, cell - levelAtlas.offset, x, y)
}
}
}
}
// * Sprite Animation
function drawSprite(entity, spriteAtlas, dt, frame) {
/* entity.start.x === 48 &&
entity.start.y === 656 &&
console.log('log frame ', entity.sprites.idle)
*/
let sprites = entity.sprites
let isJumping = entity.jumping || entity.falling
let isRunning =
(entity.right === true && !isJumping) ||
(entity.left === true && !isJumping)
let isShooting = entity.shooting || false
let sprite =
!!sprites.run && isRunning
? sprites.run
: !!sprites.jump && isJumping
? sprites.jump
: !!sprites.shoot && isShooting
? sprites.shoot
: sprites.idle
const x = entity.bullet ? entity.x : entity.x + entity.dx * dt
const y = entity.bullet
? entity.y - entity.height * 2
: entity.y + entity.dy * dt
const flipped = entity.flipped
const width = entity.bullet ? entity.width * 4 : entity.width
const height = entity.bullet ? entity.height * 4 : entity.height
let spriteTile = sprite.tiles[sprite.currentFrame]
/* entity.pickups &&
console.log(
'log drawSprite currentFrame ',
sprite.tiles[sprite.currentFrame]
) */
// make an image position using the
// current row and colum
spriteAtlas.sourceY =
Math.floor(spriteTile / spriteAtlas.atlasCol) * spriteAtlas.tileSize
spriteAtlas.sourceX =
(spriteTile % spriteAtlas.atlasCol) * spriteAtlas.tileSize
ctx.save()
// * Flip Sprite
ctx.scale(flipped ? -1 : 1, 1)
let dx = flipped ? -x - width : x
ctx.drawImage(
spriteAtlas.tileAtlas,
spriteAtlas.sourceX,
spriteAtlas.sourceY,
16, // * source Höhe
16, // * source Breite
dx,
y,
width, // * player Höhe
height // * player Breite
)
ctx.restore()
updateFrames(frame, sprite)
// entity.treasure && console.log('log drawSprite ', entity)
}
function updateFrames(frame, sprite) {
/* entity.start.x === 48 &&
entity.start.y === 656 &&
console.log('log frame ', entity) */
if (frame % sprite.framebuffer === 0) {
if (sprite.currentFrame + 1 < sprite.tiles.length) sprite.currentFrame++
else if (sprite.loop) sprite.currentFrame = 0
}
}
// * Render Player
function renderPlayer(ctx, spriteAtlas, dt, frame) {
if (player.vul === false) {
ctx.globalAlpha = 0.25 + tweenTreasure(frame * 3, 60)
}
drawSprite(player, spriteAtlas, dt, frame)
ctx.globalAlpha = 1
}
function renderHud(ctx, spriteAtlas, dt, frame) {
var n, max
const hudScaling = scalingFactor / 2
const hitpoints = globalObject.hitpoints
const killed = globalObject.killed
const collected = globalObject.collected
ctx.fillStyle = COLOR.GOLD
for (n = 0, max = collected; n < max; n++)
ctx.fillRect(
t2p(1 + n) * hudScaling,
t2p(1) * hudScaling,
(TILE / 2) * hudScaling,
(TILE / 2) * hudScaling
)
ctx.fillStyle = COLOR.SLATE
for (n = 0, max = killed; n < max; n++)
ctx.fillRect(
t2p(1 + n) * hudScaling,
t2p(2) * hudScaling,
(TILE / 2) * hudScaling,
(TILE / 2) * hudScaling
)
// ! provisorische Hitpoint-Anzeige
ctx.fillStyle = 'red'
if (hitpoints === 1) {
ctx.globalAlpha = 0.25 + tweenTreasure(frame, 60)
}
for (n = 0, max = hitpoints; n < max; n++)
ctx.fillRect(
t2p(1 + n) * hudScaling,
t2p(3) * hudScaling,
(TILE / 2) * hudScaling,
(TILE / 2) * hudScaling
)
// ! Der Atlas funktioniert so nicht weil die Tiles nicht quadratisch sind.
// drawSprite(t, spriteAtlas, dt, frame)
ctx.globalAlpha = 1
}
function fadeScreen(frame, dt) {
ctx.fillStyle = 'black'
ctx.globalAlpha = 0
let duration = 100
pulse = frame % duration
if (pulse < duration) {
opacity = pulse / duration
} else {
opacity = 0
}
ctx.fillRect(0, 0, width, height)
ctx.globalAlpha = opacity
//setInterval(show(), 800)
// console.log('log opacity ', opacity)
// console.log('log fade frame ', frame)
// console.log('log fade pulse ', pulse)
}
// * Render Pause Function
function renderPause() {
if (paused) {
ctx.save()
ctx.scale(scalingFactor / 2, scalingFactor / 2)
ctx.font = '12px C64 TrueType'
ctx.fillStyle = 'white'
ctx.textAlign = 'center'
ctx.fillText('- pause -', scaledCanvas.width, scaledCanvas.height)
ctx.restore()
}
}
function renderCameraBox() {
ctx.fillStyle = 'rgba(0, 0, 255, 0.2)'
ctx.fillRect(cameraBox.x, cameraBox.y, cameraBox.width, cameraBox.height)
}
function renderMonsters(ctx, spriteAtlas, dt, frame) {
ctx.fillStyle = COLOR.SLATE
var n, max, monster
for (n = 0, max = monsters.length; n < max; n++) {
monster = monsters[n]
if (monster.sprites === undefined && !monster.dead)
ctx.fillRect(
monster.x + monster.dx * dt,
monster.y + monster.dy * dt,
TILE,
TILE
)
if (monster.sprites !== undefined && !monster.dead) {
drawSprite(monster, spriteAtlas, dt, frame)
}
}
}
function renderPickups(ctx, spriteAtlas, dt, frame) {
//ctx.globalAlpha = 0.25 + tweenTreasure(frame, 60)
var n, max, p
for (n = 0, max = pickups.length; n < max; n++) {
p = pickups[n]
//console.log('log p.sprites.currentsprite ', p.sprites.currentFrame)
if (p.sprites !== undefined && !p.collected) {
drawSprite(p, spriteAtlas, dt, frame)
}
}
//ctx.globalAlpha = 1
}
function renderTreasure(ctx, spriteAtlas, dt, frame) {
ctx.fillStyle = COLOR.GOLD
ctx.globalAlpha = 0.25 + tweenTreasure(frame, 60)
var n, max, t
for (n = 0, max = treasure.length; n < max; n++) {
t = treasure[n]
//if (!t.collected) ctx.fillRect(t.x, t.y + TILE / 3, TILE, (TILE * 2) / 3)
if (t.sprites !== undefined && !t.collected) {
// console.log('log treasure entity ', t)
drawSprite(t, spriteAtlas, dt, frame)
}
}
ctx.globalAlpha = 1
}
function tweenTreasure(frame, duration) {
const half = duration / 2
pulse = frame % duration
return pulse < half ? pulse / half : 1 - (pulse - half) / half
}
function renderBullets(ctx, spriteAtlas, dt, frame) {
ctx.fillStyle = COLOR.GOLD
var n, max, bullet
for (n = 0, max = bullets.length; n < max; n++) {
bullet = bullets[n]
switch (bullet.ammoType) {
case 'simpleGun':
bullet.sprites = simpleBullet.sprites
break
case 'advancedGun':
bullet.sprites = simpleBullet.sprites
break
}
//console.log('log bullet ', bullet.sprites)
//console.log('log bullet spriteAtlas ', spriteAtlas)
if (bullet !== undefined) {
if (bullet.sprites === undefined) {
ctx.fillRect(bullet.x, bullet.y, ammoType.width, ammoType.height)
}
if (bullet.sprites !== undefined) {
console.log('log bullet entity ', bullet)
drawSprite(bullet, spriteAtlas, dt, frame)
}
}
}
}
+150
View File
@@ -0,0 +1,150 @@
//-------------------------------------------------------------------------
// 5. LOAD THE MAP
//-------------------------------------------------------------------------
// ! Wenn ich die Setup-Funktion zum Levelwechsel aufrufe, dann werden die Variablen nicht upgedated!
// * Die setup Funktion finde ich gut, weil sie alle Infos aus einer map mit mehreren Layern erhalten kann.
function setup(map) {
var background = map.layers[0]?.data,
data = map.layers[1].data,
objects = map.layers[2].objects,
foreground = map.layers[3]?.data,
liquidTiles = map.layers[4]?.data || null,
n,
obj,
entity
for (n = 0; n < objects.length; n++) {
obj = objects[n]
const entityType = obj.type || obj.class
const entityName = obj.name
const entityClass = obj.class
const entityProperties = Array.isArray(obj.properties)
? Object.fromEntries(
!!obj.properties &&
obj.properties?.map((obj) => [obj.name, obj.value])
)
: !!obj.properties
? obj.properties
: {}
// * Hier werden alle actors/entities in die entsprechenden Ojekte gepushed.
entity = setupEntity(obj, entityType, entityProperties, entityName)
switch (entityType) {
case 'player':
player = entity
break
case 'monster':
monsters.push(entity)
break
case 'slimeMonster':
monsters.push(entity)
break
case 'treasure':
treasure.push(entity)
break
case 'ammo':
pickups.push(entity)
break
case 'health':
pickups.push(entity)
break
case 'extralife':
pickups.push(entity)
break
case 'water':
liquids.push(entity)
break
case 'door':
doors.push(entity)
break
}
}
cells = data
bgCells = background
fgCells = foreground
lqCells = liquidTiles
}
// * Diese Funktion entspricht grob meiner actor init Funktion.
// ? Muss ich noch eine Sprite Animation Funktion erstellen, die ihre Daten aus dem Entity Object für alle Entities (player, monster, etc.) erhält?
function setupEntity(obj, entityType, entityProperties, entityName) {
let entityTemplate =
entityName == 'monster'
? bounderMonster
: entityName == 'player'
? playerObject
: entityName == 'slimeMonster'
? slimeMonster
: entityName == 'treasure'
? coinTreasure
: entityName == 'ammo'
? ammo
: entityName == 'health'
? health
: entityName == 'extralife'
? extralife
: entityName == 'door'
? null
: null
let entitySprites = entityTemplate?.sprites
const entityMaxHitpoints =
entityType == 'player' ? playerObject.maxHitpoints : 1
const entityCurrentHitpoints =
entityType == 'player'
? playerObject.currentHitpoints
: !!entityTemplate?.currentHitpoints
? entityTemplate?.currentHitpoints
: 1
console.log('log entity ', entity)
var entity = {}
entity.x = obj.x
entity.y = obj.y
entity.dx = 0
entity.dy = 0
entity.gravity = METER * (entityProperties.gravity || GRAVITY)
entity.maxdx = METER * (entityProperties.maxdx || MAXDX)
entity.maxdy = METER * (entityProperties.maxdy || MAXDY)
entity.impulse = METER * (entityProperties.impulse || IMPULSE)
entity.accel = entity.maxdx / (entityProperties.accel || ACCEL)
entity.friction = entity.maxdx / (entityProperties.friction || FRICTION)
entity.monster = entityType == 'monster'
entity.slimeMonster = entityType == 'slimeMonster'
entity.player = entityType == 'player'
entity.treasure = entityType == 'treasure'
entity.ammo = entityType == 'ammo'
entity.health = entityType == 'health'
entity.extralife = entityType == 'extralife'
entity.water = entityType == 'water'
entity.door = entityType == 'door'
entity.leadsTo = entityProperties.leadsTo
entity.left = entityProperties.left
entity.right = entityProperties.right
entity.up = entityProperties.up || false
entity.down = entityProperties.down || false
entity.start = { x: obj.x, y: obj.y }
entity.killed = 0
entity.collected = false
entity.sprites = entitySprites
entity.flipped = false
entity.width = obj.width
entity.height = obj.height
entity.maxHitpoints = entityMaxHitpoints
entity.currentHitpoints = entityCurrentHitpoints
entity.hurt = false
entity.vul = true
return entity
}
+411
View File
@@ -0,0 +1,411 @@
//-------------------------------------------------------------------------
// 3. UPDATE LOOP
//-------------------------------------------------------------------------
function update(dt) {
updatePlayer(dt)
updateBullets(dt)
updateMonsters(dt)
checkTreasure()
checkPickups()
checkLiquids()
// updateDoors(dt)
checkDoors()
// ! Vielleicht eigene update Funktion für Musik schreiben
// theme.level1.play()
}
function updatePlayer(dt) {
updateEntity(player, dt)
}
function updateMonsters(dt) {
var n, max
for (n = 0, max = monsters.length; n < max; n++)
updateMonster(monsters[n], dt)
}
function updateMonster(monster, dt) {
monster.slimeMonster && console.log('log monster ', monster)
if (!monster.dead) {
updateEntity(monster, dt)
if (
overlap(player.x, player.y, TILE, TILE, monster.x, monster.y, TILE, TILE)
) {
if (player.dy > 0 && monster.y - player.y > TILE / 2) killMonster(monster)
else if (player.vul) {
reduceHitpoints(player, 1)
}
}
}
}
function shoot(player) {
//globalObject.ammo > 0 ? sfx.shoot.play() : sfx.click.play()
if (player.shooting) return
player.shooting = true
const bulletX = player.x
const bulletY = player.y
const direction = player.flipped
const weaponType = player?.currentWeapon || 'simpleGun'
if (globalObject.ammo > 0) {
sfx.shoot.play()
globalObject.ammo--
bullets.push({
bullet: true,
width: 4,
height: 4,
flipped: player.flipped,
originX: bulletX,
originY: bulletY,
x: bulletX,
y: bulletY + player.height / 2,
directionLeft: direction,
ammoType: weaponType,
damage: 1,
})
} else {
sfx.click.play()
}
setTimeout(() => {
player.shooting = false
}, 200)
}
function updateBullets(dt) {
var n, max
for (n = 0, max = bullets.length; n < max; n++) updateBullet(bullets[n], dt)
}
function updateBullet(bullet, dt) {
if (!!bullet) {
if (bullet.directionLeft) {
bullet.x = bullet.x - ammoType.velocity
} else {
bullet.x = bullet.x + ammoType.velocity
}
var n, max, monster, thisBullet
for (n = 0, max = monsters.length; n < max; n++) {
monster = monsters[n]
thisBullet = bullet
if (!monster.dead) {
if (
overlap(
thisBullet.x,
thisBullet.y,
ammoType.width,
ammoType.height,
monster.x,
monster.y,
TILE,
TILE
)
)
reduceHitpoints(monster, bullet.damage),
sfx.explode.play(),
destroyBullet(bullet)
}
}
if (
(!bullet.directionLeft &&
bullet.x > bullet.originX + ammoType.distance) ||
(bullet.directionLeft && bullet.x < bullet.originX - ammoType.distance)
) {
destroyBullet(bullet)
}
}
}
function destroyBullet(bullet) {
const index = bullets.indexOf(bullet)
if (index > -1) {
bullets.splice(index, 1)
}
}
function checkTreasure() {
var n, max, t
for (n = 0, max = treasure.length; n < max; n++) {
t = treasure[n]
if (
!t.collected &&
overlap(player.x, player.y, TILE, TILE, t.x, t.y, TILE, TILE)
)
collectTreasure(t)
}
}
function checkPickups() {
var n, max, p
for (n = 0, max = pickups.length; n < max; n++) {
p = pickups[n]
if (
!p.collected &&
overlap(player.x, player.y, TILE, TILE, p.x, p.y, TILE, TILE)
)
collectPickup(p)
}
}
function collectPickup(p) {
// console.log('log pickup ', p)
if (p.extralife) {
addExtraLife(p)
}
if (p.ammo) {
addAmmo(p)
}
if (p.health) {
addHitPoints(p)
}
}
function addExtraLife(p) {
globalObject.lifes++
p.collected = true
sfx.smb_powerup.play()
}
function addHitPoints(p) {
if (globalObject.hitpoints < globalObject.maxHitpoints) {
globalObject.hitpoints++
p.collected = true
sfx.stomp.play()
}
}
function addAmmo(p) {
if (globalObject.ammo < globalObject.maxAmmo) {
globalObject.ammo++
p.collected = true
sfx.kick.play()
}
}
function checkLiquids() {
var n, max, l
for (n = 0, max = liquids.length; n < max; n++) {
l = liquids[n]
if (overlap(player.x, player.y, TILE, TILE, l.x, l.y, l.width, l.height)) {
!player.swimming && sfx.splash.play()
player.dy = player.dy / 1.15
if (!player.swimming) player.jumping = false
player.swimming = true
} else {
player.swimming = false
}
}
}
function checkDoors() {
// ! Hier habe ich rudimentär einen Levelwechsel eingebaut.
// ! Was noch fehlt ist, dass ein globales Player-Objekt beibehalten wird.
var n, max, t
for (n = 0, max = doors.length; n < max; n++) {
t = doors[n]
if (
overlap(
player.x,
player.y,
TILE * 2,
TILE * 2,
t.x,
t.y,
TILE * 2,
TILE * 2
)
)
player.interact && useDoor(t)
}
}
function useDoor(t) {
let leadsTo
switch (t.leadsTo) {
case 'level1':
leadsTo = levelObject.level1
break
case 'level2':
leadsTo = levelObject.level2
break
case 'level3':
leadsTo = levelObject.level3
break
}
sfx.openDoor.play()
levelTransition(leadsTo)
}
function levelTransition(level) {
setTimeout(() => {
_initLevel(level)
}, 800)
}
function killMonster(monster) {
globalObject.killed++
monster.dead = true
sfx.killMonster.play()
}
// * Take Damage Function
function reduceHitpoints(entity, damage) {
if (
entity.player ? globalObject.hitpoints > 1 : entity.currentHitpoints > 1
) {
entity.player
? (globalObject.hitpoints -= damage)
: (entity.currentHitpoints -= damage)
entity.vul = false
entity.hurt = true
sfx.takeDamage.play()
setTimeout(() => {
entity.vul = true
entity.hurt = false
}, 1500)
} else killEntity(entity)
}
function killEntity(entity) {
console.log('killEntity: ', entity)
if (entity.player === true) {
killPlayer(entity)
}
if (entity.monster === true) {
killMonster(entity)
}
}
function killPlayer(player) {
sfx.die.play()
player.x = player.start.x
player.y = player.start.y
player.dx = player.dy = 0
globalObject.hitpoints = player.maxHitpoints
globalObject.lifes--
console.log('log globalObject: ', globalObject.lifes)
}
function collectTreasure(t) {
globalObject.collected++
t.collected = true
sfx.pickup.play()
}
function updateEntity(entity, dt) {
var wasleft = entity.dx < 0,
wasright = entity.dx > 0,
falling = entity.falling,
swimming = entity.swimming || false,
friction =
entity.friction * (falling && !swimming ? 0.5 : swimming ? 0.1 : 1),
impulse = entity.impulse,
swimImpulse = entity.impulse / 3,
accel = entity.accel * (falling && !swimming ? 0.5 : swimming ? 0.1 : 1),
gravity = swimming ? METER * 8 : METER * GRAVITY
entity.ddx = 0
entity.ddy = gravity // ! Hier wir gravity angewandt
// * Hier wird die Entity bewegt.
if (entity.left) (entity.ddx = entity.ddx - accel), (entity.flipped = true)
else if (wasleft) entity.ddx = entity.ddx + friction
if (entity.right) (entity.ddx = entity.ddx + accel), (entity.flipped = false)
else if (wasright) entity.ddx = entity.ddx - friction
if (entity.jump) {
if (!entity.jumping && !falling && !swimming) {
jump(entity)
} else if (!entity.jumping && swimming) {
swim(entity)
}
}
function jump(entity) {
sfx.jump.play()
entity.ddy = entity.ddy - impulse // an instant big force impulse
entity.jumping = true
}
function swim(entity) {
sfx.swim.play()
entity.ddy = entity.down
? entity.ddy + swimImpulse
: entity.up
? entity.ddy - swimImpulse * 2
: entity.ddy - swimImpulse
entity.jumping = true
setTimeout(() => {
entity.jumping = false
}, 400)
}
// ! "dt" ist hier identisch mit "step", was in der index.js in die update() gegeben wird. Konstant bei ca. 0.01666
//console.log('log entity.x + dt * entity.dx: ', player.x + dt * player.dx)
entity.x = entity.x + dt * entity.dx
entity.y = entity.y + dt * entity.dy
entity.dx = bound(entity.dx + dt * entity.ddx, -entity.maxdx, entity.maxdx)
entity.dy = bound(entity.dy + dt * entity.ddy, -entity.maxdy, entity.maxdy)
if ((wasleft && entity.dx > 0) || (wasright && entity.dx < 0)) {
entity.dx = 0 // clamp at zero to prevent friction from making us jiggle side to side
}
var tx = p2t(entity.x),
ty = p2t(entity.y),
nx = entity.x % TILE,
ny = entity.y % TILE,
cell = tcell(tx, ty, mapWidth), //! Was ist das hier?
cellright = tcell(tx + 1, ty, mapWidth),
celldown = tcell(tx, ty + 1, mapWidth),
celldiag = tcell(tx + 1, ty + 1, mapWidth)
// ? Ist das hier der Collision Check?
if (entity.dy > 0) {
if ((celldown && !cell) || (celldiag && !cellright && nx)) {
entity.y = t2p(ty)
entity.dy = 0
entity.falling = false
entity.jumping = false
ny = 0
}
} else if (entity.dy < 0) {
if ((cell && !celldown) || (cellright && !celldiag && nx)) {
entity.y = t2p(ty + 1)
entity.dy = 0
cell = celldown
cellright = celldiag
ny = 0
}
}
if (entity.dx > 0) {
if ((cellright && !cell) || (celldiag && !celldown && ny)) {
entity.x = t2p(tx)
entity.dx = 0
}
} else if (entity.dx < 0) {
if ((cell && !cellright) || (celldown && !celldiag && ny)) {
entity.x = t2p(tx + 1)
entity.dx = 0
}
}
if (entity.monster || entity.slimeMonster) {
if (entity.left && (cell || !celldown)) {
entity.left = false
entity.right = true
} else if (entity.right && (cellright || !celldiag)) {
entity.right = false
entity.left = true
}
}
entity.falling = !(celldown || (nx && celldiag))
}
+165
View File
@@ -0,0 +1,165 @@
//-------------------------------------------------------------------------
// 1. UTILITIES
//-------------------------------------------------------------------------
function timestamp() {
return window.performance && window.performance.now
? window.performance.now()
: new Date().getTime()
}
function bound(x, min, max) {
return Math.max(min, Math.min(max, x))
}
/* function get(url, onsuccess) {
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (request.readyState == 4 && request.status == 200) onsuccess(request);
};
request.open("GET", url, true);
request.send();
} */
function overlap(x1, y1, w1, h1, x2, y2, w2, h2) {
return !(
x1 + w1 - 1 < x2 ||
x2 + w2 - 1 < x1 ||
y1 + h1 - 1 < y2 ||
y2 + h2 - 1 < y1
)
}
// * SFX function
function sound(src, volume) {
this.sound = document.createElement('audio')
this.sound.src = src
this.sound.setAttribute('preload', 'auto')
this.sound.setAttribute('controls', 'none')
this.sound.style.display = 'none'
document.body.appendChild(this.sound)
this.sound.volume = volume
this.play = function () {
this.sound.play()
}
this.stop = function () {
this.sound.pause()
}
}
// * Music function
function music(src, volume) {
this.music = document.createElement('audio')
this.music.src = src
this.music.setAttribute('preload', 'auto')
this.music.setAttribute('controls', 'none')
this.music.setAttribute('loop', 'loop')
this.music.style.display = 'none'
document.body.appendChild(this.music)
this.music.volume = volume
this.play = function () {
this.music.play()
}
this.stop = function () {
this.music.pause()
}
}
// * Pause function
function togglePause() {
console.log('log togglePause', paused)
if (!paused) {
paused = true
} else if (paused) {
paused = false
}
}
function toggleDevInfos() {
if (!showDevInfo) {
showDevInfo = true
} else if (showDevInfo) {
showDevInfo = false
}
}
function renderHudSprites() {
const hudObject = {
player_hitpoints: player.currentHitpoints,
player_collected: player.collected,
player_lifes: globalObject.lifes,
player_ammo: globalObject.ammo,
}
function hitpoints() {
ctx.save()
ctx.scale(scalingFactor / 4, scalingFactor / 4)
ctx.textAlign = 'left'
ctx.font = '12px C64 TrueType'
ctx.fillStyle = 'white'
ctx.fillText(`lifes: ${hudObject.player_lifes}`, 50, 50)
ctx.fillText(`ammo: ${hudObject.player_ammo}`, 200, 50)
ctx.restore()
}
hitpoints()
}
// * Renderfunktion für DevInfos
function renderDevInfos() {
ctx.save()
ctx.scale(scalingFactor / 6, scalingFactor / 6)
if (showDevInfo) {
const backgroundColor = 'rgba(18, 64, 90, 0.15)'
const color = 'white'
const lineHeight = 18
const devObject = {
player_accel: player.accel,
player_ddx: player.ddx,
player_ddy: player.ddy,
player_dx: player.dx,
player_dy: player.dy,
player_interact: player.interact,
player_falling: player.falling,
player_friction: player.friction,
player_gravity: player.gravity,
player_impulse: player.impulse,
player_jumping: player.jumping,
player_swimming: player.swimming,
player_shooting: player.shooting,
player_left: player.left,
player_maxdx: player.maxdx,
player_maxdy: player.maxdy,
player_right: player.right,
player_x: player.x,
player_y: player.y,
player_vul: player.vul,
player_hurt: player.hurt,
player_flipped: player.flipped,
canvas_width: canvas.height,
cameraWithBox_x: cameraWithBox.position.x,
}
const canvas_width = canvas.width
const canvas_height = canvas.height
function text() {
ctx.textAlign = 'left'
ctx.font = '12px C64 TrueType'
for (const [index, [key, value]] of Object.entries(
Object.entries(devObject)
)) {
const newIndex = Number(index) + 1
const newValue = typeof value === 'number' ? value.toFixed(2) : value
ctx.fillStyle = backgroundColor
ctx.fillRect(15, lineHeight * newIndex - 15, 500, lineHeight)
ctx.fillStyle = color
ctx.fillText(`${key}: ${newValue}`, 15, lineHeight * newIndex)
}
}
text()
}
ctx.restore()
}
+363
View File
@@ -0,0 +1,363 @@
//-------------------------------------------------------------------------
// 2. GAME CONSTANTS AND VARIABLES
//-------------------------------------------------------------------------
// ! Globales Test-Objekt
let globalObject = {
lifes: 3,
hitpoints: 3,
maxHitpoints: 5,
collected: null,
killed: null,
ammo: 10,
maxAmmo: 10,
}
// * Import tiles and level map //
const tileAtlas1 = new Image()
tileAtlas1.src = './img/tiles.png'
const tileAtlas2 = new Image()
tileAtlas2.src = './img/Grotto-escape-2-files/PNG/environment-tiles.png'
const tileAtlasBg = new Image()
tileAtlasBg.src = './img/Grotto-escape-2-files/PNG/environment-background.png'
let tileSize = 16
let tileOutputSize = 2 // can set to 1 for 32px or higher
const level1Atlas = {
tileAtlas: tileAtlas1,
offset: 1,
tileSize: 16,
tileOutputSize: 2,
updatedTileSize: tileSize * tileOutputSize,
atlasCol: 8,
atlasRow: 5,
mapIndex: 0,
sourceX: 0,
sourceY: 0,
}
const level2Atlas = {
tileAtlas: tileAtlas2,
offset: 1,
tileSize: 16,
tileOutputSize: 2,
updatedTileSize: tileSize * tileOutputSize,
atlasCol: 42,
atlasRow: 16,
mapIndex: 0,
sourceX: 0,
sourceY: 0,
}
const backgroundAtlas = {
tileAtlas: tileAtlasBg,
offset: 673,
tileSize: 16,
tileOutputSize: 2,
updatedTileSize: tileSize * tileOutputSize,
atlasCol: 28,
atlasRow: 10,
mapIndex: 0,
sourceX: 0,
sourceY: 0,
}
// * end of Import tiles and level map //
// * Test Sprite Imports
const playerSpriteAtlas = new Image()
playerSpriteAtlas.src = './img/player.png'
const playerAtlas = {
tileAtlas: playerSpriteAtlas,
tileSize: 16,
tileOutputSize: 2,
updatedTileSize: tileSize * tileOutputSize,
atlasCol: 4,
atlasRow: 2,
mapIndex: 0,
sourceX: 0,
sourceY: 0,
}
const enemySpriteAtlas = new Image()
enemySpriteAtlas.src = './img/enemies.png'
const enemyAtlas = {
tileAtlas: enemySpriteAtlas,
tileSize: 16,
tileOutputSize: 2,
updatedTileSize: tileSize * tileOutputSize,
atlasCol: 4,
atlasRow: 3,
mapIndex: 0,
sourceX: 0,
sourceY: 0,
}
const itemsSpriteAtlas = new Image()
itemsSpriteAtlas.src = './img/items.png'
const itemsAtlas = {
tileAtlas: itemsSpriteAtlas,
tileSize: 16,
tileOutputSize: 2,
updatedTileSize: tileSize * tileOutputSize,
atlasCol: 4,
atlasRow: 4,
mapIndex: 0,
sourceX: 0,
sourceY: 0,
}
const meterSpriteAtlas = new Image()
meterSpriteAtlas.src = './img/meter.png'
const meterAtlas = {
tileAtlas: meterSpriteAtlas,
tileSize: 7,
tileOutputSize: 2,
updatedTileSize: tileSize * tileOutputSize,
atlasCol: 4,
atlasRow: 6,
mapIndex: 0,
sourceX: 0,
sourceY: 0,
}
// * End Test Sprite Imports
// * Das currentLevel Objekt sollte durch irgendeine Funktion befüllt werden, wenn ein Level gewechselt wird.
let levelObject = {
level1: {
scalingFactor: 8,
levelData: level1,
levelAtlas: level2Atlas,
backgroundAtlas: backgroundAtlas,
foregroundAtlas: level2Atlas,
playerStartCoordinates: { x: 96, y: 480 }, // * Für wenn man das Level durch eine Tür erneut betritt.
},
level2: {
scalingFactor: 4,
levelData: level2,
levelAtlas: level2Atlas,
backgroundAtlas: backgroundAtlas,
foregroundAtlas: level2Atlas,
playerStartCoordinates: { x: 96, y: 480 }, // * Für wenn man das Level durch eine Tür erneut betritt.
},
level3: {
scalingFactor: 8,
levelData: level3,
levelAtlas: level2Atlas,
backgroundAtlas: backgroundAtlas,
foregroundAtlas: level2Atlas,
playerStartCoordinates: { x: 96, y: 480 }, // * Für wenn man das Level durch eine Tür erneut betritt.
},
}
let currentLevel = levelObject.level1 // ! hier kann ich noch eine function draus machen, die das currentLevel immer auf dem aktuellen Stand hält. Vorläufig erstmal mit einem Button-Press
let mapWidth = currentLevel.levelData.width
let mapHeight = currentLevel.levelData.height
let MAP = { tw: mapWidth, th: mapHeight }
let TILE = 16
let METER = TILE
let GRAVITY = 60
let MAXDX = 15, // * default max horizontal speed (15 tiles per second)
MAXDY = 60, // * default max vertical speed (60 tiles per second)
ACCEL = 1 / 2, // * default take 1/2 second to reach maxdx (horizontal acceleration)
FRICTION = 1 / 6, // * default take 1/6 second to stop from maxdx (horizontal friction)
IMPULSE = 1500, // * default player jump impulse
COLOR = {
BLACK: '#000000',
YELLOW: '#ECD078',
BRICK: '#D95B43',
PINK: '#C02942',
PURPLE: '#542437',
GREY: '#333',
SLATE: '#53777A',
GOLD: 'gold',
},
COLORS = [COLOR.YELLOW, COLOR.BRICK, COLOR.PINK, COLOR.PURPLE, COLOR.GREY],
KEY = {
SPACE: ' ',
LEFT: 'ArrowLeft',
UP: 'ArrowUp',
RIGHT: 'ArrowRight',
DOWN: 'ArrowDown',
PAUSE: 'p',
ENTER: 'Enter',
X: 'x',
Y: 'y',
L: 'l',
}
const ammoType = {
width: 4,
height: 4,
distance: 100,
velocity: 3,
}
let fps = 60,
step = 1 / fps,
canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
player = {},
monsters = [],
pickups = [],
treasure = [],
doors = [], // ! Hier sammel ich alle Door Objekte.
liquids = [], // ! Alle Bereich, die mit Wasser gefüllt sind.
bullets = [],
cells = [], // ! vielleicht kann ich hieraus aber auch einfach ein Objekt machen, das Arrays für collCells, bgCells und fgCells beinhaltet
bgCells = [], // * Die sind für die background cells.
fgCells = [], // * Die sind für die foreground cells.
lqCells = [],
paused = false,
showDevInfo = false
canvas.width = mapWidth * TILE
canvas.height = mapHeight * TILE
let width = (canvas.width = mapWidth * TILE) // * Das ist clever, weil ich so der Canvas immer von der Map Size abhängig ist. Zusammen mit ctx.scale und ctx.translate bekomme ich so auch eine Camera hin!
let height = (canvas.height = mapHeight * TILE)
let scalingFactor = currentLevel.scalingFactor // * 8 fühlt sich ungefähr nach 8bit Grafik an.
let scaledCanvas = {
width: canvas.width / scalingFactor,
height: canvas.height / scalingFactor,
}
let t2p = function (t) {
return t * TILE
},
p2t = function (p) {
return Math.floor(p / TILE)
},
cell = function (x, y) {
return tcell(p2t(x), p2t(y), mapWidth)
},
tcell = function (tx, ty, mapWidth) {
// ! Hier brauche ich entweder eine allgemeinere Funktion oder noch weiter für bgCells ud fgCells.
return cells[tx + ty * mapWidth]
},
bgTcell = function (tx, ty, mapWidth) {
return bgCells[tx + ty * mapWidth]
},
fgTcell = function (tx, ty, mapWidth) {
return fgCells[tx + ty * mapWidth]
},
lqTcell = function (tx, ty, mapWidth) {
return !!lqCells && lqCells[tx + ty * mapWidth]
}
// * Init Level Funtion
function _initLevel(newLevel) {
monsters = []
treasure = []
pickups = []
doors = []
liquids = []
cells = []
currentLevel = newLevel
player.collected = 0
mapWidth = currentLevel.levelData.width
mapHeight = currentLevel.levelData.height
canvas.width = mapWidth * TILE
canvas.height = mapHeight * TILE
scalingFactor = currentLevel.scalingFactor
scaledCanvas = {
width: canvas.width / scalingFactor,
height: canvas.height / scalingFactor,
}
setup(newLevel.levelData)
}
// * Const Sfx from audio files //
// ! Achtung: utils müssen in index.html vor den variables geladen werden.
let sfxVolume = 0.2
let musicVolume = 0.5
let sfx = {
jump: new sound('./audio/Jump.wav', sfxVolume),
pickup: new sound('./audio/Pickup_Coin.wav', sfxVolume),
lowHealth: new sound('./audio/LA_LowHealth.wav', sfxVolume),
die: new sound('./audio/Explosion.wav', sfxVolume),
takeDamage: new sound('./audio/Randomize3.wav', sfxVolume),
killMonster: new sound('./audio/Randomize.wav', sfxVolume),
powerup: new sound('./audio/Powerup.wav', sfxVolume),
openDoor: new sound('./audio/LA_Chest_Open.wav', sfxVolume),
shoot: new sound('./audio/Laser2.wav', sfxVolume),
explode: new sound('./audio/Explosion.wav', sfxVolume),
splash: new sound('./audio/Oracle_Rock_Shatter.wav', sfxVolume),
swim: new sound('./audio/SML2_Swim.ogg', sfxVolume),
click: new sound('./audio/click.wav', sfxVolume),
stomp: new sound('./audio/smb_stomp.wav', sfxVolume),
kick: new sound('./audio/smb_kick.wav', sfxVolume),
extraLife: new sound('./audio/smb_1-up.wav', sfxVolume),
smb_powerup: new sound('./audio/smb_powerup.wav', sfxVolume),
}
let theme = {
level1: new music('./audio/music/8BitCave.wav', musicVolume),
}
let globalPlayer = {
collected: 0,
killed: 0,
maxHitpoints: 5,
currentHitpoints: 3,
vul: true,
hurt: false,
interact: false,
shooting: false,
swimming: false,
sprites: {
idle: {
tiles: [5],
framerate: 5,
framebuffer: 8,
loop: true,
currentFrame: 0,
},
run: {
tiles: [0, 1, 2, 1],
framerate: 5,
framebuffer: 10,
loop: true,
currentFrame: 0,
},
jump: {
tiles: [4],
framerate: 5,
framebuffer: 8,
loop: true,
currentFrame: 0,
},
shoot: {
tiles: [3],
framerate: 5,
framebuffer: 8,
loop: true,
currentFrame: 0,
},
},
}

Some files were not shown because too many files have changed in this diff Show More