@Maiu So for the movement I’m using PhysicsCharacterController
. I played a bit with the values to get the result I like the most.
this._physicsController = new PhysicsCharacterController(position, {
capsuleHeight: PLAYER_SETTINGS.CAPSULE_HEIGHT,
capsuleRadius: PLAYER_SETTINGS.CAPSULE_RADIUS,
}, scene)
this._physicsController.keepContactTolerance = 0.01
this._physicsController.keepDistance = 0.005
this._physicsController.maxSlopeCosine = 0.9
I also have to calculate the movement direction based on the keyboard input. And at the end I also make sure that the movement vector is always following the surface.
const ray = new Ray(this._player.positionNode!.position, Vector3.Down(), 0.5)
const info = PickWithRay(scene, ray, (mesh) => {
return (mesh.layerMask & LayerMask.GROUND) !== 0
}, false)
if (info?.hit && info.pickedPoint) {
const up = info.getNormal(true)
if (up) {
// I don't really need the surface normal, but I set it for debugging
this._player.contactSurfaceNormal = up
const right = up.cross(dir)
dir = right.normalize().cross(up.normalize())
}
}
// scale is just a multiplier for each direction (forward, backward, etc.)
this._player.velocity = dir.scale(scale).scale(PLAYER_SETTINGS.MOVEMENT_VECTOR_SCALE)
'm also doing some calculations if the character is grounded or not:
scene.onBeforeRenderObservable.add(() => {
let isGrounded: boolean[] = []
this._groundingNodes.forEach((node) => {
const ray = new Ray(node.absolutePosition, Vector3.Down(), PLAYER_SETTINGS.CONTACT_GROUNDED)
const info = PickWithRay(scene, ray, (mesh) => {
return (mesh.layerMask & LayerMask.GROUND) !== 0
}, false)
if (info) {
const normal = info.getNormal(true)
if (normal) {
const dot = normal.dot(Vector3.Up())
if (Math.abs(dot) < 0.75) {
// fall off
this._isGrounded = false
return
}
}
isGrounded.push(info.hit)
}
})
this._isGrounded = isGrounded.some(value => value)
})
And then I can set the actual velocity of the PhysicsCharacterController
:
scene.onBeforePhysicsObservable.add(() => {
// save current position
const wasPos = this.positionNode!.position.clone()
const dt = GameManager.Inst.engine.getDeltaTime()
// accumulate time when falling (for acceleration)
if (!this._isGrounded) { this._timeNotGrounded += dt * 0.5 } else { this._timeNotGrounded = 0 }
// this is the velocity vector that was set with keyboard input
const outputVelocity = this._velocity.clone()
// add the gravity (when not grounded)
outputVelocity.addInPlace(Vector3.Down().scale(
PLAYER_SETTINGS.MOVEMENT_FALLING_MULTIPLIER * this._timeNotGrounded
))
// setting the velocity directly, I didn't like the helper method
this._physicsController.setVelocity(outputVelocity)
// you just have to do this
const support = this._physicsController.checkSupport(dt, Vector3.Down())
this._physicsController.integrate(dt, support, Vector3.Down().scale(10))
// and finally set the position
// the actual character position is 1 unit below the physics controller
this.positionNode!.position = this._physicsController.getPosition().clone().add(Vector3.Down())
// I had big issues when just parenting the camera to the character
// so I need this to move the camera.
const dP = this.positionNode!.position.clone().subtract(wasPos)
this._camera.target = this.positionNode!.position.clone().add(Vector3.Up())
this._camera.position = this.camera.position.clone().add(dP)
})
I had to play a lot with the settings, as the PhysicsCharacterController
is not perfect, but at least I can implement jumping in the future.
Currently the client sends updates to the server, and there’s no validation yet (like if the position is incorrect, or the velocity is way too big). But I guess I’m just going to “clamp” to maximum values, and then force them on the client.
I’m still stuck on NPCs movement and combat. So I’m not sure how to handle hacks. Maybe I will just ignore them? If there’s no actual money involved in the game (no in-game store, etc.) maybe it’s not even worth handling the hacks?