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 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()
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue