camera lock control

This commit is contained in:
Evert Prants 2022-04-16 10:44:50 +03:00
parent 5850596481
commit b58853af51
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
5 changed files with 145 additions and 14 deletions

View File

@ -1,6 +1,6 @@
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { Color, DoubleSide, MeshBasicMaterial, Vector3 } from 'three'; import { Color, DoubleSide, MeshBasicMaterial, Vector3 } from 'three';
import { isMobileOrTablet } from '../common/helper'; import { clamp, isMobileOrTablet } from '../common/helper';
import { CharacterPacket, CompositePacket } from '../common/types/packet'; import { CharacterPacket, CompositePacket } from '../common/types/packet';
import { IcyNetUser } from '../common/types/user'; import { IcyNetUser } from '../common/types/user';
import { ThirdPersonCamera } from './object/camera'; import { ThirdPersonCamera } from './object/camera';
@ -30,6 +30,7 @@ export class Game {
private _loading = LoadingManagerWrapper.getInstance(); private _loading = LoadingManagerWrapper.getInstance();
private character: CharacterPacket = {}; private character: CharacterPacket = {};
private party: string[] = []; private party: string[] = [];
private _locked = false;
public world!: ClientWorld; public world!: ClientWorld;
public renderer = new Renderer(); public renderer = new Renderer();
@ -149,6 +150,19 @@ export class Game {
this.renderer.scene.add(flowers); this.renderer.scene.add(flowers);
this.renderer.scene.add(flowers2); this.renderer.scene.add(flowers2);
this._loading.isConnecting(); this._loading.isConnecting();
window.addEventListener('keyup', (ev) => {
if (ev.key === 'Shift') {
this.toggleCamLock();
}
});
}
public toggleCamLock(): boolean {
this._locked = !this._locked;
this.thirdPersonCamera?.setLock(this._locked);
this.player?.setCameraLock(this._locked);
return this._locked;
} }
public dispose() { public dispose() {
@ -207,9 +221,18 @@ export class Game {
this.renderer.canvas, this.renderer.canvas,
); );
this.thirdPersonCamera.initialize(); this.thirdPersonCamera.initialize();
this.thirdPersonCamera.registerAltMoveFunction((x, y) => {
this.player.angularVelocity.set(
0,
clamp(x * 0.5, -Math.PI, Math.PI),
0,
);
});
this.joystick = new Joystick(player); this.joystick = new Joystick(player);
this.joystick.initialize(); this.joystick.initialize();
this.joystick.addButton(-60, -20, 'LOCK', () => this.toggleCamLock());
this.joystick.addButton(135, -20, 'JUMP', () => this.player.jump());
if (isMobileOrTablet()) { if (isMobileOrTablet()) {
this.joystick.show(); this.joystick.show();

View File

@ -21,8 +21,11 @@ export class ThirdPersonCamera {
private panning = false; private panning = false;
private pinching = false; private pinching = false;
private locked = false;
private previousPinchLength = 0; private previousPinchLength = 0;
private _moveFn?: (x: number, y: number) => void;
constructor( constructor(
private camera: PerspectiveCamera, private camera: PerspectiveCamera,
private target: Object3D, private target: Object3D,
@ -32,10 +35,16 @@ export class ThirdPersonCamera {
private dragEvent = (x: number, y: number) => { private dragEvent = (x: number, y: number) => {
this.prevMousePos.copy(this.mousePos); this.prevMousePos.copy(this.mousePos);
this.mousePos = this.mousePos.fromArray([x, y]); this.mousePos = this.mousePos.fromArray([x, y]);
const offset = this.prevMousePos.clone().sub(this.mousePos);
if (this.locked) {
if (this._moveFn && this.panning) {
this._moveFn(offset.x, offset.y);
}
return;
}
if (this.panning) { if (this.panning) {
const offset = this.prevMousePos.clone().sub(this.mousePos);
this.angleAroundPlayer = this.angleAroundPlayer =
this.angleAroundPlayer + ((offset.x * 0.3) % 360); this.angleAroundPlayer + ((offset.x * 0.3) % 360);
this.pitch = clamp(this.pitch + offset.y * 0.3, -90, 90); this.pitch = clamp(this.pitch + offset.y * 0.3, -90, 90);
@ -52,12 +61,21 @@ export class ThirdPersonCamera {
} }
}, },
mouseup: (e: MouseEvent) => { mouseup: (e: MouseEvent) => {
const wasPanning = this.panning === true;
if (e.button === 2) { if (e.button === 2) {
this.panning = false; this.panning = false;
} }
if (wasPanning && this.locked && this._moveFn && !this.panning) {
this._moveFn(0, 0);
}
}, },
mouseleave: (e: MouseEvent) => { mouseleave: (e: MouseEvent) => {
const wasPanning = this.panning === true;
this.panning = false; this.panning = false;
if (wasPanning && this.locked && this._moveFn && !this.panning) {
this._moveFn(0, 0);
}
}, },
mousemove: (e: MouseEvent) => this.dragEvent(e.clientX, e.clientY), mousemove: (e: MouseEvent) => this.dragEvent(e.clientX, e.clientY),
wheel: (e: WheelEvent) => { wheel: (e: WheelEvent) => {
@ -98,9 +116,14 @@ export class ThirdPersonCamera {
} }
}, },
touchend: (ev: TouchEvent) => { touchend: (ev: TouchEvent) => {
const wasPanning = this.panning === true;
this.pinching = false; this.pinching = false;
this.panning = false; this.panning = false;
this.previousPinchLength = 0; this.previousPinchLength = 0;
if (wasPanning && this.locked && this._moveFn && !this.panning) {
this._moveFn(0, 0);
}
}, },
}; };
@ -131,6 +154,14 @@ export class ThirdPersonCamera {
this.camera.lookAt(this.currentLookAt); this.camera.lookAt(this.currentLookAt);
} }
public setLock(isLocked: boolean) {
this.locked = isLocked;
}
public registerAltMoveFunction(fn: (x: number, y: number) => void) {
this._moveFn = fn;
}
private calculateCameraOffset() { private calculateCameraOffset() {
const hdist = this.distance * Math.cos(deg2rad(this.pitch)); const hdist = this.distance * Math.cos(deg2rad(this.pitch));
const vdist = this.distance * Math.sin(deg2rad(this.pitch)); const vdist = this.distance * Math.sin(deg2rad(this.pitch));

View File

@ -3,6 +3,7 @@ import { Player } from './player';
export class Joystick { export class Joystick {
public element = document.createElement('div'); public element = document.createElement('div');
private _inner = document.createElement('div');
private knob = document.createElement('div'); private knob = document.createElement('div');
private mousePos = new Vector2(); private mousePos = new Vector2();
@ -20,19 +21,21 @@ export class Joystick {
constructor(private player: Player, public radius = 60) {} constructor(private player: Player, public radius = 60) {}
initialize() { initialize() {
this.element.classList.add('joystick', 'joystick__wrapper'); this.element.classList.add('joystick__wrapper');
this._inner.classList.add('joystick');
this.knob.classList.add('joystick__knob'); this.knob.classList.add('joystick__knob');
this.element.append(this.knob); this._inner.append(this.knob);
this.element.append(this._inner);
document.body.append(this.element); document.body.append(this.element);
this.element.addEventListener('touchstart', (e) => { this._inner.addEventListener('touchstart', (e) => {
e.preventDefault(); e.preventDefault();
const touch = e.touches[0] || e.changedTouches[0]; const touch = e.touches[0] || e.changedTouches[0];
this.mousePos.fromArray([touch.pageX, touch.pageY]); this.mousePos.fromArray([touch.pageX, touch.pageY]);
this.dragging = true; this.dragging = true;
}); });
this.element.addEventListener('touchmove', (e) => { this._inner.addEventListener('touchmove', (e) => {
e.preventDefault(); e.preventDefault();
if (this.dragging) { if (this.dragging) {
this.prevMousePos.copy(this.mousePos); this.prevMousePos.copy(this.mousePos);
@ -49,7 +52,7 @@ export class Joystick {
} }
}); });
this.element.addEventListener('touchend', (e) => { this._inner.addEventListener('touchend', (e) => {
this.dragging = false; this.dragging = false;
this.centerKnob(); this.centerKnob();
}); });
@ -93,6 +96,32 @@ export class Joystick {
this.reset(); this.reset();
} }
public addButton(
x: number,
y: number,
text: string,
toggle: () => void | boolean,
) {
const btn = document.createElement('button');
btn.innerText = text;
btn.style.setProperty('--button-x', `${x}px`);
btn.style.setProperty('--button-y', `${y}px`);
btn.classList.add('joystick__button');
btn.addEventListener('click', (ev) => {
ev.preventDefault();
ev.stopPropagation();
const value = toggle();
if (typeof value === 'boolean') {
if (value) {
btn.classList.add('joystick__button--active');
} else {
btn.classList.remove('joystick__button--active');
}
}
});
this.element.append(btn);
}
private _windowEvent() { private _windowEvent() {
this.reset(); this.reset();
} }

View File

@ -38,6 +38,11 @@ export class Player extends PonyEntity {
*/ */
private _wasTurning = false; private _wasTurning = false;
/**
* When true, left and right movement will not rotate, instead will move.
*/
private _cameraLock = false;
constructor(public user: IcyNetUser) { constructor(public user: IcyNetUser) {
super(); super();
} }
@ -84,7 +89,7 @@ export class Player extends PonyEntity {
vector.copy(this._direction); vector.copy(this._direction);
} }
if (vector.x !== 0) { if (vector.x !== 0 && !this._cameraLock) {
this.angularVelocity.set(0, Math.PI * vector.x, 0); this.angularVelocity.set(0, Math.PI * vector.x, 0);
this.changes.angular = this.angularVelocity.toArray(); this.changes.angular = this.angularVelocity.toArray();
@ -96,10 +101,20 @@ export class Player extends PonyEntity {
this.changes.rotation = this.container.rotation.toArray(); this.changes.rotation = this.container.rotation.toArray();
} }
if (vector.y !== 0) { if (vector.y !== 0 || (this._cameraLock && vector.x !== 0)) {
const directional = this._lookVector const directional = this._lookVector
.clone() .clone()
.multiplyScalar(vector.y * 2.5); .multiplyScalar(vector.y * 2.5);
if (this._cameraLock && vector.x !== 0) {
const sideways = new Vector3();
sideways.copy(this._lookVector);
sideways.applyAxisAngle(new Vector3(0, 1, 0), Math.PI / 2);
sideways.normalize();
sideways.multiplyScalar(vector.x * 2.5);
directional.add(sideways);
}
this.velocity.set(directional.x, this.velocity.y, directional.z); this.velocity.set(directional.x, this.velocity.y, directional.z);
this.changes.velocity = this.velocity.toArray(); this.changes.velocity = this.velocity.toArray();
@ -145,4 +160,8 @@ export class Player extends PonyEntity {
this._prevKeydownMap = { ...this.keydownMap }; this._prevKeydownMap = { ...this.keydownMap };
} }
public setCameraLock(isLocked: boolean) {
this._cameraLock = isLocked;
}
} }

View File

@ -14,17 +14,20 @@ body {
} }
.joystick { .joystick {
display: none;
position: absolute;
width: var(--size); width: var(--size);
height: var(--size); height: var(--size);
bottom: 10px;
left: calc(50% - var(--size) / 2);
background-color: #e7e7e7b3; background-color: #e7e7e7b3;
border-radius: 100%; border-radius: 100%;
border: 2px solid #ddd; border: 2px solid #ddd;
z-index: 1000; z-index: 1000;
&__wrapper {
display: none;
position: absolute;
left: calc(50% - var(--size) / 2);
bottom: 10px;
}
&__knob { &__knob {
width: calc(var(--size) / 2); width: calc(var(--size) / 2);
height: calc(var(--size) / 2); height: calc(var(--size) / 2);
@ -35,6 +38,32 @@ body {
cursor:grab; cursor:grab;
transform-origin: center center; transform-origin: center center;
} }
&__button {
position: absolute;
appearance: none;
left: var(--button-x);
top: var(--button-y);
width: 48px;
height: 48px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 100%;
background-color: rgba(231, 231, 231, 0.7019607843);
border: 2px solid #ddd;
z-index: 1001;
color: #5c5c5c;
font-weight: bold;
transition: border 250ms linear, background-color 250ms linear;
&--active {
border-width: 4px;
border-color: #db8f00;
background-color: rgb(231 231 231 / 78%);
}
}
} }
.chat { .chat {