improvements to stroke fighter, game screen layout

This commit is contained in:
Lars Berning 2022-02-15 21:16:34 +01:00
parent 9beca6cf72
commit b2d8fe070c
No known key found for this signature in database
GPG Key ID: 028E73C9E1D8A0B3
6 changed files with 121 additions and 37 deletions

View File

@ -24,7 +24,7 @@ export function createRowingGames (canvasElement, clientWidth, clientHeight) {
height: clientHeight
})
// for now show debug infos all the time
k.debug.inspect = true
// k.debug.inspect = true
// todo: once there are multiple games, asset loadingshould be moved to the individual games
const assets = '/assets'

View File

@ -17,7 +17,7 @@ export default function addSpaceBackground (k) {
k.add([
k.rect(k.width() + 50, k.height() + 50),
k.pos(-25, -25),
k.color(0, 0, 0),
k.color(0, 9, 28),
k.layer('background')
])
@ -45,10 +45,10 @@ export default function addSpaceBackground (k) {
])
}
k.onUpdate('star', (component) => {
component.move(0, component.speed)
if (component.pos.y - component.height > k.height()) {
k.destroy(component)
k.onUpdate('star', (star) => {
star.move(0, star.speed)
if (star.pos.y - star.height > k.height()) {
k.destroy(star)
addStar(true)
}
})

View File

@ -12,6 +12,14 @@ import addSpaceBackground from './SpaceBackground.js'
* @param {import('kaboom').KaboomCtx} k Kaboom Context
*/
export default function StrokeFighterBattleScene (k) {
// how much stroke power is needed to fire high power lasers
const THRESHOLD_POWER = 180
// training duration in seconds
const TARGET_TIME = 5 * 60
// strokes per minute at start of training
const SPM_START = 14
// strokes per minute at end of training
const SPM_END = 28
const BULLET_SPEED = 1200
const ENEMY_SPEED = 60
const PLAYER_SPEED = 480
@ -28,6 +36,8 @@ export default function StrokeFighterBattleScene (k) {
{ sprite: 'spaceShips_009', health: 2 }
]
let trainingTime = 0
k.layers([
'background',
'game',
@ -67,8 +77,8 @@ export default function StrokeFighterBattleScene (k) {
}
}
player.onCollide('enemy', (e) => {
k.destroy(e)
player.onCollide('enemy', (enemy) => {
k.destroy(enemy)
k.shake(4)
k.play('hit', {
detune: -1200,
@ -89,13 +99,13 @@ export default function StrokeFighterBattleScene (k) {
}
})
function addLaserHit (p, n) {
function addLaserHit (pos, n) {
for (let i = 0; i < n; i++) {
k.wait(k.rand(n * 0.1), () => {
for (let i = 0; i < 2; i++) {
k.add([
k.sprite('laserRed09'),
k.pos(p.sub(0, 10)),
k.pos(pos.sub(0, 10)),
k.scale(k.vec2(0.5)),
k.lifespan(0.1),
grow(k.rand(0.5, 2)),
@ -106,11 +116,11 @@ export default function StrokeFighterBattleScene (k) {
}
}
function spawnBullet (p) {
function spawnBullet (pos) {
k.add([
k.sprite('laserRed01'),
k.area(),
k.pos(p),
k.pos(pos),
k.origin('center'),
k.move(k.UP, BULLET_SPEED),
k.cleanup(),
@ -141,8 +151,7 @@ export default function StrokeFighterBattleScene (k) {
})
}
function spawnEnemy () {
const enemy = k.choose(ENEMIES)
function spawnEnemy (enemy) {
k.add([
k.sprite(enemy.sprite),
k.area(),
@ -152,16 +161,18 @@ export default function StrokeFighterBattleScene (k) {
'enemy',
{ speed: k.rand(ENEMY_SPEED * 0.5, ENEMY_SPEED * 1.5) }
])
k.wait(3, spawnEnemy)
}
k.on('death', 'enemy', (e) => {
k.destroy(e)
k.every('bullet', (bullet) => { k.destroy(bullet) })
k.on('death', 'enemy', (enemy) => {
k.destroy(enemy)
k.every('bullet', (bullet) => {
addLaserHit(bullet.pos, 1)
k.destroy(bullet)
})
k.shake(2)
})
k.on('hurt', 'enemy', (e) => {
k.on('hurt', 'enemy', (enemy) => {
k.shake(1)
k.play('hit', {
detune: k.rand(-1200, 1200),
@ -174,25 +185,24 @@ export default function StrokeFighterBattleScene (k) {
k.text('0'),
k.pos(12, 32),
k.fixed(),
k.layer('ui'),
{ time: 0 }
k.layer('ui')
])
timer.onUpdate(() => {
timer.time += k.dt()
timer.text = timer.time.toFixed(2)
trainingTime += k.dt()
timer.text = trainingTime.toFixed(2)
})
k.onCollide('bullet', 'enemy', (b, e) => {
k.destroy(b)
e.hurt(1)
addLaserHit(b.pos, 1)
k.onCollide('bullet', 'enemy', (bullet, enemy) => {
k.destroy(bullet)
enemy.hurt(1)
addLaserHit(bullet.pos, 1)
})
k.onUpdate('enemy', (sprite) => {
sprite.move(0, sprite.speed)
if (sprite.pos.y - sprite.height > k.height()) {
k.destroy(sprite)
k.onUpdate('enemy', (enemy) => {
enemy.move(0, enemy.speed)
if (enemy.pos.y - enemy.height > k.height()) {
k.destroy(enemy)
}
})
@ -208,16 +218,31 @@ export default function StrokeFighterBattleScene (k) {
}
function driveFinished (metrics) {
if (metrics.power < 120) {
if (metrics.power < THRESHOLD_POWER * 0.75) {
fireWeapons(1)
} else if (metrics.power < 180) {
} else if (metrics.power < THRESHOLD_POWER) {
fireWeapons(2)
} else {
fireWeapons(3)
}
}
spawnEnemy()
function scheduleNextEnemy () {
const percentTrainingFinished = trainingTime / TARGET_TIME
const currentSPM = SPM_START + (SPM_END - SPM_START) * percentTrainingFinished
let maxEnemyHealth = 1
if (percentTrainingFinished < 0.4) {
maxEnemyHealth = 1
} else if (percentTrainingFinished < 0.8) {
maxEnemyHealth = 2
} else {
maxEnemyHealth = 3
}
spawnEnemy(k.choose(ENEMIES.filter((enemy) => enemy.health <= maxEnemyHealth)))
k.wait(60 / currentSPM, scheduleNextEnemy)
}
scheduleNextEnemy()
return {
appState

View File

@ -7,23 +7,77 @@
import { customElement } from 'lit/decorators.js'
import { createRowingGames } from '../arcade/RowingGames.js'
import { icon_exit } from '../lib/icons.js'
import { AppElement, css, html } from './AppElement.js'
@customElement('game-component')
export class GameComponent extends AppElement {
static styles = css`
:host {
width: 100%;
height: 100%;
display: block;
display: flex;
}
#arcade {
width: 100vh;
height: 100vh;
}
@media (orientation: portrait) {
:host {
flex-direction: column
}
#arcade {
width: 100vw;
height: 100vw;
}
}
button {
outline:none;
background-color: var(--theme-button-color);
border: 0;
border-radius: var(--theme-border-radius);
color: var(--theme-font-color);
margin: 0.2em 0;
font-size: 60%;
text-decoration: none;
display: inline-flex;
width: 3.5em;
height: 2.5em;
justify-content: center;
align-items: center;
}
button:hover {
filter: brightness(150%);
}
.icon {
height: 1.7em;
}
#controls {
width: 100%;
height: 100%;
text-align: center;
position: relative;
}
#buttons {
padding: 0.5em 0 0.5em 0;
margin: 1vw;
background: var(--theme-widget-color);
border-radius: var(--theme-border-radius);
}
`
render () {
return html`<canvas id="arcade"></canvas>`
return html`
<canvas id="arcade"></canvas>
<div id="controls">
<div id="buttons">
<button @click=${this.openDashboard}>${icon_exit}</button>
</div>
</div>
`
}
openDashboard () {
this.sendEvent('triggerAction', { command: 'openDashboard' })
}
firstUpdated () {

View File

@ -135,6 +135,10 @@ export function createApp (app) {
app.updateState({ ...app.getState(), activeRoute: 'ROWINGGAMES' })
break
}
case 'openDashboard': {
app.updateState({ ...app.getState(), activeRoute: 'DASHBOARD' })
break
}
case 'reset': {
resetFields()
if (socket)socket.send(JSON.stringify({ command: 'reset' }))

View File

@ -26,3 +26,4 @@ export const icon_compress = svg`<svg aria-hidden="true" focusable="false" class
export const icon_bluetooth = svg`<svg aria-hidden="true" focusable="false" class="icon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M292.6 171.1L249.7 214l-.3-86 43.2 43.1m-43.2 219.8l43.1-43.1-42.9-42.9-.2 86zM416 259.4C416 465 344.1 512 230.9 512S32 465 32 259.4 115.4 0 228.6 0 416 53.9 416 259.4zm-158.5 0l79.4-88.6L211.8 36.5v176.9L138 139.6l-27 26.9 92.7 93-92.7 93 26.9 26.9 73.8-73.8 2.3 170 127.4-127.5-83.9-88.7z"></path></svg>`
export const icon_upload = svg`<svg aria-hidden="true" focusable="false" class="icon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M537.6 226.6c4.1-10.7 6.4-22.4 6.4-34.6 0-53-43-96-96-96-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32c-88.4 0-160 71.6-160 160 0 2.7.1 5.4.2 8.1C40.2 219.8 0 273.2 0 336c0 79.5 64.5 144 144 144h368c70.7 0 128-57.3 128-128 0-61.9-44-113.6-102.4-125.4zM393.4 288H328v112c0 8.8-7.2 16-16 16h-48c-8.8 0-16-7.2-16-16V288h-65.4c-14.3 0-21.4-17.2-11.3-27.3l105.4-105.4c6.2-6.2 16.4-6.2 22.6 0l105.4 105.4c10.1 10.1 2.9 27.3-11.3 27.3z"></path></svg>`
export const icon_gamepad = svg`<svg aria-hidden="true" focusable="false" class="icon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M448 64H192C85.96 64 0 149.1 0 256s85.96 192 192 192h256c106 0 192-85.96 192-192S554 64 448 64zM247.1 280h-32v32c0 13.2-10.78 24-23.98 24c-13.2 0-24.02-10.8-24.02-24v-32L136 279.1C122.8 279.1 111.1 269.2 111.1 256c0-13.2 10.85-24.01 24.05-24.01L167.1 232v-32c0-13.2 10.82-24 24.02-24c13.2 0 23.98 10.8 23.98 24v32h32c13.2 0 24.02 10.8 24.02 24C271.1 269.2 261.2 280 247.1 280zM431.1 344c-22.12 0-39.1-17.87-39.1-39.1s17.87-40 39.1-40s39.1 17.88 39.1 40S454.1 344 431.1 344zM495.1 248c-22.12 0-39.1-17.87-39.1-39.1s17.87-40 39.1-40c22.12 0 39.1 17.88 39.1 40S518.1 248 495.1 248z"/></svg>`
export const icon_exit = svg`<svg aria-hidden="true" focusable="false" class="icon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M96 480h64C177.7 480 192 465.7 192 448S177.7 416 160 416H96c-17.67 0-32-14.33-32-32V128c0-17.67 14.33-32 32-32h64C177.7 96 192 81.67 192 64S177.7 32 160 32H96C42.98 32 0 74.98 0 128v256C0 437 42.98 480 96 480zM504.8 238.5l-144.1-136c-6.975-6.578-17.2-8.375-26-4.594c-8.803 3.797-14.51 12.47-14.51 22.05l-.0918 72l-128-.001c-17.69 0-32.02 14.33-32.02 32v64c0 17.67 14.34 32 32.02 32l128 .001l.0918 71.1c0 9.578 5.707 18.25 14.51 22.05c8.803 3.781 19.03 1.984 26-4.594l144.1-136C514.4 264.4 514.4 247.6 504.8 238.5z"/></svg>`