initial commit
This commit is contained in:
+118
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user