fixes game start condition, better placement of opponents
This commit is contained in:
parent
3c8c4f6914
commit
c454d4169d
|
|
@ -20,10 +20,10 @@ export default function StrokeFighterBattleScene (k, args) {
|
|||
const SPM_END = 28
|
||||
const BULLET_SPEED = 1200
|
||||
const ENEMY_SPEED = 50
|
||||
const PLAYER_SPEED = 500
|
||||
const PLAYER_ACCELERATION = 500
|
||||
const PLAYER_SPEED = 600
|
||||
const PLAYER_ACCELERATION = 600
|
||||
const PLAYER_LIFES = 3
|
||||
const SPRITE_WIDTH = 90
|
||||
const SPRITE_WIDTH = 100
|
||||
const ENEMIES = [
|
||||
{ sprite: 'enemyLight1', health: 1 },
|
||||
{ sprite: 'enemyLight2', health: 1 },
|
||||
|
|
@ -148,12 +148,12 @@ export default function StrokeFighterBattleScene (k, args) {
|
|||
})
|
||||
|
||||
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 } })
|
||||
if (closestEnemy?.pos?.x) {
|
||||
const distance = closestEnemy.pos.x - player.pos.x
|
||||
// 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 (closestEnemy.pos.x > player.pos.x + tolerance) {
|
||||
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.destroy(enemy)
|
||||
k.every('bullet', (bullet) => {
|
||||
|
|
@ -261,8 +248,8 @@ export default function StrokeFighterBattleScene (k, args) {
|
|||
})
|
||||
|
||||
const timer = ui.add([
|
||||
k.text('00:00', { size: 25 }),
|
||||
k.pos(10, 10),
|
||||
k.text('', { size: 30 }),
|
||||
k.pos(10, k.height() - 27),
|
||||
k.fixed()
|
||||
])
|
||||
|
||||
|
|
@ -272,7 +259,7 @@ export default function StrokeFighterBattleScene (k, args) {
|
|||
const newTrainingTimeRounded = Math.round(trainingTime)
|
||||
if (trainingTimeRounded !== newTrainingTimeRounded) {
|
||||
const time = newTrainingTimeRounded >= targetTime ? newTrainingTimeRounded : targetTime - newTrainingTimeRounded
|
||||
timer.text = `${secondsToTimeString(time)} / ${k.debug.fps()}fps`
|
||||
timer.text = secondsToTimeString(time)
|
||||
trainingTimeRounded = newTrainingTimeRounded
|
||||
if (newTrainingTimeRounded >= targetTime) {
|
||||
// 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.sprite('playerLife'),
|
||||
k.scale(0.5),
|
||||
k.pos(k.width() - i * 40, 10),
|
||||
k.pos(k.width() - i * 40, k.height() - 27),
|
||||
k.z(100),
|
||||
'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 () {
|
||||
const percentTrainingFinished = trainingTime / targetTime
|
||||
// linearly increase the SPM over time
|
||||
let currentSPM = SPM_START + (SPM_END - SPM_START) * percentTrainingFinished
|
||||
let maxEnemyHealth = 1
|
||||
let minEnemyHealth = 1
|
||||
if (percentTrainingFinished < 0.4) {
|
||||
|
|
@ -374,15 +384,21 @@ export default function StrokeFighterBattleScene (k, args) {
|
|||
}
|
||||
// insane mode (keep on rowing after winning)
|
||||
if (percentTrainingFinished > 1) {
|
||||
// cap SPM at 20% above SPM_END (for insane mode)
|
||||
currentSPM = Math.max(currentSPM, SPM_END * 1.2)
|
||||
minEnemyHealth = 2
|
||||
if (percentTrainingFinished > 1.3) {
|
||||
minEnemyHealth = 3
|
||||
}
|
||||
}
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
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
|
||||
|
|
@ -67,29 +67,16 @@ export default function StrokeFighterEndScene (k, args) {
|
|||
}
|
||||
}
|
||||
|
||||
let motionDetectionEnabled = false
|
||||
let appState
|
||||
if (args?.overtimePossible) {
|
||||
k.wait(5, () => {
|
||||
motionDetectionEnabled = true
|
||||
})
|
||||
}
|
||||
const rowingDetector = createRowingDetector(5000, driveFinished)
|
||||
|
||||
let lastStrokeState = 'DRIVING'
|
||||
function appState (appState) {
|
||||
if (!motionDetectionEnabled) {
|
||||
return
|
||||
appState = function (appState) {
|
||||
rowingDetector.appState(appState)
|
||||
}
|
||||
if (appState?.metrics.strokeState === undefined) {
|
||||
return
|
||||
function driveFinished (metrics) {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import addSpaceBackground from './SpaceBackground.js'
|
||||
import { addButton } from './arcadeHelper.js'
|
||||
import { addButton, createRowingDetector } from './arcadeHelper.js'
|
||||
|
||||
/**
|
||||
* Creates the start screen scene of Stroke Fighter
|
||||
|
|
@ -35,8 +35,8 @@ export default function StrokeFighterStartScene (k, args) {
|
|||
addButton({
|
||||
k,
|
||||
pos: selectorPos.add(100, 0),
|
||||
width: 60,
|
||||
height: 48,
|
||||
width: 66,
|
||||
height: 54,
|
||||
text: '-',
|
||||
textOptions: { size: 28 },
|
||||
onClick: () => {
|
||||
|
|
@ -54,8 +54,8 @@ export default function StrokeFighterStartScene (k, args) {
|
|||
addButton({
|
||||
k,
|
||||
pos: selectorPos.add(230, 0),
|
||||
width: 60,
|
||||
height: 48,
|
||||
width: 66,
|
||||
height: 54,
|
||||
text: '+',
|
||||
textOptions: { size: 28 },
|
||||
onClick: () => {
|
||||
|
|
@ -138,22 +138,10 @@ export default function StrokeFighterStartScene (k, args) {
|
|||
k.origin('center')
|
||||
])
|
||||
|
||||
let motionDetectionEnabled = false
|
||||
k.wait(5, () => {
|
||||
motionDetectionEnabled = true
|
||||
})
|
||||
let lastStrokeState = 'DRIVING'
|
||||
const rowingDetector = createRowingDetector(5000, driveFinished)
|
||||
|
||||
function appState (appState) {
|
||||
if (!motionDetectionEnabled) {
|
||||
return
|
||||
}
|
||||
if (appState?.metrics.strokeState === undefined) {
|
||||
return
|
||||
}
|
||||
if (lastStrokeState === 'DRIVING' && appState.metrics.strokeState === 'RECOVERY') {
|
||||
driveFinished(appState.metrics)
|
||||
}
|
||||
lastStrokeState = appState.metrics.strokeState
|
||||
rowingDetector.appState(appState)
|
||||
}
|
||||
|
||||
function driveFinished (metrics) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
|
||||
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 }) {
|
||||
const button = node.add([
|
||||
k.rect(width, height),
|
||||
|
|
@ -28,12 +32,6 @@ export function addButton ({ k, node = k, pos, width, height, text, textOptions,
|
|||
button.onUpdate(() => {
|
||||
if (button.isHovering()) {
|
||||
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 {
|
||||
// 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...
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,10 +98,6 @@ export class GameComponent extends AppElement {
|
|||
${metrics?.heartrate
|
||||
? 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'>
|
||||
<button @click=${this.openDashboard}>${icon_exit}</button>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue