noa-engine
Version:
Experimental voxel game engine
165 lines (125 loc) • 4.28 kB
JavaScript
import vec3 from 'gl-vec3'
/**
*
* State object of the `movement` component
*
*/
export function MovementState() {
this.heading = 0 // radians
this.running = false
this.jumping = false
// options
this.maxSpeed = 10
this.moveForce = 30
this.responsiveness = 15
this.runningFriction = 0
this.standingFriction = 2
// jumps
this.airMoveMult = 0.5
this.jumpImpulse = 10
this.jumpForce = 12
this.jumpTime = 500 // ms
this.airJumps = 1
// internal state
this._jumpCount = 0
this._currjumptime = 0
this._isJumping = false
}
/**
* Movement component. State stores settings like jump height, etc.,
* as well as current state (running, jumping, heading angle).
* Processor checks state and applies movement/friction/jump forces
* to the entity's physics body.
* @param {import('..').Engine} noa
*/
export default function (noa) {
return {
name: 'movement',
order: 30,
state: new MovementState(),
onAdd: null,
onRemove: null,
system: function movementProcessor(dt, states) {
var ents = noa.entities
for (var i = 0; i < states.length; i++) {
var state = states[i]
var phys = ents.getPhysics(state.__id)
if (phys) applyMovementPhysics(dt, state, phys.body)
}
}
}
}
var tempvec = vec3.create()
var tempvec2 = vec3.create()
var zeroVec = vec3.create()
/**
* @param {number} dt
* @param {MovementState} state
* @param {*} body
*/
function applyMovementPhysics(dt, state, body) {
// move implementation originally written as external module
// see https://github.com/fenomas/voxel-fps-controller
// for original code
// jumping
var onGround = (body.atRestY() < 0)
var canjump = (onGround || state._jumpCount < state.airJumps)
if (onGround) {
state._isJumping = false
state._jumpCount = 0
}
// process jump input
if (state.jumping) {
if (state._isJumping) { // continue previous jump
if (state._currjumptime > 0) {
var jf = state.jumpForce
if (state._currjumptime < dt) jf *= state._currjumptime / dt
body.applyForce([0, jf, 0])
state._currjumptime -= dt
}
} else if (canjump) { // start new jump
state._isJumping = true
if (!onGround) state._jumpCount++
state._currjumptime = state.jumpTime
body.applyImpulse([0, state.jumpImpulse, 0])
// clear downward velocity on airjump
if (!onGround && body.velocity[1] < 0) body.velocity[1] = 0
}
} else {
state._isJumping = false
}
// apply movement forces if entity is moving, otherwise just friction
var m = tempvec
var push = tempvec2
if (state.running) {
var speed = state.maxSpeed
// todo: add crouch/sprint modifiers if needed
// if (state.sprint) speed *= state.sprintMoveMult
// if (state.crouch) speed *= state.crouchMoveMult
vec3.set(m, 0, 0, speed)
// rotate move vector to entity's heading
vec3.rotateY(m, m, zeroVec, state.heading)
// push vector to achieve desired speed & dir
// following code to adjust 2D velocity to desired amount is patterned on Quake:
// https://github.com/id-Software/Quake-III-Arena/blob/master/code/game/bg_pmove.c#L275
vec3.subtract(push, m, body.velocity)
push[1] = 0
var pushLen = vec3.length(push)
vec3.normalize(push, push)
if (pushLen > 0) {
// pushing force vector
var canPush = state.moveForce
if (!onGround) canPush *= state.airMoveMult
// apply final force
var pushAmt = state.responsiveness * pushLen
if (canPush > pushAmt) canPush = pushAmt
vec3.scale(push, push, canPush)
body.applyForce(push)
}
// different friction when not moving
// idea from Sonic: http://info.sonicretro.org/SPG:Running
body.friction = state.runningFriction
} else {
body.friction = state.standingFriction
}
}