Загрузка данных


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();
}
}