fixes game start condition, better placement of opponents

This commit is contained in:
Lars Berning 2022-03-09 20:24:28 +01:00
parent 3c8c4f6914
commit c454d4169d
No known key found for this signature in database
GPG Key ID: 028E73C9E1D8A0B3
5 changed files with 96 additions and 77 deletions

View File

@ -20,10 +20,10 @@ export default function StrokeFighterBattleScene (k, args) {
const SPM_END = 28 const SPM_END = 28
const BULLET_SPEED = 1200 const BULLET_SPEED = 1200
const ENEMY_SPEED = 50 const ENEMY_SPEED = 50
const PLAYER_SPEED = 500 const PLAYER_SPEED = 600
const PLAYER_ACCELERATION = 500 const PLAYER_ACCELERATION = 600
const PLAYER_LIFES = 3 const PLAYER_LIFES = 3
const SPRITE_WIDTH = 90 const SPRITE_WIDTH = 100
const ENEMIES = [ const ENEMIES = [
{ sprite: 'enemyLight1', health: 1 }, { sprite: 'enemyLight1', health: 1 },
{ sprite: 'enemyLight2', health: 1 }, { sprite: 'enemyLight2', health: 1 },
@ -148,12 +148,12 @@ export default function StrokeFighterBattleScene (k, args) {
}) })
player.onUpdate(() => { player.onUpdate(() => {
const tolerance = 0 const tolerance = 3
const closestEnemy = k.get('enemy').reduce((prev, enemy) => { return enemy?.pos.y > prev?.pos.y ? enemy : prev }, { pos: { y: 0 } }) const closestEnemy = k.get('enemy').reduce((prev, enemy) => { return enemy?.pos.y > prev?.pos.y ? enemy : prev }, { pos: { y: 0 } })
if (closestEnemy?.pos?.x) { if (closestEnemy?.pos?.x) {
const distance = closestEnemy.pos.x - player.pos.x const distance = closestEnemy.pos.x - player.pos.x
// distance in pixel to stop the spaceship with full break throttle // distance in pixel to stop the spaceship with full break throttle
const stopDistance = Math.pow(player.speed, 2) / (2 * PLAYER_ACCELERATION) * (player.speed < 0 ? -1.1 : 1.1) const stopDistance = Math.pow(player.speed, 2) / (2 * PLAYER_ACCELERATION) * (player.speed < 0 ? -1.3 : 1.3)
// if we are on the left side of enemy // if we are on the left side of enemy
if (closestEnemy.pos.x > player.pos.x + tolerance) { if (closestEnemy.pos.x > player.pos.x + tolerance) {
if (distance > stopDistance) { accelerateRight() } else { accelerateLeft() } if (distance > stopDistance) { accelerateRight() } else { accelerateLeft() }
@ -229,19 +229,6 @@ export default function StrokeFighterBattleScene (k, args) {
}) })
} }
function spawnEnemy (enemy) {
k.add([
k.sprite(enemy.sprite),
k.scale(0.5),
k.area(),
k.pos(k.rand(0 + SPRITE_WIDTH / 2, k.width() - SPRITE_WIDTH / 2), 0),
k.health(enemy.health),
k.origin('bot'),
'enemy',
{ speed: k.rand(ENEMY_SPEED * 0.8, ENEMY_SPEED * 1.2) }
])
}
k.on('death', 'enemy', (enemy) => { k.on('death', 'enemy', (enemy) => {
k.destroy(enemy) k.destroy(enemy)
k.every('bullet', (bullet) => { k.every('bullet', (bullet) => {
@ -261,8 +248,8 @@ export default function StrokeFighterBattleScene (k, args) {
}) })
const timer = ui.add([ const timer = ui.add([
k.text('00:00', { size: 25 }), k.text('', { size: 30 }),
k.pos(10, 10), k.pos(10, k.height() - 27),
k.fixed() k.fixed()
]) ])
@ -272,7 +259,7 @@ export default function StrokeFighterBattleScene (k, args) {
const newTrainingTimeRounded = Math.round(trainingTime) const newTrainingTimeRounded = Math.round(trainingTime)
if (trainingTimeRounded !== newTrainingTimeRounded) { if (trainingTimeRounded !== newTrainingTimeRounded) {
const time = newTrainingTimeRounded >= targetTime ? newTrainingTimeRounded : targetTime - newTrainingTimeRounded const time = newTrainingTimeRounded >= targetTime ? newTrainingTimeRounded : targetTime - newTrainingTimeRounded
timer.text = `${secondsToTimeString(time)} / ${k.debug.fps()}fps` timer.text = secondsToTimeString(time)
trainingTimeRounded = newTrainingTimeRounded trainingTimeRounded = newTrainingTimeRounded
if (newTrainingTimeRounded >= targetTime) { if (newTrainingTimeRounded >= targetTime) {
// if we already lost the game before, go back to loose message without possibility for overtime // if we already lost the game before, go back to loose message without possibility for overtime
@ -307,7 +294,7 @@ export default function StrokeFighterBattleScene (k, args) {
k.add([ k.add([
k.sprite('playerLife'), k.sprite('playerLife'),
k.scale(0.5), k.scale(0.5),
k.pos(k.width() - i * 40, 10), k.pos(k.width() - i * 40, k.height() - 27),
k.z(100), k.z(100),
'playerLife' 'playerLife'
]) ])
@ -359,10 +346,33 @@ export default function StrokeFighterBattleScene (k, args) {
} }
} }
function spawnEnemy (enemyType) {
// prevent enenmies from spawning above each other by defining a prohibited spawn area
const closestEnemy = k.get('enemy').reduce((prev, enemy) => { return enemy?.pos.y < prev?.pos.y ? enemy : prev }, { pos: { y: k.height() } })
let closestEnemyRangeX = [0, 0]
if (closestEnemy?.pos?.x) {
closestEnemyRangeX = [closestEnemy.pos.x - closestEnemy.width / 2, closestEnemy.pos.x + closestEnemy.width / 2]
}
const prohibitedRange = [Math.max(closestEnemyRangeX[0], 0), Math.min(closestEnemyRangeX[1], k.width())]
const prohibitedWidth = prohibitedRange[1] - prohibitedRange[0]
let posX = k.rand(0 + SPRITE_WIDTH / 2, k.width() - prohibitedWidth - SPRITE_WIDTH / 2)
if (posX > prohibitedRange[0]) posX += prohibitedWidth
k.add([
k.sprite(enemyType.sprite),
k.scale(0.5),
k.area(),
k.pos(posX, 0),
k.health(enemyType.health),
k.origin('bot'),
'enemy',
{ speed: k.rand(ENEMY_SPEED * 0.8, ENEMY_SPEED * 1.2) }
])
}
function scheduleNextEnemy () { function scheduleNextEnemy () {
const percentTrainingFinished = trainingTime / targetTime const percentTrainingFinished = trainingTime / targetTime
// linearly increase the SPM over time
let currentSPM = SPM_START + (SPM_END - SPM_START) * percentTrainingFinished
let maxEnemyHealth = 1 let maxEnemyHealth = 1
let minEnemyHealth = 1 let minEnemyHealth = 1
if (percentTrainingFinished < 0.4) { if (percentTrainingFinished < 0.4) {
@ -374,15 +384,21 @@ export default function StrokeFighterBattleScene (k, args) {
} }
// insane mode (keep on rowing after winning) // insane mode (keep on rowing after winning)
if (percentTrainingFinished > 1) { if (percentTrainingFinished > 1) {
// cap SPM at 20% above SPM_END (for insane mode)
currentSPM = Math.max(currentSPM, SPM_END * 1.2)
minEnemyHealth = 2 minEnemyHealth = 2
if (percentTrainingFinished > 1.3) { if (percentTrainingFinished > 1.3) {
minEnemyHealth = 3 minEnemyHealth = 3
} }
} }
spawnEnemy(k.choose(ENEMIES.filter((enemy) => enemy.health >= minEnemyHealth && enemy.health <= maxEnemyHealth))) spawnEnemy(k.choose(ENEMIES.filter((enemy) => enemy.health >= minEnemyHealth && enemy.health <= maxEnemyHealth)))
k.wait(60 / currentSPM, scheduleNextEnemy) k.wait(60 / currentSPM(), scheduleNextEnemy)
}
function currentSPM () {
const percentTrainingFinished = trainingTime / targetTime
// linearly increase the SPM over time
const currentSPM = SPM_START + (SPM_END - SPM_START) * percentTrainingFinished
// cap SPM at 20% above SPM_END (for insane mode)
return Math.min(currentSPM, SPM_END * 1.2)
} }
drawPlayerLifes() drawPlayerLifes()

View File

@ -6,7 +6,7 @@
*/ */
import addSpaceBackground from './SpaceBackground.js' import addSpaceBackground from './SpaceBackground.js'
import { addButton } from './arcadeHelper.js' import { addButton, createRowingDetector } from './arcadeHelper.js'
/** /**
* Creates the game over screen scene of Storke Fighter * Creates the game over screen scene of Storke Fighter
@ -67,29 +67,16 @@ export default function StrokeFighterEndScene (k, args) {
} }
} }
let motionDetectionEnabled = false let appState
if (args?.overtimePossible) { if (args?.overtimePossible) {
k.wait(5, () => { const rowingDetector = createRowingDetector(5000, driveFinished)
motionDetectionEnabled = true
})
}
let lastStrokeState = 'DRIVING' appState = function (appState) {
function appState (appState) { rowingDetector.appState(appState)
if (!motionDetectionEnabled) {
return
} }
if (appState?.metrics.strokeState === undefined) { function driveFinished (metrics) {
return k.go('strokeFighterBattle', args)
} }
if (lastStrokeState === 'DRIVING' && appState.metrics.strokeState === 'RECOVERY') {
driveFinished(appState.metrics)
}
lastStrokeState = appState.metrics.strokeState
}
function driveFinished (metrics) {
k.go('strokeFighterBattle', args)
} }
return { return {

View File

@ -6,7 +6,7 @@
*/ */
import addSpaceBackground from './SpaceBackground.js' import addSpaceBackground from './SpaceBackground.js'
import { addButton } from './arcadeHelper.js' import { addButton, createRowingDetector } from './arcadeHelper.js'
/** /**
* Creates the start screen scene of Stroke Fighter * Creates the start screen scene of Stroke Fighter
@ -35,8 +35,8 @@ export default function StrokeFighterStartScene (k, args) {
addButton({ addButton({
k, k,
pos: selectorPos.add(100, 0), pos: selectorPos.add(100, 0),
width: 60, width: 66,
height: 48, height: 54,
text: '-', text: '-',
textOptions: { size: 28 }, textOptions: { size: 28 },
onClick: () => { onClick: () => {
@ -54,8 +54,8 @@ export default function StrokeFighterStartScene (k, args) {
addButton({ addButton({
k, k,
pos: selectorPos.add(230, 0), pos: selectorPos.add(230, 0),
width: 60, width: 66,
height: 48, height: 54,
text: '+', text: '+',
textOptions: { size: 28 }, textOptions: { size: 28 },
onClick: () => { onClick: () => {
@ -138,22 +138,10 @@ export default function StrokeFighterStartScene (k, args) {
k.origin('center') k.origin('center')
]) ])
let motionDetectionEnabled = false const rowingDetector = createRowingDetector(5000, driveFinished)
k.wait(5, () => {
motionDetectionEnabled = true
})
let lastStrokeState = 'DRIVING'
function appState (appState) { function appState (appState) {
if (!motionDetectionEnabled) { rowingDetector.appState(appState)
return
}
if (appState?.metrics.strokeState === undefined) {
return
}
if (lastStrokeState === 'DRIVING' && appState.metrics.strokeState === 'RECOVERY') {
driveFinished(appState.metrics)
}
lastStrokeState = appState.metrics.strokeState
} }
function driveFinished (metrics) { function driveFinished (metrics) {

View File

@ -4,6 +4,10 @@
Implements some common helpers for the games Implements some common helpers for the games
*/ */
/**
* creates a button in the active game scene
*/
export function addButton ({ k, node = k, pos, width, height, text, textOptions, onClick }) { export function addButton ({ k, node = k, pos, width, height, text, textOptions, onClick }) {
const button = node.add([ const button = node.add([
k.rect(width, height), k.rect(width, height),
@ -28,12 +32,6 @@ export function addButton ({ k, node = k, pos, width, height, text, textOptions,
button.onUpdate(() => { button.onUpdate(() => {
if (button.isHovering()) { if (button.isHovering()) {
k.cursor('pointer') k.cursor('pointer')
const t = k.time() * 10
button.color = k.rgb(
k.wave(0, 255, t),
k.wave(0, 255, t + 2),
k.wave(0, 255, t + 4)
)
} else { } else {
// todo: resetting the cursor here will not work as expected if we have multiple buttons on the page // todo: resetting the cursor here will not work as expected if we have multiple buttons on the page
// seems like kaboom does not yet have an elegant way of how to do this... // seems like kaboom does not yet have an elegant way of how to do this...
@ -42,3 +40,37 @@ export function addButton ({ k, node = k, pos, width, height, text, textOptions,
} }
}) })
} }
/**
* creates a detector that can be used to detect when the user begins rowing
* @param {number} initialDelay initial delay in milliseconds before monitoring starts
* @param {Function} activityCallback callback function which is called when rowing begins
* @returns {Object}
*/
export function createRowingDetector (initialDelay, activityCallback) {
let motionDetectionEnabled = false
let lastStrokeState = 'DRIVING'
setTimeout(() => {
motionDetectionEnabled = true
}, initialDelay)
function appState (appState) {
if (!motionDetectionEnabled) {
return
}
if (appState?.metrics.strokeState === undefined) {
return
}
if (lastStrokeState === 'RECOVERY' && appState.metrics.strokeState === 'DRIVING') {
if (activityCallback) {
motionDetectionEnabled = false
activityCallback(appState.metrics)
}
}
lastStrokeState = appState.metrics.strokeState
}
return {
appState
}
}

View File

@ -98,10 +98,6 @@ export class GameComponent extends AppElement {
${metrics?.heartrate ${metrics?.heartrate
? html`<div>${icon_heartbeat}${metricValue(metrics, 'heartrate')}<span class="metric-unit">bpm</span></div>` ? html`<div>${icon_heartbeat}${metricValue(metrics, 'heartrate')}<span class="metric-unit">bpm</span></div>`
: ''} : ''}
<div>${icon_bolt}${metricValue(metrics, 'instantaneousTorque')}<span class="metric-unit">trq</span></div>
<div>${icon_bolt}${metricValue(metrics, 'powerRatio')}<span class="metric-unit">ratio</span></div>
<div>${icon_bolt}${metricValue(metrics, 'strokeState')}</div>
<div id='buttons'> <div id='buttons'>
<button @click=${this.openDashboard}>${icon_exit}</button> <button @click=${this.openDashboard}>${icon_exit}</button>
</div> </div>