restructure a bit, start work on client
This commit is contained in:
parent
84c320faa2
commit
8350249591
|
@ -7,6 +7,7 @@
|
|||
],
|
||||
"main": "./dist/client.umd.cjs",
|
||||
"module": "./dist/client.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/client.js",
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
<template>
|
||||
<h1>test</h1>
|
||||
<GameWrapper />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import GameWrapper from './components/GameWrapper.vue';
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,39 @@
|
|||
<template>
|
||||
<div class="game-wrapper"></div>
|
||||
<div class="viewport">
|
||||
<div class="canvas-wrapper" ref="wrapperRef"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue';
|
||||
import { Game } from '../game';
|
||||
import { ViewportComponent } from '@freeblox/engine';
|
||||
|
||||
const wrapperRef = ref();
|
||||
const editorRef = shallowRef<Game>(new Game());
|
||||
|
||||
const resize = () =>
|
||||
editorRef.value.getComponent(ViewportComponent).setSizeFromViewport();
|
||||
|
||||
onMounted(() => {
|
||||
editorRef.value.mount(wrapperRef.value);
|
||||
// TODO: for dev
|
||||
editorRef.value.loadLevel('https://lunasqu.ee/freeblox/test-level.json');
|
||||
window.addEventListener('resize', resize);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resize);
|
||||
editorRef.value?.stop();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.viewport {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import {
|
||||
EnvironmentComponent,
|
||||
MouseComponent,
|
||||
ViewportComponent,
|
||||
EventEmitter,
|
||||
LevelComponent,
|
||||
Engine,
|
||||
assetManager,
|
||||
} from '@freeblox/engine';
|
||||
import { GameEvents } from '../types/events';
|
||||
import { GameplayComponent } from './gameplay';
|
||||
|
||||
export class Game extends Engine {
|
||||
public events = new EventEmitter<GameEvents>();
|
||||
|
||||
mount(element: HTMLElement): void {
|
||||
super.mount(element);
|
||||
|
||||
this.use(ViewportComponent);
|
||||
this.use(EnvironmentComponent);
|
||||
this.use(LevelComponent);
|
||||
this.use(GameplayComponent);
|
||||
this.use(MouseComponent);
|
||||
|
||||
this.getComponent(ViewportComponent).setSizeFromViewport();
|
||||
this.start();
|
||||
}
|
||||
|
||||
loop(now: number): void {
|
||||
const delta = this.getDelta(now);
|
||||
this.running && requestAnimationFrame((ts) => this.loop(ts));
|
||||
this.update(delta);
|
||||
this.render.render();
|
||||
}
|
||||
|
||||
async loadLevel(path: string) {
|
||||
this.events.emit('reset');
|
||||
const data = await assetManager.loadJsonData(path);
|
||||
await this.getComponent(LevelComponent).deserializeLevelSave(data);
|
||||
this.events.emit('initialized');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
import {
|
||||
EngineComponent,
|
||||
EventEmitter,
|
||||
World,
|
||||
instanceCharacterObject,
|
||||
getCharacterController,
|
||||
Humanoid,
|
||||
} from '@freeblox/engine';
|
||||
import { GameEvents } from '../types/events';
|
||||
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
import { Vector3 } from 'three';
|
||||
|
||||
/**
|
||||
* Gameplay manager.
|
||||
*/
|
||||
export class GameplayComponent extends EngineComponent {
|
||||
public name = GameplayComponent.name;
|
||||
public events = new EventEmitter<GameEvents>();
|
||||
public world!: World;
|
||||
public cleanUpEvents?: () => void;
|
||||
|
||||
public characters: Humanoid[] = [];
|
||||
|
||||
public controls!: OrbitControls;
|
||||
public character!: Humanoid;
|
||||
public movementSpeed = 16;
|
||||
public movement = {
|
||||
forward: 0,
|
||||
backward: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
};
|
||||
|
||||
override initialize(): void {
|
||||
this.world = this.renderer.scene.getObjectByName('World') as World;
|
||||
this.cleanUpEvents = this.bindEvents();
|
||||
this.controls = new OrbitControls(
|
||||
this.renderer.camera,
|
||||
this.renderer.renderer.domElement
|
||||
);
|
||||
this.controls.enablePan = false;
|
||||
|
||||
this.loadCharacter('Diamond');
|
||||
}
|
||||
|
||||
override update(delta: number): void {
|
||||
this.controls.update();
|
||||
for (const character of this.characters) {
|
||||
character.tick(delta);
|
||||
}
|
||||
this.character?.setVelocity(
|
||||
new Vector3(
|
||||
this.movement.left + -this.movement.right,
|
||||
0,
|
||||
this.movement.forward + -this.movement.backward
|
||||
)
|
||||
);
|
||||
this.character?.getWorldPosition(this.controls.target);
|
||||
}
|
||||
|
||||
override cleanUp(): void {
|
||||
this.cleanUpEvents?.();
|
||||
}
|
||||
|
||||
public async loadCharacter(name: string, uuid?: string) {
|
||||
const char = await instanceCharacterObject(name);
|
||||
if (uuid) char.uuid = uuid;
|
||||
const ctrl = getCharacterController(char);
|
||||
this.world.add(char);
|
||||
this.characters.push(ctrl);
|
||||
ctrl.initialize();
|
||||
this.character = ctrl;
|
||||
}
|
||||
|
||||
private bindEvents() {
|
||||
const keyDownEvent = (event: KeyboardEvent) => {
|
||||
switch (event.code) {
|
||||
case 'KeyW':
|
||||
this.movement.forward = this.movementSpeed;
|
||||
break;
|
||||
case 'KeyS':
|
||||
this.movement.backward = this.movementSpeed;
|
||||
break;
|
||||
case 'KeyA':
|
||||
this.movement.left = this.movementSpeed;
|
||||
break;
|
||||
case 'KeyD':
|
||||
this.movement.right = this.movementSpeed;
|
||||
break;
|
||||
}
|
||||
};
|
||||
const keyUpEvent = (event: KeyboardEvent) => {
|
||||
switch (event.code) {
|
||||
case 'KeyW':
|
||||
this.movement.forward = 0;
|
||||
break;
|
||||
case 'KeyS':
|
||||
this.movement.backward = 0;
|
||||
break;
|
||||
case 'KeyA':
|
||||
this.movement.left = 0;
|
||||
break;
|
||||
case 'KeyD':
|
||||
this.movement.right = 0;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', keyDownEvent);
|
||||
window.addEventListener('keyup', keyUpEvent);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', keyDownEvent);
|
||||
window.removeEventListener('keyup', keyUpEvent);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from './core/game';
|
||||
export * from './types/events';
|
|
@ -0,0 +1,5 @@
|
|||
import { EngineEvents } from '@freeblox/engine';
|
||||
|
||||
export type Events = {};
|
||||
|
||||
export type GameEvents = Events & EngineEvents;
|
|
@ -7,24 +7,16 @@ import {
|
|||
Renderer,
|
||||
LevelComponent,
|
||||
WorldFile,
|
||||
Engine,
|
||||
} from '@freeblox/engine';
|
||||
import { EditorEvents } from '../types/events';
|
||||
import { WorkspaceComponent } from './workspace';
|
||||
import { HistoryComponent } from './history';
|
||||
import { ShortcutsComponent } from './shortcuts';
|
||||
|
||||
export class Editor extends GameRunner {
|
||||
export class Editor extends Engine {
|
||||
public lastTick = performance.now();
|
||||
public events = new EventEmitter<EditorEvents>();
|
||||
public render!: Renderer;
|
||||
public element!: HTMLElement;
|
||||
public viewport!: ViewportComponent;
|
||||
public workspace!: WorkspaceComponent;
|
||||
public history!: HistoryComponent;
|
||||
public shortcuts!: ShortcutsComponent;
|
||||
public mouse!: MouseComponent;
|
||||
public environment!: EnvironmentComponent;
|
||||
public level!: LevelComponent;
|
||||
public running = false;
|
||||
|
||||
override mount(element: HTMLElement) {
|
||||
|
@ -32,43 +24,25 @@ export class Editor extends GameRunner {
|
|||
this.render = new Renderer(element);
|
||||
this.render.renderer.autoClear = false;
|
||||
|
||||
this.viewport = new ViewportComponent(this.render, this.events);
|
||||
this.viewport.initialize();
|
||||
|
||||
this.workspace = new WorkspaceComponent(this.render, this.events);
|
||||
this.workspace.initialize();
|
||||
|
||||
this.history = new HistoryComponent(this.render, this.events);
|
||||
this.history.initialize();
|
||||
|
||||
this.shortcuts = new ShortcutsComponent(this.render, this.events);
|
||||
this.shortcuts.initialize();
|
||||
|
||||
this.mouse = new MouseComponent(this.render, this.events);
|
||||
this.mouse.initialize();
|
||||
|
||||
this.environment = new EnvironmentComponent(this.render, this.events);
|
||||
this.environment.initialize();
|
||||
|
||||
this.level = new LevelComponent(this.render, this.events);
|
||||
this.level.initialize();
|
||||
|
||||
this.viewport.setSizeFromViewport();
|
||||
this.use(ViewportComponent);
|
||||
this.use(EnvironmentComponent);
|
||||
this.use(LevelComponent);
|
||||
this.use(WorkspaceComponent);
|
||||
this.use(HistoryComponent);
|
||||
this.use(ShortcutsComponent);
|
||||
this.use(MouseComponent);
|
||||
this.getComponent(ViewportComponent).setSizeFromViewport();
|
||||
this.start();
|
||||
}
|
||||
|
||||
override loop(now: DOMHighResTimeStamp) {
|
||||
const delta = (now - this.lastTick) / 1000;
|
||||
this.lastTick = now;
|
||||
|
||||
const delta = this.getDelta(now);
|
||||
this.running && requestAnimationFrame((ts) => this.loop(ts));
|
||||
|
||||
this.viewport.update(delta);
|
||||
this.workspace.update(delta);
|
||||
this.mouse.update(delta);
|
||||
this.update(delta);
|
||||
|
||||
this.render.render();
|
||||
this.workspace.render();
|
||||
this.getComponent(WorkspaceComponent).render();
|
||||
}
|
||||
|
||||
override start() {
|
||||
|
@ -77,22 +51,12 @@ export class Editor extends GameRunner {
|
|||
this.events.emit('initialized');
|
||||
}
|
||||
|
||||
override stop() {
|
||||
this.running = false;
|
||||
this.viewport.cleanUp();
|
||||
this.workspace.cleanUp();
|
||||
this.mouse.cleanUp();
|
||||
this.render.cleanUp();
|
||||
this.history.cleanUp();
|
||||
this.shortcuts.cleanUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current scene tree for the level, starting with two virtual
|
||||
* objects (world and environment).
|
||||
*/
|
||||
public getSceneTree() {
|
||||
return this.level.getSceneTree();
|
||||
return this.getComponent(LevelComponent).getSceneTree();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,7 +64,7 @@ export class Editor extends GameRunner {
|
|||
* @param name World name
|
||||
*/
|
||||
public export(name: string) {
|
||||
return this.level.serializeLevelSave(name);
|
||||
return this.getComponent(LevelComponent).serializeLevelSave(name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,13 +72,13 @@ export class Editor extends GameRunner {
|
|||
* @param data World data
|
||||
*/
|
||||
public load(data: WorldFile) {
|
||||
return this.level.deserializeLevelSave(data);
|
||||
return this.getComponent(LevelComponent).deserializeLevelSave(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get selected objects.
|
||||
*/
|
||||
public getSelection() {
|
||||
return this.workspace.selection;
|
||||
return this.getComponent(WorkspaceComponent).selection;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ type HistoryType = [Object, Changes, number, number?];
|
|||
* Manages history (undo, redo) for editor operations.
|
||||
*/
|
||||
export class HistoryComponent extends EngineComponent {
|
||||
public name = HistoryComponent.name;
|
||||
public cleanUpEvents?: Function;
|
||||
private history: HistoryType[] = [];
|
||||
private restory: HistoryType[] = [];
|
||||
|
|
|
@ -5,6 +5,7 @@ import { EditorEvents } from '..';
|
|||
* Provides editing shortcuts for the editor.
|
||||
*/
|
||||
export class ShortcutsComponent extends EngineComponent {
|
||||
public name = ShortcutsComponent.name;
|
||||
public cleanUpEvents?: Function;
|
||||
|
||||
constructor(
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
Brick,
|
||||
CameraControls,
|
||||
ChangeEvent,
|
||||
Cylinder,
|
||||
EngineComponent,
|
||||
Environment,
|
||||
EventEmitter,
|
||||
|
@ -9,10 +8,7 @@ import {
|
|||
GameObject3D,
|
||||
MouseButtonEvent,
|
||||
Renderer,
|
||||
Sphere,
|
||||
Wedge,
|
||||
WedgeCorner,
|
||||
WedgeInnerCorner,
|
||||
TransformControls,
|
||||
World,
|
||||
} from '@freeblox/engine';
|
||||
import {
|
||||
|
@ -32,16 +28,17 @@ import {
|
|||
TransformModeEvent,
|
||||
} from '../types/events';
|
||||
import { ViewHelper } from 'three/addons/helpers/ViewHelper.js';
|
||||
import { CameraControls } from './controls/camera-controls';
|
||||
import { TransformControls } from './controls/transform-controls';
|
||||
|
||||
/**
|
||||
* This component does most of the work related to editing the level.
|
||||
* Acts as a middle man for most events.
|
||||
*
|
||||
* Most importantly, handles selection, the clipboard and the editor scene.
|
||||
*
|
||||
* LevelComponent must be initialized before this component.
|
||||
*/
|
||||
export class WorkspaceComponent extends EngineComponent {
|
||||
public name = WorkspaceComponent.name;
|
||||
public background = new Object3D();
|
||||
public world = new World();
|
||||
public helpers = new Object3D();
|
||||
|
@ -72,6 +69,7 @@ export class WorkspaceComponent extends EngineComponent {
|
|||
}
|
||||
|
||||
initialize() {
|
||||
this.world = this.renderer.scene.getObjectByName('World') as World;
|
||||
this.addToScene(this.renderer.scene);
|
||||
this.initializeHelpers();
|
||||
this.initializeControls();
|
||||
|
@ -102,11 +100,11 @@ export class WorkspaceComponent extends EngineComponent {
|
|||
private addToScene(scene: Object3D) {
|
||||
this.background.name = '_background';
|
||||
this.helpers.name = '_helper';
|
||||
scene.add(this.background, this.world, this.helpers);
|
||||
scene.add(this.background, this.helpers);
|
||||
}
|
||||
|
||||
private removeFromScene(scene: Object3D) {
|
||||
scene.remove(this.background, this.world, this.helpers);
|
||||
scene.remove(this.background, this.helpers);
|
||||
}
|
||||
|
||||
private bindEvents() {
|
||||
|
|
|
@ -173,6 +173,10 @@ export class AssetManagerFactory extends EventEmitter<AssetsEvents> {
|
|||
this.gltfLoader.load(path, (entry) => resolve(entry), undefined, reject);
|
||||
});
|
||||
}
|
||||
|
||||
async loadJsonData(path: string) {
|
||||
return fetch(path).then((res) => res.json());
|
||||
}
|
||||
}
|
||||
|
||||
export const assetManager = new AssetManagerFactory();
|
||||
|
|
|
@ -6,10 +6,11 @@ import { EventEmitter } from '../utils/events';
|
|||
import { Environment } from '../gameobjects/environment.object';
|
||||
|
||||
/**
|
||||
* This component manages game environment and world lighting
|
||||
* This component manages game environment and world lighting.
|
||||
* @listens setEnvironment
|
||||
*/
|
||||
export class EnvironmentComponent extends EngineComponent {
|
||||
public name = EnvironmentComponent.name;
|
||||
public ambient!: AmbientLight;
|
||||
public directional!: DirectionalLight;
|
||||
public object = new Environment();
|
||||
|
|
|
@ -13,18 +13,21 @@ import { assetManager } from '../assets/manager';
|
|||
import { WorldFile } from '../types/world-file';
|
||||
import { World } from '../gameobjects/world.object';
|
||||
import { instancableGameObjects } from '../gameobjects';
|
||||
import { Color, Matrix4, Object3D, Vector3 } from 'three';
|
||||
import { Object3D } from 'three';
|
||||
import { GameObject, SerializedObject } from '../types/game-object';
|
||||
import { environmentDefaults } from '../defaults/environment';
|
||||
|
||||
/**
|
||||
* Game level management component
|
||||
* Game level management component. This component provides the World.
|
||||
*
|
||||
* EnvironmentComponent must be initialized before this component.
|
||||
* @listens change Applies changes to objects
|
||||
* @listens remove Removes objects from scene
|
||||
* @listens reparent Reparents object
|
||||
*/
|
||||
export class LevelComponent extends EngineComponent {
|
||||
private world!: World;
|
||||
public name = LevelComponent.name;
|
||||
public world = new World();
|
||||
private environment!: Environment;
|
||||
private cleanUpEvents?: Function;
|
||||
|
||||
|
@ -36,7 +39,7 @@ export class LevelComponent extends EngineComponent {
|
|||
}
|
||||
|
||||
initialize(): void {
|
||||
this.world = this.renderer.scene.getObjectByName('World') as World;
|
||||
this.renderer.scene.add(this.world);
|
||||
this.environment = this.renderer.scene.getObjectByName(
|
||||
'Environment'
|
||||
) as Environment;
|
||||
|
|
|
@ -9,8 +9,11 @@ type MouseMap = [boolean, boolean, boolean];
|
|||
|
||||
/**
|
||||
* Manage mouse and object picking from screen.
|
||||
*
|
||||
* ViewportComponent and LevelComponent must be initialized before this component.
|
||||
*/
|
||||
export class MouseComponent extends EngineComponent {
|
||||
public name = MouseComponent.name;
|
||||
private world!: World;
|
||||
|
||||
private mouseButtons: MouseMap = [false, false, false];
|
||||
|
@ -52,7 +55,10 @@ export class MouseComponent extends EngineComponent {
|
|||
|
||||
private bindEvents() {
|
||||
const mouseDown = (ev: MouseEvent) => {
|
||||
const [object] = this.ray.intersectObjects(this.world.children, true);
|
||||
const [object, ...rest] = this.ray.intersectObjects(
|
||||
this.world.children,
|
||||
true
|
||||
);
|
||||
this.mouseButtons[ev.button] = true;
|
||||
this.events.emit('mouseDown', {
|
||||
position: this.mousePosition,
|
||||
|
@ -63,11 +69,15 @@ export class MouseComponent extends EngineComponent {
|
|||
control: ev.ctrlKey,
|
||||
alt: ev.altKey,
|
||||
raw: ev,
|
||||
otherTargets: rest,
|
||||
});
|
||||
};
|
||||
|
||||
const mouseUp = (ev: MouseEvent) => {
|
||||
const [object] = this.ray.intersectObjects(this.world.children, true);
|
||||
const [object, ...rest] = this.ray.intersectObjects(
|
||||
this.world.children,
|
||||
true
|
||||
);
|
||||
this.mouseButtons[ev.button] = false;
|
||||
this.events.emit('mouseUp', {
|
||||
position: this.mousePosition,
|
||||
|
@ -78,6 +88,7 @@ export class MouseComponent extends EngineComponent {
|
|||
control: ev.ctrlKey,
|
||||
alt: ev.altKey,
|
||||
raw: ev,
|
||||
otherTargets: rest,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import { EventEmitter } from '../utils/events';
|
|||
* Manage viewport sizing
|
||||
*/
|
||||
export class ViewportComponent extends EngineComponent {
|
||||
public name = ViewportComponent.name;
|
||||
private cleanUpEvents?: Function;
|
||||
|
||||
constructor(
|
||||
|
|
|
@ -9,6 +9,13 @@ const _changeEvent = { type: 'change' };
|
|||
|
||||
const _PI_2 = Math.PI / 2;
|
||||
|
||||
/**
|
||||
* Freefly camera controls.
|
||||
* WASD + QE to move around.
|
||||
* Holding Right Mouse button rotates the view.
|
||||
* Holding Middle Mouse button pans the view.
|
||||
* Shift slows down movement.
|
||||
*/
|
||||
class CameraControls extends EventDispatcher {
|
||||
private cameraMoving = false;
|
||||
private cameraPanning = false;
|
|
@ -0,0 +1,2 @@
|
|||
export * from './camera-controls';
|
||||
export * from './transform-controls';
|
|
@ -80,6 +80,9 @@ class DefinedProperties extends Object3D {
|
|||
public eye!: Vector3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform controls - move, scale and rotate objects.
|
||||
*/
|
||||
class TransformControls extends DefinedProperties {
|
||||
public isTransformControls = true;
|
||||
public visible = false;
|
|
@ -1,39 +1,65 @@
|
|||
import { EngineComponent } from '..';
|
||||
import { EngineComponent } from '../types/engine-component';
|
||||
import { EngineEvents } from '../types/events';
|
||||
import { GameRunner } from '../types/game-runner';
|
||||
import { EventEmitter, EventMap } from '../utils/events';
|
||||
import { Instancable } from '../types/instancable';
|
||||
import { EventEmitter } from '../utils/events';
|
||||
import { Renderer } from './renderer';
|
||||
|
||||
export class Engine<
|
||||
TEvents extends EventMap = EngineEvents
|
||||
> extends GameRunner {
|
||||
export class Engine extends GameRunner {
|
||||
public lastTick = performance.now();
|
||||
public running = false;
|
||||
public events = new EventEmitter<TEvents>();
|
||||
public events = new EventEmitter<EngineEvents>();
|
||||
public render!: Renderer;
|
||||
public element!: HTMLElement;
|
||||
public components: EngineComponent[] = [];
|
||||
|
||||
mount(element: HTMLElement): void {
|
||||
override mount(element: HTMLElement): void {
|
||||
this.element = element;
|
||||
this.render = new Renderer(element);
|
||||
this.render.renderer.autoClear = false;
|
||||
}
|
||||
|
||||
override loop(now: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
override start(): void {
|
||||
this.running = true;
|
||||
this.loop(this.lastTick);
|
||||
}
|
||||
|
||||
override stop(): void {
|
||||
this.running = false;
|
||||
for (const component of this.components) {
|
||||
component.initialize();
|
||||
component.cleanUp();
|
||||
}
|
||||
|
||||
this.start();
|
||||
}
|
||||
|
||||
loop(now: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
/**
|
||||
* Update every component.
|
||||
*/
|
||||
update(dt: number) {
|
||||
for (const component of this.components) {
|
||||
component.update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
start(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
/**
|
||||
* Use and initialize an engine component
|
||||
* @param Component Engine Component
|
||||
*/
|
||||
use(Component: Instancable<EngineComponent>) {
|
||||
const component = new Component(this.render, this.events);
|
||||
this.components.push(component);
|
||||
component.initialize();
|
||||
return component;
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
/**
|
||||
* Get an engine component.
|
||||
* @param component Component
|
||||
*/
|
||||
getComponent<T extends EngineComponent>(component: Instancable<T>) {
|
||||
return <T>this.components.find((cls) => cls.name === component.name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export * from './renderer';
|
||||
export * from './engine';
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
Object3D,
|
||||
Skeleton,
|
||||
SkinnedMesh,
|
||||
Vector3,
|
||||
} from 'three';
|
||||
import { GameObject } from '../types/game-object';
|
||||
import { Ticking } from '../types/ticking';
|
||||
|
@ -28,6 +29,7 @@ export class Humanoid extends GameObject implements Ticking {
|
|||
private skeleton!: Skeleton;
|
||||
private _health = 100;
|
||||
private _maxHealth = 100;
|
||||
private _velocity = new Vector3(0, 0, 0);
|
||||
public static bodyPartNames = [
|
||||
'Head',
|
||||
'Torso',
|
||||
|
@ -107,9 +109,14 @@ export class Humanoid extends GameObject implements Ticking {
|
|||
action.play();
|
||||
}
|
||||
|
||||
setVelocity(velocity: Vector3) {
|
||||
this._velocity.copy(velocity);
|
||||
}
|
||||
|
||||
tick(dt: number): void {
|
||||
if (!this.ready) return;
|
||||
this.mixer.update(dt);
|
||||
this.parent?.position.add(this._velocity.clone().multiplyScalar(dt));
|
||||
}
|
||||
|
||||
detach(bodyPart?: HumanoidBodyPart) {
|
||||
|
|
|
@ -6,3 +6,4 @@ export * from './gameobjects';
|
|||
export * from './assets';
|
||||
export * from './defaults/environment';
|
||||
export * from './decorators';
|
||||
export * from './controls';
|
||||
|
|
|
@ -3,6 +3,8 @@ import { EventEmitter } from '../utils/events';
|
|||
import { EngineEvents } from './events';
|
||||
|
||||
export abstract class EngineComponent {
|
||||
public abstract name: string;
|
||||
|
||||
constructor(
|
||||
protected renderer: Renderer,
|
||||
protected events: EventEmitter<EngineEvents>
|
||||
|
|
|
@ -26,6 +26,7 @@ export interface MouseButtonEvent extends MousePositionEvent {
|
|||
shift?: boolean;
|
||||
control?: boolean;
|
||||
alt?: boolean;
|
||||
otherTargets?: Intersection<Object3D>[];
|
||||
}
|
||||
|
||||
export interface EnvironmentEvent {
|
||||
|
|
|
@ -4,7 +4,18 @@ import { EditorProperty } from '../decorators/property';
|
|||
import { readMetadataOf } from '../utils/read-metadata';
|
||||
|
||||
export class GameObject extends Object3D {
|
||||
/**
|
||||
* Type of this Game Object.
|
||||
*/
|
||||
public objectType = 'GameObject';
|
||||
|
||||
/**
|
||||
* Virtual game objects are usually built-in types that
|
||||
* do not have physical representations in the game.
|
||||
*
|
||||
* These types still extend Object3D so they could
|
||||
* have effects in the 3D world.
|
||||
*/
|
||||
public virtual = false;
|
||||
|
||||
@EditorProperty({ type: String, exposed: false, readonly: true })
|
||||
|
@ -16,6 +27,10 @@ export class GameObject extends Object3D {
|
|||
@EditorProperty({ type: Boolean })
|
||||
public override visible: boolean = true;
|
||||
|
||||
/**
|
||||
* Object gets included in exported files if `true`.
|
||||
* @default true
|
||||
*/
|
||||
@EditorProperty({ type: Boolean })
|
||||
public archivable: boolean = true;
|
||||
|
||||
|
|
|
@ -1,6 +1,43 @@
|
|||
export abstract class GameRunner {
|
||||
/**
|
||||
* Last tick length in milliseconds.
|
||||
*/
|
||||
public lastTick = performance.now();
|
||||
|
||||
/**
|
||||
* Game running flag.
|
||||
*/
|
||||
public running = false;
|
||||
|
||||
/**
|
||||
* Create the viewport and start the game.
|
||||
* @param element Viewport element
|
||||
*/
|
||||
abstract mount(element: HTMLElement): void;
|
||||
|
||||
/**
|
||||
* This is called on every frame of the game.
|
||||
* Use this to render and update your components.
|
||||
*/
|
||||
abstract loop(now: DOMHighResTimeStamp): void;
|
||||
|
||||
/**
|
||||
* Start running.
|
||||
*/
|
||||
abstract start(): void;
|
||||
|
||||
/**
|
||||
* Stop running.
|
||||
*/
|
||||
abstract stop(): void;
|
||||
|
||||
/**
|
||||
* Get delta time in seconds.
|
||||
* @param now Time since last frame in milliseconds
|
||||
*/
|
||||
getDelta(now: number) {
|
||||
const delta = (now - this.lastTick) / 1000;
|
||||
this.lastTick = now;
|
||||
return delta;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export interface Instancable<T> {
|
||||
new (): T;
|
||||
new (...args: any[]): T;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ export const loadBaseCharacter = async () => {
|
|||
return cachedMeta;
|
||||
};
|
||||
|
||||
export const instanceCharacterObject = async () => {
|
||||
export const instanceCharacterObject = async (name: string) => {
|
||||
const base = await loadBaseCharacter();
|
||||
|
||||
const cloned = SkeletonUtils.clone(base.root!);
|
||||
|
@ -73,14 +73,20 @@ export const instanceCharacterObject = async () => {
|
|||
baseObject.animations = base.clips;
|
||||
baseObject.add(bone as Bone);
|
||||
baseObject.archivable = false;
|
||||
baseObject.name = name;
|
||||
convertedBodyParts.forEach((object) => baseObject.add(object));
|
||||
convertedBodyParts.forEach((object) => (object.archivable = false));
|
||||
|
||||
head.texture = base.faceTexture;
|
||||
|
||||
const controller = new Humanoid();
|
||||
controller.position.set(0, 4.75, 0);
|
||||
controller.archivable = false;
|
||||
baseObject.add(controller);
|
||||
|
||||
return baseObject;
|
||||
};
|
||||
|
||||
export const getCharacterController = (object: Object3D) => {
|
||||
return object.getObjectByName('Humanoid') as Humanoid;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue