Rob Colbert
2 years ago
20 changed files with 410 additions and 110 deletions
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 7.8 KiB |
@ -0,0 +1,32 @@ |
|||
// lib/game-butterfly.js
|
|||
// Copyright (C) 2022 Rob Colbert @[email protected]
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
import { NiceVector2d } from 'dtp-nice-game'; |
|||
|
|||
import GameObjectBehaviors from './game-object-behaviors.js'; |
|||
import GameObject from './game-object.js'; |
|||
|
|||
export default class GameButterfly extends GameObject { |
|||
|
|||
constructor (game, image, position) { |
|||
super(game, image, position); |
|||
|
|||
this.registration = new NiceVector2d( |
|||
this.image.width / 2, |
|||
this.image.height / 2, |
|||
); |
|||
|
|||
this.navigator = new GameObjectBehaviors.BasicNavigator(game, this); |
|||
this.navigator.setMoveSpeed(30.0); |
|||
this.navigator.setRotateSpeed(1.0); |
|||
this.navigator.setTerminateAtTarget(false); |
|||
this.addBehavior(this.navigator); |
|||
} |
|||
|
|||
update (elapsed, now) { |
|||
super.update(elapsed, now); |
|||
} |
|||
} |
@ -1,50 +0,0 @@ |
|||
// lib/game-enemy-behaviors.js
|
|||
// Copyright (C) 2022 Rob Colbert @[email protected]
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
export class DescendToSurface { |
|||
|
|||
constructor (game, enemy, moveSpeed) { |
|||
this.game = game; |
|||
this.enemy = enemy; |
|||
this.moveSpeed = moveSpeed; |
|||
|
|||
this.isDescending = true; |
|||
this.targetX = this.enemy.position.x; |
|||
} |
|||
|
|||
update (/* elapsed, now */) { |
|||
if (this.isDescending) { |
|||
if (this.enemy.position.y < 480) { |
|||
this.enemy.position.y += this.moveSpeed; |
|||
} else { |
|||
this.enemy.position.y = 480; |
|||
this.isDescending = false; |
|||
this.game.audio.playSound('beardson-touchdown'); |
|||
return false; // end of behavior
|
|||
} |
|||
} |
|||
|
|||
if (Math.random() > 0.998) { |
|||
this.targetX = this.game.getRandomPlayfieldX(this.enemy.image.width / 2); |
|||
} |
|||
|
|||
if (this.enemy.position.x > this.targetX) { |
|||
this.enemy.position.x -= this.moveSpeed; |
|||
if (this.enemy.position.x < this.targetX) { |
|||
this.enemy.position.x = this.targetX; |
|||
} |
|||
} |
|||
|
|||
if (this.enemy.position.x < this.targetX) { |
|||
this.enemy.position.x += this.moveSpeed; |
|||
if (this.enemy.position.x > this.targetX) { |
|||
this.enemy.position.x = this.targetX; |
|||
} |
|||
} |
|||
|
|||
return true; // keep running
|
|||
} |
|||
} |
@ -1,27 +0,0 @@ |
|||
// lib/game-enemy.js
|
|||
// Copyright (C) 2022 Rob Colbert @[email protected]
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
import NiceSprite from 'dtp-nice-game/lib/nice-sprite.js'; |
|||
|
|||
export default class GameEnemy extends NiceSprite { |
|||
|
|||
constructor (game, image, position) { |
|||
super(game, position); |
|||
this.image = image; |
|||
this.behaviors = [ ]; |
|||
} |
|||
|
|||
addBehavior (behavior) { |
|||
this.behaviors.push(behavior); |
|||
} |
|||
|
|||
update (elapsed, now) { |
|||
const behavior = this.behaviors[0]; |
|||
if (behavior && !behavior.update(elapsed, now)) { |
|||
this.behaviors.shift(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,13 @@ |
|||
// lib/game-object-behaviors.js
|
|||
// Copyright (C) 2022 Rob Colbert @[email protected]
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
import { DescendToSurface } from "./object-behaviors/descend-to-surface.js"; |
|||
import { BasicNavigator } from "./object-behaviors/basic-navigator.js"; |
|||
|
|||
export default { |
|||
DescendToSurface, |
|||
BasicNavigator, |
|||
}; |
@ -0,0 +1,64 @@ |
|||
// lib/game-object.js
|
|||
// Copyright (C) 2022 Rob Colbert @[email protected]
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
import NiceSprite from 'dtp-nice-game/lib/nice-sprite.js'; |
|||
|
|||
export default class GameObject extends NiceSprite { |
|||
|
|||
constructor (game, image, position) { |
|||
super(game, position); |
|||
this.image = image; |
|||
this.behaviors = [ ]; |
|||
} |
|||
|
|||
addBehavior (behavior) { |
|||
this.behaviors.push(behavior); |
|||
} |
|||
|
|||
update (elapsed, now) { |
|||
const behavior = this.behaviors[0]; |
|||
if (behavior && !behavior.update(elapsed, now)) { |
|||
this.behaviors.shift(); |
|||
} |
|||
} |
|||
|
|||
render (ctx) { |
|||
super.render(ctx); |
|||
|
|||
if (this.navigator) { |
|||
ctx.beginPath(); |
|||
ctx.fillStyle = '#ff0000'; |
|||
ctx.ellipse( |
|||
this.navigator.targetPosition.x, |
|||
this.navigator.targetPosition.y, |
|||
8, |
|||
8, |
|||
0, |
|||
0, |
|||
Math.PI * 2, |
|||
true |
|||
); |
|||
ctx.fill(); |
|||
|
|||
ctx.beginPath(); |
|||
ctx.moveTo(this.position.x, this.position.y); |
|||
ctx.lineTo( |
|||
this.position.x + (Math.cos(this.navigator.desiredRotation) * this.navigator.moveSpeed), |
|||
this.position.y + (Math.sin(this.navigator.desiredRotation) * this.navigator.moveSpeed) |
|||
); |
|||
ctx.strokeStyle = '#ff0000'; |
|||
ctx.lineWidth = 2.0; |
|||
ctx.stroke(); |
|||
|
|||
const oldTextAlign = ctx.textAlign; |
|||
ctx.textAlign = 'center'; |
|||
ctx.fillStyle = '#ffffff'; |
|||
ctx.font = 'bold 12px Larabie'; |
|||
ctx.fillText(`${this.navigator.desiredRotation}`, this.position.x, this.position.y); |
|||
ctx.textAlign = oldTextAlign; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,45 @@ |
|||
// lib/game-zoomer.js
|
|||
// Copyright (C) 2022 Rob Colbert @[email protected]
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
import { NiceVector2d } from 'dtp-nice-game'; |
|||
|
|||
import GameObjectBehaviors from './game-object-behaviors.js'; |
|||
import GameObject from './game-object.js'; |
|||
|
|||
export default class GameZoomer extends GameObject { |
|||
|
|||
constructor (game, image, position) { |
|||
super(game, image, position); |
|||
|
|||
this.registration = new NiceVector2d( |
|||
this.image.width / 2, |
|||
this.image.height / 2, |
|||
); |
|||
|
|||
this.navigator = new GameObjectBehaviors.BasicNavigator(game, this); |
|||
this.navigator.setMoveSpeed(120.0); |
|||
this.navigator.setRotateSpeed(5); |
|||
this.navigator.setTerminateAtTarget(false); |
|||
this.addBehavior(this.navigator); |
|||
|
|||
this.chooseNewTarget(); |
|||
} |
|||
|
|||
chooseNewTarget ( ) { |
|||
this.navigator.setTargetPosition(new NiceVector2d( |
|||
this.game.getRandomPlayfieldX(this.image.width / 2.0), |
|||
32 + (Math.random() * 200), |
|||
)); |
|||
} |
|||
|
|||
update (elapsed, now) { |
|||
super.update(elapsed, now); |
|||
|
|||
if (this.navigator.idle) { |
|||
this.chooseNewTarget(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,126 @@ |
|||
// lib/behaviors/basic-navigator.js
|
|||
// Copyright (C) 2022 Rob Colbert @[email protected]
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
import { NiceMath } from "dtp-nice-game"; |
|||
|
|||
export class BasicNavigator { |
|||
|
|||
constructor (game, gameObject) { |
|||
this.game = game; |
|||
this.gameObject = gameObject; |
|||
|
|||
this.moveSpeed = 2; |
|||
this.rotateSpeed = 0.01; |
|||
|
|||
this.targetPosition = gameObject.position.clone(); |
|||
this.terminateAtTarget = true; |
|||
|
|||
this.desiredRotation = 0.0; |
|||
this.idle = false; |
|||
} |
|||
|
|||
/** |
|||
* Sets the move speed of the gameObject. |
|||
* @param {Number} moveSpeed The speed at which the gameObject will be moved |
|||
* in units per second. |
|||
*/ |
|||
setMoveSpeed (moveSpeed) { |
|||
this.moveSpeed = moveSpeed; |
|||
} |
|||
|
|||
/** |
|||
* Sets the rotation speed of the character in radians per second. |
|||
* @param {Number} rotateSpeed The rotation speed of the gameObject in radians |
|||
* per second. |
|||
*/ |
|||
setRotateSpeed (rotateSpeed) { |
|||
this.rotateSpeed = rotateSpeed; |
|||
} |
|||
|
|||
/** |
|||
* Sets the destination/target to which the navigator will navigate. |
|||
* @param {NiceVector2d} targetPosition The position to which the sprite |
|||
* should navigate |
|||
*/ |
|||
setTargetPosition (targetPosition) { |
|||
this.targetPosition = targetPosition; |
|||
this.idle = false; |
|||
} |
|||
|
|||
/** |
|||
* Indicates whether the behavior should terminate when the gameObject reaches |
|||
* the target position or remain active but idle. |
|||
* @param {Boolean} shouldTerminate true when the behavior should terminate |
|||
* when it reaches the target position or false if the behavior should persist |
|||
* and remain idle. |
|||
* @returns this for method call chaining |
|||
*/ |
|||
setTerminateAtTarget (shouldTerminate = true) { |
|||
this.terminateAtTarget = shouldTerminate; |
|||
} |
|||
|
|||
/** |
|||
* Orients the gameObject sprite towards the target position and takes one |
|||
* step along the gameObject sprite's current forward vector (rotation). If |
|||
* at the target position, will idle or terminate as configured. |
|||
* @param {Number} elapsed The number of milliseconds that have elapsed since |
|||
* the last call |
|||
* @returns true to let the behavior keep running, false to end the behavior. |
|||
*/ |
|||
update (elapsed/*, now */) { |
|||
const TIME_SCALE = elapsed / 1000.0; |
|||
const MOVE_AMOUNT = this.moveSpeed * TIME_SCALE; |
|||
const TURN_AMOUNT = this.rotateSpeed * TIME_SCALE; |
|||
|
|||
const AT_DESTINATION = this.gameObject.position.equals(this.targetPosition); |
|||
|
|||
/* |
|||
* Orient and move towards the target position |
|||
*/ |
|||
|
|||
|
|||
// set our desired rotation to point at the target position
|
|||
if (!AT_DESTINATION) { |
|||
this.desiredRotation = this.gameObject.position.angleTo(this.targetPosition); |
|||
} |
|||
|
|||
// update gameObject rotation towards the desired rotation
|
|||
if (this.gameObject.rotation !== this.desiredRotation) { |
|||
const ldist = Math.abs(this.gameObject.rotation - this.desiredRotation); |
|||
const rdist = Math.abs(this.desiredRotation - this.gameObject.rotation); |
|||
if (ldist <= rdist) { |
|||
this.gameObject.rotation += TURN_AMOUNT; |
|||
} else { |
|||
this.gameObject.rotation -= TURN_AMOUNT; |
|||
} |
|||
if (NiceMath.isAngleNear(this.gameObject.rotation, this.desiredRotation, TURN_AMOUNT)) { |
|||
this.gameObject.rotation = this.desiredRotation; |
|||
} |
|||
} |
|||
|
|||
if (AT_DESTINATION) { |
|||
return !this.terminateAtTarget; // terminate or be idle at the target position
|
|||
} |
|||
|
|||
/* |
|||
* take this frame's step towards the target position in the direction of |
|||
* the gameObject's current rotation |
|||
*/ |
|||
|
|||
let deltaX = Math.cos(this.gameObject.rotation) * this.moveSpeed * TIME_SCALE; |
|||
let deltaY = Math.sin(this.gameObject.rotation) * this.moveSpeed * TIME_SCALE; |
|||
this.gameObject.position.x += deltaX; |
|||
this.gameObject.position.y += deltaY; |
|||
|
|||
if (this.gameObject.position.isNear(this.targetPosition, MOVE_AMOUNT)) { |
|||
this.gameObject.position = this.targetPosition.clone(); |
|||
this.desiredRotation = 0; |
|||
this.idle = true; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
@ -0,0 +1,50 @@ |
|||
// lib/behaviors/descend-to-surface.js
|
|||
// Copyright (C) 2022 Rob Colbert @[email protected]
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
export class DescendToSurface { |
|||
|
|||
constructor (game, gameObject, moveSpeed) { |
|||
this.game = game; |
|||
this.gameObject = gameObject; |
|||
this.moveSpeed = moveSpeed; |
|||
|
|||
this.isDescending = true; |
|||
this.targetX = this.gameObject.position.x; |
|||
} |
|||
|
|||
update (/* elapsed, now */) { |
|||
if (this.isDescending) { |
|||
if (this.gameObject.position.y < 480) { |
|||
this.gameObject.position.y += this.moveSpeed; |
|||
} else { |
|||
this.gameObject.position.y = 480; |
|||
this.isDescending = false; |
|||
this.game.audio.playSound('beardson-touchdown'); |
|||
return false; // end of behavior
|
|||
} |
|||
} |
|||
|
|||
if (Math.random() > 0.998) { |
|||
this.targetX = this.game.getRandomPlayfieldX(this.gameObject.image.width / 2); |
|||
} |
|||
|
|||
if (this.gameObject.position.x > this.targetX) { |
|||
this.gameObject.position.x -= this.moveSpeed; |
|||
if (this.gameObject.position.x < this.targetX) { |
|||
this.gameObject.position.x = this.targetX; |
|||
} |
|||
} |
|||
|
|||
if (this.gameObject.position.x < this.targetX) { |
|||
this.gameObject.position.x += this.moveSpeed; |
|||
if (this.gameObject.position.x > this.targetX) { |
|||
this.gameObject.position.x = this.targetX; |
|||
} |
|||
} |
|||
|
|||
return true; // keep running
|
|||
} |
|||
} |
Loading…
Reference in new issue