Skip to content

Commit

Permalink
implements variable and double jumps and pane control settings (#138)
Browse files Browse the repository at this point in the history
* implements variable and double jumps and pane control settings

* applies Marcus's patch to improve animation switching cleanliness and readability
  • Loading branch information
TheCodeTherapy authored May 20, 2024
1 parent 8e124c7 commit f169fed
Show file tree
Hide file tree
Showing 13 changed files with 437 additions and 84 deletions.
3 changes: 3 additions & 0 deletions example/assets/models/anim_double_jump.glb
Git LFS file not shown
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { AudioListener, Euler, Scene, Vector3 } from "three";

import hdrJpgUrl from "../../../assets/hdr/puresky_2k.jpg";
import airAnimationFileUrl from "../../../assets/models/anim_air.glb";
import doubleJumpAnimationFileUrl from "../../../assets/models/anim_double_jump.glb";
import idleAnimationFileUrl from "../../../assets/models/anim_idle.glb";
import jogAnimationFileUrl from "../../../assets/models/anim_jog.glb";
import sprintAnimationFileUrl from "../../../assets/models/anim_run.glb";
Expand All @@ -30,6 +31,7 @@ const animationConfig: AnimationConfig = {
idleAnimationFileUrl,
jogAnimationFileUrl,
sprintAnimationFileUrl,
doubleJumpAnimationFileUrl,
};

// Specify the avatar to use here:
Expand Down
2 changes: 2 additions & 0 deletions example/multi-user-3d-web-experience/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Networked3dWebExperienceClient } from "@mml-io/3d-web-experience-client

import hdrJpgUrl from "../../../assets/hdr/puresky_2k.jpg";
import airAnimationFileUrl from "../../../assets/models/anim_air.glb";
import doubleJumpAnimationFileUrl from "../../../assets/models/anim_double_jump.glb";
import idleAnimationFileUrl from "../../../assets/models/anim_idle.glb";
import jogAnimationFileUrl from "../../../assets/models/anim_jog.glb";
import sprintAnimationFileUrl from "../../../assets/models/anim_run.glb";
Expand All @@ -21,6 +22,7 @@ const app = new Networked3dWebExperienceClient(holder, {
idleAnimationFileUrl,
jogAnimationFileUrl,
sprintAnimationFileUrl,
doubleJumpAnimationFileUrl,
},
hdrJpgUrl,
mmlDocuments: [{ url: `${protocol}//${host}/mml-documents/example-mml.html` }],
Expand Down
47 changes: 28 additions & 19 deletions packages/3d-web-client-core/src/camera/CameraManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class CameraManager {
public damping: number = camValues.damping;
public dampingScale: number = 0.01;
public zoomScale: number = camValues.zoomScale;
public zoomDamping: number = camValues.zoomDamping;
public invertFOVMapping: boolean = camValues.invertFOVMapping;
public fov: number = this.initialFOV;

Expand Down Expand Up @@ -66,7 +67,7 @@ export class CameraManager {
this.targetPhi = initialPhi;
this.theta = initialTheta;
this.targetTheta = initialTheta;
this.camera = new PerspectiveCamera(this.fov, window.innerWidth / window.innerHeight, 0.1, 300);
this.camera = new PerspectiveCamera(this.fov, window.innerWidth / window.innerHeight, 0.1, 400);
this.camera.position.set(0, 1.4, -this.initialDistance);
this.rayCaster = new Raycaster();

Expand All @@ -77,6 +78,7 @@ export class CameraManager {
[document, "mouseup", this.onMouseUp.bind(this)],
[document, "mousemove", this.onMouseMove.bind(this)],
[targetElement, "wheel", this.onMouseWheel.bind(this)],
[targetElement, "contextmenu", this.onContextMenu.bind(this)],
]);

if (this.hasTouchControl) {
Expand Down Expand Up @@ -136,21 +138,30 @@ export class CameraManager {
}
}

private onMouseDown(): void {
this.dragging = true;
private onMouseDown(event: MouseEvent): void {
if (event.button === 0 || event.button === 2) {
// Left or right mouse button
this.dragging = true;
document.body.style.cursor = "none";
}
}

private onMouseUp(_event: MouseEvent): void {
this.dragging = false;
private onMouseUp(event: MouseEvent): void {
if (event.button === 0 || event.button === 2) {
this.dragging = false;
document.body.style.cursor = "default";
}
}

private onMouseMove(event: MouseEvent): void {
if (!this.dragging || getTweakpaneActive()) return;
if (this.targetTheta === null || this.targetPhi === null) return;
this.targetTheta += event.movementX * this.dampingScale;
this.targetPhi -= event.movementY * this.dampingScale;
this.targetPhi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, this.targetPhi));
event.preventDefault();
if (getTweakpaneActive()) return;
if (this.dragging) {
if (this.targetTheta === null || this.targetPhi === null) return;
this.targetTheta += event.movementX * this.dampingScale;
this.targetPhi -= event.movementY * this.dampingScale;
this.targetPhi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, this.targetPhi));
event.preventDefault();
}
}

private onMouseWheel(event: WheelEvent): void {
Expand All @@ -164,6 +175,10 @@ export class CameraManager {
event.preventDefault();
}

private onContextMenu(event: MouseEvent): void {
event.preventDefault();
}

public setTarget(target: Vector3): void {
if (!this.isLerping) {
this.target.copy(target);
Expand Down Expand Up @@ -201,13 +216,6 @@ export class CameraManager {
}

public adjustCameraPosition(): void {
/*
The purpose for the offsetDistance is to set the rayCaster further from the player
than the camera is on the z relative axis, so we can avoid having a camera collider
and expensive checks to prevent seeing clipped wals or floors or objects when we
readjust the camera. 50cm (the current offset) should get a good balance for most
indoor environments
*/
const offsetDistance = 0.5;
const offset = new Vector3(0, 0, offsetDistance);
offset.applyEuler(this.camera.rotation);
Expand Down Expand Up @@ -266,7 +274,8 @@ export class CameraManager {
this.theta !== null &&
this.targetTheta !== null
) {
this.distance += (this.targetDistance - this.distance) * this.damping * 0.21;
this.distance +=
(this.targetDistance - this.distance) * this.damping * (0.21 + this.zoomDamping);
this.phi += (this.targetPhi - this.phi) * this.damping;
this.theta += (this.targetTheta - this.theta) * this.damping;

Expand Down
1 change: 1 addition & 0 deletions packages/3d-web-client-core/src/character/Character.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type AnimationConfig = {
jogAnimationFileUrl: string;
sprintAnimationFileUrl: string;
airAnimationFileUrl: string;
doubleJumpAnimationFileUrl: string;
};

export type CharacterDescription = {
Expand Down
8 changes: 6 additions & 2 deletions packages/3d-web-client-core/src/character/CharacterManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { Euler, Group, Quaternion, Vector3 } from "three";

import { CameraManager } from "../camera/CameraManager";
import { CollisionsManager } from "../collisions/CollisionsManager";
import { ease } from "../helpers/math-helpers";
import { KeyInputManager } from "../input/KeyInputManager";
import { VirtualJoystick } from "../input/VirtualJoystick";
import { Composer } from "../rendering/composer";
import { TimeManager } from "../time/TimeManager";
import { TweakPane } from "../tweakpane/TweakPane";

import { AnimationConfig, Character, CharacterDescription } from "./Character";
import { CharacterModelLoader } from "./CharacterModelLoader";
Expand Down Expand Up @@ -43,7 +43,7 @@ export class CharacterManager {
public remoteCharacterControllers: Map<number, RemoteController> = new Map();

private localCharacterSpawned: boolean = false;
private localController: LocalController;
public localController: LocalController;
public localCharacter: Character | null = null;

private speakingCharacters: Map<number, boolean> = new Map();
Expand Down Expand Up @@ -102,6 +102,10 @@ export class CharacterManager {
this.localCharacterSpawned = true;
}

public setupTweakPane(tweakPane: TweakPane) {
tweakPane.setupCharacterController(this.localController);
}

public spawnRemoteCharacter(
id: number,
username: string,
Expand Down
87 changes: 64 additions & 23 deletions packages/3d-web-client-core/src/character/CharacterModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,28 +46,38 @@ export class CharacterModel {

public mmlCharacterDescription: MMLCharacterDescription;

private isPostDoubleJump = false;

constructor(private config: CharacterModelConfig) {}

public async init(): Promise<void> {
await this.loadMainMesh();
await Promise.all([
this.setAnimationFromFile(
this.config.animationConfig.idleAnimationFileUrl,
AnimationState.idle,
),
this.setAnimationFromFile(
this.config.animationConfig.jogAnimationFileUrl,
AnimationState.walking,
),
this.setAnimationFromFile(
this.config.animationConfig.sprintAnimationFileUrl,
AnimationState.running,
),
this.setAnimationFromFile(
this.config.animationConfig.airAnimationFileUrl,
AnimationState.air,
),
]);
await this.setAnimationFromFile(
this.config.animationConfig.idleAnimationFileUrl,
AnimationState.idle,
true,
);
await this.setAnimationFromFile(
this.config.animationConfig.jogAnimationFileUrl,
AnimationState.walking,
true,
);
await this.setAnimationFromFile(
this.config.animationConfig.sprintAnimationFileUrl,
AnimationState.running,
true,
);
await this.setAnimationFromFile(
this.config.animationConfig.airAnimationFileUrl,
AnimationState.air,
true,
);
await this.setAnimationFromFile(
this.config.animationConfig.doubleJumpAnimationFileUrl,
AnimationState.doubleJump,
false,
1.3,
);
this.applyCustomMaterials();
}

Expand Down Expand Up @@ -108,6 +118,15 @@ export class CharacterModel {
}

public updateAnimation(targetAnimation: AnimationState) {
if (this.isPostDoubleJump) {
if (targetAnimation === AnimationState.doubleJump) {
// Double jump is requested, but we're in the post double jump state so we play air instead
targetAnimation = AnimationState.air;
} else {
// Reset the post double jump flag if something other than double jump is requested
this.isPostDoubleJump = false;
}
}
if (this.currentAnimation !== targetAnimation) {
this.transitionToAnimation(targetAnimation);
}
Expand Down Expand Up @@ -214,16 +233,23 @@ export class CharacterModel {
private async setAnimationFromFile(
animationFileUrl: string,
animationType: AnimationState,
loop: boolean = true,
playbackSpeed: number = 1.0,
): Promise<void> {
return new Promise(async (resolve, reject) => {
const animation = await this.config.characterModelLoader.load(animationFileUrl, "animation");
const cleanAnimation = this.cleanAnimationClips(this.mesh!, animation as AnimationClip);
if (typeof animation !== "undefined" && cleanAnimation instanceof AnimationClip) {
this.animations[animationType] = this.animationMixer!.clipAction(cleanAnimation);
this.animations[animationType].stop();
this.animations[animationType].timeScale = playbackSpeed;
if (animationType === AnimationState.idle) {
this.animations[animationType].play();
}
if (!loop) {
this.animations[animationType].setLoop(LoopRepeat, 1); // Ensure non-looping
this.animations[animationType].clampWhenFinished = true;
}
resolve();
} else {
reject(`failed to load ${animationType} from ${animationFileUrl}`);
Expand All @@ -235,22 +261,37 @@ export class CharacterModel {
targetAnimation: AnimationState,
transitionDuration: number = 0.15,
): void {
if (!this.mesh) return;
if (!this.mesh) {
return;
}

const currentAction = this.animations[this.currentAnimation];
this.currentAnimation = targetAnimation;
const targetAction = this.animations[targetAnimation];

if (!targetAction) return;
if (!targetAction) {
return;
}

if (currentAction) {
currentAction.enabled = true;
currentAction.fadeOut(transitionDuration);
}

if (!targetAction.isRunning()) targetAction.play();
targetAction.reset();
if (!targetAction.isRunning()) {
targetAction.play();
}

if (targetAnimation === AnimationState.doubleJump) {
targetAction.getMixer().addEventListener("finished", (_event) => {
if (this.currentAnimation === AnimationState.doubleJump) {
this.isPostDoubleJump = true;
// This triggers the transition to the air animation because the double jump animation is done
this.updateAnimation(AnimationState.doubleJump);
}
});
}

targetAction.setLoop(LoopRepeat, Infinity);
targetAction.enabled = true;
targetAction.fadeIn(transitionDuration);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export enum AnimationState {
"jumpToAir" = 3,
"air" = 4,
"airToGround" = 5,
"doubleJump" = 6,
}

export type CharacterState = {
Expand Down
Loading

0 comments on commit f169fed

Please sign in to comment.