소스 검색

Continue working...

Cixo Develop 8 달 전
부모
커밋
33a7314e95

+ 4 - 0
example_config/enviroment.json

@@ -0,0 +1,4 @@
+{
+    "Name": "Example Museum",
+    "Description": "Hello in the example museum. The best museum ever!"
+}

+ 2 - 0
example_config/items.json

@@ -0,0 +1,2 @@
+{
+}

+ 7 - 0
example_config/rooms.json

@@ -0,0 +1,7 @@
+{
+    "first": {
+        "name": "Example room",
+        "description": "Welcome in the example room.",
+        "texture": "example_room.exr"
+    }
+}

BIN
example_config/textures/example_room.exr


+ 55 - 0
make.py

@@ -49,6 +49,48 @@ script_loader_result_file = build / pathlib.Path("bundle.js")
 sass_loader_result_file = build / pathlib.Path("bundle.css")
 sass_loading_screen_tmp_file = build / pathlib.Path("loading-screen.css")
 static_files_output = build / pathlib.Path("static")
+output_config = static_files_output / pathlib.Path("config")
+
+# App config directory
+app_config = root / pathlib.Path("config/")
+app_example_config = root / pathlib.Path("example_config/")
+
+# Check app config
+if not app_config.is_dir():
+    app_config = app_example_config
+    print("Custom config not found. Using example config.")
+
+if not app_config.is_dir():
+    print("App example config not exists.")
+    exit(-1)
+
+# App config files and directories
+enviroment = app_config / pathlib.Path("enviroment.json")
+rooms = app_config / pathlib.Path("rooms.json")
+items = app_config / pathlib.Path("items.json")
+textures = app_config / pathlib.Path("textures")
+objects = app_config / pathlib.Path("objects")
+
+# Checking config directories
+if not enviroment.is_file():
+    print("Enviroment file not exists.")
+    exit(-1)
+
+if not rooms.is_file():
+    print("Rooms index config not exists.")
+    exit(-1)
+
+if not items.is_file():
+    print("Items index config not exists.")
+    exit(-1)
+
+if not textures.is_dir():
+    print("Textures directory in config not exists.")
+    exit(-1)
+
+if not objects.is_dir():
+    print("Objects directory in config not exists.")
+    exit(-1)
 
 # Checking that directories exists
 if not source.is_dir():
@@ -212,9 +254,14 @@ sass_link.href = "./" + sass_loader_result_file.name + "?version=" + release
 js_script = script()
 js_script.src = "./" + script_loader_result_file.name + "?version=" + release
 
+# Add Three.js
+three_js = script()
+three_js.src = "https://unpkg.com/[email protected]/build/three.min.js"
+
 bundle_items = (
     sass_link.render() + 
     js_script.render() + 
+    three_js.render() + 
     loading_screen.render()
 )
 
@@ -257,4 +304,12 @@ compile(
     str(static_files_output)
 )
 
+# Copy config folder
+compile(
+    "cp",
+    "-r",
+    str(app_config), 
+    str(output_config)
+)
+
 print("Build success!")

+ 100 - 0
source/scripts/assets/assets-getter.js

@@ -0,0 +1,100 @@
+/**
+ * This class is responsible for loading assets from the server.
+ */
+export class assets_getter {
+    #app_url;
+    
+    /**
+     * This create new assets getter.
+     *  
+     * @param {string} app_url - This is base app url
+     */
+    constructor(app_url) {
+        this.#app_url = app_url;
+    }
+
+    /**
+     * This fetch file from the config directory.
+     * 
+     * @async
+     * @param {string} file - Name of the file to load
+     * @returns {string, object} - Content of the file, or object if json
+     * @throws {Error} - When file not exists on the server
+     * @throws {TypeError} - When JSON file is bad formated
+     */
+    async load(file) {
+        const url = this.#get_url(file);
+        const result = await fetch(url);
+
+        if (!result.ok) {
+            let info = `Can not load file: "${url}", `;
+            info += `status: "${result.status}".`;
+
+            throw new Error(info);
+        }
+
+        if (this.#is_json(file)) {
+            try {
+                return await result.json();
+            } catch (error) {
+                let info = `JSON file "${file}" is bad formated. `;
+                info += `More: "${error.message}".`;
+
+                throw new TypeError(info);
+            }   
+        }
+
+        return await result.text();
+    }
+
+    /**
+     * @async
+     * @returns {object} - Return enviroment json as object
+     */
+    get enviroment() {
+        return this.load("enviroment.json");
+    }
+
+    /**
+     * @async
+     * @returns {object} - Return items json as object
+     */
+    get items() {
+        return this.load("items.json");
+    }
+
+    /**
+     * @async
+     * @returns {object} - Return rooms json as object
+     */
+    get rooms() {
+        return this.load("rooms.json");
+    }
+
+    /**
+     * @returns {string} - Base config directory on server
+     */
+    get #base_url() {
+        return "/static/config";
+    }
+
+    /**
+     * This function generate full URL to the file on server.
+     * 
+     * @param {string} file - Name of the file in config location
+     * @returns {string} - Full path to the file on server 
+     */
+    #get_url(file) {
+        return `${this.#app_url}/${this.#base_url}/${file}`;        
+    }
+
+    /**
+     * This function check that file is json.
+     * 
+     * @param {string} file - Name of the file to load
+     * @returns {bool} - True if file is json, os false when not
+     */
+    #is_json(file) {
+        return file.substring(file.length - 5) === ".json";
+    }
+}

+ 10 - 2
source/scripts/assets/cx-ui.js

@@ -4,6 +4,7 @@ export class cx_ui {
             const item = document.createElement(type);
            
             item.id = `${type}-${name}`;
+            item.classList.add(type);
             item.classList.add(name);
             item.classList.add(`${type}-${name}`);
 
@@ -27,11 +28,18 @@ export class cx_ui {
         return this.#generator("div");
     }
 
-    static get push() {
-        return this.#generator("input", (item) => {
+    static get canvas() {
+        return this.#generator("canvas");
+    }
+
+    static push(name, text, click = null, manipulate = null) {
+        const generator = this.#generator("input", (item) => {
+            item.value = text;
             item.type = "button";
             item.classList.add("push");
         });
+
+        return generator(name, click, manipulate);
     }
 
     static p(name, text, click = null, manipulate = null) {

+ 2 - 13
source/scripts/assets/dictionary.js

@@ -1,24 +1,13 @@
 import { language_pl_pl } from "../languages/pl_pl";
 
 export class dictionary {
-    static #instance;
-
-    constructor() {
-        if (dictionary.#instance) {
-            return dictionary.#instance;
-        }
-
-        dictionary.#instance = this;
-        return new dictionary();
-    }
-
-    get #dict() {
+    get dict() {
         return language_pl_pl;
     }
 
     get(name) {
         const splited = name.split(".");
-        let dict = this.#dict;
+        let dict = this.dict;
        
         for (let count in splited) {
             if (!(splited[count] in dict)) {

+ 1 - 1
source/scripts/assets/loading-screen.js

@@ -13,7 +13,7 @@ export class loading_screen {
     }
 
     get time() {
-        return 1000;
+        return 400;
     }
 
     #init(classname) {

+ 44 - 0
source/scripts/assets/phrase.js

@@ -0,0 +1,44 @@
+import { language_pl_pl } from "../languages/pl_pl";
+
+export class phrase {
+    #text;
+    static #dictionary;
+
+    static #init() {
+        phrase.#dictionary = language_pl_pl;
+    }
+
+    get dictionary() {
+        if (!phrase.#dictionary) {
+            phrase.#init()
+        }
+
+        return phrase.#dictionary;
+    }
+
+    constructor(location) {
+        const splited = location.split(".");
+        let dict = this.dictionary;
+
+        for (let count in splited) {
+            if (!(splited[count] in dict)) {
+                this.#text = location;
+                break;
+            }
+
+            dict = dict[splited[count]];
+        }
+        
+        if (!this.#text) {    
+            this.#text = dict;
+        }
+    }
+
+    toString() {
+        return this.#text;
+    }
+
+    get text() {
+        return this.#text;
+    }
+}

+ 66 - 0
source/scripts/assets/room.js

@@ -0,0 +1,66 @@
+/** This class represents room. */
+export class room {
+    #id;
+    #name;
+    #description;
+    #texture;
+
+    /**
+     * This create new room from the config object.
+     * 
+     * @param {string} id - ID of the room
+     * @param {object} config - Config from the assets
+     * @return {room} - New room from the config
+     */
+    constructor(id, config) {
+        this.#id = id;
+
+        this.#name = this.#save_get("name", config);
+        this.#description = this.#save_get("description", config);
+        this.#texture = this.#save_get("texture", config);
+    }
+
+    /**
+     * @return {string} - Name of the room
+     */
+    get name() {
+        return this.#name;
+    }
+
+    /**
+     * @return {string} - ID of the room
+     */
+    get id() {
+        return this.#id;
+    }
+
+    /**
+     * @return {string} - Description of the room
+     */
+    get description() {
+        return this.#description;
+    }
+
+    /**
+     * @return {string} - Link to the texture file
+     */
+    get texture() {
+        return this.#texture;
+    }
+
+    /**
+     * This function return property of the object, if it exists. When it no
+     * exists, the return null.
+     * 
+     * @param {string} property - Property to get from object
+     * @param {object} source - Source object to get property from
+     * @return Property of the object or null
+     */
+    #save_get(property, source) {
+        if (!source.hasOwnProperty(property)) {
+            return null;
+        }
+
+        return source[property];
+    }
+}

+ 5 - 3
source/scripts/assets/shelf.js

@@ -1,5 +1,6 @@
 import { cx_ui } from "./cx-ui";
 import { dictionary } from "./dictionary";
+import { phrase } from "./phrase";
 import { selector } from "./selector";
 import { view } from "./view";
 
@@ -12,10 +13,11 @@ export class shelf extends view {
     }
 
     show() {
-        return cx_ui.push("App", () => {
+        const back = new phrase("selector.return");
+        const return_button = cx_ui.push("return", back, () => {
             this.#manager.view = new selector(this.#manager);
-        }, (button) => {
-            button.value = new dictionary().get("selector.return");
         });
+        
+        return return_button;
     }
 }

+ 59 - 0
source/scripts/assets/space-scene.js

@@ -0,0 +1,59 @@
+export class space_scene {
+    #renderer;
+    #canvas;
+    #camera;
+    #scene;
+    #working;
+    #framerate;
+
+    constructor(canvas, context) {
+        this.#framerate = 30;
+        this.#canvas = canvas;
+        this.#renderer = new THREE.WebGLRenderer({canvas, context: context});
+        this.#camera = new THREE.PerspectiveCamera(75, 1);
+        this.#scene = new THREE.Scene();
+        
+        this.update_size();
+    }
+
+    update_size(width, height) {
+        this.#canvas.width = width;
+        this.#canvas.height = height;
+        this.#camera.aspect = width / height;
+
+        this.#camera.updateProjectionMatrix();
+        this.#renderer.setSize(width, height);
+    }
+
+    get scene() {
+        return this.#scene;
+    }
+
+    start() {
+        this.#working = true;
+
+        setTimeout(() => { this.#rendering(); }, 1);
+    }
+
+    stop() {
+        this.#working = false;
+    }
+
+    #rendering() {
+        const before = performance.now();
+
+        this.#renderer.render(this.#scene, this.#camera);
+        
+        if (!this.#working) {
+            return;
+        }
+
+        const after = performance.now();
+        const delta = after - before;
+        const next = 1000 / 30 - ((delta >= 0) ? delta : 0);
+
+        setTimeout(() => {
+            requestAnimationFrame(() => { this.#rendering(); });
+        }, (next > 0) ? next : 1);
+    }
+}

+ 69 - 5
source/scripts/assets/space.js

@@ -1,21 +1,85 @@
 import { view } from "./view";
 import { cx_ui } from "./cx-ui.js";
 import { selector } from "./selector.js";
-import { dictionary } from "./dictionary.js";
+import { phrase } from "./phrase.js";
+import { space_scene } from "./space-scene.js";
 
 export class space extends view {
     #manager;
+    #canvas;
+    #context;
+    #scene;
+    #on_resize;
 
     constructor(manager) {
         super();
         this.#manager = manager;
     }
 
+    get canvas() {
+        return this.#canvas;
+    }
+
+    get context() {
+        return this.#context;
+    }
+
+    #init_canvas() {
+        this.#context = this.canvas.getContext("webgl2");
+        this.#context = this.#context || this.canvas.getContext("webgl");
+
+        if (this.#context) {
+            return;
+        }
+
+        throw new MediaError("WebGL is not supported by browser");
+    }
+
+    update_size() {
+        this.#scene.update_size(window.innerWidth, window.innerHeight);
+    }
+
     show() {
-        return cx_ui.push("return", () => {
-            this.#manager.view = new selector(this.#manager); 
-        }, (button) => {
-            button.value = new dictionary().get("selector.return");    
+        const ui = cx_ui.div("ui");
+        const render = cx_ui.div("render");
+
+        const back = new phrase("selector.return");
+        const return_button = cx_ui.push("return", back, () => {
+            this.#manager.view = new selector(this.#manager);
         });
+
+        this.#canvas = cx_ui.canvas("space-render");
+
+        ui.appendChild(return_button);
+        render.appendChild(this.canvas);
+
+        try {
+            this.#init_canvas();
+            this.#scene = new space_scene(this.canvas, this.context);
+            this.update_size();
+            this.#scene.start();
+        } catch (fail) {
+            const error = cx_ui.div("error");
+            const code = cx_ui.p("code", new phrase("error.webgl-support"));
+
+            error.appendChild(code);
+            render.appendChild(error);
+
+            this.canvas.remove();
+            console.error(fail);
+        }
+
+        this.#on_resize = () => { this.update_size(); };
+        window.addEventListener("resize", this.#on_resize);
+
+        return cx_ui.div("container", null, (container) => {
+            container.appendChild(render);
+            container.appendChild(ui);
+        });
+    }
+
+    destroy() {
+        this.#scene.stop();
+        window.removeEventListener("resize", this.#on_resize);
     }
 }

+ 2 - 2
source/scripts/loader.js

@@ -1,7 +1,6 @@
-import { cx_ui } from "./assets/cx-ui.js";
 import { screen } from "./assets/screen.js";
 import { selector } from "./assets/selector.js";
-import { loading_screen } from "./assets/loading-screen.js";
+import { assets_getter } from "./assets/assets-getter.js";
 
 document.addEventListener("DOMContentLoaded", () => {
     if (!document.querySelector("body")) {
@@ -12,6 +11,7 @@ document.addEventListener("DOMContentLoaded", () => {
 
     const app = document.querySelector(".app");
     const view_manager = new screen(app);
+    const assets_loader = new assets_getter(document.location.href);
 
     setTimeout(() => {
         view_manager.view = new selector(view_manager);

+ 3 - 1
source/theme/loader.sass

@@ -2,4 +2,6 @@
 @import "settings"
 @import "fonts"
 @import "document"
-@import "selector"
+@import "selector"
+@import "space"
+@import "push"

+ 12 - 0
source/theme/push.sass

@@ -0,0 +1,12 @@
+.push
+    border: none
+    border-radius: $padding / 2
+    padding: $padding / 2
+    font-size: 18px
+    color: $content-color
+    background-color: $primary-color
+    transition: color 0.5s, background-color 0.5s
+    
+    &:hover
+        color: $background-color !important
+        background-color: $content-color !important

+ 23 - 0
source/theme/space.sass

@@ -0,0 +1,23 @@
+.container
+    .space-render
+        position: absolute
+        top: 0px
+        left: 0px
+        background: $background-color
+
+    .return
+        position: fixed
+        top: 30px
+        left: 30px
+
+    .error
+        position: absolute
+        width: 100%
+        height: 100%
+        display: flex
+        align-items: center
+        justify-content: center
+        background-color: $background-color
+
+        .code
+            color: $content-color