From 15eafb95e4679cc2ed933ce11f1755c56b65a96b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Wed, 6 Nov 2024 19:06:17 +0100 Subject: [PATCH 1/4] chore: update engine --- package-lock.json | 11 +++++++---- package.json | 2 +- src/core/modules/game_terrain.js | 17 ++++++++++++++--- src/core/utils/terrain/chunk_utils.js | 5 ++++- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 35a9400a..b82d513e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "@aresrpg/aresrpg-dapp", "version": "5.4.33", "dependencies": { - "@aresrpg/aresrpg-engine": "2.3.1", + "@aresrpg/aresrpg-engine": "^2.6.1", "@aresrpg/aresrpg-protocol": "^5.0.1", "@aresrpg/aresrpg-sdk": "4.1.7", "@aresrpg/aresrpg-world": "1.3.9", @@ -170,9 +170,12 @@ } }, "node_modules/@aresrpg/aresrpg-engine": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@aresrpg/aresrpg-engine/-/aresrpg-engine-2.3.1.tgz", - "integrity": "sha512-dFp5UyanqMaVJk1GsuUMTbVwDVrvFp2ZFMID9rpk6VnqDGIbxaCIWWqyOpZwNaEca98QGBI/kkymWKNWtLxwXg==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@aresrpg/aresrpg-engine/-/aresrpg-engine-2.6.1.tgz", + "integrity": "sha512-ZfT3ZpDGyf7bU6Pe9C6jIaNB/n7rG3bZERWH4y5bs7UeRSh7E1k44cdD0yncO87CyxRZHPQb3/5Zik6D3MwNEg==", + "engines": { + "node": ">=20.0.0" + }, "peerDependencies": { "three": ">=0.166.0" } diff --git a/package.json b/package.json index 18b5e711..4de8b899 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "publish:mainnet": "git tag mainnet-$(node -p -e \"require('./package.json').version\") && git push origin mainnet-$(node -p -e \"require('./package.json').version\")" }, "dependencies": { - "@aresrpg/aresrpg-engine": "2.3.1", + "@aresrpg/aresrpg-engine": "^2.6.1", "@aresrpg/aresrpg-protocol": "^5.0.1", "@aresrpg/aresrpg-sdk": "4.1.7", "@aresrpg/aresrpg-world": "1.3.9", diff --git a/src/core/modules/game_terrain.js b/src/core/modules/game_terrain.js index 25e91de5..9febe565 100644 --- a/src/core/modules/game_terrain.js +++ b/src/core/modules/game_terrain.js @@ -3,6 +3,7 @@ import { setInterval } from 'timers/promises' import { EComputationMethod, + HeightmapViewer, TerrainViewer, VoxelmapViewer, VoxelmapVisibilityComputer, @@ -147,7 +148,7 @@ export default function () { }, } - const patch_size = { xz: 64, y: 64 } + const patch_size = { xz: world_patch_size, y: world_patch_size } const min_patch_id_y = Math.floor(min_altitude / patch_size.y) const max_patch_id_y = Math.floor(max_altitude / patch_size.y) const voxelmap_viewer = new VoxelmapViewer( @@ -162,7 +163,12 @@ export default function () { }, }, ) - const terrain_viewer = new TerrainViewer(map, voxelmap_viewer) + const heightmap_viewer = new HeightmapViewer(map, { + basePatchSize: patch_size.xz, + voxelRatio: 2, + maxLevel: 5, + }) + const terrain_viewer = new TerrainViewer(heightmap_viewer, voxelmap_viewer) terrain_viewer.parameters.lod.enabled = true const voxelmap_visibility_computer = new VoxelmapVisibilityComputer( @@ -334,7 +340,12 @@ export default function () { const data = fill_chunk_from_patch(cached_patch, chunk_bbox) const size = Math.round(Math.pow(data.length, 1 / 3)) const dimensions = new Vector3(size, size, size) - const chunk = { data, size: dimensions, isEmpty: false } + const chunk = { + data, + size: dimensions, + isEmpty: false, + dataOrdering: 'zyx', + } // feed engine with chunk voxelmap_viewer.enqueuePatch(patch_id, chunk) // query the map (asynchronous) to get data for this patch diff --git a/src/core/utils/terrain/chunk_utils.js b/src/core/utils/terrain/chunk_utils.js index d87d3af9..7a61684e 100644 --- a/src/core/utils/terrain/chunk_utils.js +++ b/src/core/utils/terrain/chunk_utils.js @@ -4,6 +4,7 @@ import { PatchBlocksCache, } from '@aresrpg/aresrpg-world' import { Vector3, MathUtils, Box3 } from 'three' +import { voxelmapDataPacking } from '@aresrpg/aresrpg-engine' import { world_patch_size } from './world_settings.js' @@ -39,7 +40,9 @@ const write_chunk_blocks = ( cache[cache_index] !== undefined && !buffer_over[buff_index] if (!skip) { - cache[cache_index] = block_type ? block_type + 1 : BlockType.NONE + cache[cache_index] = block_type + ? voxelmapDataPacking.encode(false, block_type) + : voxelmapDataPacking.encodeEmpty() } buff_index-- h-- From 0e0287fe6b807036c3389434a50329baeda4f084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Wed, 6 Nov 2024 19:15:45 +0100 Subject: [PATCH 2/4] feat: new player physics Stable even under low fps --- src/core/game/game.js | 14 ++ src/core/modules/game_terrain.js | 3 +- src/core/modules/player_movement.js | 296 ++++++++++++++-------------- 3 files changed, 166 insertions(+), 147 deletions(-) diff --git a/src/core/game/game.js b/src/core/game/game.js index 6d0531be..2d04d94b 100644 --- a/src/core/game/game.js +++ b/src/core/game/game.js @@ -32,6 +32,7 @@ import CameraControls from 'camera-controls' import { create_client } from '@aresrpg/aresrpg-protocol' import { useWebSocket } from '@vueuse/core' import { ref, watch } from 'vue' +import { VoxelmapCollider, VoxelmapCollisions } from '@aresrpg/aresrpg-engine' import { combine } from '../utils/iterator.js' import ui_fps from '../modules/ui_fps.js' @@ -66,6 +67,7 @@ import { i18n } from '../../i18n.js' import game_entites_stroll from '../modules/game_entites_stroll.js' import player_entities_interract from '../modules/player_entities_interract.js' import game_fights from '../modules/game_fights.js' +import { world_patch_size } from '../utils/terrain/world_settings.js' import { handle_server_error, notify_reconnected } from './error_handler.js' import { get_spells } from './spells_per_class.js' @@ -449,6 +451,14 @@ function connect_ws() { }) } +const voxelmap_collider = new VoxelmapCollider({ + chunkSize: { x: world_patch_size, y: world_patch_size, z: world_patch_size }, + voxelsChunkOrdering: 'zyx', +}) +const voxelmap_collisions = new VoxelmapCollisions({ + voxelmapCollider: voxelmap_collider, +}) + const context = { events, actions, @@ -505,6 +515,10 @@ const context = { // context.camera_controls.maxAzimuthAngle = Infinity // Allow full horizontal rotation // context.camera_controls.minAzimuthAngle = -Infinity // Allow full horizontal rotation }, + physics: { + voxelmap_collider, + voxelmap_collisions, + }, } const modules = MODULES.map(create => create()) diff --git a/src/core/modules/game_terrain.js b/src/core/modules/game_terrain.js index 9febe565..509a35ab 100644 --- a/src/core/modules/game_terrain.js +++ b/src/core/modules/game_terrain.js @@ -235,7 +235,7 @@ export default function () { tick() { terrain_viewer.update() }, - observe({ camera, events, signal, scene, get_state }) { + observe({ camera, events, signal, scene, get_state, physics }) { window.dispatchEvent(new Event('assets_loading')) // this notify the player_movement module that the terrain is ready events.emit('CHUNKS_LOADED') @@ -346,6 +346,7 @@ export default function () { isEmpty: false, dataOrdering: 'zyx', } + physics.voxelmap_collider.setChunk(patch_id, chunk) // feed engine with chunk voxelmap_viewer.enqueuePatch(patch_id, chunk) // query the map (asynchronous) to get data for this patch diff --git a/src/core/modules/player_movement.js b/src/core/modules/player_movement.js index 05574fb6..eb4285b7 100644 --- a/src/core/modules/player_movement.js +++ b/src/core/modules/player_movement.js @@ -2,50 +2,94 @@ import { setInterval } from 'timers/promises' import { aiter } from 'iterator-helper' -import { Object3D, Vector3 } from 'three' -import { lerp } from 'three/src/math/MathUtils.js' - -import { GRAVITY, context, current_three_character } from '../game/game.js' +import { + // CylinderGeometry, + Group, + // Mesh, + // MeshPhongMaterial, + Object3D, + Vector3, +} from 'three' +import { VoxelmapCollisions } from '@aresrpg/aresrpg-engine' + +import { context, current_three_character } from '../game/game.js' import { abortable } from '../utils/iterator.js' import { get_terrain_height } from '../utils/terrain/chunk_utils.js' import { sea_level } from '../utils/terrain/world_settings.js' import { play_step_sound } from './game_audio.js' -const SPEED = 10 +const RUN_SPEED = 10 const WALK_SPEED = 6 const SWIM_SPEED = 10 -const WATER_GRAVITY = 3 -const JUMP_FORCE = 10 -const ASCENT_GRAVITY_FACTOR = 3 -const APEX_GRAVITY_FACTOR = 0.3 -const DESCENT_GRAVITY_FACTOR = 5 +const GRAVITY = 50 +const GRAVITY_UNDERWATER = 5 +const JUMP_FORCE = 13 const JUMP_FORWARD_IMPULSE = 3 -const JUMP_COOLDWON = 0.1 // one jump every 100ms +const JUMP_COOLDOWN = 0.1 // one jump every 100ms + +function compute_inputs_horizontal_movement(camera, inputs) { + const inputs_horizontal_movement_camera = new Vector3( + Number(inputs.right) - Number(inputs.left), + 0, + Number(inputs.forward || (inputs.mouse_left && inputs.mouse_right)) - + Number(inputs.backward), + ) + const has_inputs_horizontal_movement = + inputs_horizontal_movement_camera.lengthSq() > 0 + if (has_inputs_horizontal_movement) { + inputs_horizontal_movement_camera.normalize() + } -const jump_states = { - ASCENT: 'ASCENT', - APEX: 'APEX', - DESCENT: 'DESCENT', - NONE: 'NONE', + const camera_forward = new Vector3(0, 0, -1) + .applyQuaternion(camera.quaternion) + .setY(0) + .normalize() + const camera_right = new Vector3(1, 0, 0) + .applyQuaternion(camera.quaternion) + .setY(0) + .normalize() + const inputs_horizontal_movement_world = new Vector3() + inputs_horizontal_movement_world.addScaledVector( + camera_forward, + inputs_horizontal_movement_camera.z, + ) + inputs_horizontal_movement_world.addScaledVector( + camera_right, + inputs_horizontal_movement_camera.x, + ) + return inputs_horizontal_movement_world } /** @type {Type.Module} */ export default function () { const velocity = new Vector3() - let jump_state = jump_states.NONE let jump_cooldown = 0 - let on_ground = false - let chunks_loaded = false let current_action = 'IDLE' let last_action = 'IDLE' let already_cancelled = false const dummy = new Object3D() + const player_collider_object = new Group() + const player_collider_radius = 0.25 + const player_collider_height = 1.1 + // { + // const player_collider_mesh = new Mesh( + // new CylinderGeometry( + // player_collider_radius, + // player_collider_radius, + // player_collider_height, + // ), + // new MeshPhongMaterial({ color: 0xcccccc }), + // ) + // player_collider_mesh.position.y = 0.5 * player_collider_height + // player_collider_object.add(player_collider_mesh) + // } + return { - tick(state, { camera }, delta) { + tick(state, { camera, physics }, delta) { const player = state.characters.find( character => character.id === state.selected_character_id, ) @@ -55,15 +99,6 @@ export default function () { const origin = player.position.clone() const is_underwater = player.position.y < sea_level - const camera_forward = new Vector3(0, 0, -1) - .applyQuaternion(camera.quaternion) - .setY(0) - .normalize() - const camera_right = new Vector3(1, 0, 0) - .applyQuaternion(camera.quaternion) - .setY(0) - .normalize() - if (player.target_position) { player.target_position.y = get_terrain_height( player.target_position, @@ -71,12 +106,34 @@ export default function () { ) player.move(player.target_position) player.target_position = null + player_collider_object.position.copy(player.position) return } - // we don't want to go futher if no chunks are loaded - // this check must be after the target_position check - if (!chunks_loaded) return + const /** @type VoxelmapCollisions */ { voxelmap_collisions } = physics + const player_collisions = voxelmap_collisions.entityMovement( + { + radius: player_collider_radius, + height: player_collider_height, + position: player.position + .clone() + .sub({ x: 0, y: 0.5 * player.height, z: 0 }), + velocity, + }, + { + deltaTime: delta, + gravity: is_underwater ? GRAVITY_UNDERWATER : GRAVITY, + considerMissingVoxelAs: 'blocking', + }, + ) + + if (player_collisions.computationStatus === 'partial') { + // some required voxeldata was missing -> abort + return + } + + player.position.copy(player_collisions.position).y += 0.5 * player.height + velocity.copy(player_collisions.velocity) // Avoid falling to hell // TODO: tp to nether if falling to hell @@ -86,128 +143,77 @@ export default function () { return } - const movement = new Vector3() - - if (inputs.forward || (inputs.mouse_left && inputs.mouse_right)) - movement.add(camera_forward) - if (inputs.backward) movement.sub(camera_forward) - if (inputs.right) movement.add(camera_right) - if (inputs.left) movement.sub(camera_right) - - const speed = inputs.walk ? WALK_SPEED : SPEED + const inputs_horizontal_movement = compute_inputs_horizontal_movement( + camera, + inputs, + ) + const has_inputs_horizontal_movement = + inputs_horizontal_movement.lengthSq() > 0 - // normalize sideways movement - if (movement.length()) movement.normalize().multiplyScalar(speed * delta) + const movement = new Vector3() - // Apply jump force - if (on_ground) { - if (jump_cooldown > 0) jump_cooldown -= delta - if (inputs.jump && jump_cooldown <= 0) { - velocity.y = JUMP_FORCE + current_action = 'IDLE' - const forward_impulse = movement + if (is_underwater) { + const water_movement = inputs_horizontal_movement + .clone() + .multiplyScalar(SWIM_SPEED) + velocity.x = water_movement.x + velocity.z = water_movement.z + player.rotate(water_movement) + + if (inputs.jump) { + velocity.y = 1.5 * GRAVITY_UNDERWATER + } else { + velocity.y = -GRAVITY_UNDERWATER + } + current_action = 'RUN' + } else { + if (player_collisions.isOnGround) { + const ground_movement = inputs_horizontal_movement .clone() - .normalize() - .multiplyScalar(JUMP_FORWARD_IMPULSE) - - velocity.x += forward_impulse.x - velocity.z += forward_impulse.z + .multiplyScalar(inputs.walk ? WALK_SPEED : RUN_SPEED) + velocity.x = ground_movement.x + velocity.z = ground_movement.z + + if (has_inputs_horizontal_movement) { + if (inputs.walk) { + current_action = 'WALK' + } else { + current_action = 'RUN' + } + + player.rotate(ground_movement) + play_step_sound() + } - jump_state = jump_states.ASCENT - jump_cooldown = JUMP_COOLDWON - on_ground = false + // Apply jump force + if (jump_cooldown > 0) { + jump_cooldown -= delta + } + if (inputs.jump && jump_cooldown <= 0) { + const forward_impulse = inputs_horizontal_movement + .clone() + .multiplyScalar(JUMP_FORWARD_IMPULSE) + velocity.x += forward_impulse.x + velocity.y = JUMP_FORCE + velocity.z += forward_impulse.z + + jump_cooldown = JUMP_COOLDOWN + } } else { - jump_state = jump_states.NONE - - // reset jump impulse - velocity.x = 0 - velocity.z = 0 - velocity.y = 0 + if (velocity.y > 0) { + current_action = 'JUMP_RUN' + } else { + current_action = 'FALL' + } } } - switch (jump_state) { - case jump_states.ASCENT: - if (current_action !== 'JUMP_RUN') current_action = 'FALL' - // if started jumping, apply normal gravity - velocity.y -= GRAVITY * ASCENT_GRAVITY_FACTOR * delta - // prepare apex phase - if (velocity.y <= 0.2) jump_state = jump_states.APEX - break - case jump_states.APEX: - // ignore falling if jumping while running - if (current_action !== 'JUMP_RUN') current_action = 'FALL' - // if apex phase, apply reduced gravity - velocity.y -= GRAVITY * APEX_GRAVITY_FACTOR * delta - // prepare descent phase - if (velocity.y <= 0) jump_state = jump_states.DESCENT - break - case jump_states.DESCENT: - // ignore falling if jumping while running - if (current_action !== 'JUMP_RUN') current_action = 'FALL' - // if descent phase, apply increased gravity - velocity.y -= GRAVITY * DESCENT_GRAVITY_FACTOR * delta - // and also cancel forward impulse - velocity.x = lerp(velocity.x, 0, 0.1) - velocity.z = lerp(velocity.z, 0, 0.1) - break - case jump_states.NONE: - default: - current_action = 'IDLE' - // if not jumping, apply normal gravity as long as chunks are there - if (on_ground) velocity.y = -GRAVITY * delta - else velocity.y -= GRAVITY * DESCENT_GRAVITY_FACTOR * delta - } - - if (is_underwater) { - jump_state = jump_states.NONE - - velocity.y = -WATER_GRAVITY - if (inputs.jump) velocity.y += SWIM_SPEED - } - movement.addScaledVector(velocity, delta) dummy.position.copy(origin.clone().add(movement)) - const { x, z } = dummy.position - const ground_height = get_terrain_height({ x, z }, 0) - - if (!ground_height) return - - const target_y = ground_height + player.height * 0.5 - const dummy_bottom_y = dummy.position.y - player.height * 0.5 - const ground_height_distance = ground_height - dummy_bottom_y - - if (dummy_bottom_y <= ground_height) { - dummy.position.y = lerp(dummy.position.y, target_y, 0.2) - velocity.y = 0 - on_ground = true - } else { - on_ground = false - } - - if (ground_height_distance > 2) dummy.position.copy(origin) - - if (player.position.distanceTo(dummy.position) > 0.01) { - player.move(dummy.position) - } - const is_moving_horizontally = movement.x !== 0 || movement.z !== 0 - if (is_moving_horizontally) { - player.rotate(movement) - current_action = 'RUN' - - if (on_ground) { - if (inputs.walk) current_action = 'WALK' - } else if (jump_state === jump_states.ASCENT) - current_action = 'JUMP_RUN' - - if (on_ground) play_step_sound() - } - - if (!is_underwater && !(dummy_bottom_y - 4 < ground_height)) - current_action = 'FALL' - const should_cancel_other_actions = is_moving_horizontally && !['RUN', 'WALK', 'JUMP', 'JUMP_RUN', 'FALL'].includes(player.action) && @@ -242,7 +248,9 @@ export default function () { } return state }, - observe({ events, signal, dispatch, send_packet }) { + observe({ events, signal, dispatch, send_packet, scene }) { + scene.add(player_collider_object) + aiter(abortable(setInterval(50, null, { signal }))).reduce( last_position => { const player = current_three_character() @@ -274,10 +282,6 @@ export default function () { { x: 0, y: 0, z: 0 }, ) - events.once('CHUNKS_LOADED', () => { - chunks_loaded = true - }) - // @ts-ignore dispatch('') // dispatch meaningless action to trigger the first state change and allow player_spawn.js to register the player }, From 6885b190c0b3167cca1c4cc058350d8262788967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Sat, 9 Nov 2024 21:28:59 +0100 Subject: [PATCH 3/4] feat: camera collisions --- src/core/modules/game_camera.js | 87 ++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 19 deletions(-) diff --git a/src/core/modules/game_camera.js b/src/core/modules/game_camera.js index bfccf989..beae13e5 100644 --- a/src/core/modules/game_camera.js +++ b/src/core/modules/game_camera.js @@ -3,6 +3,7 @@ import { on } from 'events' import { aiter } from 'iterator-helper' import CameraControls from 'camera-controls' import { clamp, smootherstep } from 'three/src/math/MathUtils.js' +import { Vector3 } from 'three' import { abortable, state_iterator } from '../utils/iterator.js' import { context, current_three_character } from '../game/game.js' @@ -13,34 +14,84 @@ import { is_hovering_mob_group } from './player_entities_interract.js' const CAMERA_MIN_DISTANCE = 0.001 const CAMERA_DISTANCE_STEP = 1 const CAMERA_MAX_DISTANCE = 50 +const CAMERA_DISTANCE_MAX_SPEED = 15 +const ENABLE_CAMERA_COLLISIONS = true /** @type {Type.Module} */ export default function () { + let wanted_distance = 0 + return { - tick(state, { camera_controls, dispatch, camera }, delta) { + tick(state, { camera_controls, dispatch, camera, physics }, delta) { const player = current_three_character(state) if (!player?.position) return const camera_state = state.settings.camera - if (!camera_state.is_free) { + if (camera_state.is_free) { + camera_controls.update(delta) + } else { const { x, y, z } = player.position // Set the perspective camera position to follow the player - const center_camera_on_head = - 1 - smootherstep(camera_controls.distance, 0, 10) - const head_height = 1 - const y_shift = state.current_fight - ? -5 - : head_height * center_camera_on_head - camera_controls.moveTo(x, y + y_shift, z, true) - camera_controls.setTarget(x, y + y_shift, z, true) + const head_height = player.height + const y_shift = state.current_fight ? -5 : head_height + const camera_target = new Vector3(x, y + y_shift, z) + camera_controls.moveTo( + camera_target.x, + camera_target.y, + camera_target.z, + true, + ) + camera_controls.setTarget( + camera_target.x, + camera_target.y, + camera_target.z, + true, + ) if (typeof player.object3d !== 'undefined') { player.object3d.visible = camera_controls.distance > 0.75 } + + camera_controls.update(delta) + + const camera_distance_speed = + delta * + CAMERA_DISTANCE_MAX_SPEED * + Math.min(3, Math.abs(wanted_distance - camera_controls.distance)) + if (camera_controls.distance < wanted_distance) { + camera_controls.distance = Math.min( + wanted_distance, + camera_controls.distance + camera_distance_speed, + ) + } else if (camera_controls.distance > wanted_distance) { + camera_controls.distance = Math.max( + wanted_distance, + camera_controls.distance - camera_distance_speed, + ) + } + + if (ENABLE_CAMERA_COLLISIONS) { + const epsilon = 1e-2 + const potential_position = camera_target + .clone() + .addScaledVector( + new Vector3() + .subVectors(camera.position, camera_target) + .normalize(), + CAMERA_MAX_DISTANCE, + ) + const intersection = physics.voxelmap_collisions.rayCast( + camera_target, + potential_position, + ) + const intersection_distance = intersection?.distance ?? Infinity + if (camera_controls.distance > intersection_distance - epsilon) { + camera_controls.distance = intersection_distance - epsilon + } + } } - camera_controls.update(delta) const is_underwater = camera.position.y <= sea_level if (camera_state.is_underwater !== is_underwater) { @@ -73,12 +124,13 @@ export default function () { camera_controls.dollyTo(8) camera_controls.rotate(0, 1) + wanted_distance = camera_controls.distance + // let is_dragging = false const set_distance = distance => { distance = clamp(distance, CAMERA_MIN_DISTANCE, CAMERA_MAX_DISTANCE) - distance = Math.round(distance) - camera_controls.dollyTo(distance, true) + wanted_distance = Math.round(distance) } const on_mouse_down = () => { @@ -88,12 +140,9 @@ export default function () { } const on_mouse_wheel = event => { - const delta_abs = Math.max( - CAMERA_DISTANCE_STEP, - 0.35 * camera_controls.distance, - ) + const delta_abs = Math.max(CAMERA_DISTANCE_STEP, 0.35 * wanted_distance) const delta = delta_abs * Math.sign(event.deltaY) - set_distance(camera_controls.distance + delta) + set_distance(wanted_distance + delta) } renderer.domElement.addEventListener('mousedown', on_mouse_down, { @@ -133,7 +182,7 @@ export default function () { renderer.domElement.addEventListener('wheel', on_mouse_wheel, { signal, }) - set_distance(camera_controls.distance) + set_distance(wanted_distance) // @ts-ignore camera_controls.mouseButtons.right = CameraControls.ACTION.ROTATE // @ts-ignore From 2d87bc624a94b7c8c81c4c6967501db6ac274ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 11 Nov 2024 10:01:24 +0100 Subject: [PATCH 4/4] fix: initial position --- src/core/modules/game_terrain.js | 2 -- src/core/modules/player_movement.js | 10 ++++++---- src/types.d.ts | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/core/modules/game_terrain.js b/src/core/modules/game_terrain.js index 509a35ab..c9760491 100644 --- a/src/core/modules/game_terrain.js +++ b/src/core/modules/game_terrain.js @@ -237,8 +237,6 @@ export default function () { }, observe({ camera, events, signal, scene, get_state, physics }) { window.dispatchEvent(new Event('assets_loading')) - // this notify the player_movement module that the terrain is ready - events.emit('CHUNKS_LOADED') scene.add(terrain_viewer.container) diff --git a/src/core/modules/player_movement.js b/src/core/modules/player_movement.js index eb4285b7..7512a976 100644 --- a/src/core/modules/player_movement.js +++ b/src/core/modules/player_movement.js @@ -100,10 +100,12 @@ export default function () { const is_underwater = player.position.y < sea_level if (player.target_position) { - player.target_position.y = get_terrain_height( - player.target_position, - player.height, - ) + const terrain_height = get_terrain_height(player.target_position, 0) + if (terrain_height < 5) { + // chances are, the chunk is not loaded yet. Wait a bit + return + } + player.target_position.y = terrain_height + 0.5 * player.height player.move(player.target_position) player.target_position = null player_collider_object.position.copy(player.position) diff --git a/src/types.d.ts b/src/types.d.ts index ba0bc3ee..2da542dd 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -181,7 +181,6 @@ declare namespace Type { MOVE_MENU_CAMERA: [number, number, number] // move the camera of the menu screen CONNECT_TO_SERVER: void // request ws connection to the server SET_TIME: number // set the time of the day - CHUNKS_LOADED: void // notify that the loading of new chunks is finished SKY_CYCLE_PAUSED: boolean // pause/resume the normal running of time SKY_CYCLE_CHANGED: { value: number; fromUi: boolean } // the daytime has changed DISPLAY_DAMAGE_UI: {