КОТ++
Прикладное программирование
Системное программирование
Программирование микроконтроллеров

640 Кб должно хватить для любых задач. (c) Билл Гейтс

Опубликовано
Комментарии 0

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

Почему я не взял готовый движок? Во-первых, NIH, я уже писал про это (там я и сказал пару слов о движке), во-вторых, желание разобраться в работе движков. Движок был назван изначально EEngine – Egor Engine, это название я придумал ещё до момента, когда был выработан концепт движка, после того как уже был готовый движок у меня появилась небольшая капля скромности и название мне перестало нравиться. Но я его не стал менять, просто первую букву E нужно расшифровывать как-то иначе.

Концепт движка заключается в следующем: EEngine отрисовывает графику через GPU – graphics processing unit, на мобилках это видеочип, на компах это видеокарта. GPU по сути работает с 3D графикой, но мой движок 2D, а 2D для GPU это тот же 3D только без манипуляций осью Z, представьте плоский куб, у которого толщина равна 0, на передней и единственной грани рендерится (отрисовывается) текстура – спрайт.

Отрисовка производится шейдерами. Шейдеры – это программы для рендеринга, которые скармливаются GPU, они сообщают видеочипу как отрисовывать каждый пиксель. Так как GPU манипулирует только треугольниками, то в шейдере в общих словах алгоритм таков: рисуем два треугольника, два треугольника это по сути прямоугольник, потом берём текстуру и натягиваем поверх нарисованного треугольника – так мы получим отрисованный 2D спрайт. То есть каждый графический объект является прямоугольником из двух треугольников, такая конструкция называется мешом, меш – набор треугольников определяющих форму графического объекта.

Работа движка осуществлена с помощью паттерна «Игровой цикл», в общем виде игровой цикл должен выглядеть так:

while (1)
{
	processInput(); //обработка пользовательского ввода
	update(); //обработка логики игры 
	render(); //отрисовка
}

В бесконечном цикле вызываются у корневой сцены методы processInput(), render() и update(). Общая суть паттерна игровой цикл в том, что есть бесконечный цикл, а в нём последовательно обрабатываются пользовательский ввод (processInput), отрисовка (render), просчёт логики игры (update) и просчёт физики (физику не разбираю в этой статье). О игровых паттернах очень хорошо рассказано в этой книге, как раз таки в ней я о них и узнал.

Все графические объекты являются нодой (узлом) связного списка, в итоге возможность ноды иметь дочерние объекты порождает дерево графических объектов, отрисовка выполняется обходом по дереву, у каждой ноды берётся текстура и отрисовывается по необходимым координатам шейдером. Аналогичным образом идёт вызов метода update() у объектов. И аналогичным образом реализованы сигналы и слоты у элементов UI, реализовал работу сигналов-слотов будучи вдохновлённым Qt. UI элементы это те же графические элементы, но расширены функционалом сигналов-слотов для работы с пользовательским вводом. У UI элемента достаточно подписаться на определённое событие, добавив слот. Потом когда движок породит сигнал, что событие произошло, то также через обход дерева найдётся объект, который был подписан на этот сигнал.


root – корневая нода, с неё начинается обход дерева

Также был реализован пул объектов, что очень полезно в играх, где требуется постоянное появление одинаковых объектов, вместо удаления и создания, движок кладёт объект в пул и при надобности достаёт из пула, это существенно экономит вычислительные ресурсы.

После я принялся за разработку физического движка, к сожалению работу над физическим движком я не завершил до конца. Если у меня дойдут руки, то выложу материал по физическому движку.

Технические подробности (новичкам можно пропустить абзац): движок был написан на AS3, использован для мобильных игр на Adobe AIR, например эта. Движок я не выполнил отдельной библиотекой, поэтому исходники смешаны с исходниками самой игры по ссылке, потому что разрабатывал его параллельно с написанием игры. Изначально игра была написана чисто на Flash без движков, но настал момент, что потребовалась оптимизация, для оптимизации я решил написать свой движок. Шейдеры были написаны на AGAL – Adobe Graphics Assembly Language, адобовский мини ассемблер. Бесконечный цикл у меня реализован не особо привычном образом, флеш генерирует событие ENTER_FRAME строго соотвествуя FPS и я подключил цикл к этому событию.

Теги , , , , ,
Автор

Опубликовано
Комментарии 0

Спустя 16 лет эта книга до сих остаётся актуальной, это многое говорит как об авторе так и о материале книги. Материал книги на самом деле очень сложный, но читается с большим интересом. После прочтения появится более глубокое понимание об устройстве компьютера, оперативной памяти, процессора и кэша процессора. Конечно, эти знания вряд ли можно применить при разработке прикладных программ, и зачастую на работах такое глубокое понимание не требуется и никому не нужно. Но для собственного интереса прочитать стоит. Я думаю, что те люди, которые по-настоящему любят программирование, оценят эту книгу по достоинству.

Книга содержит объяснение различных методик оптимизации, листинги C и ASM программ с примерами оптимизации (развернутые примеры представлены на CD диске, который прилагается к книге), а также методы определения слабых, с точки зрения оптимизации, мест в программе.

Теги ,
Автор

Опубликовано
Комментарии 0

Кокос на момент написания статьи не поддерживает из коробки Dragon Bones спрайты. Поэтому сначала нужно подружить кокос и драгон бонс. Для этого нужно скачать исходники Dragon Bones C++ Runtime. Нам понадобятся из этого репозитория директории: DragonBones/src/dragonBones/dragonBones, Cocos2DX_3.x/src/dragonBones/cocos2dx, 3rdParty/rapidjson. В проекте должна быть такая структура:

Ваш проект
    |-- Classes
        |-- rapidjson
            |-- ...
        |-- dragonBones
            |-- animation
            |-- armature
            |-- ...
            |-- cocos2dx
            |-- ...
    |-- Resources
        |-- ...

Далее, если проект win32, то остаётся добавить все эти файлы в проект через Добавить->Существующий элемент…

Использовать Dragon Bones спрайты в коде не сложно, допустим ваш Dragon Bones проект называется man, тогда вы должны кинуть в папку Resources файлы: man_ske.json, man_tex.json, man_tex.png (эти файлы получаются через экспорт из программы Dragon Bones).
Подгрузит анимацию и добавит её на сцену следующий код:

#include "cocos2d.h"
#include "dragonBones/cocos2dx/CCDragonBonesHeaders.h"
...
auto factory = dragonBones::CCFactory::getFactory();
factory->loadDragonBonesData("man_ske.json");
factory->loadTextureAtlasData("man_tex.json");
auto armatureDisplay = factory->buildArmatureDisplay("Armature", "man");
scene->addChild(armatureDisplay);

Проиграть анимации по названию поможет следующий код:

armatureDisplay->getAnimation()->play("idle"); //зацикленно
armatureDisplay->getAnimation()->play("hit", 1); //один раз

Советую не наследоваться от класса CCArmatureDisplay, потому что я уже наступил на эти грабли и ловил непонятные ошибки времени выполнения. Поэтому лучше сменить отношение к классу CCArmatureDisplay с является на содержит, только так я смог избавиться от проблем непонятного происхождения во время выполнения программы.

Важно: для прорисовки всех объектов в анимации в игре – каждый объект должен иметь кость. Если у какого-то объекта не будет кости, то в игре этот объект не прорисуется, хотя сама программа Dragon Bones будет отображать.

Также в используемых c++ исходниках Dragon Bones существует проблема прорисовки спрайтов, если спрайт находится на границе видимой области сцены. Эта проблема фиксится в файле CCArmatureDisplay.cpp в методе DBCCSprite::_checkVisibility. Я подробно не разбирался в алгоритме работы этого метода, только в общих чертах, мне помогло увеличение рамки у прямоугольника visiableRect, исправленный фрагмент кода из метода _checkVisibility:

visiableRect.origin.x -= wshw * 4;
visiableRect.origin.y -= wshh;
visiableRect.size.width += wshw * 8;
visiableRect.size.height += wshh * 2;

В заключение хочу сказать, что очень хорошо чувствуется бесплатность Cocos2d и Dragon Bones, в них присутствуют различные проблемы, которых вероятно нет в платных аналогах. Но что кокос, что драгон бонс не стоят на месте, развиваются, а данная публикация должна помочь разобрать некоторые проблемы, связанные с этими инструментами.

Теги , , ,
Автор

Опубликовано
Комментарии 0

Недавно достаточное количество времени промучался с собиранием кокосовского проекта под android. И так небольшое количество туториалов, статей и видео зарубежных коллег, да и эти не точны, а вероятно относятся к старым версиям, поэтому и не работают. А на русском инструкций практически нет, а которые есть в основном бесполезны. Так как проблему сборки я решил, то хочу поделиться решением. Для особо внимательных – да да я снова занимаюсь разработкой игры.

И так, я предполагаю, что проект cocos2d-x у вас уже создан и вы уже посмотрели как выглядит этот проект на windows, к слову сборка именно под windows очень простая, достаточно открыть win32 проект в Visual Studio и скомпилить. Под android не так просто, нам понадобится Android Studio. Устанавливаем через Android Studio одну из актуальных Android SDK, и ещё ставим NDK, все эти действия легко делаются в Tools->Android->SDK Manager. Добавляем в переменную среды Path путь до cmake, cmake находится в папке с Android SDK %LocalAppData%\Android\sdk\cmake\3.10.2.4988404\bin (название папки с версией вероятно у вас может быть другое, сами посмотрите какое у вас). JDK нам устанавливать не нужно, в пакете Android Studio оно уже есть.

Выбираем NDK (если возникнут какие-то проблемы, то вероятно нужны ещё другие компоненты что на скрине, но у меня они уже были установлены)

Ставим путь до cmake

Теперь нам надо исправить следующие файлы: CMakeLists.txt (лежит в корне кокос проекта) и Android.mk (лежит в proj.android/app/jni). Если у вас HelloWorld проект и больше не добавляли никаких классов, то можно пропустить этот шаг. Суть в том, что нужно добавить недостающие пути к исходникам в эти файлы. В CMakeLists.txt нужно добавить и .cpp и .h файлы. В Android.mk добавить только все .cpp файлы, а также прописать все директории в которых есть .h файлы.

Пример исправлений в файле CMakeLists.txt:

<...> - тут остальная часть файла, которую менять не надо
list(APPEND GAME_SOURCE
     Classes/AbstractGameObject.cpp     
     Classes/AppDelegate.cpp     
     Classes/ChoiceMenu.cpp     
     Classes/Contestant.cpp     
     Classes/dragonBones/animation/Animation.cpp     
     Classes/dragonBones/animation/AnimationState.cpp     
     Classes/dragonBones/animation/BaseTimelineState.cpp     
     Classes/dragonBones/animation/TimelineState.cpp     
     Classes/dragonBones/animation/WorldClock.cpp     
     Classes/dragonBones/armature/Armature.cpp     
     Classes/dragonBones/armature/Bone.cpp     
     Classes/dragonBones/armature/Constraint.cpp     
     Classes/dragonBones/armature/DeformVertices.cpp     
     Classes/dragonBones/armature/Slot.cpp     
     Classes/dragonBones/armature/TransformObject.cpp  
     <...> - тут ещё куча файлов .cpp
     )
list(APPEND GAME_HEADER
     Classes/AbstractGameObject.h
     Classes/AppDelegate.h
     Classes/ChoiceMenu.h
     Classes/Contestant.h
     Classes/dragonBones/animation/Animation.h
     Classes/dragonBones/animation/AnimationState.h
     Classes/dragonBones/animation/BaseTimelineState.h
     Classes/dragonBones/animation/IAnimatable.h
     Classes/dragonBones/animation/TimelineState.h
     Classes/dragonBones/animation/WorldClock.h
     Classes/dragonBones/armature/Armature.h
     Classes/dragonBones/armature/Bone.h
     Classes/dragonBones/armature/Constraint.h
     Classes/dragonBones/armature/DeformVertices.h
     Classes/dragonBones/armature/IArmatureProxy.h
     Classes/dragonBones/armature/Slot.h
     Classes/dragonBones/armature/TransformObject.h
     <...> - тут ещё куча файлов .h
     )
<...> - остальная часть файла, которую менять не надо

Пример исправлений в файле Android.mk:

<…> – тут остальная часть файла, которую менять не надо
LOCAL_SRC_FILES := hellocpp/main.cpp \ ../../../Classes/AbstractGameObject.cpp \ ../../../Classes/AppDelegate.cpp \ ../../../Classes/ChoiceMenu.cpp \ ../../../Classes/Contestant.cpp \ ../../../Classes/dragonBones/animation/Animation.cpp \ ../../../Classes/dragonBones/animation/AnimationState.cpp \ ../../../Classes/dragonBones/animation/BaseTimelineState.cpp \ ../../../Classes/dragonBones/animation/TimelineState.cpp \ ../../../Classes/dragonBones/animation/WorldClock.cpp \ ../../../Classes/dragonBones/armature/Armature.cpp \ ../../../Classes/dragonBones/armature/Bone.cpp \ ../../../Classes/dragonBones/armature/Constraint.cpp \ ../../../Classes/dragonBones/armature/DeformVertices.cpp \ ../../../Classes/dragonBones/armature/Slot.cpp \ ../../../Classes/dragonBones/armature/TransformObject.cpp \ <…> – тут ещё куча файлов .cpp

LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../Classes \ $(LOCAL_PATH)/../../../dragonBones/ \ $(LOCAL_PATH)/../../../dragonBones/animation \ $(LOCAL_PATH)/../../../dragonBones/armature \ $(LOCAL_PATH)/../../../dragonBones/core \ $(LOCAL_PATH)/../../../dragonBones/events \ $(LOCAL_PATH)/../../../dragonBones/factories \ $(LOCAL_PATH)/../../../dragonBones/geom \ $(LOCAL_PATH)/../../../dragonBones/model \ $(LOCAL_PATH)/../../../dragonBones/parsers \ $(LOCAL_PATH)/../../../dragonBones/cocos2dx \ $(LOCAL_PATH)/../../../dragonBones/textures \ $(LOCAL_PATH)/../../../Classes/rapidjson \ $(LOCAL_PATH)/../../../Classes/rapidjson/error \ $(LOCAL_PATH)/../../../Classes/rapidjson/internal \ $(LOCAL_PATH)/../../../Classes/rapidjson/msinttypes \ <…> – тут ещё остальные директории
<…> – остальная часть файла, которую менять не надо

Лайфхак как добавить исходные файлы если у вас их очень много. Зайдите в cmd в папку с исходниками, наберите:

dir /B /s | find ".cpp"

Выведет список всех .cpp в проекте, осталось только подкорректировать текст автозаменой и добавить пути в CMakeLists.txt и в Android.mk.

Почти готово, теперь открываем папку proj.android в Android Studio как проект. Главное иметь достаточно места на диске, сборка съест около 2-3 гигов. Нажимаем Build->Build APK в Android Studio, ждём минут 15, после можем радоваться собранному APK. В итоге всё достаточно просто если есть инструкция.

Теги , , , ,
Автор

Опубликовано
Комментарии 0

Давно не прогал под андроид, и вот опять. Говорил я уже или нет, но дико не люблю разработку под android, потому что с появлением новых версий андроида мы получаем несовместимость старых уже опробованных функций, а версии обновляются чаще чем пишутся статьи и туториалы. А если ещё добавить к этому уравнению обилие китайских прошивок, в которых поведение системы не так предсказуемо как в официальных, то при разработке на android вместо написания приложения приходится заниматься исследованиями того, какие функции работают иначе на различных прошивках.

В этой статье как уже было ясно, я бы хотел поговорить о создании файлов на android. И так, андроид предлагает нам два способа записи: на внутреннюю память и на внешнюю. Для записи на внутреннюю нам не нужны никакие разрешения, мы просто должны записать в:

context.getDataDir()
– этот метод вернёт скрытую директорию приложения, то есть файлы потом будут доступны только приложению, которое их записало. Хорошо, безопасность и всё такое, но, а что если нужно записать в галерею или документы, или просто в какое-то общее хранилище? Тогда мы должны писать во внешнюю помять и просить разрешения WRITE_EXTERNAL_STORAGE. В некоторых туториалах пишут, что это разрешение для записи на sd карту, но если следовать здравому смыслу, то под external понимается память, которая не internal, а internal – это память, которая доступна только одному приложению, которое и пишет эти файлы. То есть external – это не то внешнее что на sd карте, а то внешнее что доступно всем, то есть общее хранилище, которое нам и нужно. К слову, в Android Q уже не нужно это разрешение и там «гении» из гугла придумали что-то новое для разработчиков.

Добавляем разрешение, пишем код для записи в файл, но не работает. Ах да, с Android 6.0 у нас же появились runtime разрешения, и для записи во внешнее хранилище нужно запросить разрешение прямо из кода. Пишем:

//проверяем принято ли данное разрешение
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
{   
        //запрашиваем разрешение у пользователя
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
}

Ага круто, теперь то мы действительно можем записывать файлы. Пишем:
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
File file = new File(path, FILE_NAME);
FileWriter fileWriter = new FileWriter(file, true);
fileWriter.append(data);
fileWriter.flush();
fileWriter.close();

Проверяем на телефоне, файл есть, так теперь подключаем телефон к компьютеру и ищем наш файл. Файла нет, класс! Windows не видит файл или android не даёт увидеть файл? Ну, конечно, виноват android, кто же ещё. Смысл в том, что android должен просканить media, найти новый файл и тогда каким-то чудом он покажет файл винде. Делается это так:
MediaScannerConnection.scanFile(context, new String[]{filePath}, null, null);

Таким образом мы заставляем андроид просканить медиа и показать файл в windows. К слову при перезаписи файла, надо снова выполнять метод scanFile, иначе windows не видит изменений. Но, конечно, вышесказанное справедливо только для windows 7, на windows 10 я не проверял, а linux вероятно всего и так всё увидит, но линуксом не пользуются рядовые пользователи, поэтому обсуждать тут это не буду.

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

Теги , , ,
Автор

Назад | 1 | 2 | 3 | 4 | 5 | | 8 |