Pull to refresh

Редактор игрового движка: визуализация файловой структуры проекта

Reading time 5 min
Views 3.5K
image

Всем привет, меня зовут Александр, я frontend-разработчик. Моя карьера программиста началась относительно недавно, у меня нет специального образования и долгое время я работал совершенно в другой области. Поскольку IT сфера весьма быстро развивается, мне приходится постоянно учиться в ускоренном темпе, чтобы поспеть за своими коллегами с большим опытом за спиной. Для саморазвития, в свободное от работы время, я решил начать свой pet-проект.

Я обозначил важные для себя аспекты в выборе проекта


  1. Проект должен использовать максимального количества актуальных технологий, которые должны дополнять и раскрывать друг друга (за основу можно взять выборку из требования работодателей по направлениям Javascript-разработчик frontend/backend/fullstack).
  2. Он должен давать возможность на дальнейшее использование его как презентации для себя в качестве разработчика (на текущем месте работы nda).
  3. У работы должна быть перспектива того, что она может стать не только pet-проектом, а полноценным продуктом, которым заинтересуются и будут пользоваться.

С детства мне всегда хотелось написать свою игру, а после первого знакомства с ролевой онлайн-игрой « Lineage II», понял, моя обязательно должна быть многопользовательской. Я решил попробовать это реализовать, и сроки в данном случае не горят и первостепенным для меня было получение необходимых hard skills. Так я начал писать свой игровой движок со встроенным редактором и сервером.

В этой статье я опишу, с какими сложностями я столкнулся и как с ними справлялся


При реализации редактора игрового движка, я столкнулся с потребностью выводить файловую структуру проекта для последующих манипуляций с ней, к примеру, редактирования файлов кода, загрузки/изменения картинок, создания новых директорий и т.д, собственно для этого нужен костяк самой структуры.

План действий


  1. Собрать все файлы проекта в единую систему — для этого на сервере реализуем скрипт который будет отвечать за это.
  2. Реализовать запрос к бизнес логике — для получения структуры проекта.
  3. Получить и обработать данные на клиенте.
  4. Реализовать компонент, отображающий древовидную структуру проекта.
  5. Реализовать компонент, отображающий содержание директории по клику на элемент этой директории.

Приступим


    /**
     * Метод рексрусивного спуска по директории проекта ,для формирование структуры проекта в виде массива объектов
     * @param folder
     * @param arrayOfStructures
     * @returns {[Object]}
     */

    readFolder(folder: string, arrayOfStructures: object[]): object[] {
        const projectStructure = {};
        let currentDirectory = fs.readdirSync(folder, 'utf8');
        currentDirectory.forEach(file => {
            let pathOfCurrentItem = path.join(folder, file);

            if (fs.statSync(pathOfCurrentItem).isFile()) {
                arrayOfStructures.push({
                    name: file,
                    path: folder.concat('/' + file),
                    extension: path.extname(file),
                    type: 'file'
                });
            }
            else {
                projectStructure[file] = {name: file, path: folder, type: 'directory', arrayOfStructures: []};
                arrayOfStructures.push(projectStructure[file]);
                this.readFolder(pathOfCurrentItem, projectStructure[file].arrayOfStructures);
            }
        });
        return arrayOfStructures;
    }

Данный скрипт получает на вход директорию, от которой нужно начинать сбор информации о ее вложенных элементах, и пустой массив, в который мы будем складывать полученные данные. Для простоты разделим получаемые item на два типа: файлы и директории.
fs.readdirSync(folder, 'utf8') возвращает нам результат чтения директории, после чего нам остаётся пробежаться по нему циклом и посмотреть, что в нем собственно лежит. Если это файл, то просто добавляем его в массив arrayOfStructures, но если полученный тип это directory то вызываем нашу функцию readFolder рекурсивно и начинаем все с начала, только в этом случае, мы уже передаём имя новой директории. На выходе мы получим массив объектов, которые уже можно будет отправить клиенту.

    /**
     * Метод отправки массива объектов со структурой директории клиенту
     * @param request
     */
    sendDirectoryEngine(request: Context<object>): void {
        const folder = '/GameEngine/Client';
        const structure = this.readFolder(folder, [{}]);

        request.options.parentCtx.params.res.writeHead(200, {'Content-Type': 'text/plain'});
        request.options.parentCtx.params.res.end(JSON.stringify({
            data: [{
                name: 'root',
                type: 'directory',
                arrayOfStructures: structure
            }]
        }));
    }

В этом методе мы получаем результат работы метода readFolder, после чего сериализуем его при помощи JSON.stringify, и отправляем на клиент.

   /**
     * Метод получение структуры проекта с сервера
     * @returns {Promise<Response>}
     */
    static  getDirectoryProject(): Promise<Response> {
        let url = 'http://localhost:3001/api/getProjectStructure';

        return fetch(url, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json;charset=utf-8'
            },
        });
    }

На клиенте у нас есть простой метод который шлёт get запрос к серверу для получения данных, собственно сам метод возвращает promise.

Примерная такая структура у нас получается


//В arrayOfStructures массив с глубокой вложенностью элементов подобных представленному ниже
{name: "root", type: "directory", arrayOfStructures: Array(33)}

А вот сейчас самое интересное


На клиенте я использую React и Typescrypt:

    /**
     * Метод получения данных с бизнес логики и запись их в state
     **/
    getDirectory(): void {
        const projectDirectory = BusinessLogic.getDirectoryProject();
        projectDirectory.then(response => response.json())
            .then(result => {
                result.data[0].arrayOfStructures.sort((a, b) => a.type === 'directory' ? -1 : 1);
                this.setState({
                    directoryProject: result.data,
                    selectedDirectory: result.data[0]
                });
            });
    }

Метод getDirectory вызывает метод обращения к бизнес логике и далее работает с полученным promise, полученные данные мы записываем в state где:

  • directoryProject реализует объект с помощью которого мы будем строить компонент визуализации древовидной структуры проекта.
  • selectedDirectory реализует массив с данными для построения компонента визуализации выбранной директории (по дефолту это корень проекта).

Рендерить мы будем два компонента master из данных directoryProject и detail из selectedDirectory.

image
Компонент отображения структуры и компонент выбранной директории

 /**
     * Метод отображения выбранной директории
     * @param directoryProject
     * @returns {[any,any,any,any,any]}
     */
    showDirectoryStructureSelectedFolder(directoryProject): object[] {

        if (directoryProject && directoryProject.type === 'directory') {
            {

                directoryProject.arrayOfStructures.sort((a, b) => a.type === 'directory' ? -1 : 1);
                return directoryProject.arrayOfStructures.map((directoryItem) => {
                    return (

                        <div className="project_container-elementContainer_element"
                             onClick={this.getInfo.bind(this, directoryItem)}>
                            {directoryItem.type === 'directory' ?
                                <img src="/Client/Editor/Tools/Project/DirectoryItem/icon/directory.png"
                                     className="project_container-elementContainer_element-image" alt=""/> :
                                <img src="/Client/Editor/Tools/Project/DirectoryItem/icon/file.png"
                                     className="project_container-elementContainer_element-image" alt=""/>}
                            <div
                                className="project_container-elementContainer_element-text">{directoryItem.name}</div>
                        </div>

                    );
                })

            }

        }
    }

В методе showDirectoryStructureSelectedFolder мы создаём item директории по которой кликнул пользователь в древе директорий (простите за «масло масляное»). Но поскольку мы не реализовывали сортировку на сервере, то нам придётся сделать это на клиенте, чтобы придать эстетичный вид, соответственно мы просто вызываем метод sort для полученного массива элементов и сортируем по типу (если директория то в начало иначе в конец).

image
Структура выбранной директории

Итог


С помощью несложных манипуляций мы смогли визуализировать структуру нашего проекта и дать пользователю возможность взаимодействовать с ней. В дальнейшем на основе полученных данных из структуры мы можем легко создать компоненты редактора кода, превью/редактора изображений, создавать/редактировать/удалять папки и файлы, ну и многое другое, на что хватит нашей фантазии.

Спасибо за уделенное внимание, надеюсь этот материал сможет вам помочь!

image
немного мясца
Tags:
Hubs:
+5
Comments 0
Comments Leave a comment

Articles