Загрузка данных
import type { Engine } from "@babylonjs/core/Engines/engine";
import { Scene } from "@babylonjs/core/scene";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
import { ArcRotateCamera } from "@babylonjs/core/Cameras/arcRotateCamera";
import { HemisphericLight } from "@babylonjs/core/Lights/hemisphericLight";
import {CubeTexture, MeshBuilder, StandardMaterial, Texture} from "@babylonjs/core/";
import { ASSET_URLS, AUDIO_URLS } from "../assets/paths";
import { loadGLBAsContainer } from "../assets/loaders";
import { Level } from "../world/Level";
import { EnemyPrefab } from "../entities/EnemyPrefab";
import { EnemySpawner } from "../entities/EnemySpawner";
import { setupInspectorHotkey } from "../debug/inspectorHotkey";
import { YukaWorld } from "../ai/YukaWorld";
import {
ENEMY_PATROL_ROUTE,
GOBLIN_PATROL_START_NODE_INDICES, //Изменинь при смене модельки
ORC_PATROL_START_NODE_INDICES, //Изменинь при смене модельки
getPatrolSpawnPints,
} from "../world/enemyPatrolRoute";
import { AmbientAudio } from "../audio/AmbientAudio";
export class GameScene {
#scene: Scene;
#canvas: HTMLCanvasElement;
#spawner: EnemySpawner | null;
#prefabs: EnemyPrefab[];
#level: Level | null;
#ai: YukaWorld | null;
#audio: AmbientAudio | null;
constructor(engine: Engine, canvas: HTMLCanvasElement) {
this.#canvas = canvas;
this.#scene = new Scene(engine);
this.#level = null;
this.#spawner = null;
this.#prefabs = [];
this.#ai = null;
this.#audio = null;
const camera = new ArcRotateCamera(
"cam",
Math.PI / 2,
Math.PI / 3,
35,
new Vector3(0, 2, 0),
this.#scene
);
camera.attachControl(this.#canvas, true);
new HemisphericLight("light", new Vector3(0, 1, 0), this.#scene);
setupInspectorHotkey(this.#scene);
this.createSkybox();
}
get scene(): Scene {
return this.#scene;
}
async init() {
// контейнеры
const levelContainer = await loadGLBAsContainer(this.#scene, ASSET_URLS.level);
const goblinContainer = await loadGLBAsContainer(this.#scene, ASSET_URLS.goblin);
const orcContainer = await loadGLBAsContainer(this.#scene, ASSET_URLS.orc);
// уровень
const level = new Level(this.#scene, levelContainer, {
scale: 50,
placeOnGround: true,
logBounds: true,
});
this.#level = level;
//объекты земли
const groundMeshes = level.pickMeshes.filter(
(m) =>
m.name.endsWith("_primitive0") ||
m.name.endsWith("_primitive1")
);
// префабы врагов с автоподгоном targetHeight
const goblinPrefab = new EnemyPrefab(this.#scene, goblinContainer, "goblin", { targetHeight: 1.6 });
const orcPrefab = new EnemyPrefab(this.#scene, orcContainer, "orc", { targetHeight: 2.2 });
this.#prefabs.push(goblinPrefab, orcPrefab);
//спавнер врагов
const spawner = new EnemySpawner();
this.#spawner = spawner;
// Спав врагов на поверхность уровня
const goblins = spawner.spawnMany( //переименовать при смене модельки
goblinPrefab,
getPatrolSpawnPints(GOBLIN_PATROL_START_NODE_INDICES),
"goblin", //переименовать при смене модельки
{ groundMeshes }
);
const orcs = spawner.spawnMany( //переименовать при смене модельки
orcPrefab,
getPatrolSpawnPints(ORC_PATROL_START_NODE_INDICES),
"orc", //переименовать при смене модельки
{ groundMeshes }
);
// Yuka
const ai = new YukaWorld(this.#scene);
this.#ai = ai;
// Гоблины — чуть быстрее
for (const [index, g] of goblins.entries()) {
ai.addPatrolEnemy(g, {
groundMeshes,
route: ENEMY_PATROL_ROUTE,
startNodeIndex: GOBLIN_PATROL_START_NODE_INDICES[index],
speed: 1.4,
raycastTopY: 10000,
raycastLength: 20000,
yawOffset: 0, // -> Math.PI
});
}
// Орки — чуть медленнее
for (const [index, o] of orcs.entries()) {
ai.addPatrolEnemy(o, {
groundMeshes,
route: ENEMY_PATROL_ROUTE,
startNodeIndex: ORC_PATROL_START_NODE_INDICES[index],
speed: 1.0,
raycastTopY: 10000,
raycastLength: 20000,
yawOffset: 0, // можно Math.PI
});
}
const audio = new AmbientAudio();
this.#audio = audio;
await audio.init(AUDIO_URLS.ambienceForest);
}
// Skybox
createSkybox() {
const skybox = MeshBuilder.CreateBox("skyBox", { size: 4800.0 }, this.scene);
const skyboxMaterial = new StandardMaterial("skyBox", this.scene);
const skyboxFiles = [
"/textures/skybox/skybox_nx.jpg",
"/textures/skybox/skybox_py.jpg",
"/textures/skybox/skybox_pz.jpg",
"/textures/skybox/skybox_nx.jpg",
"/textures/skybox/skybox_ny.jpg",
"/textures/skybox/skybox_nz.jpg"
];
const texture = CubeTexture.CreateFromImages(skyboxFiles, this.scene);
skyboxMaterial.reflectionTexture = texture;
skyboxMaterial.reflectionTexture.coordinatesMode = Texture.SKYBOX_MODE;
skyboxMaterial.backFaceCulling = false;
skyboxMaterial.disableLighting = true;
skybox.material = skyboxMaterial;
}
update(dt: number) {
this.#ai?.update(dt);
this.#spawner?.update(dt);
}
dispose() {
this.#level?.dispose();
this.#level = null;
this.#audio?.dispolse();
this.#audio = null;
this.#spawner?.disposeAll();
this.#spawner = null;
this.#ai?.dispose();
this.#ai = null;
for (const p of this.#prefabs) p.dispose();
this.#prefabs = [];
this.#scene.dispose();
}
}