Нужно курить мануалы как с драйвером монитора. Предполагаю, что на FreeBSD должен быть некий набор системных вызовов для работы с монитором. Есть конечно уже готовые api типо Xlib, но у нас нет права получать зависимости на пустом месте. Xorg довольно устарел. Игра должна быть независима от какого-то всратого Xorg. Итак, поняв как работать с драйвером монитора, мы можем выводить визуальную информацию, закрашивать пиксели. Следовательно нам нужно создать алгоритмы 3D графики. Алгоритм брезенхема, Z-буфер и тд. и тп. Теперь самое сложное. Как, собственно, разработать игру? Для этого нужно задаться вопросом, а что такое вообще игра? Вот персонаж в игре - это некий конечный автомат, который получает события с клавиатуры, и под них меняет своё состояние. В это же время, события могут поступить не только с клавиатуры, но и сами собой, по каким-то внутренним причинам. В конечном итоге, событие может быть просто фактом истечения некотороего времени. И игра, это некоторая сложная система, которая все эти факторы как-то организует. Как сделать самую простую 3d игру с самой простой архитектурой, чтобы потом просто немного расширить функционал? Создание игры получается сводится к срзданию своего xorg.
>>265645 (OP) Если ты хочешь вкатиться в разработку (похоже что) движка, попробуй сначала сделать 2D игру. Игра - это так называемый геймлуп, игровой цикл который гоняется по кругу в разных сценах. Даже так - каждая игровая сцена это свой небольшой геймлуп, который ожидает ввода пользователя. Также можно смотреть именно на дизайн игры с точки зрения цикла запустил->поиграл->подкачался->начал заново. Или иначе. Игра не привязана к наличию персонажа, кстати. Конкретно по твоему вопросу - посмотри что такое так называемые ивенты и шины ивентов. Вкратце можно реализовать так - каждый промежуток времени прогоняется вся шина (динамический массив), и ты либо изменяешь состояние события, либо выполняешь его окончательно. Событие выполнено - супер! Удаляй его. При необходимости добавляй ивенты, которые
>могут поступить не только с клавиатуры, но и сами собой, по каким-то внутренним причинам
или да, по пользовательскому вводу. Самая простая реализация событий в твоем случае. Но, имхо, самая большая проблема щас будет раскурить графоний. Строй абстракции на абстракциях, хуле.
>>266287 >Если ты хочешь вкатиться в разработку (похоже что) движка Я убеждён что движок и игра, это практически одно и то же. Конечно, можно назвать несколько примеров, когда на одном и том же движке делались разные игры, например Contract Wars на Unity. Но в любом случае, тот кто создал Contract Wars был вынужден изучить весь api движка. То есть движок - вещь неотделимая от игры. И если ты хочешь создать игру, то ты должен создать движок. Либо, если же ты профессионал, ты можешь использовать чужой движок, проанализировав код или документацию на него. Клепание поделок без владения документацией и опыта работы - это весьма юеспдлжгл >Конкретно по твоему вопросу - посмотри что такое так называемые ивенты и шины ивентов. Вкратце можно реализовать так - каждый промежуток времени прогоняется вся шина (динамический массив) Это получается событийно-ориентированное программирование? >Но, имхо, самая большая проблема щас будет раскурить графоний. А я считаю, это лишнее. Почему никому не нравится ранний 3D, типо того же краш бандикута, doom, quake. Достаточно ведь примитивного затенения, текстур и сглаживания по гауссу. На этом визуал можно оставить.
>>265741 >Нынче модно смотреть гайды по пятому анрилу Бесплодное занятие. Особенно смотреть. На видеороликах мало чему научиться можно. На них можно только посмотреть, и заинтересоваться.
>>268068 >Я убеждён что движок и игра, это практически одно и то же. Почему? Как ты и говоришь, дорогой анон, можно создать на одном движке разные игры. А ещё можно на разных движках делать одну и ту же игру. Движки ж есть разные, одни заточены под жанр (Adventure Game Studio), другие под конкретную игру (пример Factorio), третьи под большинство игр разных студий (пример Unity). Зазубривание кода и доскональное знание чужого движка нужно только для "больших" игр для "больших" студий. Типо хуйоверсов. Которым, похоже, было лень делать своё двигло. Остальные же говнокодят (ультракилл лол) и крутят лавешечку. > Это получается событийно-ориентированное программирование? Да. Думаю лучший выбор для начала.
>>268610 >Почему? Ну, ты впринципе прав, но мне казалось движок делается под конкретный жанр. Можно ли на idtech2 сделать рпг? Кажестя будто бы idtech создан чисто для шутеров. Если вычленять элементы, убирать ненужное, оптимизировать модицикацию под рпг, то это ведь уже получиться другой движок, пускай местами взятый с idtech. Тот же goldsrc много чего у doom перенял. Но доделал своё. Если взять многожанровые движки, типо Unity, то ведь сам по себе Unity это стек технологий.На каждом этапе каждой технологии может возникнуть ошибка, и если ты новичок, увы, эта ошибка останется с тобой навсегда. Получается, что если ты не профессионал и не понмаешь что к чему, то для тебя этот Unity ничем полезен быть не может, и даже в каком-то смысле вреден.
Ну допустим я хочу сделать игру. (Забудем ту фигню что я написал в предыдущих постах). Начнём с самого простого: как мне нарисовать что-то на мониторе?. Вопрос в лоб.
Если мы уже умеем что-то рисовать на мониторе, то далее уже ясно что делать: создавать алгоритм брезенхема, оптимизировать его, создавать Z буфер, создавать модель трёхмерного пространства которую потом проецировать на двухмерный канвас экрана. В общем, там уже много чего можно придумать.
Но как чьёрт возьми мне что-то вывести на экран? Ясно, что есть OpenGL. Я не хочу OpenGL. Также есть Xlib, Xcb. Я не хочу пользоваться xorg тем более. Просто принципально не хочу наворачивать готовые нестандартные решения. Они может быть и лучше, но это не правильно, так нельзя. Нельзя просто сказать "вот эта сторонняя непонятная щтючка всё сделает за меня". Это невежество.
У меня есть операционная система. ПОПРОШУ ЗАМЕТИТЬ: она умеет выводить буквы на экран даже без Xorg, без OpenGL, в общем-то даже без встроенной видеокарты. То есть ей даже видеокарта не нужна. ОС умеет работать с монитором, она умеет закрашивать пиксели на мониторе. У неё должен быть какой-то стандартный интерфейс для работы с монитором, какие-то системные вызовы для работы с драйвером файла "/dev/монитор".
Несколько погуглив я нашёл только linux framebuffer. Во-первых, такого на BSD я не нашёл. Во-вторых, на википедии не написано понятным языком что это такое. Написано что просто какой-то "интерфейс". Это очень посредственный ответ на вопрос, который не добавляет смысла ни на йоту.
Прочёл 60 страниц мануала на FreeBSD - такая нудятина. Ноль полезной информации. Всё какие-то глупости бесполезные пишут типо "ооо установить бсд вы можете и на иксбокс... [спустя страницу] установка бсд на иксбокс сложна и весьма бесполезна, поэтмоу обсуждать мы её не будем". Ок. Нахуй мне эта информация?! И вот 60 страниц такой абсолютной нудятины, рассуждений про какие-то системные требования, история юникс. Зачем?? Почему нельзя конструктивно подходить, и писать по делу. Лушче бы они в истории юник рассказали чем UFS от старой S51K отличается. Но нет, там зачем-то упомянуты эти две системы, но мне это ни о чём не говорит, для меня это тупо названия, которые ничего не значат. Пафоса много, а толку ноль. Ввод-вывод перенаправлять я по-итогу по википедии научился, а не по мануалам. Стандартные команды Unix, тоже по википедии прочитал. А в книге по бсд одна вода, про то как охуенно ей пользоваться. Пиздец. Охуенно было бы, если бы меньше рассказывали про то как ей охуенно пользоваться и по-больше бы пользовались.
И по-больше бы рассказывали о самой системе, а не пичкали её тысячью сторонних программ. Я вот действительно не понимаю: Я установил FreeBSD. Я хочу поставить себе Discord. Я интересуюсь, есть ли клиент дискорда на фряхе? И попадаю на ветку форума, где пользователя настоятельно просят установить себе дополнительное ядро линкус, и с него запустить Discord. Или электрон. Или браузер. ЗБС. А нахуй тогда вообще этот BSD нужен, если вы на 80% захламляете его какими-то сторонними сервисами? В чём у вас Unix-wayность и свобода, если вы просто зависимы от Xorg. Просто большая часть софта не способна работать без Xorg. Я действительно не понимаю. Давайте не будем пиздеть, давайте будем поступать честно: установил Unix - выжимаешь максимум из системы, а не устанавливаешь подярдо пинуса и не сидишь с линупс, всем показывая что у тебя фрибзде. Шизофрения. Пользоваться нужно операционной системой, а не тремя виртуалками внутри ещё одной виртуалки под электроном.
Если кто знает годных книг по устройству и API юниксов, то был бы признателен.
И не надо посылать меня на три буквы. Потому что я прав. Я понимаю, если бы я работал под Windows, то действительно, ядро windows не предназначено чтобы туда лез непрофессионал. Но уж извините, если я поставил себе свободную операционную систему, рассчитаную, в том числе, и на обучающихся, то я имею полное право знать как работают драйверы монитора на FreeBSD. Ну это просто факт. Для этого ведь она и создана, чтобы студенты изучали именно её, а не обмазывались виртуалками чтобы запустить тот же софт что и под виндой.
Весь день убил на поиск драйвера монитора FreeBSD и так ничего и не нашёл. Я так и не понял какое устройство из /dev отвечает за монитор. Более того, я почему-то даже нигда не нашёл внятного описания для каждого из устройств. Каждое гуглил - монитора так и не нашёл. Конечно, тут есть /dev/dri - это как раз иксорговский модуль ядра для работы с драйверами графических устройств. Но это не то. Мне надо прям чтоб моя графика работала на голой системе. Ну и ладно. Решил немного сдаться, хоть что-нибудь поделать, скачал SDL. Он хотя бы пустое окошко выдаёт. Хоть чёто поделаю. Может стоит забить просто и танненбаума почитать, вроде там вообще всё подробно про операционные системы рассказано.
Помниться, кстати, я как-то пробовал запустить SDL на голой системе... и он мне выдал рендер в ASCII буквоках. Меня это одновременно и порадовало и расстроило. Порадовало тем, что этот SDL хотя бы работает без xorg (хотя в описании написано что для него необходим xlib). То есть это что-то рабочее и стабильное. Расстроило тем, что он выводит всё в псевдографику, даже не работает с драйвером монитора. Псевдографику и на ncurses лучше делать. То есть без иксов он весьма бесполезен.
Кстати, авторам на заметку >вам не нужно полностью понимать как это работает. Просто используйте этот код НИ ЗА ЧТО не пишите вот такое! У меня сразу ухудшается к вам отношение. Вы что, считаете что я какой-то подневольный раб, что не имею права знать как этот код работает? Это буквально звучит как "Посиди сычуша, покопируй код, пока умные дяди его разрабатывают.". Неуважение! Откуда такое завистливое скотское отношение к обучающимся, мол "а вот это тебе знать не нужно". Всё нужно знать! Потому что я ЛИЧНОСТЬ и я делаю как я хочу, а не как уёбищный автор статьи этого хочет.
Охуительный курс ргб, пацаны. А вы знали что rgb это красный зелёный и голубой? а? а? знали? Пиздец. Эту хуйню ещё во втором классе знают. Просто, блядь, автор издевается. Он считает меня за какого-то умственно неполноценного деграданта. Что дальше? Дальше он познакомит нас "немного с математикой" и начнёт рассказывать про 1+1 = 2. Ну бля... ну не пишите вот так..... это же троллинг какой-то, а не уроки. Лучше бы он сказал из чего состоит структура типа SDL_Surface. Лучше бы он рассказал что он своим кодом сделал. Я вот вижу что он тупо вычислил цвет для разной глубины 8, 16, 24 и 32. А зачем? Это же процедура. Она должна как-то рисовать пиксель. Что она делает то, как она его рисует. Опишите процесс, блядь. Это было бы намного полезнее этих ваших дебильтных "так диточки, а вы знали что RGB это типо типлет на экране? знали да? злани". Буквально подача на уровне тупорылой школьной училки. Какая мне разница чё у меня за триплет, мудак. Может у меня вообще кинескоп, или терминал. Хули ты доебался до RGB. Пиши про SDL, блядь. Я урок по SDL читаю, или про что? Урод.
Я имелл виду, что после switch case там идёт завершение процедуры. SDL_MapRGB который вначале, похоже что подгоняет 24-битный rgb под тот формат, что в структуре screen указан по-умолчанию. bufp - буфер пикселя мб. Указатель на байт. >bufp = (Uint8 *)screen->pixels + y*screen->pitch + x >*bufp = color Вот это последняя строчка в каждом кейсе. То есть типо мы узнаём какой-то адрес, может быть shared памяти, и туда заносим нужный цвет. Пока что предположение только такое.
>Заметьте, что это нужно только при работе с пикселями, но никак не со спрайтами Какими впизду "спрайтами", я не пью газировку. Это шутка. Просто, я вот не понимаю зачем авторы статей это пишут. Ранее нигде не было упомянуто что такое "спрайт". И тут посередине текста какашечка про то, что что-то не работает со спрайтами. Зачем это писать? Чтобы что? В этом предложении ровно 0 информации для меня. Вот когда будешь писать про спрайты, тогда и напиши. Нах это в первый урок пихать я не понимаю. >проверяет надо ли блокировать экран Какой экран? Что блокируют? Лучше бы ты сказал что всё это значит. Да и функция SDL_LockSurface(SDL_Surface *screen) принимает тип SDL_Surface, а не экран. SDL_Surface то это поверхность, это может быть и окно. Он тогда заблокирует получается окно, а не экран, судя по названию. Это же LockSurface, а не LockMonitor. Правильное построение урока, должно выглядеть так: вначале должны быть описаны объекты, которыми работает библиотека, а потом уже что-то ковырять. Вот когда я читал документацию на Xorg, там было чётко написано, как устроен x-server, что такое "событие", что такое "окно", что такое "монтиор", чем они отличаются. Тут же просто автор ведёт рассуждение просто как училка информатики: общие фразочки какие-то вставлены тупо для количества буков, но сути никакой это не добавляет, да и код он считает что нужно тупо копировать. От этого больше всего горит жопа, когда автор говорит: "скопируйте код", чувствуется наёб. Типо, я для того и пришёл, чтобы обучаться коду, а не пространным рассуждениям. При том, когда подробно рассуждают это хорошо. Например, я слышал что у танненбаума, глава про сети начинается с рассуждений про физические являения передачи сигналов. Ну, это как бы не по теме, но всё равно хорошо, всё равно хорошо что в поднобностях всё описывается, лишним не будет так сказать. А тут просто никаких подробностей, просто "шмяк.. короче ёпта рендер"
>Если скомпилировать этот код, то получим черное окно, которое появится и быстро исчезнет. >Попробуйте добавить флаг SDL_FULLSCREEN и посмотреть что произойдет. >А теперь давайте рисовать. Кто нибудь мне объяснит как эти три предложения взаимосвязаны? Чиво, блядь. >"Если мы скомпилируем код, то получим черное окно" да ну нахуй, а я не додумался откомпилирвать пример с окном ранее. >"Попробуйте добавить флаг SDL_FULLSCREEN и посмотреть что произойдет" Будет фулскрин. И... чО?????? Окно что ли пропадать перестанет? Нет. Также пропадает. Флаг, сука, называется Fullscreen, выше идёт описание что этот флаг для фулскрина, и автор посчитал НЕОБХОДИМЫМ сообщить нам, что нужно поставить этот флаг и проверить что будет. Зачем?? Чтобы что?? Что ты этим добъёшься?? Какая учебная польза от этого?? Автор, будто бы училка, которая пытается впарить школьникам какой-нибудь фокус, типо она работает не зря. >А теперь давайте рисовать. Да, охуенно. Сделали хуйню какую-то, ни разу не разрбрались, а теперь будем рисовать. Что к чему? Вахуи.
Этот гайд меня разозлил. Теперь буду срать просто на всё подряд, потому что могу. Всё равно сюда никто не заходит.
Вопрос к додикам, которые постоянно говорят, что "ты изобретаешь свой движок, зачем ты это делаешь если есть готовый". Вот, заходим на оффициальный сайт, и смотрим "что такое SDL". Там написано, что вот SDL это бибилиотека даёт доступ к аудио, видео, тредам, системному таймеру итд итп. Собственно вопрос.. а что по вашему делает ядро операционной системы?! Не то же ли самое, а? Собственно, заходим на сайт FreeBSD, и смотрим. Видим абсолютно тоже: операционная система даёт доступ к процессам, потокам ввода-вывода, файловым системам, сокетам, и главное "даёт доступ к переферийному оборудованию". Что это значит? Это значит, что в твоём ядре ОС уже должна быть возможность работать со всеми поддерживаемыми устройствами. Монитор - поддерживаемое устройство? Безусловно, да. Мы же работаем на мониторе даже без специальных графических драйверов. Установите чистую фряху и экран-то работать будет! Терминал то буковки показывает. Тут, может кто-то подумать, что я ищу /dev/tty, но нет, это эмулятор терминала. А я ищу именно монитор. Я бы понял, если бы у меня был настоящий терминал, то тогда, безусловно, возможно никакой графики выводить было бы просто невозможно. Но у меня ЭМУЛЯТОР терминала. То есть существует какой-то примитивный графический драйвер, со своей системой шрифтов (у которых кстати расширение .fnt вроде, а не truetype), на которой уже и основана программа tty. И это ТОЧНО можно перепрограмировать. Знаете почему? Потому что я установил svgalib, без всяких специальных драйверов, и моя тестовая программа даже запустлась. Запустилась, правда, некорректно: вместо чёткой картинки я получил какие-то смятые пиксели и шум. Но это уже сильно отличается от стандартных букв терминала, то есть монитором можно пользоваться напрямую. SVGAlib работает с драйвером монитора напрямую, в обход видеокарты. Безусловно, SDL, OpenGL, Unity, это наверняка хорошие инструменты. Я не хочу говорить про них ничего плохого, я уверен их делали грамотные и обученные люди. Я уверен эти технологии позволяют пользоваться ресурсами компьютера более рационально, тот же SDL_Init воспринимает флаги для рендера как на харде, так и на софте (то есть наверное SDL можно заставить всё на чистом проце рендерить, без помощи видеокарты. Но мне абсолютно не важно что это за технология. Если можно обойтись без неё - нужно обходиться без неё. Если можно заменить что-то собственным велосипедом - это делать НУЖНО. Уже потом, когда будет потребность в чём-то, нужно использовать фреймворки итд и тп. А получать на пустом месте зависимости от SDL когда тебе дано ядро ОС - это невежество, это слабость. Так если бы уроки по SDL были бы нормальными, я бы ещё прогнулся под эту библиотеку, в надежде что я хотя бы какой-то полезный опыт приобрету. Но это обезьяне "скопируйте код" это скучная поебота, а не опыт. Не нужно копировать код. Мы, блядь, должны заниматься изучением кода, а не чистописанием, и копипастингом.
Я поначалу даже string.h не использовал. Делал функции для строк вручную. Только потом я заметил что string.h довольно более гибкая, удобная, а глваное, что идёт почти с любым компилятором С. Также и с math.h. Поначалу, возведение в степень я делал вручную. Потом, мне пришла мысль, что возможно сущестувют более оптимальные алгоритмы возведения в степень, и я начал использовать pow(). ncurses я практически сразу принял без торга, потому что он довольно компактный и выглядит как что-то хорошее. Тем не менее, со всеми принятыми допущениями зависимости минимальны: зависимость от стандартной библиотеки ничтожна, так как библиотека С практически намертва склеена с операционными системами. Формально наверное их можно как-то разделить, но по сути пользоваться ОС без библиотеки С - это какое-то фричество, ведь в мануале FreeBSD сделан целый раздел поствящённый стандартной библиотеке. Так, что использовать стандартную библиотеку вполне допустимо. Почему тогда ncurses? это же не часть стандартной библиотеки? Ну, ncruses стабильная, компактная, и удобная. Вот в общем-то и всё. Я могу работать и без Ncurses, просто перепрограммировав драйвер терминала. Таким образом, использовать ncurses, это мой свободный, осознанный выбор. Использовать же SDL, это выбор вынужденный. Я не могу вывести графику без него. Это меня и напрягает в первую очередь. Это не выбор, это уход от разработки. Вот задумайся, сегодня ты попустился и сказал себе: "ну ладно, я не буду пытаться изобретать велосипед, я буду делать в Unity", а что мешает завтра же тебе сказать: "ну ладно, а я не буду делать свои 3d-модели, я возьму бесплатные из ассетов, я не буду делать свои текстурки, я возьму бесплатные, я не буду делать свои звуки, я возму с сайта, я не буду делать код, я скопирую его у того человека". Приехали. А что, ты нахуй будешь делать сам??? Делай тогда игры в "конструкторе 3d игр", вперёд, пиздуй. (если кто не вкурсе, это в детстве была такая дебильная программа, где тупо по-пунктам можно выбрать модельки и звуки, и на выходе получить какую-то однотипную фигню). Давай. Пиздуй делать игры в редакторе космических приключений Spore. Вот это игропром.. уровня кал говна. Помнится, я в детстве как раз из-за этого бросил пытаться делать игры, потому что ютуб просто заполнен роликами про "давайте сделаем бродилку в юнити.. повторяйте за мной".И был абсолютно прав. Это бесполезное говнище. А изобретение велосипедов - единственный путь, стать хорошим механником.
>>323253 Я вот только сейчас задумался, а зачем он каждый кейс в {} заводит? case: это же метка, всё равно что goto label:. Там не нужны скобки, он же тупо начинает с этой части кода.
>>323698 Ого. Оказывается эти скобки не просто так. Почему-то без скобок, компилятор считает что bufp объявлены одновременно, и начинает ругаться (что в общем-то верно, но очевидно что код из case 2 никогда не попадёт в case 3). И скобки эту ошибку фиксят. Хоть что-то новое узнал.
Ну и уродство. Эта программа заставляет куллер на моём ноутбуке шуметь. Возможно это из-за активного ожидания. Идея создавать бесконечные циклы для отрисовки графики изначатьно звучит так себе. Если нужно показать картинку, можно было бы сделать проще: //код по отрисовке char ⚹buf = malloc(1024); fread(buf,1024,1,stdin); free(buf); fread тут работает в блокирующем режиме, и он просто заблокирует всю программу с отрисованым канвасом, до тех пор, пока ему не придёт eof, или будет введено 1024 байт в буфер. Зачем вводит "эвенты", если даже толком не рассказано что такое эвенты и как они устроены. Да даже про сёрфейсы не особо подробно рассказали, а уже эвенты.
У меня появилась забавная идея. Мы же можем рисовать по-пикселям? А что, если сделать программу, которая на вход получает картинку, а на выходе выдаёт текстовый файл, с кодом на С, который бы содержал функцию, которая отрисовывает эту картинку при помощи drawpixel! Для экономии можно было бы сделать что-то типо RLE сжатия, чтобы пиксели одинакового цвета рисовались сразу строчкой. И тогда, можно было бы сделать игру без ресурсов, в том плане, что все картинки в ней выполнялись бы програмно.
Вообще, я так подумал, maby im a cringe. Типо, что мне этот монитор сдался вообще? Игра на 90% состоит из вещей, никак с переферией не связанных. В игры же играют, а не смотрят. Нужно сделать саму игру, т.е. некий конечный автомат, который реагирует на события. Я могу выводить все результаты тупо в консоль, даже без ncurses. Даже то что касается графической части, можно выводить в консоль в виде результатов типа: "линия построена по коодринатам:...". Так даже легче отлаживать программу.
Да. Я просто-напросто занимаюсь не тем чем нужно. От этого и бомблю почём зря. Надо заняться разработкой игры, а не чего-то другого.
Собственно, надо подумать над вопросом, в чём заключается игровой процесс программы которую я хочу создать. Допустим, пускай это будет очередной рогалик (первое что пришло в голову) Из чего же может состоять будущая игра? 1)Меню. Реализация мыслиться простой. Может это некий конечный автомат, который просто ждёт наступления событий (щелчка мыши по нужной координате например), и после что-то делает, например меняет свое состояние, переходит в другое подменю. 2) Сама игра. Если так подумать, то ведь управление персонажем это тоже как конечный автомат: нажал - он идёт, отпустил - стоит. Однако, кроме событий с клавиатуры (или другого ввода), в игре ведь существует и внутренние события. Например, игрок наступил на жабу - чисто внутренне событие, которое может выражаться как решение системы линейных управлений в трезмерном пространстве. Нет. Решать СЛАУ - звучит максимально глупо, тактов процессора не хватит, чтобы бесконечно решать задачи на столкновение трёхмерных объектов. Очевидно, можно же упростить задачу, добавив дэмэджбоксы", (хотя наверное выгоднее были бы джмэдж-сферы, мне почему-то кажется что из вычислять проще, ведь тогда достаточно просто вычесть разницу в координатах между двумя точками в трёхмерном пространстве, и посчитать не является ли эта разница слишком маленькой. т. е. не находятся ли эти объекты близко). Такими же внутренними событиями, являются: столкновение камеры с поверхностью. столкновение оружия противника с персонажем, нахождение персонажа на поверхности. То есть игра уже образует собой некоторую среду, в которой могут возникать внутренние события. В отличие от меню, игра сама в себя играет. Допустим, мы сделаем игру по тому же принципу что и меню: бесконечный цикл, который блокируется на одной итерации в ожидании событий. Так как игра сама в себя играет, то параллельно должен выполняться другой процесс, который мог бы вызвать это самое событие. Например это может быть совершенно такой же конечный автомат, но только он каждый цикл смотрит, изменились ли координаты всех объектов в игре, и если изменились, то вычисляет разницу. Если разница нулевая, то генерирует событие "столкновение". Получается что у нас есть отдельный модуль который занимается вычислением физики точек в пространстве. ... Создать игру это почти как создать свой xorg server получается. Хотя и вправду, а чем xorg не игра? Принципиально тыканье кнопок xcalc ничем не отличается от игры в сапёра... (внезапная мысль)
По-аналогии, можно создать и несколько других процессов, которые тоже генерировали события исходя из набора правил. Всё это можно объединить в большую диаграмму, разбив по группам.
Что касается графики, так у нас есть условно "камера" - это наше двухмерное полотно, canvas или surface, как угодно. Мы можем рисовать линии на этом холсте. Таким образом, мы можем координаты трёхмерного объекта спроецировать на полотно. Нужно разработать алгоритм отрисовки линий так, чтобы он рисовал только то, что помещается на экран. Похоже что 3d модели придётся погружать в ОЗУ, иначе просто неудобно будет постоянно парить. obj файл. Раз уж массив точек лежит в ОЗУ, то почему бы не сделать функции для его поворота. Поворот объекта будет осуществляется полным перезаписыванием всех его координат на новые значения, домноженные на матрицу поворота (тут конечно неплохо было бы использовать кватернионы, а не матрицу эйлера). Сама же камера может тоже иметь координаты. В таком случае, перед тем как спроецировать трёхмерный объект она будет все его координаты изменять на новые с новым поворотом, после чего обнулять свои.
В общем, нафантазировался сегодня. Попробую что-то сделать. Уже лучше чем пустота в голове.
Точки можно хранить в двухмерном массиве, где строки - номера точек, а столбцы - координаты. типо vertex[1][x] А вот с полигонами чуть по-сложнее, полигон ведь это не обязательно четыре точки, это может быть и три и пять и шесть, и вообще сколь угодно много. Поэтому, полигон, это указатель на указатель на список. То есть это массив из списков (список желательно сделать закольцованным, чтобы можно было все элементы последовательно прокрутить). Каждый индекс - номер полигона. Список в каждом индексе может иметь неограниченое количество элементов. Каждый элемент содержит в себе номер точки из этого полигона. Вот так, на мой взгляд логично. Впринипе, парсер сделан. Я позволил себе использовать переменные типа float, по той простой причине, что загружается модель в память всего один раз, так что тут наверное можно позволить float'ы. Зато флоаты позволять масштабировать объект при импорте. Без флоатов пришлось бы изобретать снова велосипед для маштабирования, чтобы например число 0.02, ипортировать не как 0, а как 2. А с флоатами просто домножил на 100 и всё. Можно позже этот велосипед доизобретать, сли вдруг окажется что числа с плавающей точкой мешают.
Осталость сделать какие-нибудь прикладные функции для работы с типом "wavefront object". Функция импорта содержит в себе malloc'и, следовательно нужно сделать функцию для уничтожения всего этого объекта. Нужно также сделать фнукцию которая позволять умножать и скалировать объект, производить над ним математически операции. И тогда уже можно помещать его на экран, и выполнять всякие повороты над ним.
Наконец-то сделал проволочный рендер. Большую часть времени фиксил очевидные ошибки.
Ошибка 1: Забыл инициализировать список значением NULL, из-за чего на выходе получался рандомный мусор, заканчивающийся сегфолтом. Я не сразу догадался, что ошибка меняется в зависимости от времени когда ты запустил программу.
Ошибка 2: при переборе списка, поставил на вывод не временную переменную, а основную, от чего список выдавал всегда только последний элемент. Я не сразу понял, что ошибка всего в одной переменной, и начал искать ошибки в функции добавления элемента в список. Заодно пока искал несуществующие ошибки, нашёл новые недочёты, которые пока не пофиксил.
Недочёт 1: Парсер полигонов работает неверно. А дело в том, что полигоны могут быть заданы в форматах 1/2/3/4 или 1/2/3 или вообще 1. Когда scanf, берёт строку он работает только с одним форматом. Про другие форматы я не знал. Другие форматы зависят от наличия групп сглаживания/нормалей/текстур/ещё чего-то. То есть для полноценной работы нужно дополнить парсер, чтобы он узнавал формат и уже под него сканировал строку.
Недочёт 2: Оказывается, номер точки у полигона может быть отрицательным. Отрицательный номер означает, что номер берётся с конца, то есть (макс_кв_точек - х). Этот недочёт пофиксил.
Пришла в голову идея, а что если функцию по отрисовке линий сделать как callback-функцию. Тогда функция DrawObj будет просто перебирать координаты точек объекта по-полигонам,а функция-отработчик уже будет что-нибудь с этими кординатами делать. Может не обязательно линию строить, может она будет закрашивать область по координатам, или текстурку мапить на неё.
Ошибка 3: Алгоритм брезенхема не рассичнат на отрицательные значения. Я почем-то наивно полагал что все координаты больше нуля. Но так-то могут быть и меньше. Поэтому нужно допилить защиту выхода за области экрана с учётом всего этого
Пофиксил. Теперь оно может вылезать за рамки, и в отрицательную часть.
Если быть точным, фактически оно ни при каких условиях не может вылезти за пределы экрана, ведь тогда получится segfault, и программа завершится. Просто предварительно происходит позиционирование начальной точки: если она находится за пределами экрана, то она либо проецируется на оси экрана, либо линия вообще не рисуется (в случае когда очевидно что линия не будет отображена. Например, восходящая линия хотя бы с одной координатой больше ширины экрана уже не будет отрисована, потому что вторая точка по-любому находися также за пределами экрана и линия экран никак не задевает). С падающими линиями всё немного сложнее, ведь точки могут находится и за пределами, но сама линия частично пересекает экран. Но тут есть преимущество, в том, что проецировать нужно только первую точку. Вторая точка нам безразлична, просто потому что мы в самом алгоритме брезенхема можем сделать условие, что если текущая координата выходит за пределы ширины экрана - цикл следует завершить. Таким образом, вторая точка в проецировании не нуждается.
Дополнил парсер. Теперь он различает как формат a/b/c так и a. Форматы a//c и a/b, ещё не сделал, но это делается аналогично в пару строк кода.
Надо бы сделать функции для увеличения загруженной модели.
Масштабирование объекта - задача простая. Просто умножить все координаты на коэффициент. Параллельное перемещение объекта тодже в теории просто - добавить ко всем координатам какое-то число. А вот с поворотом начинаются проблемы. Матрица поворота эйлера - кал. Выглядят как плохая идея. Кватернионы - непонятно. Попробую покурить математику. Если пойму как эти кватернионы работают, реализую сразу их. Если нет - придётся с каловыми матрицами работать, которые ещё и кривизну выдаёт при повороте на 360.
При масштабировании возникла проблема: целочисленные значения искажаются((( Насколько я знаю float довольно вредно влияют на процесс рендера. Придётся подгонять изображение под конкретные размеры.
Ну и пох. Сделал на флоатах. Теперь в кашицу конечно всё не превращается, но что это за артефакты? Почему какие-то точки остаются на месте? Это может значить что алгоритм брезенхема неправильно сделан. В общем, нужно искать ошибку.
Похоже что ошибка возникает именно во время поворота. Если отрисовать заранее повёрнутую модель, но всё нормально. Проблема возникает именно на поворотах. Значит, мне кажется, что алгорим брезенхема работает верно. Проблемы именно в вычислении поворота, матриц. Я использовал формулы с википедии. Чъёрт знает. Надо попробовать кватернионы + другие варианты матрицы поворота.
Я думал, что может искажения происходят от угла 90 градусов. Ведь гладкий валькнут не даёт никаких сбоев. Но и куб под 90 градусов тоже не даёт никаких сбоев. Завтра разберусь.
Добавил возможность перемещения объекта. Проблемные точки искажаются даже при перемещении объекта. Значит проблема не в матрице поворота.
Возможно модель импортируется некорректно: например может некоторые точки считываются неправильно.
Может быть алгоритм перебора массива точек неверный. Надо попробовать перебирать точки в любом порядке, а не в порядке отрисовки.
Может быть сама отрисовка сделана неверно и с ошибками. Например, алгоритм брезенхема на некоторых углах может даёт сбой, неправильно что-нибудь проецирует.. Надо попробовать запустить алгоритм без защиты канваса.
До меня долшло. Один полигон может содержать точки 1/2/3/4, и другой полигон может содержать точки 4/5/6/7. Таким образом точка 4 повторяется дважды, а значит она дважды будет повёрнута. Вот и всё. Надо перебирать просто все точки. А я перебираю по полигонам.
Попробовал откомпилировать игру с флажком -static, для того, чтобы сделать ещё протативной. Результат плохой. Почему-то программа теперь запускается только в ncurses. И если бы оно хотя бы показывало что-то, но нет, просто загружается пустой ncurses, а при попытке переместить 3D обьект программа падает. Это никуда не годиться. Видимо придётся отказаться от SDL, и делать всё чисто под XCB. Под XCB получилось статически откомпилить программу. Минус, что SDL по-идее должен работать и под wayland и под xlib и даже под windows, а xcb работает только под x-server. Также, говорят что xcb довольно тормознутый. Или xlib лучше.
Надо переработать защиту от выхода за поля картики. Видно, что при увеличении изображения линии начинают "искажаться". В целом, подход тот же, только переделать функцию OutOfCanvas. Уже протестить всё.
К слову о тестах. И всё же сонсолечка надёжнее. Решил я протестить вообще все варианты линий от -5 до 5 координат. Надо сказать, что это весьма много вариантов (19 мб текстовый файл со всеми вариантами весит). И мой любимый текстовый редактор XNedit, на этом начал подвисать. А вот консольный nano справляется запросто. И если так подумать, графическому интерфейсу уделяют слигком много, вместо того чтобы развивать надёжный текстовый. Зачем вообще нам визуальная информация? 1.Чтобы было понятнее 2.Для творчества Всё. Больше впринципе незачем. Результат любой программы можно представить в виде текста. Просто, иногда бывает удобнее смотреть на пересекающиеся графики, а не на таблицу из их значений. Поэтому, графике надо уделять меньше внимания. Программы по возможности должны работать без графики. Например, зачем нам графический плеер? moc отлично воспроизводит музыку и из терминала. Графика нужна только при просмотре видео, картинок, игр, книг. Вот бы графический модуль был более утилитарный.
Вот. По сути, на моей модификации алгорима брезенхема, мы можем разбить все линии на возрастающие и убывающие (все до 45 градусов, так как когда координаты линии приходят в функцию bresenham, они уже перевёрнуты и отражены как надо). Возрастающие линии (красные) ТОЧНО можно не отрисовывать если они выходят за росовую область. А убывающие - за бирюзовую. Отсечём две эти области, а для всех остальных линий, сделаем функции для проецирования X и Y соответственно (функции сделал на флоатах. Долго тестировал, деление на целых числах так и не получилось реализовать, всегда получается какая-то безумная погрешность. Решил забить, пусть пока на флоатах поработает). Причём, если точка начала линии лежит за диагональю экрана(жёлтая линия), то проецировать её следует на ось X, а если выше диагонали то на ось Y. Осталось написать код.
А вот, опять проблема. "вычислить условие нахождения на диоганали" - это означает вычислить проекцию начальной точки на диоганали и сравнить их x координату. Это опять использование чисел с плавающей запятой. Думаю, поскольку в СИ существуют "ленивые вычисления" (то есть например у условия А || B выражение В не будет вычислино, если А истино), я думаю, можно вначале узнать, выше ли y0 нуля. Если он выше, то тут и так ясно, что точка выше диоганали. А если ниже, только тогда следует сделать проекцию
Отрубили электричество - всесь исходный код программы исчез к хуям. ЧЗХ?! Не может же это быть проблемой FreeBSD. Наверняка это всратый редактор XNedit испортил мне всё. В общем... всё заново. А я уж было только сделал защиту канваса.
пиздец... всё с самого начала. Благо хотя бы парсер .obj сохранился, не придётся делать этот ебучий импорт изображений. Тогда длать под xlib попробую. Нах этот SDL.
Было бы интересно полностью избавиться от чисел с плавающей точкой. Тогда, нужно было бы создать свои функции для целых чисел. Например функцию поворота объекта. Суть в том, чтобы результат был обратим: повернув объект, его можно было бы вернуть заново. Как это сделать, пока не знаю. Очевидно, что такая функция не сможет повернуть объект на 1 градус: при таком повороте координаты практически не изменятся. Но вот на 90 градусов повернуть объект без потерь довольно просто: достаточно поменять местами его координаты. То есть тут нужно придумать какую-то логическую замену тригонометрическим функциям, чтобы получать однозначный обратимый результат. Звучит как что-то невозможное. Ко всему прочему, функции с плавающими точками не ограничиваются поворотом. Также нужно реализовать перспективу на целых числах, заполнение поверхностей.
А что если функцию реализовать как просто множество значений!? Грубо говоря, вычисляем огромную таблицу результатов поворота на действительных числах. Задаемся погрушностью и делаем функцию, которая тупо по заданным значениям выдаёт заранее заготовленный результат поворота.
А формат анимаций реализовать как последовательность необходимых действий над точками. То есть анимации могут хранится в объектном файле в виде функции. Например функция anim_fly() выглядела бы как for(int i = 0; i < 200; i++) moveobj(obj, 0,0,12);
Начал делать шейдер. Вот первая весия. Пока-что очень сырая. 1. почему-то векторное произведение считает самой яркой ту часть, которая находится с обратной стороны. видимо необходимо придумать рефлексы. 2. векторное произведение переполняет буфер. яркость считается неверно, из-за чего выдаёт просто мозаику. Я так веременно пофиксил, просто поделив яркость на 1000. Но с этим явно что-то нужно делать
>>360674 Ну и что я написал? Иногда такое состояние, когда чувствуешь себя ужасно глупым - сложно подумать о чём-то, сложно сформулировать правильно мысль. И ты лежишь и смотришь в стену. Надо нормально сформулировать ошибки, а то потом перечитаю и не пойму что я вообще имел ввиду. >переполняет буфер. Не буфер переполняет, а переменну. Яркость высчитывается неверно. Произведение: яркость*цвет переполняет переменную, от чего яркость распределяется мозаикой. При этом, если значение поделить на 100, то результат будет удовлетворительный, следовательно функция вычисления векторного и скаларного произведения векторов, вероятно работают верно. Только, что-то необдуманно скомбинированно.
Пофиксил. Достаточно было просто нормализовать вектор - т.е. сделать его единичным. С другой стороны, я вот сейчас подумал, а ведь можно избежать этих вычислений с плавающей точкой, и придумать более хитрый способ нормализовать вектор. Теперь всё шейдится так как нужно. Выход за рамки сделал довольно примитивно: добавил функцию DrawPixelProt, которая не рисует пискель если он находится вне канваса. Плохая функция. Ведь на те полигоны, которые находятся за пределом камеры тратятся такты процессора - каждую такую точку программа извлекает, смотрит что её отрисовывать ненужно, и завершается. Вместо этого, лучше бы сделать алгоритм, который заранее рисует только те полигоны, что умещаются в камеру. Но это, наверное позже, так как у меня пока и камеры никакой нет, всё это отрисовывается в ортогональной проекции, а поворачивается не камера а сам объект. Нужно сделать перспективную проекцию, это во-первых, во-вторых нужно сделать и саму камеру, чтобы можно было объекты с разных сторон отображать, при этом чтобы сами объекты координаты не изменяли. Плюс, нужно сделать YZ буфер, чтобы не отображать ненужные полигоны. Нужно реализовать сглаживаение по-гауссу. И когда вот эта "база" будет готова, нужно вначале сделать оптимизицию: 1 всё что находится вне экрана, не должно обрабатываться отрисовщиками. 2 все операции надо перевести на целые числа, 3. надо сделать программу более модульной и гибкой. Возможно реорганизовать некоторые функции. Например, можно было бы затенять полигоны алгоритмом брезенхема, или другим каким-нибудь алгоритмом (главное чтобы был отдельный алгоритм по затенению).
Похоже что работать напрямую с монитором - невозможно. Точнее, вероятно, никакого "драйвера монитора" в системе может и не быть. Монитор похоже никак не связан с компьютером (с шиной). Мне как пользователю ноутбука, это не очевидно, так как у меня от монитора идёт какая-то шина внутри сразу на плату. А вот на стационарных пк это явно заметно: от монитора идёт только VGA кабель до видеокарты, и питание. Конечно, монитор можно подключить и прямо к материнской плате, но сам факт того, что он может работать и без материнской платы заставляет задуматься. Раз монитор подключается только к видеокарте, то если так подумать... возможно никто и не предполагал использовать компьютер без видеокарты. Ещё немного погуглив, я прочитал что >VGA (разъём) — 15-контактный субминиатюрный аналоговый разъём Обратите внимание на слово "аналоговый". Получается, я не могу никак напрямую из памяти обратится к vga. VGA просто не может быть подключен к шине, так как на шине сигналы цифровые, логические. Мне нужно иметь как минимум какой-нибудь АЦП (аналого-цифровой преобразователь) чтобы обратится к монитору. Если я подключу к Com порту ацх, и буду какие-то алгоритмы делать - то это и будет своего рода примитивная видеокарта. Поэтому, искать "драйвера монитора" похоже что бессмысленно. Имеет смысл искать только "драйвера видеокарты". И тут сразу же два минуса: похоже, что работа в текстовом режиме - это уже не один драйвер. И пользовательский интерфейс к этим драйверам есть только текстовый. Таким образом, чтобы сделать графику из сонсоли, нужно пилить свои драйвера. А второй минус, заключается в том, что походу то что рабоатет на одном драйвере, не работает на другом. Таким образом, игра, зависящая от конкретных драйверов - это кал. Нужно отделить игру от драйверов. Нужно создать отдельную папку api_setting, в котором составить обёртки для функций драйверов или бибилиотек. То есть, моя игра должна воспринимать функци типо DrawPixel. А с помощью какого драйвера этот DrawPixel реализован, должно находится в отдельном модуле. Таким образом, если захочется перевести игру с SDL на Xlib, или вовсе на виндоузовские функции, то достаточно будет добавить новый модуль в api_setting, например SDL_base.c, Xlib_base.c, Win32_base.c, SVGAlib_base.c итд, и откомпилировать игру, поменяв include в главном файле current_set.h. И сама игра должна содержать модули ТОЛЬКО из стандартной библиотеки. все сторонние модули системной библиотеки должны быть вынесены в файл current_set.h, и заполнены обёртками, чтобы можно было в случае чего портировать программу под любую другую ОС/api если вдруг старые устареют.
Почитал про Z-буфер, про Алгоритм художника и Back Culling. По-сути, применять необходимо все три алгоритма. Для построения пересекающихся и самопересекающихся объектов нужен только Z буфер. Для различных непересекающихся объектов подойдёт алгоритм художника. Для одного единственного объекта можно использовать back culling, чтобы рендерилась только его видимая часть.
Откомпилировать так и не смог, потому что игра сделана под windows, dos, но ни как не под unix. Может быть в те времена фреймворки ещё не были обыденностью, поэтому Кармак решил писать всё железо-зависимое с нуля.
Но не суть. Главное, что я понял, что я делаю не движок, а игру. Разница между движком не в проделанной работе, а в иерархии и устройстве. Тот анон, который говорил что я хочу создать движок >>266287 отказался немного не пра всё-таки.
Так в чём же разница? На примере Quake видно, что сам Quake представляет собой некоторую программу для обработки игровых ресурсов. Игровые ресурсы - это скрипты, сценарии, текстурки, модели, уровни итд. То есть, ресурсы, это грубо говоря файлы игры. А движок - это сам .exe файл. Если запустить движок без ресурсов, то движок вежливо сообщит об ошибке, что он не нашёл что запускать. А ресурсы без движка запустить попросту нельзя. Для того, чтобы запустить скрипты движка нужен интерпритатор внутри движка. Получается, что сама игра Движок+Ресурсы. Мод на игру - это Движок+видоизменённые ресурсы Вот примерно так.
К слову, игру можно создать и без движка, а точнее с монолитным движком. Пример такой игры - Painkiller. Вся игра написана на С, и чтобы её как-то модифицировать придётся перекомпилировать код. Так, что игра может быть как с движокм, так и без, это не показатель её сложности. Можно сделать простую игру с движком: условно, движок будет считывать буквы из файла res.txt, и ожидать действия от пользователя. Если пользователь нажал ту же букву, что сейчас находится в переменной движка, то движок считывает следующую букву. Если же, буква различная, то движок ничего не делает. Вот и вся игра. У этой игры есть и движок и ресурсы. При этом, ясно дело, что эта игра намного проще пэинкиллера.
При этом, иерархию движок/игра можно реализовать по-разному. В случае с Quake. движок просто ожидает основной мод на игру. Современные движки могут быть ещё более навороченными, и вообще представлять из себя среду программирования. Например unity, представляет из себя редактор, который все ресурсы компилирует в отдельную standalone программу. В таком случае движок вообще представляет собой обобщённый инструмент.
Я бы наверное не стал делать игру монолитной, как пэинкиллер. Вместе с этим, мне кажется глупым и неэффективным обобщать игру до сложных движков. Поэтому, я пока не знаю что делать. Буду писать документацию на несуществующую игру, чтобы 1 было проще структуировать мысли 2 в случае если получися что-то разработать, не пришлось бы маятся над документацией, так как она бы уже была готова по ходу разработки. Итак ридми.тхт.мд
# OVERVIEW Для обзора, продемонстрирую игру созданную на этом движке. # INFRASTRUCTURE Движок является серверной программой, которая получает на вход файлы игры, производит настройку а затем работает в режиме конечного автомата, ожидая наступления событий (событий клавиатуры, либо внутренних собы- тий движка). Средства которыми обрабатываются события, нахоятся в каталоге SYS, это может быть любой интерфейс, например xserver или SDL. Игра - жто движок+ресурсы. Ресурсы игры - это набор файлов, в которых содержаться все необходимые настройки, указания серверу, текстуры, анимация, звуки, сценарии, и прочие необходимые объекты, которые задают конкретику игры. Далее, будет более детально описано устройство движка по каталогам и файлам. ## cd ./SYS/ В данном каталоге собраны обёртки для всех нестанартных функций. Основной движок имеет зависимость либо от стандартной библиотеки, либо от обёрток функций из этой папки. Например, центральной функцией графической части, является функция DrawPixel(), которая объявлена и описана тут. В данном модуле функция реализована при помощи библиотеки SDL. Но в случае портирования движка на другую платформу, можно пере- писать функцию под другой фреймворк/библиотеку, сохранив только название. В результате, чтобы портировать игру под другую платформу, достаточно переписать только этот модуль, оставив модули движка без изменений. ## cd ./ENGINE/GRAPHIC/ Часть движка отвечающая за графику (включая трёхмерную). ./wavefront.c - Модуль для работы с объектами формата wavefornt obj. Включает в себя функции на импорт файла вместе с инициализацией всех необходимых структур данных, а также включает в себя функции по удалению из памяти не нужных более 3d объектов. ./math.c - Модуль для работы с некоторой полезной математикой. Операции над числами с плавающей точкой довольно сильно нагружают систему, особенно когда приходится их выполнять над одной точкой по несколько раз. Поэтому был разработан специальный модуль который позволяет выполнять те же самые операции над целыми числами, с определённой погрешностью. Также в этом модуле созданы полезные функции и процедуры над математическими объектами такими как "точка", вектор, матрица. ./graphic.c - Главный модуль, который использует функции из всех предыдущих подмодулей, образуя интерфейс для работы с трёхмерным пространством. ## cd ./ENGINE/main.c Ядро движка. Компилируется в исполняемый файл, который является файлом для запуска игры. Как уже говорилось ранее, движок ожидает ресурсов игры, в случае отсутствия каталога "game", программа завершится с ошибкой. ## cd ./GAME/ Ресурсы игры. Тут размещены все файлы, определяющие конкретный игровой уровень/действия внутри игры.
Сделал Z-буффер... ну и муть. Это получается карта глубины для всего изображения. То есть каждый кадр НА МОДЕЛЬ проецируются точки. То есть каждый кадр, моя программа вычисляет несколько определителей матрицы, для того чтобы найти точку на плоскости. Почти как рейтрейсинг. Ужас.. Копьютер подпёрживает от количества. Как по мне, алгоритм который просто не рендерит все полигоны, которые развёрнуты от наблюдателя - более оптимальный. Но вот, сразу же непонятно, что делать с "углублениями" и "канавками" на модели. Ведь задняя стенка канавки развёрнута на камеру, но при этом видимой быть не должны так как перекрыватся передней стороной. Z-буферизация позволяет разрешить эти проблемы. Но вместе с этим.. это двольно затратно. С другой стороны, backculling предварительно отрезает заднюю часть. Тогда можно сделать так, чтобы буферизировалась только передняя часть.
Я вот сейчас поняо, что в игре можно обойтись без шейдеров. Можно же просто запекать текстурки - делать так, чтобы собственные тени были заранее подкрашены в текстуре. Это, правда не работает с движущимися объектами. Если что-то двигается, то у него тени будут смотреться доольно странно при движении. Можно сделать типо систему флагов из макросов, чтобы каждой модели ставить способ её отрисовки: проволочный, сплошной, затенённый, затенённый со сглаживанием по гауссу. Преимущество сплошного отображения, в том, что не нужно считать векторы, ни на сглаживание, ни на свет: всё уже заранее содержится в текстуре.
Перспектива. "он был настолько жирным что вытекал из треда" Я так и не понял ничего про какое-то "четырёхмерное пространство". Я довольно глупый - всё что я не проходил в вузе даётся сложно. Мне показалось, что примитивном случае, построение перспективы сводится к нахождению проекции прямой, которая проведена из точки свода до точки объекта. В моём случае, точка свода находится в точке 0,0,0. Плоскость на которую проецируются лучи из точки свода, параллельна плоскости Z, и находится от неё на расстоянии 100, а сам объект находится на расстоянии 200. Вроде походит на что-то правдоподобное. Нужно по-подробнее разобраться как это преобразование происходит. А потом всё это добро можно переводить на целые числа, и переписывать всё заново, так как идеи поднакопились за это время
Я кажется понял, почему на рисовании мы всегда делали две точки схода. Точка схода, похоже что всегда одна. Похоже что вторая точка схода - это просто вычисление расстояния геометрическим методом. То есть вторая точка обеспечивает паралельность линий, и всё. У художников точек схода может быть сколь угодно много, но при этом в трёхмерном пространстве точка всегда одна.
Вот, точку схода поставил на середину экрана. и воде бы искажений не наблюдаю. Сразу же можно выявить несколько минусов: во первых, если объект попадает за плоскость проецирования, то он начинает искажатся. Надо сделать так, чтобы полигоны сзади камеры вообще не принимались в расчёт. Также, есть верояность, что изображение на самом деле рисуется "вывернутым". С другой стороны, Z-буферу безразлично что-там как вывернуто, перспектива - это же всего лишь проекция. Закрашивать то он будет так как положено.
ну, чтож. Всё заново. Начну с математического модуля. Сделал пока-что только рациональные числа и операции над ними. А производительнее ли это вообще? Я посмотрел fixed-point арифметику, и похоже что там какая-то другая реализация. Во всяком случае можно протестировать debugger'ом, и посмотреть на производительность
Да. Получилось только хуже. На моих "рациональных числах" большая часть времени в normalize() простаивает. Итого, на main функцию уходит 5% времени. А на float, на функцию main уходит 20% времени. То есть Float'ы более меньше времени занимают чем мои рациональные.
Но я попытаюсь ещё что-то сделать. Во-первых, нужно представить числа не в формате i10^p, а в i2^p. Это позволит использовать битовые сдвиги. И нужно отказаться от циклов, при нормализации.
Наконец-то протестил и завершил числа с фиксированной точкой. Теперь реализую операции над ними.
Также, входе прошлого рендера, я заметил что пихать тысячу аргументов в функции, может быть и экономно, но неудобно. Поэтому я решил сделать тип "вектор" который содержит три точки. По-сути это радиус-вектор.
Теперь векторное произведение векторов может возвращать значение
Аноним Вс 08 сен 2024 17:49:34ИзмененоИзменено модератором№41196667
Ну чё. Давайте думать, подсказывайте, чё вы мозги ебёте. Продолжаем. За сегодня, я переписал весь wavefront_obj под мои "целые" числа. А также немного почитал xlib manual при помощи гуголтранслейта https://tronche.com/gui/x/xlib/utilities/XCreateImage.html И переделал главную программу под xlib. Преимущества: наконец-то я хотя бы отдалённо понимаю что моя программа делает. В том смысле, что я понимаю, что вот у меня запущен Xserver и я к нему обращаюсь с запросами типо "выдай указатель на дисплей" или "подожди событие". Вместе с этим, в моей игре пропал весь интерактив: я не знаю как сделать чтобы кнопку можно было "зажать" надолго, ведь есть только события keypress и keyrelease. Я прочитал что строить изображение путём XPlotPixel - преступление против эффективности, ведь эта функция постоянно отправляет запрос серверу. То есть нужно мучиться, думать, как бы "подготовить кадр" и отправить его серверу на вывод. То есть минусов дохрена получилось. Зато теперь можно начинать думать и как-то "выстраивать" систему по отрисовке. То есть делать непосредственно игру, её основу. Я решил пока не заморачиваться по поводу кросплатформенности, потому что я до сих пор ещё не понимаю как выглядит игра, и какой "набор функций" требуется от API системы. Посторяюсь конечно же всё это наследие xlib, максимально не распостранять на другие файлы. Пусть оно не выходит за рамки маина, пока что. Ну он конечно и друной этот Xlib. Отключил "автоповторение" и оно так и не закрылось с закрытия программы. Получается, это мне нужно фиксировать когда на моей игре сфокусировались и только тогда отключать "автоповторение". А потом ещё и свои механизмы для реакций на мышь вводить. А я ещё xcb пытался освоить. Мне xlib уже кажестя "слишком" низкоуровневым. Начну пожалуй с графики. Нужно сделать систему "пространство-камера-объекты". Объекты - это конкретно obj файлы (которые уже реализованы). Объекты включают в себя информацию о точках, нормалях, текстурных координатах и типе объекта (затенённый/сплошной/сглаживаемый). Пространство - это некоторый тип данных, который хранит объекты, хранит некоторую нулевую точку, от которой все объекты отсчитвыаются. Правильнее сказать, это "сцена" а не пространство. Также, в пространство можно отнести скайбокс (у меня это был просто градиент с зади). То есть некоторая картинка которая отрисовывается сзади по-умолчанию. Также в сцену можно добавить "солнце", просто как вектор откуда солнце светит, чтобы потом можно было какие-нибудь фишки с освещением делать (да и просто для модульности и понятности кода). Чё ещё? Ещё камера. Нужно сделать некоторый объект, который хранил бы в себе координату камеры, направление камеры, и плоскость проецирования. Тогда метод rander_scene(scene ⚹scn1, camera ⚹cam1) отрисовывал бы сцену. Тфу.. какой метод. Это же не С++. Начал учить С++, и вот всё бы это можно было бы сделать намного менее "топорным". Не удивительно что все движки после дума делались на C++ а не на С. Чё пальцы себе ломать говнокодом. Ну да ладно... начал на С, значит будет на С.
Аноним Вс 08 сен 2024 17:53:09ИзмененоИзменено модератором№41197468
Преимущество xlib только в том, что мануал подробный есть.
Аноним Вс 08 сен 2024 19:26:13ИзмененоИзменено модератором№41209469
охжбл мне же весь парсер переделывать, ведь нужно чобы он ещё и координаты текстур брал
Аноним Вс 08 сен 2024 23:52:10ИзмененоИзменено модератором№41241270
Насегодня - спать. Завтра - вспомнить, что проблема парсилки оказалась в подсчёте элементов. Из-за того что добавились текстурные координаты и нормали, элементов стало больше, и старая схема с (вначале точки - потом полигоны), уже не работает. А сделать цепочку - точки-текстуры-полигоны, было бы ошибочно, так как не все модели содержат текстуры и нормали.
Зря узнал про существование С++. Теперь меня мучают мысли, что я занимаюсь полной хернёй. Прогал бы на С++ мог бы несколько типов моделек сделать, для удобства. А так, начнёшь второй тип моделек делать - опять проблемы будут.
Аноним Пн 09 сен 2024 12:52:42ИзмененоИзменено модератором№41273571
доблае утличка. Удалил все функции count, и сделал процедуру которая считает вообще всё: и точки и текстуры и полигоны, и всю инфу записывает в массив. Теперь всё работает. Тут я понял, что у меня в программе предполагается, что модель содержит хотя бы одну точку и хотя бы один полигон. При попытке загрузить модель только из точке, программа упадёт при завершении, потому что будет пытаться очистить поле "полигоны" которое и не было заполнено вовсе. Также и если модель вообще ничего не содержит (ни точек ни полигонов), при попытке очистить программа ляжет. Хз фиксить это или нет. С одной стороны: надо чобы всё работало, с другой: а зачем загружать модельки без полигонов?! в чём суть то? они всё равно не будут видны. Тогда уж проще отдельно сделать структуру "облако точек".
Аноним Пн 09 сен 2024 16:18:31ИзмененоИзменено модератором№41322372
Так.. Я понял что делать камеру и сцену пока рано. Надо разобраться с выводом на экран. Я кажется понял, что в играх используется двойная буферизация, за это на sdl и отвечала flush вроде. Пока один буфер рисуется, другой выводится. Поэтому, для начала проделать более так сказать "низкоуровенвую работу" (в том смысле, что мы занимаемся по-сути выводом кадра в видеопамять, а не самой графикой), сделать функции которые бы отрисовывали кадр, типо draw frame, и потом уже реализовывать осточертевшое тридееее, с камерами и сценами и пространством.
И ведь это правильно. Сцен и уровней должно быть много. А кадр всё равно один отрисовывает.
Тут правда я понял, что мне лучше бы изначально начинать с 2д огрызков от игр, а не полноценных 3д игр. Почему? Потому, что я понял, что 3d объекты на уровне могут быть тесно связаны с геймплеем. А могут не быть связаны, могут быть просто декорацией. Нужно ли для какого-то объекта считать столкновения или не нужно - это довольно не тривиальный вопрос с точки зрения программирования на С. Тут язык топорный.. вначале нужно три раза подумать, а потом написать, иначе если не так напишешь - всё заново переделывать придётся. Поэтому, лучше заранее придумать некоторую "игру" (в кавычках потому что игрового процесса никакого не будет. Суть только в том что разработать систему которая демонстрировала бы все возможности игрового процесса. Не придумав такую систему, можно напороться на большую такую иглу: графический движок уже будет готов, вот только использовать его будет совсем неудобно в рамках игры, придётся изобретать тысячи костылей либо напроч переделывать графический движок под свои нужны). Поэтому, пожалуй не нужно спешить с камерами. Нужно пподумать о фундаментальных вещах: время, видеопамять, правила игры (под правилами я подразумеваю общие устройства, которым следует такая программа как "игра". То есть в частности, расчёт столкновений колизий, является правилом игры) Нужно вначале почесать репу, подумать как это будет работать, потом разработать "прототип", упрощённую модель, и уже потом можно сыпать в неё трёхмерные модельки, затенения, и всё подряд.
В своих мыслях я пришёл к тому с чего начал: "ну и как собственно разрабатывать". Давайте думать. Погуглим немного подрочим, и придумаем.
Аноним Пн 09 сен 2024 17:54:29ИзмененоИзменено модератором№41361273
В голове какая-то каша из всего подряд. Так, что пожалуй начну мозговой штурм сам с собой! Накидываю идей на вентилятор
№1. Игру можно рассматривать как набор уровней. Таким образом, с такой точки зрения, создать игру это почти как создать систему хранения уровней. Что тут можно сделать? Подумать над тем, а по каким правилам мы переходим из одного уровня в другой. И что, определяет эти правила. Являются ли правила перехода между уровнями, свойствами самих уровней. Например, уровни могут храниться в линейном односвязном списке, и тогда в игре может выполняться вызов функции Next_Level(), что переводит игровой процесс на следующий уровень. При этом Netx_Level() сама явно не знает какой уровень следующий. И тогда, последовательность уровней поределяет то, в каком порядке они записаны в структуру. Или же можно хранить уровни в массиве, и Next_level(int lvl) вызывать в индексом уровня. Тогда порядок уровней становиться не важен, ведь ко всем ним можно обратиться по индексу.
№2 Можно было заметить из предыдущего пунтка, что с "переходом" в новый уровень не всё так гладко. Начнём с того, что, а что такое вообще этот "переход" межу уровенями?! Когда мы в Half-life переходим на новый уровень, у нас заменяется только моделька карты и все entyty на ней. А персонаж то остаётся. Получается, что персонаж - не часть уровня. А значит рассматривать игру как набор уровней уже нельзя. Получается, что игра должна иметь "состояние", помимо уровней. Набор уровней уходит на второй план. Раз уж мы думаем о "состоянии", то исходя из смысла слов, оно должно иметь возможность изменяться во времени (иначе непонятно зачем оно нужно). То есть игра это что-то из событийно-ориентированного программирования. Игра, это некоторый конечный автомат. В игру мы играем. Поэтому, игра должна на вход получать ввод с клавиатуры и мыши, а на выходе показывать нам экран, при этом каждый кадр меняя своё внутренее состояние.
№3. Можно заметить, что и с конечным автоматом не всё так гладно. Представить ситуацию, когда мы запустили игру, и не трогаем клавиатуру. В таком случае, игра должна замереть в одном состоянии. Ну а что ей ещё делать, если нет входных сигналов? Отображать то же самое что отображало. Но ведь персонаж в игре может умереть не по кнопке игрока. Подойдёт к нашему персонажу моб и убъёт его. Ввода не было - а игра должна перенестись на нулевой уровень, в меню. Получается, что игра должна сама в себя играть отчасти. Игра должна сама уметь влиять на своё состояние. Это всё наталкивает на мысль, что кроме пользователя который вводит команды, должен быть и второй игрок "мир", который собственно влияет на состояние игры совместно с игроком. Нет проблем - мы можем создать отдельный процесс для этого, который будет как искуственный интелект играть в игру.
№4. И снова, нельзя не заметить недостаток. Пользователя у нас два, значит ввода два, а результат - один. Вопрос, чей ввод сильнее? Получается, что нам необходим посредник, который бы и определял взаимодействие мир/пользователь. Игрок бы давал свой ввод на посредника, мир свой, и задача посредника-среды, как раз правильно организовать всё это. И как раз вот эта "среда" и является непосредственно похоже что игрой.
Пока что вот такие мысли. Надо ещё подумать, и поделать простые програмки в консоли, чтобы посмотреть как будет работать обработка событий от двух вводов.
Аноним Пн 09 сен 2024 18:57:38ИзмененоИзменено модератором№41372874
Я же уже прокручивал эти мысли! И в прошлый раз я пришёл к выводу, что моя система схожа с Xorg-server. Так и есть. В качестве клиентов у xorg-serverа выступают программы. Правда, клавиатура и мышь находятся на сервере.
Но в начшем случае немного по-другому. Надо организовать работу трёх процессов. 1.(client) Позльзователь. Может посылать на сервер данные о нажатии кнопок, о нажатии мыши. Может получать от сервера все необходимые данные по отрисовке кадра. Клиент, впринципе может работать при помощи системного вызова select(). Ведь если отрисовывать нечего, и кнопок каких не нажато, то зачем выполнять работу? Таже в этой части находится и рендер, ведь только на клиенте рендерить и нужно. Рендер должен принимать информацию о сцене и правильно её отображать. Пользователь напрямую управлять игрой не может, он посылает сигналы на сервер. 2.(client) Мир игры. Работает как вечный цикл, причём каждое обновление вызывает системные часы, для определения времени прошедшего с предыдущего обновления. Содержит в себе игровую сцену, и все правила взаимодействия между объектами. Тут нужно придумать реализацию самих "правил игры" 3.(server) Серверная часть игры. Занимается координацией мира игры и пользователя, и хранит общие данные. На сервере харнится "состояние" игры и все необходимые компоненты. Также сервер может выполнять загрузку уровеней, и посылать данные для воспроизведения уровня клиенту 2 (мир). Также, севрер может получать запросы от клиента 1, на управление миром игры, или на явное пререключение уровня (например пользователь нажал esc - значит нужно перейти в меню). Также сервер занимается сохранением состояния уровней и игры. То есть сервер может формировать сейвы, и временные сейвы (например, чтобы можно было выйти из меню).
Аноним Пн 09 сен 2024 19:51:40ИзмененоИзменено модератором№41387575
С другой стороны, почему бы тогда пользователя не соединить с миром в один процесс.
Или вовсе не сделать всё в одном цикле.
Чьёрт знает. Сделаю какой-нибудь калыч, по типо "чубрики против собачки", где собачка @ уклоняется от чубриков O. На Ncurses само собой разумеется
Аноним Пн 09 сен 2024 19:56:46ИзмененоИзменено модератором№41388576
Ахах. А что если на дваче такой кринж высрать.. там же постоянно типо делают серьёзные игры, а я приду к ним, создам свой собственный тред назову его "Собачка VS чубрики" и буду срать копеечным говном на ncurses. Ахаха. Ещё и в стим это говно запущу (или хотя бы прифотошоплю и сымитирую что моей игре гринлайт дали) чтобы у доверчивых долбоёбов бомбануло что за какой-то hello world дают миллионы.
Аноним Пн 16 сен 2024 23:14:08ИзмененоИзменено модератором№43158577
Что-то я очень грустный в последнее время. Ничего разрабатывать не хочется.
Но вот.. сделал такой эксперемент. тут просто генерируется матрица из случайных букв, и выводится на экран. Получается некоторый конечный автомат, из трёх состояний "меню/игра/пауза". Для начала игры нажимаешь m, и игра переходит из меню в игровое состояение. По нажатию p игра останавливается, и выводит время прошедшее с начала игры. При этом время берётся по системным часам, то есть пауза никак не влияет на время. По идее, весь мир может работать от функции изменения времени. То есть можно считать время прошедшее с предыдущего кадра, а не абсолютное время.
Я так подумал, пользоваться паралельными процессами не обязательно. Что такое вообще параллельный процесс? Он ведь на самом деле не парралельный. Все процессы выполняются в пакетном режиме. То что мы создаём процесс - это мы просто передаём полномочия по управлению операционной системе. Это накладывает кучу очевидных проблем, со всеми этими мьютексами, семаформаи и прочим. В это же время можно всё просто впихивать в бесконечный цикл. Надо только организовать собственное управление, чтобы, например, в меню и на паузе не было активного ожидания. А сам игровой процесс без активного ожидаения немыслим. Так, что зачем нужны паралельные процессы - мне не совсем понятно.
Аноним Ср 18 сен 2024 16:29:02ИзмененоИзменено модератором№43354778
И опять же, заново. Пока что представлю что вся игра находится в одном .с файле. Что у нас есть на поверхности? Игра - это событийно-ориентировання программа с одним вечным циклом. Сходу, можно назвать 3 состояния в которых может находиться игра: 1.меню, 2.проигрывание уровня, 3, временное меню (пауза) Действия, которые выполняются в состоянии "меню" кажется не таким сложным: обыкновенная реакция на кнопки и клики мыши. Программа может даже заблокироваться в меню, и быть вычеркнута из планировщика ос, до тех пор пока пользователь не кликнет мышкой. В следствии нажатий на некоторые менюшки, игра может переключиться в состояние "playing", в котором проигрывается текущий уровень. Подрезюмировав этот абзац, можно сказать что "игра" - это структура из полей typedef struct{ enum game_states state; //menu/playing/paused/loading/saving int level_descriptor; //номер уровня struct ⚹level; //указатель на структуру уровня struct ⚹ current_gui; //указатель на текущий 2д интерфейс } session;
Вот теперь всё сложнее. Как видно из структуры, тут есть поле cur_gui, которое предполагается не просто "двухмерную картинку", а некоторую структуру, с набором "кликабельных" элементов. Игра ведь не просто картинка. И если меню, можно было бы сделать "в упор" просто написав и расставив функции для отрисовки кнопок там где требуется, то в игре такое не прокатит, ведь игра должна изменяться со временем. В игре нужно много что придумать, и без удобного разделения кода тут не обойтись. Начнём с простого. В отличии от меню, игра, никогда не блокируется. Известно, что игра должна делать следующие действия: 1.Каждый цикл игра считает сколько времени прошло с предыдущего цикла. 2.Каждый цикл, игра отрисовывает текущую сцену. Так называемую "сцену" я ещё даже не накодил, но как видно из моих предыдущих постов, сцена - это некоторое 3d пространство с камерой и объектами. Грубо говоря "сцена - всё что необходимо для вывода трёхмерной графики в динамике". Сама сцена не содержит ничего "живого" чтобы позволяло объектам двигаться. Объекты и камера двигаются отдельно, а сцена - просто удобная структура которая это всё содержит. Так, если мы отрисовываем "сцену", то она должна как-то двигаться. Не зря же мы её отрисовываем каждый цикл. Делать всё в главном цикле, очевидно идея глупая. Так, что можно создать процедуру в которой бы производились все необходимые изменения над сценой. UpdateScene(scene ⚹cur_scn);
Продолжаем думать дальше. Допустим, у нас есть функция которая обновляет свойства объектов, по нажатию клавишь. Что с того? Нам ведь нужна игра, а не Paint3D для простмотра трёхмерных объектов. Следовательно, кроме клавишь, должна быть ещё и "игровая логика". Что же я подраумеваю под "игровой логикой"? Это по-сути и есть игра. Это набор правил, которым следует программа, чтобы воспроизводить тот результат, что мне нужен. Например, если мы идём вперед, а впереди нас бочка - мы не должны проходить сквозь бочку (а если кусты, то должны). То есть функция, которая прежде просто перемещала персонажа на координату вперёд, должна ничего не делать, на том основании, что уравнение вычисляющее пересечение мешей, выдало положительный результат. Вот примерно так....(продолжение в следующем посте)
Аноним Ср 18 сен 2024 16:29:44ИзмененоИзменено модератором№43355279
(продолжение предыдущего поста) Тут автоматически возникает несколько соображений: 1.A выгодно ли нам вообще вычислять сложное "уравнение" по пересечению поверхностей? Это же всего лишь игра. Почему бы не запретить персонажу проходить сквозь радиус от центра бочки, а не от самой сложной геометрии бочки. Это соображение подталкивает нас на мысль, что вообще-то могут существовать и "невидимые" объекты. Этот радиус - получается, невидимый циллиндр колизий. Он есть в игре, при этом передавать его в функцию для отрисовки абсолютно бессмысленно, ведь это даже не трёхмерный объект. Это наталкивает на мысль, что должно быть что-то обобщённое, типо "entity" или "игровой объект" - это ни как таковой объект, а как некоторая сущность, которая может быть связана с 3d мешем, а может и не быть. 2.Если продолжить мысль, то получается подобных функций может быть много. Нас ведь могут интересовать совершенно разные вопросы внутри игрового мира. Не обязательно связанные со столкновениями. Например, вопрос, "а находится ли игрок на поверхности? если не находится то пусть падает" Или "игроку нанесён урон? или был промах" или (а нужно ли поменять камеру для отрисовки сцены?). Собственно, необходима некоторая "система" функций-отработчиков, которые бы вычисляли какой-то факт (то есть отвечали на вопрос "да/нет") и может быть в качестве побочного эффекта что-то бы изменяли на уровне, либо же выдавали некоторые данные для изменения. Собственно, появилась идея "разделить" всё что у нас есть на три категории: 1) Данные - это сами 3д модельки-меши, текстуры, объекты коллизии, точки, камеры итд. Короче, всё что представляет из себя тип данных "структура". 2)Свойства - это некоторые отдалённые характеристики данных. Грубо говоря, это свойство, это к какой "группе относятся" данные, какое значение они имеют для игры. Свойства можно описать тремя полями typedef struct{ enum entities usage; //назначение-свойство объекта (невидимая декорация, или объект для расчёта столкновений). Благодаря этой информации, другие функции знают, нужно ли рассчитывать колизию для объектов. enum data_types type; //тип структуры (мэш, или точка, или вовсе камера). struct ⚹pointer; //указатель на эти данные } entity; Возможно этот тип является избыточным, либо написан он с избыточными/не теми полями. В будущем я это уточню. Но прямо сейчас, у меня возникло желание сделать поле usage, типом "список отработчиков". То есть сделать так, чтобы было несколько "свойств", и весь список свойств тупо полистывается, и данные из него используются в качестве функции Poll, для обработчиков. 3)Действия. Это некоторый глобальный массив, из указателей на функции. Каждая функция возвращает логическое значение 1 или 0, а также либо выполняет какой-то побочный эффект, либо (если побочные эффекты сделаю программу недостаточно гибкой) вместо побочного эффекта возвращает универсальный парамер, который в дальнейшем может использоваться для изменения объектов уровня. К слову, одной такой функцией я уже пользовался при кодиге графики. Это функция OutOfCanvas(), которую я так долго и многострадально переписывал. Она получает объект - точки для рисования линии, и в качестве результата возвращает истину либо ложь. Если функция возвращает истину - это означает, что линия "полностью выходит за рамки холста", то есть такую линию не имеет смысла отрисовывать, ведь она невидимая. Если же хотя-бы кусочек линии попадает на холст, то функция "обрезает" ненужную часть и передаёт новые координаты только видимой части линии, "теперь то линия находится на холсте" пишет функция в поток. Если же линия изначально помещалась на экран, функция ничего не делает (хотя правильнее было бы сделать вариант, где она даже не вызывается, чтобы лишний стековый фрейм не тратить)
Таким образом, расчёт геймплея сводится к выполнению отработчиков над объектами игры, и последующем витвлении. Где-то тут, и следует добавит само понятие "игровой уровень", как упорядоченный набор всех игровых объектов. Пока, что я предполагаю "отрисовывать" сцену отдельно от всего уровня, ведь если бы функция UpdateScene принимала себе на вход именно "уровень", то ей приходилось бы каждый кадр выполнять поиск объектов для отрисовки. Зачем? если можно подавать функции объекты которые заранее необходимо отрисовать, т.е. сцену. Это пока что очень поверхностное описание. У меня на реализацию простого конечного автомата ушло столько строк, что уже хочется что-то "отделить" в модуль. А тут ведь и расчёт колизий, и всего. Так, что скорее всего это выйдет не одним модулем, а будет неоднократно разбиваться. Главное что я сделал - я в голове построил некоторую "модель" движка. И в дальнейшем буду пытаться её построить, посмотреть как она работает. Фронт работ обозначен, трудиться можно в двух направлениях 1) Создание и тестировка игры с самого её начала. То есть реализовывать вот то, что я настрочил в предыдущем посте (когда по клавише p программа становиться на паузу). Вот таким же образом нужно подумывать некоторый "шаблон" для подгрузки уровней. То есть делать игру непосредственно с начала. 2)Делать игру с конца. То есть копаться в способах вывода звука, например. Улучшать и модифицировать графическую часть движка, делать эти самые "сцены", закодить текстурки наконец-то.
Кстати, относительно второго пункта. Я ведь нашёл доступ к видеопамяти! За два месяца, я таки нашёл устройство /dev/mem, и нашёл там видеобуффер для своего VGA устройства. Но это всё такая бесполезная затея... Какбы.. то что он выводит графику мешая другим программам-то это я предвидел, в этом ничего необычного нет. А вот то, что видеопамять храниться не "прямым" массивом, на это я почему-то не рассчитывал. Я вывел на экран простой градиент, и он начал выводиться небольшими двойными полосками, сверху которой была тёмная часть, а снизу светлая. Видимо, в целях оптимизации, видеобуфер состоит из некоторых "страниц"-массивов, которые располагаются в шахматном порядке. Получается, чтобы вывести что-то мне нужно ещё и свой драйвер для монитора писать. Таким образом это уже нихрена не "Games for FreeBSD"... Это "Games for моя личная видеокарта and FreeBSD ", нахуй. Нет такой глупостью я заниматься точно не буду. Ещё и пытаться организовать доступ к разделяемой памяти.. не нужно. Буду использовать Xlib. Для звуков какие-нибудь стандарнтые драйвера есть наверное.
Аноним Ср 18 сен 2024 16:59:45ИзмененоИзменено модератором№43362680
И.. совсем забыл. Реализация анимаций! Как сделать самую простую анимацию? Ну вон, посмотрите выше, я трёхмерный логотип FreeBSD замоделлил и отрендерил. Он поворачивается. Что это если не анимация? Для простого платформера подойдёт: персонаж двигается - шарик крутится, персонаж стоит - шарик стоит. Как обычно, переходим от простого к сложному. Если так посмотреть - варвар с топором отличается от шарика только тем, что в нем координата меняется не у всех точек, а только у некоторых (например, у точек руки и ног). Так, что мы можем в модуль для импорта 3d мэшей, добавить не только функции, которые бы позволяли двигать весь объект, но и те, которые двигают некоторый список его точек. Тогда, реализация танцующего варвара, представлялась бы как код для станка с ЧПУ: функции одна за другой просто бы переносили его точки кадр за кадром. Нельзя не заметить, что хранение анимации в таком виде, весьма ресурсозатратно: это ведь сколько кадров требуется чтобы простую ходбу реализовать. Поэтому, вместо покадровой анимации мы можем реализовать ключевую. Функция для ключевой анимации получает только начальное состояние и конечное, затем вычисляет направление по которому следует двигаться, и затем циклом добавляет координаты до тех пор пока модель не "приедет" в конечное положение. Получается "плавный переход" между кадрами. И тогда файл анимации будет содержать только наборы ключевых точек, и количество итераций, за сколько до них нужно дойти. Можно либо написать свой аниме-модуль, либо впихнуть поддержку анимации в wavefront-парсер. Написать свой модуль более логично, так как появляется некоторый отдельный тип "анимация" .anim. С другой стороны, лучше бы всё соединить для компактности, чтобы не засорять всё тысячью модулей.
Аноним Ср 18 сен 2024 17:05:28ИзмененоИзменено модератором№43363681
Аноним Пт 20 сен 2024 00:42:29ИзмененоИзменено модератором№43649382
Во! Правильно сказал анон, что лучше начинать с 2d игр. Ncurses минималистичнее, и в целом отражает суть. Поэтому пока что нужно потренироваться на вот таких текстовых програмках, чтобы выявить подводные камни.
Создал протитип, описанной выше системы. Тут меню, является по-сути уровнем, но все объекты на нём имеют тип "кнопка" и поэтому не могут никак перемещаться. Писать такой уровень-меню оказалось крайне неудобно. Формат для уровней я решил сделать простой: просто в каждую строку записаны атрибуты одного объекта, и сколько строк - столько и объектов на уровне. Уровень воспроизводится в зависимости от свойств объектов. Если объект имеет тип "персонаж", то по кнопкам он перемещается.
Как видно, в меню, программа работает в "блокирующем" режиме, и ничего не делает пока пользователь ничего не вводит. А в режиме "игра" программа работает всегда (время идёт).
Тут сразу возникает много вопросов по организации кода. Во-первых, хранить все свойства в одном "объекте" неудобно. Некоторые свойства, например касаются только кнопок, а хранятся они всегда. Нужно как-то расфасовать сущности на подсистемы, чтобы кнопки могли существовать только для гуи, а объекты только для уровня итд. Больше того, меня сильно беспокоит чистота кода. Я читал, что "конечный автомат не может управлять наступлением событий". Вот такая вот фигня. А у меня он фактически управляет: из функции MenuKeyResponse, можно перекочевать в функцию, которая изменяет состояние на loading. Лоадинг и вовсе напрямую меняет состояние на playing, единственно что делая - подгружая требуемый уровень. А Playing может снова вызвать Menu. Хотя с другой точки зрения, а что значит "извне"? Может быть то, что функция подгружает новый уровень, это вполне себе "из вне", она же не знает когда уровень завершит загрузку. Так, что хз если честно. Надо с этим разобраться. главное что какой-то прототип есть.
Аноним Пт 20 сен 2024 14:30:28ИзмененоИзменено модератором№43709083
Откомпилил статически для винды, через mingw по-приколу. Запустилось.
Сделал коллизию. Пожалуй на этом нужно завершать, потому что уже пошёл говнокод. Основное понятно, что нужно чётче продумать зону "отвественности" каждой системы. Нужно точно рассчитать, что система будет делать, а чего точно не будет делать, и из этого разделить данные на группы, а программу на части. Вот в этом примере всё попало, где попало, а поле "коллизия" у объекта вообще не используется.
Аноним Пт 20 сен 2024 23:22:36ИзмененоИзменено модератором№43821284
В последнее время меня так ко сну клонт. Чъёрт знает что. Сложно думать. Мотивация есть, а думать не могу, всё в тумане каком-то, как угашенный залипаю в одну точку и могу просидеть так день.
Надо соображать, страраться.
Всё-таки сделать игру кросплатформенной выгодно ещё и с точки зрения структуры кода. Отдельный модуль обёрток функций для Xlib позволяет рассматривать ввод-вывод, как отдельную часть движка. Когда мы реализуем функции ввода-вывода сразу на Xorg, мы перемешиваем зоны отвественности. Нам нужно мыслить и с точки зрения api xorg-a ( конкретные окна, дисплей, ивенты) и с точки зрения игры (двойной буфер, кадр, итд). То есть код заведомо получается слегка "грязный" для понимания. Если же, всю конкретику xlib инкапсулировать в модуле core_xlib.c, то тогда, мы уже будем размышлять с точки зрения абстрактного фреймворка. "создать окно" и "вывести кадр" для нас становятся соверешнно понятными функциями, ведь они посылают вывод не в конкретную нагромождённую загагаулину XPutImage(*куча параметров*), а в вполне себе ёмкую UpdateFrame(frame *f).
Кстати, возможно поэтому в dwm и существует модуль drv. Я как-то его открывал, и находил там незначительные функции типл draw_rectangle, которые тупо копировали XDrawRect. Я думал: а зачем они это наговнокодили!? А теперь похоже что понимаю, что не зря!
Кстати, заметил, что я перестал стесняться писать большие модули сразу же. Ранее, я после каждой функции запускал компилятор и проверял её. Почему? Потому, что я опасался, что вот если возникнет ошибка, то я её в таком большом коде больше никогда не найду, и брошу всё и начну писать заново. Теперь как-то "приловчился". И ошибок стало меньше, и править их стало очевиднее. Ставишь printf в случайное место и смотришь результат, находишь подозрительные места которые могли повлиять на негативный результат и так всё отлаживаешь. Вот напишу свой движок, тогда наверное навыки разовьются и смогу вообще любой чужой код на раз-два прочитать!
Аноним Сб 21 сен 2024 00:18:52ИзмененоИзменено модератором№43834785
>>438212 Важная заметка! похоже что реализация зажатя клавишь как раз лежит в том числе и на этом модуле! Если тип Window из Xlib, можно заменить простой структурой window . Нам ведь не важны делали реазации окна, единственное что мы делаем - это выводим в него, и знаем его ширину. Остальное нас не волнует, поэтому инкапсулировать window довольно просто. Чего не скажешь об XEvent, ведь для реализации программы нам нужен доступ к его полям. Поэтому нам придётся сделать свой event! и свой PollEvent(), который бы и содержал оболочки для эветнов. Также, сюда автоматически стоит встроить focusin, чтобы отключить залипание. И раз уж у нас есть "свои" события, то зачем нам всё подчистую копировать у xlib, если можно сделать события типо key_pressed чтобы обозначить именно длительное зажатие клавиши.
Аноним Сб 21 сен 2024 21:46:15ИзмененоИзменено модератором№44018086
Я пришёл к тому, что вообще нужно сказать отдельный интерфейс, который бы мог работать с "комбинациями" клавишь, "двойными нажатиями" итд.
Аноним Сб 21 сен 2024 22:27:39ИзмененоИзменено модератором№44042987
нагуглил в интернете про какие-то "паттерны". Надо почитать, звучит интересно.
Аноним Сб 21 сен 2024 23:47:41ИзмененоИзменено модератором№44071988
А "паттерны" оказались годнотой! То что нужно. Это по-сути готовые "подходы к созданию". Я и ранее использовал их, но я названия не знал. Причём, я часто просто копировал целые функции из одного проекта в другой, чтобы "пользоваться удобной фигней и не заморачиватьчя". Один раз протестил - тащу повсюду. Например я сделал один раз реализацию односвязного списка, и функции, которые позволяют в него что-то записывать/извлекать, или вовсе уничтожить весь список сразу. У меня всегда голова ломалась на этих списках, потому я один раз понял, один раз сделал хорошо, а остальное время тупо эти функции тащил, изменяя лишь поля ячейки списка. Такой паттерн, когда задают "обьект" и операции над ними называется "фабрика". Правда, вот чем это от классов отличается? Добавить в структуру указатели на функции - вот тебе и методы класса. Также, ещё одна фабрика - это двойной массив. Мне постоянно лень его правильно инициализацировать (это занимает много строк), поэтому у меня есть такие же функции типо init_canvas которые просто двухмерный массив инициализируют, и уничтожают. Кроме фабрик я ещё использовал паттерн "стратегия", который я называл "callback" функциями или хэндлерами. Чем они отличаются я не знаю. Суть та же: некоторая функция принимает в качестве параметра другую функцию. И подобных "других функций" делается много, и все они рассчитаны на какое-то различное воздействие над данными. На практике я такой паттерн не применял. Только в учебных целях сделал, и в скором времени понял что это какая-то безделушка. Хочется всегда применить этот подход, но, я не понимаю зачем, ведь все это запросто заменяется на набор if() else. Следующий паттерн "одиночка", я вообще не применял, и немного не понял, зачем он нужен. Суть в том, чтобы создать некоторый единственный объект. И если он неинициализирован, то при обращении он бы инициализировался и затем работал как обычно , а если инициаоищипован, то просто работал бы как обычно. А зачем? Где это может пригодиться? Наиболее интересным мне показался шаблон "наблюдатьель". Вот это наверное можно попробовать применить для обработки событий. Суть этого шаблона, что вот есть "объект", и если что-то в нем изменяется, все кто с ним связан, должны обновить информацию. То есть, две основные функции и одна полочная: 1.Инициализировать объект и наблюдателей 2.Изменить значение обьекта (при этом после изменения должен быть вызвана функция для оповещения об этом наблюдателей. 3. static Оповещение наблюдателей. Тут у всех наблюдателей меняются свойства в зависимости от нового значения объекта. Вот такие штуки интересные.
Аноним Вс 22 сен 2024 16:44:29ИзмененоИзменено модератором№44181789
Вот в таком стиле охота игры делать. Лоу-фай триде.
Аноним Вс 22 сен 2024 23:48:20ИзмененоИзменено модератором№44262490
Так, сделал "зажатие клавишь". То есть, у нас есть объект event, который может заполняться чем нибудь после вызова PollEvent (то есть в его полях появляется информация о длительности зажатия клавиши, итд). После того, как эвент получен, вызывается функция Notify Obsevers которая отсылает списку функций-наблюдателей, новые параметры на отработку. Функции-наблюдатели имеют одинаковый профиль. Так, что мы может указатели на такие функции добавлять в список и исключать. Каждая функция, что получает event, как-то на него реагирует, изменяет как-то устройство сцены. Сам объект events->keyboard имеет массив из 104 элементов. Каждый элемент равен 1 или 0. Если 1, значит клавиша зажата, если 0, значит не зажата. Также есть поле hold, которое отвечает за время которое продержалась клавиша.
Идея кажется довольно топорной и неудачной, но может потом когда будут возникать трудности, придумаю что-нибудь. Ведь чисто технически, вдруг игре нужны будут "поля для ввода", чисто текстовые, такие же как везде. Проще было бы сбросить весь код по зажатию клавишь, и вернуть на момент ввода всё в каноничный режим "автоповтора".
Ладно, посмотрим. Может всё это вообще удалять придётся. Главное, что пока что я понимаю, как переоборудовать весь этот модуль под ncurses не меняя при этом функций.
Также я добавил объект "фрейм". Его суть в том, тчтобы иметь два буфера. Функция UpdateFrame выводит первый буфер на экран, и в случае, если активирован флаг Ready, то меняет буферы местами, а если неактивен, то оставляет второй буфер дозаполняться.
Аноним Пн 23 сен 2024 19:19:56ИзмененоИзменено модератором№44364491
Оно работает! Я правда так и не понял, а зачем двойной буфер вообще нужен? Думал, что для того, чтобы выводить из одного буфера, а второй заготавливать. Но у меня же нет паралельных программ. Отчего получилось вот такое "дрожание" нарисованной картинки.
Но в целом паттерн Observer реализован. Наблюдатель Pencil, рисует пиксель, когда клавиша зажата. Единственный минус, а как связать наблюдателя с системой? Я так навскидку добавил ей параметр frame, чтобы наблюдатель имел доступ к картинке. Но ведь по-идее он должен иметь доступ и к игровой логике. То есть он должен быть разнообразным. Есть идея, сделать так чтобы он возвращал какое-то значение, и это значение было бы управляющим для других программ. Например, он бы возвращал число, исходя из которого вызывался бы отработчик уже в главной программе. То есть if(Pencil(e) == 25){ SetPixel(X,Y) } Вот типо такого. ЧТобы отработчики возвращали просто универсальный идентификатор, исходя из которого выполнялись бы программы. То есть правильно было бы функцию Pencil назвать не Pencil, а MouseHold. Таким образом, наблюдатели - это универсальные наборы клавишь.
Аноним Пн 23 сен 2024 22:05:39ИзмененоИзменено модератором№44394992
И нафиг я туда код паттерна "наблюдатель" запихнул? Он же совсем никак не связан. Сейчас вот переделал модуль под SDL и код "добавления в список" остался тем же".
Аноним Пн 23 сен 2024 23:23:16ИзмененоИзменено модератором№44417593
Впизду. Я понял, что моя система - полная хуйня. Кнопки управления залипают. Почему? Потому что они ориентируются на неправильные переменные. Короче, всё на переделку.
Аноним Пн 23 сен 2024 23:35:45ИзмененоИзменено модератором№44419494
Или стоп паника, похоже что нашёл ошибку. У меня для некоторых ветвей else не был прописан выбор ивента. Из-за этого события focusIn, не ставили нулевой ивент, но и не меняли его на какой-то другой. Вот, теперь перед каждой итерацией событие это none, и уже потом если необходимо ему присваивается значение. Залипать перестало. А то я уж было в демонов не уверовал
Аноним Пн 23 сен 2024 23:52:56ИзмененоИзменено модератором№44421495
Но всё равно, сделал я какую-то откровенно путаницу. Типо, вот по-идее "наблюдатели" должны реализовывать все механники клавиш. И они могут сделать реакцию, скомбинировав "состояние" клавиатуры. Но они ведь нихрена не могут реализовать такие вещи как "KeyToggle" (не знаю как назвать, ну короче, когда нажимаешь и отпускаешь клавишу, и результат 1, ещё раз нажимаешь и отпускаешь - результат 0. Как рычаг короче). Чтобы реализовать такую механнику нужно создавать отдельную переменную, которая бы отвечала на вопрос "активна ли кнопка". И тогда получается, что управление кнопками выходит за рамки функций-наблюдателей. Тогда, внимание, вопрос: нахрена эти наблюдатели нужны?!. Вот и всё. Надо придумать что-то более компактное.
Аноним Вт 24 сен 2024 13:34:12ИзмененоИзменено модератором№44461496
Упростил код. Теперь тут нет всяких бесполезных нагромождений функций, связанных со временем нажатия клавишь. Убрал вообще все бесполезные типы "время" "мышь" "клавиатура" "кадр", нафиг всё это нужно. Теперь типа всего два "окно" и "контролс" (кнопки, мышь хз, короче всё что управляет). Окно - это просто инкапсулированный тип "окна" для данного фрейморка/библиотеки. Окно можно отрисовать (на самом деле отрисовывается только буфер, а окно выводится только в функции UpdateFrame. Дело в том, что некоторые библиотеки могут не иметь никаких фреймов. Например SDL достаточно высокоуровневый, что там можно выводить сразу в некоторое окно. Так что окно, и буфер для окна тут "склеены" в одну структуру, для удобства). Контроллеры - это реефецированная структура, для данных от устройств управления. hold - содержащий информацию нажата ли кнопка. Toggle - массив, содержащий информацию относительно механники кнопки с самоподхватом. Индекс массива - номер кнопки. Причём кнопки мыши считаются там же (так как я не заметил особой разницы между кнопками клавиатуры и мыши, разве что кнопки мыши могут остутствовать. В любом случае, Xlib представляет абстракцию кнопок в keysym (есть конечно и keycode, но зачем его использовать если разрабочтики xlib позаботились об абстракции вместо меня)). Поля x и y - это координаты куда перемесилась мышь. Поле type - записывает тип события (none. press, release) то есть нажатие клавиши, отпуск клавиши, или ничего. Таким образом, можно сформировать три макроса PRESS HOLD и TOGGLE, каждая из которой, формирует логическое выражение, касаемо нажата ли клавиша единично, зажата ли, и была ли нажата ранее. Теперь всё работает как по маслу. Главная цель выполнена - абстракция от конкретного фрейморка. Для теста я переделал модуль под SDL и он прекрасно работал. То есть теперь уже можно пытаться реализовать какие-то механники. Вот первая простейшая механника - Pencil. Нажимешь клавишу "а" и теперь ты можешь рисовать, удерживая кнопку мыши. Нажимаешь ещё раз "а" и режим рисования исчезает. Работает без залипаний.
Аноним Вт 24 сен 2024 14:14:09ИзмененоИзменено модератором№44464197
Ещё надо бы прикрутить функцию SetCanonical которая бы возвращала каноничный режим ввода. Например, если в программе есть поле, в которое нужно ввести текст. Стандартные средства xlib вполне себе пригодны для ввода текста, потому что включено автозалипание итд. Поэтому неплохо было переводить программу обратно в некоторый "стандартный" режим ввода. К тому же, что касается шрифтов, нужно использовать функции xlib или ещё чего, ведь сам я работать со шрифтами и текстом никакого желания не имею. Мне хватило, что я в детстве пытался сделать красивый шрифт, а он вышел кривым и косым. Есть уже FreeType и прочее - вот этого достаточно. Писать собственную библиотеку шрифтов и текста - это как писать собственный xlib. Я же на самом деле избегаю движков и SDL не потому что они плохие. Даже нет так. Движки точно не плохие, godot какой-нибудь условно, довольно приятный движок и по документации и по комплектности. Дело в другом. Дело в том, что готовые технологии - не панацея, и даже на мой вгляд вред для обучения. Обмазывясь кучей фреймворков, ты делаешь: А) (делаешь) cвой код менее портативным. Некоторые библиотеки наотказ не компилируются статически (SDL например). А под Windows и вовсе, обычным делом является DLL, так что неприятно было бы получить программу которая заваливает сообщениями "ошибка, dll qt sdl gtk openai opengl не найден). Я считаю важен принцип "чем меньше зависимостей, тем лучше". В идеале программа вообще должна запускаться на голой операционке. Но на FreeBSD нет никакой стандартной поддержки графики, а Х11 стал практически каноничным (всё пишется под иксы). Поэтому я считаю что X11 использовать допустимо, ведь он есть у 99% десткопов. Учитывая что я обернул все функции xlib в "обёртки", программа становится портативной, и если так уж вышло что xlib нет, то адаптировать программу под Wayland не составит труда. Да хоть под свои драйвера адаптируй, суть в том, что программа отделена от конкретной библиотеки. В случае, когда таких библиотек много, отделиить программу уже не так просто. Б)Ты делаешь одинаковые действия. Не зря в народе бытует мнение, что веб-фронтэндер - это макака. Поиск библиотек на каждый пук оставляет разработчику ничего: все сложные задачи за него выполняют библиотеки, а сам он просто пишет програмки не сложнее hello world. Таким образом, программирование превращается в рутину, которую легко заменит ЧатГПТ. Да да, чат гпт между прочим такой же тактикой и идёт: он тупо ищет готовую библиотеку и выдаёт код на пайтон. Чем чатгпт отличается от веб-макаки? Тем что чатгпт более вежливый. Для того, чтобы совершенствовать навыки необходимы нетривиальные задачи. Суть библиотек - свести нетривиальные задачи к тривиальным. Поэтому, невозможно развиваться используя библиотеку на каждый пук. Необходимо отказываться от библиотек. Необходимо изобретать велосипеды. Библиотеки можно использовать исключительно тогда, когда разработка их функционала не представляет никакого так скажем "научного интереса". То есть, вот я создал свою библиотеку для работы с TGA картинками. Я буду её использовать для хранения текстур. Почему? Потому что она милималистична, она моя, и если что я смогу её отладить. А если я захочу сделать свой растровый редактор картинок, то тогда я выберу стороннюю библиотеку для работы с картинками. Почему? Потому что я в общих чертах понимаю как происходит сжатие и хранение в памяти картинок. Мне не интересно изучать особенности jpeg формата, тем более что уже полно добровольцев которые сделали это за меня. Поэтому, от того что я реализую очередную библиотеку для нового формата скилы у меня не возрастут. Там ведь используются все те же принципы и конструкции. А вот если я релизую собственный гуй, и инструменты для рисования - вот это уже что-то новенькое. Вот также и при разработке игры: мне лень разбираться в форматах звука, в драйверах видеокарт, поэтому я использую Xlib + OSS. Технологии железные и проверенные. А разрабатываю саму игру + некоторые модули, типо графический итд. Когда я сделаю свой всрато-движок, возможно я перейду на OpenGL и на более нормальные технологии, которые реализовали профессионалы до меня. Но пока мне интересно сделать свои.
Аноним Вт 24 сен 2024 15:28:16ИзмененоИзменено модератором№44475898
Добавил к функции PollEvent параметр mode. Пока что он воспринимает один флаг BLOCK_POLL и NON_BLOCK, т.е. будет ли блокироваться программа при выполнении опроса клавишь, или нет. Если программа не предполагает внутреннего мира (например меню игры), то пусть она блокируется. А если предполагает, то блокироваться она не может, потому что потом наступит обновления других событий. Далее, я придумал следующую идею для "наблюдателей". Наблюдатели теперь имею в качестве второго параметра не окно, а некоторый тип Changes (скорее всего список). Каждый отработчик добавляет свою ячейку в список для изменений. И затем этот список подаётся в функцию UpdateWorld, которая реагирует на изменения в своотвествии с игровой логикой. Тогда наблюдатели тупо реализуют какую-то клавомышечную механнику, и заполняют декларацию changes, чё нужно поменять. А функция UpdateWolrld уже это всё меняет.
Аноним Вт 24 сен 2024 15:34:31ИзмененоИзменено модератором№44476399
Таким образом в режиме реального времени можно "подгружать" механники нажатия клавишь, добавляя их в список и удаляя. Правда, для того чтобы добавить что-то в список нужна механника клавишь?! получается в главном цикле управляется то, какие механники влияют на игру, а какие нет.
Аноним Вт 24 сен 2024 16:03:56ИзмененоИзменено модератором№444789100
Вот примерно такой цикл получается. Верхняя линяя нарисована пунктиром, потому что PollControls и DrawSession, никак не связаны. DrawSession, отрисовывает сессию в текущий кадр, в то время как PollControls опрашивает дисплей на наличие изменений в клавишах. То есть эти две функции пользуются совершенно разными полями в струкруте window. В то время как controls полученый из Poll, передаётся наблюдателям, для обработки и выдачи ивентов "e", и список ивентов отправляется на UpdateSession, чтобы повоздействовать на игровую сессию, (в общем-то сессия может обновляться и без событий, событие тут как дополнительный фактор), и затем функция DrawSession, выбирает ту часть сессии которая отрисовываема, и рисует её (например поле "сцена")..
Аноним Вт 24 сен 2024 16:10:33ИзмененоИзменено модератором№444793101
Остаётся вопрос, а какой тип данных выбрать для списка "e"? Обычный int, который означал бы идентификатор изменений? А может указатель на функцию? Тогда можно было бы сразу применить изменения. Но тогда у функции должен быть одинаковый профиль. А на с int можно идентифицировать всё что угодно.
Аноним Вт 24 сен 2024 16:27:46ИзмененоИзменено модератором№444808102
С другой стороны, а нахрен нужно это всё, когда можно напрямую подавать в функции-наблюдатели параметр session, чтобы они изменяли в сесии вообще всё что хотели.
Аноним Вт 24 сен 2024 22:37:13ИзмененоИзменено модератором№445428103
Сделал кнопку из Windows 7. По её нажатию активируется рисовака рандомными цветами!! Собственно, первый элемент гуя. Дальше только усложнять - сделать кнопку более настраиваемой. Сделать другие типы графических элементов. И сделать список всех элементов, который назвать "гуй". И уже сделать порядок: отрисовывать фон->отрисовывать гуй.
Аноним Ср 25 сен 2024 23:50:58ИзмененоИзменено модератором№447409104
Реализовал шрифты. Ну... как реализовал... нашёл на гитхабе stb_truetype.h и тупо включил его через инклуд. Я хотел его отдельно откомпилировать, но он отдельно почему-то не компилируется - начинает выдавать ошибки. Короче, решил использовать так, как задумано автором. А задумано довольно странно - файл тупо инклудится в проект, и получается весь ворох функций просто в мой модуль компилится. Ну и ладно. Он всё равно вроде кросплатформенный, а это главное. Аллиасинг и сглаживание - насрать, главное что буквы хоть рисует, и на том спасибо. Функция просто "в лоб" рисует текст, и ничего более. DrawText(w,200,200,50,f,0xffAA1122,"Caaaп эскейпчан фан!"); Вот так примерно. тут f - это шрифт. Думал сделать структуры типо "текст" но потом не понял зачем. В чём смысл, если текст удобнее хранить просто как строку. Что ещё сделал? Ещё сделал побочный иструмент tga_to_pixmap, который tga-картинку переводит в массив const int [] = { 0xFF00AA11 ... Таким образом проще хранить всякие иконки, кнопки и прочее. Я думал, ещё таким образом сделать один "встроенный" шрифт, который был бы всегда по-стандарту. Но это уже на будущее. Короче, наговнокодился и рад!
Аноним Чт 26 сен 2024 00:14:56ИзмененоИзменено модератором№447443105
Чем всратее - тем лучше. Получается эдакий "гараждный стиль". Игра в стиле поделок 90х. Не понимаю людей, которые отказываются разрабатывать свой движок. Никто же не говорит, что нужно разработать хороший движок. Никто не просит стать пизже unreal engine. Достаточно сделать что-то, что демонстрирует какой-то аспект. Например, вот добавил я шрифты - теперь мне не придётся отрисовывать тысячи кнопок меняя только название. Более гибкая технолгия. Теперь на кнопку можно нацепить "шрифт" и сделать хоть 100 таких кнопок. Сама кнопка правда не масштабируется, и анимация у неё всратенькая - она просто менят указатель на битмап, когда на неё нажимаешь или наводиль. В Windows 7 было не совсем так, на семёрке когда ты наврдил мышь на кнопку - под мышью был красивый эффект градиента. Надо сказать, седьмая винда вприципе самая красивая. Не понимаю почему так любях XPюшу. У икс-пи вполне обычный дизаин. Вообще, если брать старые дизайны, то у xorg как раз по-красивее будет. А вот семёрка уже была с кучей подсветочек эффектов, всё полу-прозрачное аква. Красота в общем. Потом зачем-то ушли в плоский "минимализм" который по какой-то причине систему нагружает намного сильнее чем семёрочный фрутигер аеро. Если потрудиться, то интерфейс семёрки можно переписать на иксах. В этом ничего принципиально неразрешимого нет: кадр зарисовывать умеем, мышь отследить можем, следовательно нужно сделать что-то на подобии "xeyes" которые бы следили за мышью и туда направляли градиент. Но всё это слишком запарно делать, и не-интересно. Мне интересны именно какие-то демонстратинвные фишки, типо "вот 3d" вот "уровень". Если я и использую сторонние библиотеки, то только те, которые несложно откомпилировать. Если библиотека легко компилиться как sqlite, то не вижу причин не включить её. Не самому же с этими глифами возиться. Если уж возиться, то я бы попроще что-нибудь сделал, типо битмапов.
Аноним Чт 26 сен 2024 22:30:08ИзмененоИзменено модератором№448526106
Рисуй треугольник - треугольник сам себя не нарисует!
Сделал отрисовку треугольников. Но не всё так просто. Я же ранее создал свои "рациональные" числа. А поэтому, рассчёты конечно же решил проводить на них. И сразу же повылезали баги, которые я фиксил целый день. Точнее бага всего два, но фиксил я их целый день. Первый баг я быстро заметил - программа падала с ошибкой "ошибка чисел с плавающей запятой". Очевидно, произошло деление на нуль. Но где? Моя функция деления защищена от деления на нуль. Ошибка оказалась в макросе INT_TO_RAT. Знак у числа почему-то вычислялся делениме его модуля на его самого. Разумеется, если это число 0, то результат будет - делением на нуль. Поэтому я заменил эту фигню "тернарной операцией". Вторую ошибку я искал долго. Дело в том что треугольник превращался в многоугольники невиданной формы. Я не могу понять почему. Я начал тестить все функции. И все функции работали идеально, погрешность меньше 0,5. Собственно, раз дело не в функциях, то что ещё остаётся? Ошибка оказалась в макросах. Причём, в ближайшем к предыдущему макросе - RAT_TO_INT, который собственно, переводит число обратно в целое. Дело в том, что в этом макросе не учитывался знак. И от этого собственно вся ошика. Добавив учёт знака, всё стало отлично.
Наконец-то понадобились Callback-функции. Вот, треугольник же нужно будет уметь красить тремя разными способами, зачастую всячески модифицируя: со сглаживанием, с текстурой, со светом. И поэтому, я сделал саму функцию для отрисовки пикселя Callback функцией. Таким образом, можно одну и ту же функцию для построения треугольника использовать с разными отработчиками. Вот три тестовых: первая закрашивает просто цветом, вторая закрашивает градиентом по y, третья закрашивает рандомным цветом. Кстати, тут можно сделать и примитивную поддержку альфа-канала. Вот на третьей картнке все цвета с первым байтом 0x00 не закрашиваются.
Аноним Пт 27 сен 2024 12:56:47ИзмененоИзменено модератором№449279107
Короче, с рациональными числами я обосрался. Они работают намного медленее чем флоаты. НАМНОГО. 60 секунд против 9. Не знаю, чего я там намудрил, но похоже что идея гиблая. Хорошо хоть я сохранил старую версию с флоатами. C оптимизатором вышло 22 секунды. Но это всё равно намного больше чем 9 (ещё неизвестно сколько бы на флоатах с оптимизатором вышло). Идея гиблая, похоже что. Но можно попробовать оптимизировать.
Аноним Пт 27 сен 2024 14:49:59ИзмененоИзменено модератором№449556108
Походу операция вызова функции сама по себе берёт столько же тактов процессора сколько и операция над float. Поэтому бессмысленно её оптимизировать.
Аноним Пт 27 сен 2024 15:06:31ИзмененоИзменено модератором№449594109
Поэтому... Я уничтожил функци! Благодаря тернарной операции, всё это добро можно упаковать в макрос. И оно работает быстрее в разы! Вот только выдаёт неверный результат - опять многоугольникик какие-то. Но подход ясен. Нужно использовать как можно меньше операций/структур. Только битовые сдвиги. Ещё можно попробовать inline-функции, но я не понял как их использовать, если они в другие модули не экспортируются. Главное, что добиться скорости - возможно!
Аноним Сб 28 сен 2024 01:02:56ИзмененоИзменено модератором№450622110
Успех! Урезал всё по-максимуму. Теперь результат всего 3 секунды! Единственный минус - у чисел выше 1000 высокий риск переполнения. А значит экран не может быть больше 1000 Вариант решения - попробовать снизить точность до 4 битов (чем меньше битов на дробную часть, тем больше на целую. Тогда точность падает, зато вместимость целого возрастает). Результат: 3 пик. Надо сказать, что вообще-то прикольный эффект. Типо игры 90-х. Если первая картинка это разделение [1|21|10] (бит под знак| битов под целое| битов под дробное] то это было sqrt(2^(21)) = 1448 допустимое целое исключающее переполнение и 1/(2^(10)) = 0,0009765625 точность числа Если мы урезаем по-максимуму: [1|27|4] то макс число sqrt(2^(27))= 11 585 хватает для самого большого экрана и 1/(2^(4)) = 0,0625 погрешность большая, но даёт прикольный эффект. Делать число большим чем 32 бит я пробовал, но это сказывается на быстродействии. Почему-то числа типа long long работают 11 секунд, что в общем-то медленее чем числа с плавающей точкой. Поэтому делать числа больше - нельзя. Но вот, впринципе можно найти что-то оптимальное типо [1|24|8] Идеальное соотношение. Особо никакой видимой погрешности не даёт, вместе с этим максимальное целое число становиться 4096, чего достаточно, если правильно сделать защиту выхода за пределы экрана для треугольника.
Аноним Сб 28 сен 2024 13:21:31ИзмененоИзменено модератором№450928111
Начал интересоваться тем, а как бы мне "отсечь" ненужные треугольники и разбить их на несколько других, подобно тому как я делал с линиями. Оказалось, этой теме повсящено не мало алгоритмов, и оказывается, алгоритм по "отсечению" линий придумали до меня. Надо посмотреть, может у них оптимизированее Алгоритм Коэна — Сазерленда. Также полно алгоритмов для отсечения произвольных многоугольников. Но мне нужен конкретно для отсечения треугольника квадратом, и дальнейшего разбиения треугольника на другие части.
Аноним Сб 28 сен 2024 14:00:28ИзмененоИзменено модератором№450984112
Я дурачок. Я понял, что флоаты то я без оптимизатора компилировал. А если с оптимизатором, то float занимают всего 2.9 секунды! так, что придётся всё переделать во флоаты
Аноним Сб 28 сен 2024 17:00:07ИзмененоИзменено модератором№451264113
Профайлер ломает программу. Почему-то при компиляции с флагом -pg программа зависает когда делает линии большие чем экран. Иногда вылетает с ошибкой чёто там "mutex", хотя очевидно я никаких мьютексов не использовал, следовательно это ошибка связанная не со мной. Жаль, я хотел протестить, что быстрее, мой старый алгоритм клиппинга или новый. Хотя у меня есть идея, поставить координаты экранна намеренно меньше чем они есть, чтобы сымитировать подрезку.
Аноним Сб 28 сен 2024 18:04:17ИзмененоИзменено модератором№451299114
Да. Так и сделал. И результат: одинаково. Что мой клиппер, что клиппер Cohen–Sutherland работают одинаково быстро. Оставлю пожалуй второй. Потому что мой клиппер занимает две страницы, и работает только с линиями до 45 градусов, а этот работает с любыми линиями, и довольно компактный и логичный в плане читаемости кода.
Аноним Сб 28 сен 2024 23:32:26ИзмененоИзменено модератором№451881115
List outputList = subjectPolygon;
for (Edge clipEdge in clipPolygon) do List inputList = outputList; outputList.clear();
for (int i = 0; i < inputList.count; i += 1) do Point current_point = inputList[i]; Point prev_point = inputList[(i − 1) % inputList.count];
Point Intersecting_point = ComputeIntersection(prev_point, current_point, clipEdge)
if (current_point inside clipEdge) then if (prev_point not inside clipEdge) then outputList.add(Intersecting_point); end if outputList.add(current_point);
else if (prev_point inside clipEdge) then outputList.add(Intersecting_point); end if
done done
Аноним Сб 28 сен 2024 23:51:57ИзмененоИзменено модератором№451913116
короче, устал думать как реализовать этот алгоритм хитрым способом. Поэтому сделаю "в лоб" как на википедии написано. Сделаю мини-структуру для хранения точек, и буду тупо туда записывать все отрезанные точки
Аноним Сб 28 сен 2024 23:53:42ИзмененоИзменено модератором№451915117
алгоритм простой, просто вот думаешь, как бы не писать эти cur_x на тысячу строк. Ещё и чтобы удобно всё это хранить.
Аноним Вс 29 сен 2024 00:05:44ИзмененоИзменено модератором№451926118
а.. постой, список то требует динамической памяти. Это наверное медленее будет, если он будет malloc по каждому пуку вызывать. Надо макросы тогда попробовать, для большей читаемости
Аноним Вс 29 сен 2024 02:23:03ИзмененоИзменено модератором№452188119
Наконец-то, получилось. 5 часов утра. Всю ноч этот алгоритм по отсечению шаманил
Аноним Вс 29 сен 2024 12:51:13ИзмененоИзменено модератором№452541120
Аноним Вс 29 сен 2024 13:04:29ИзмененоИзменено модератором№452563121
А вот с окружностями уже не всё так просто. Как вообще теоретически "обрезать" окружность? Если окружность обрезана, то это уже не окружность и под алгоритм рисования окружности она не попадает
Аноним Вс 29 сен 2024 13:29:48ИзмененоИзменено модератором№452595122
стресс тест!
Аноним Вс 29 сен 2024 14:50:28ИзмененоИзменено модератором№452745123
вроде ошибок нет. Круги хз зачем нужны. Это ведь даже не элипс, не кривая. Что можно сделать одним лишь кругом? Может только эффекты какие-нибудь. Да и учитывая, что круг нельзя подрезать - применение кругов становиться ещё более узким. Вот кривые и элипсы - это да. В детстве я мечтал создать свою "криволинейную" графику. Я тогда программировать не умел. Но тогда уже начали появляться игры с NextGen реалистичной графикой, типо beyond two shouls. И там мне показалось, что на персонажах слишком много полигонов (на самом деле это не так. Я проверял. Там всё максимально ужато, и сделано по-уму с картами глубины, с нормалями. Но мне как полному нубу казалось что чем больше деталей - тем больше полигонов). Мне также почему-то показалось, что "много полигонов" это плохая идея, и нужно развиваться в других направлениях типо воксельной графики, или криволинейной графики. Я вот видел кривые Бизье и я думал: а почему нельзя такие же трёхмерные сделать? Сейчас, я уже понимаю, что вообще-то полигоны - это самое разумное и оптимальное решение, лучше которого, пока что теоретически ничего не существет. Воксели - ещё туда-сюда идея. И как ни странно, они имет своё применение - те же карты parallax глубины, это по-сути и есть воксели. Спрайты и трёхмерные эффекты - тоже воксели. Хоть сама по себе отрисовка вокселя - это вещь менее затратная, чем функции по отрисовке полигонов (воксель ведь достаточно всего лишь спроецировать), но вот описать сложную геометрию уже будет затратнее, это ведь получается придётся каждую точку проецировать, в то время как у полигона нужно спроецировать всего 3 точки, а остальное уже дело двухмерного отрисовщика треугольников. На счёт кривых NURMS, они точно не будут быстрее полигонов, просто потому что в каком-то смысле NURMS это такие же полигоны, только с точками изменяющимися нелинейно. То есть, если ранее достаточно было выполнить простой алгоритм брезенхема для отрисовки, то теперь вместо него работает вычисление некоторого более сложного закона. Вопрос: что быстрее "рендерить NURMS" или рендерить полигоны, это практически тот же вопрос, "что сложнее нарисовать 100 линий или 10 парабол". Кажется что 10 парабол то быстрее наверное! что вообще-то чистая правда. Вот только с увеличением сложности геометрии, эти 10 парабол превратятся в 200 парабол, при том, что приблизительно геометрию объекта можно изобразить всего 50 линиями. Вот 200 парабол против 50 линий, это уже значительно медленее. Это я так, рассуждения в сферическом вакууме. Резюмиру, разница между кривыми и полигонами, примерно такая же как между растром и вектором: растровые картинки хранить выгодно пока они маленького разрешения, а картинки высокого разршения выгодно хранить в векторе, ведь 4 точки и 4 параболы это быстрее чем матрица из миллиона точек. Вот только когда картинка не представляет собой треугольник на синем фоне, векторная картинка усложняется и в неё добавляется тысячи других механник. Что заставляет задуматься, а не проще ли обойтись растром? Хотя, это опять же демонстрирует, что некоторые элементы NURMS могли бы быть полезны в графике. А именно я намекаю на простые гладкие формы. Сделать сферу или ткань проще как раз кривыми безье, и это будет намного эффективнее, с точки зрения рендера (для физики вроде как безразлично, 4 параболы у тебя или 4 линии, так что на других расчётах это не скажется). Но что касается более сложных объектов типо тела человека, то выигрышнее всё же использовать полигоны.
Аноним Вс 29 сен 2024 15:01:52ИзмененоИзменено модератором№452783124
А. Что-то меня пробило на флешбек. Вспомнил какая у меня идея была в детстве. Криволинейная графика + воксели. Я вот только что сказал, что при помощи "криволинейных" пресетов можно намного быстрее отрисовать условно "сферу". Ведь что такое сфера - это всего лишь круг с шейдерами. Проекция сферы всегда будет кругом. Но вот только сразу возникает вполне резонный вопрос "на какой чьёрт мне ваш круг? куда мне его заcунуть куда применить? я хочу модельки красивых дренеек в трусиках, а не чёртов кружочек.". И вправду. Когда мы начинаем делать что-то сложнее круга из составных частей (например при помощи кривых безье), то тут же алгоритм отрисовки усложняется в разы, и становится намного медленее чем тот же результат на полигонах. Вот только, возникает другая идея: а что если найти применение примитивам"? Сфера, ну кружочек какой-то, только солнце нарисовать им можно, а ничего прочего абсолютно круглого в мире нет. А если добавить на неё воксельную карту глубин, и превратить сферу в голову? достаточно ведь вокселями часть формы искривить. вот это уже интереснее подход. Но всё это так... пища для размышления. Полигоны показали свою эффективность уже много лет, и копротивляться против классического подхода смысла особо не имеет.
Аноним Вс 29 сен 2024 15:08:37ИзмененоИзменено модератором№452807125
К тому же есть способы рендеринга вообще без расчёта геометрии! Это так называемый "рей трейсинг". Это подход, при котором мы из камеры выпускаем "линии" (лучи), (точно также как мы делали с Z-буффером), а каждый объект у нас начинает иметь свойство "отражать" линии, то есть выпускать обратный луч, но под некоторым углом и интенсивности, цвета. И мы, просто камерой, практически как глазом, ловим эти лучи и рисуем на их месте соостветсующий цвет. Насколько я знаю, это очень медленный тип рендерига, его используют не для игр, а чтобы красивые картинки заранее рендерить.
Аноним Вс 29 сен 2024 17:25:13ИзмененоИзменено модератором№453049126
проволочный рендер сделан. Значит парсел wavefront импортирует всё верно.
Короче, если не выёбываться, и не строить из себя самого умного, и просто взять и списать решение с интернета, то там вообще всё по-другому. Все эти функции по нахождению пересечения линии и плоскости - в помойку. Там идут более хитрым способом. Вместо того, чтобы пытаться спозициронировать плоскость в пространстве, камера вообще никаких плоскостей не содержит. Камера по-сути содержит только свою систему координат. И координаты любого объекта, пересчитываются в систему координат камеры, и затем, проецируются прощённой, ранее известной формулой x' = xfov/z y' = yfov/z В каком-то смысле это и есть проекция линии на плоскость. Вот только плоскость и линии специально подобраны так, чтобы умещаться в этот частный случай. Вывод формул я не понял. К тому же что в интернете часто матрицами пользуются. Страшны не сами матрицы, вроде как программу второго курса вспомнить могу.. проблема в четырёхмерных матрицах. Везде используют "кватернионы" и четырёхмерные пространства для удобства. Вроде как оно иногда бывает понятно, что вот в этом четвётром измерении мы можем хранить что-то дополнительно, но это вообще-то как-то неинуитивно и непонятно, типо вот что это за четвёртая фигня. Но, на самом деле так то пофиг. Математика интересная вещь, но лень, хочется чего-нибудь забабахать забавного и смешного! Надо делать тупое пока не надоест
Протестил программу с флагом -DRATIONAL, который включает мои числа с фиксированной точкой (я типо чтобы не заморачиваться сделал две обёртки - первая просто макросы повторяющие обычные операции над флоатами, вторая - числа с фиксированной точкой). И всё... программа снова не работает. Оказалось, я ошибся в операциях как раз в тот момент когда переводил их в макросы. У меня неправильно были сделаны вычитания и сложения. 0 - 1 выдавало 1, а не -1. Я уж было подумал бросить это бесполезное дело. Но что-то мне стало лень, подумал, может всё-таки пофиксить эти числа. В общем-то, частично всё пофиксил. И оно работает. Вот только когда запускаю анимацию - программа не выводится. Зависает на функции RenderWireframe. Надо чинить
Вот это тряска. Причём считается намного дольше чем флоаты. И зачем нужны эти "числа с фиксированной точкой". В википедии столько пиздежа про их эффективность, а на деле одни тормоза неточности и лаги.
Короче впизду. Нахуй это говно. 1. Оно работает в 2 раза медленее 2. Оно постоянно переполняется при малейшем пуке. 32 битов просто не хватает. а 64 бита считаются в два раза дольше. 3. Оно даёт ебейщую погрешность и тряску. 4. Чем больше оптимизируешь, тем менее защищёнными становятся операции над числами. 5.Записи функций становятся навыносимыми. Место x*y - z приходится писать rat_sub(rat_mul(x,y),z) или вовсе расписывать функции построчно, будто бы пишу на особо стрёмном виде ассемблера. Короче впизду. 0/10. 100% говно. 0% профит. Кал калыч, кал говна. Перепишу всё на флоаты. На них всё нормально работает. Пусть если кому-то нужно, у кого FPU нет, тот сам на уровне компилятора С свои флоаты и реализует.
Я это к тому, что если программа тупит по причине "чисел с плавающей точкой", то проблема явно не в числах с плавающей точкой, а в безграмотном их применении, потому что лично на моём FPU такие числа считаются довольно быстро. Целые числа считаются примерно в два раза больше, вот только для того, чтобы реализовать "минималистичный" набор функций, необходимо как минимум две операции (умножение и битовый сдвиг и деление и битовый сдвиг). То есть такие числа уже такие по скорости примерно одинаковы с флоатами. А если их сделать ещё и "по уму" чтобы не было деления на нуль, чтобы они не переполнялись, чтобы могли поддерживать как отрицательные так и положительные значения, то скорость таких конструкций оставляет желать лучшего. 7 сек против 4.
Идея создать структуру "шэйдер", в которой хранилось бы три компонента: typedef struct shader_t{ void ⚹data; int(⚹Constructor)( void ⚹userdata); void(⚹Plotter)(window ⚹w,int x,int y, int color, void ⚹userdata); int(⚹Destructor)( void ⚹userdata); } shader; Эдакое ООП на чистом С (хотя ес честно я незнаю правильно ли это так называть). Суть в чём. Функция RenderShaded берёт в качестве аргумента шэйдер (кроме всего прочего берёт также "сцену", в данном случае это всего лишь объект, так как никакой "сцены" я ещё не придумал, и также берёт "окно" для отрисовки). Этот шэйдер имеет конструктор, плоттер, деструктор и пользовательские данные. Что такое "пользовательские данные"? Это всё, что необходимо дать программе, для отрисовки треугольника. Это могут быть текстуры, или ещё что-то что как-то может задействоваться в функции plot. Например, там может быть z-буффер, текстуры итд итп. Функция Constructor вызывается чтобы инициализировать данные перед отрисовкой, например, эта функция может заполнять z-буффер для текущего расположения камеры. Деструктор, собственно, этот z-буффер очищает. Плоттер, ставит точку с учётом этого Z-буфера, и с учётом текстуры Тогда можно функцию оставить такой же миниатюрной какая она есть, и добиться гибкости, чтобы можно было создавать разные "шейдеры"
Что-то ночью я задумался: а не дурак ли я? И походу эта.. дурак. Все ведь эти проблемы со сложением и вычитанием чисел с фиксированной точкой вызваны тем, что у меня тип unsigned int . Приходится самому програмно реализовывать "отрицательные" значения, а значит и операции с учётом этих отрицательных значений. А почему я не сделал просто int ? Unsigned int кажется логичным, поскольку мы же делим 32 бита на части, что не входил в концепцию "знакового числа" которое на половине переполняется. Но вот сейчас до меня дошло. А зачем я вообще этим байтоёбством занят? Мне, благородным языком С уже дана абстракция целых чисел, (как отрицательных так и положительных). Битовый сдвиг - эквивалентен домножению на 2^(n) для любого целого числа. Так тогда пусть они и сдвигаются с учётом знака, мне то какая разница как там биты заполняются, если результат будет интерпритирован верно.
Я уже перепелил всё под флоаты. Но может если сейчас реализовать fixed-point то может можно внедрить их куда-нибудь.
А можно ведь сделать более гибко: каждую функцию с флоатами, дополнительно переписать под fixedы, и вторую часть отделить макросом условной компиляции #ifdef FIXEDPOINT #endif, и компилировать "выборочно". Некоторые функции например, может лучше оставить float, а некоторые fixed
И ведь идеально работает. Пока что никаки ошибок typedef int fixed; #define N 8 #define mul(a,b) ( ((a)*(b)) >> N ) #define div(a,b) (((a) << N)/(b)) #define FIXED_TO_FLOAT(f) ( (float)(f) * pow(2,(-N)) ) #define FLOAT_TO_FIXED(f) ( (fixed)((f) * pow(2,(N))) ) Почему я раньше так не сделал?!
Ну... Переполнения всё равно ломают весь рендерер. Тогда я сделал чисто растеризатор на фикседпоинтах, но это всё равно никакого выигрыша не дало. Примерно одинаково: 5.56 seconds на Floats 5.61 seconds на Fixed Что я и говорил. Современные железки хорошо сделаны, их так просто не обманешь. Но, во всяком случае, минусом это явно не будет, поэтому оставлю фиксед-поинты в тех местах, где диапазон значений заранее известен. Например, в функции Rasterize которая закрашивает треугольник, все точки заранее подрезаны под экран. Так, что там не может быть впринципе чисел больше чем 4096 (по крайней мере я не видел таких экранов). При вычислении интенсивности света, тоже очевиден диапазон от 0 до 1, можно применить фикседпоинты, разницы особо не будет. Зато может на некоторых старых некропк они дадут прирост производительности.
Вот так примерно буду упаковывать userdata. Тупо все указатели в массив, затем передаём в юзердата и интерпритируем.
Переписал модуль IO под винду. И всё работает. Единственное, на винде функции SetPixel и CloseWindow заняты стандартным апи. Поэтому я переименовал все функции префиксом io_, чтоб точно ни с чем не было конфликтов в будущем.
Это Z буфер это победа Кстати, вот ещё одно применение чисел с фиксированной точкой: буфер глубины. Он как раз требует чёткий диапазон значений, от максимального до нуля.
Со структурами типа "шейдер" не стал заморачиваться. Тем более что обычно такой подход в С заранее карается какой-то бессмыслицой. Если уж классы делать то на С++, а на С практически всегда лучше оставить всё как есть, и пользоваться тем что дано. Поэтому, я тупо сделал отдельное поле в камере fixed ⚹⚹zbuffer, которое инициализируется при создании камеры, и заполняется функцияей FillZBuffer, которая представляет собой по-сути тот же рендерер, только в качестве отработчика Plot, тут выступает функция DepthFilter, которая вообще не вызывает io_SetPixel, ни в каком виде, а просто заполняет буфер по указанным x и y. Рендерится этот буфер в один подход, просто перебором всех точек. Очищается также, просто двумя циклами перебираются все точки и обнуляются. При отрисовке затенённого объекта с учётом Z-буфера, работает другой отработчик, который называется DepthPlot, он уже берёт из Z-буфера точку, сравнивает её с отрисовываемой, и в случае если точка имеет аналог ближе, то не рисует. Удобно, что функции "отделены", так что каждая помещается в 80 строк. Нет путаницы. Однако есть и минус: если пользователь не знает ничего про Zбуфер, то программа просто ничего не отрисует, если не найдёт заполненного Zбуфера. Простое решение - поставить флажки, о необходимости z-буфера. Но мне почему-то кажется, что есть решение по-лучше, поэтому пока что оставлю так.
Короче, я хотел сделать так, чтобы типо буффер заполнялся тогда же когда и рендерится. Но я не понял как это сделать. И чисто теоретически: чтобы понять, лежит точка ближе или дальше от нас, нам нужно вначале знать, а нет ли на другом каком-то полигоне другой такой же точки. Чтобы это узнать, нам для начала нужно просканировать всю модель, а потом уже принимать решение. Полигоны ведь порядка не имеют, поэтому, по-сути невозможно найти "противоположный полигон" не перебрав все полигоны. Поэтому, я пришёл к выводу, что это какая-то глупая и нереалистичная идея. Поэтому, я просто добавил флаг buffer_refil_required. Если он истина - буфер нужно заново заполнить. Если ложь, то рендеринг происходит с тем буфером, что есть. Буфер надо заполнять, если камера сдвинулась. Мы же не можем предсказать, камера будет ближе или дальше, поэтому если буфер заново не занулять, то он может просто отражать неверную картину. Поэтому, все эти манипуляции с zбуфером, я сделал статиками. Теперь это внутренние функции модуля. При инициализации камеры, ставится флаг о том, что необходимо заполнить Zбуфер. Если камера повернулась - буфер обнуляется, и ставится флаг о том, что нужно заполнить Zбуфер. Сам буфер заполняется в функции рендеринга. Если у камеры есть флаг, что необходимо обновить z-буфер, то функция рендеринга вначале заполняет буфер, а потом уже рисует объект. Если такого флага нет (например, камера не поворачивалась), то функция рисует по старому буферу.
Ура! запилил текстурки, при помощи самописного модуля tgatool. Вообще, tgatool это моя первая программа, (хотя это не программа это интерфейс для импорта, экспорта и рисования на tga картинках. Формат tga я выбрал только из-за простоты). И вот, это поделие мне пригодилось.
Дополнил модуль Wavefront.c функцией CalculateNormals. Теперь, даже если в модели нет нормалей, функция вначале вычисляет все нормали, и потом уже выполняет затенение.
Итак, графический модуль готов на 99%. За сегодня я сделал функции ImportObjEmbed и ImportTgaEmbed, для импорта файлов изнутри самого файла .с представленного в виде массива. То есть, теперь есть возможность "вшить" некоторые объекты прямо в игру. Чем это может быть полезно? Ну как минимум сделать базовый графический интерфейс какой-то, если необходимо. Чтобы игра не ломалась если меню удалить. На винде эта функция работает костыльно. Оказывается на винде нет fopenmem ни в каком виде (там ядро ос просто не позволяет себе таких вещей). Поэтому я сделал функцию которая просто все массивы записывает во временный файл, затем открывает этот временный файл и отдаёт дескриптор. Работает. Перевёл ещё несколько функций на fixed-point. Что дальше? "отполировать" модуль можно ещё тремя новшествавами: - клиппинг трехмерного пространства. Аналогично модулю basics, надо бы найти простейшие алгоритмы которые отрезают модели по границам камеры. Это наверное самое важное новшество, потому что модели которые находятся вне диапазона видимости камеры все равно тратят обороты расчёта рендерера, и тем самым добавляют много лишней работы. - Полный перевод на числа с фиксированной точкой. Я так посмотрел: да нормально они работают. Возможно в прошлый раз я просто что-то не так сделал. Нужно выкинуть вторую версию на fixedах. Авось и полезно будет. - Поддержка альфа-канала. Неплохо было бы создать некоторый "прозрачный рендер". Зачем?! Как минимум, сделать имитацию падающих теней. Сами тени я рассчитывать не хочу, как и динамический свет делать в мои планы не входит. А сделать прозрачную тёмную лужицу-текстуру под объектом - вот это было бы прикольно. Также, раз уж есть альфа-канал, то можно и стекло делать, в стиле cs 1.6.
Далее нужно уже задумываться и об игре. Иерархия кода следующая. Вся игра состоит из трех глобальных объектов: окно, ввод, и сессия. Окно - это реефикат таких понятий как "экран, дисплей, окно конкретного фреймворка, звук, видеобуфер, итд". Одним словом, окно это единственный универсальный ввод-вывод. На окне мы можем рисовать, у окна мы можем запрашивать обновление событий, окно мы можем попросить обновить картинку. Далее, ввод - это информация о том, что ввёл пользователь, какую клавишу нажал. То есть ввод - это просто структура в которой есть вся информация о клавишах, о мыши итд. Далее, сессия - это "данные о текущем состоянии игры". Тут хранится указатель на уровень, state игры, (на паузе она или нет). Сессия нужна для управления всей игрой вцелом. Операции над сессией это: подгрузка уровней, изменение режима ввода, итд (в общем, все что лежит вне игрового процесса).
main() { w = InitWindow(); c = InitControls(); s = InitSession(); while(s.state! = exit){ switch(s.state) { case menu: MainMenu(&s.events) ; break; case loading: LoadLevel(&s) break; case playing: GameLoop(&s.events) ; break; case paused: PauseLoop(&s.event); break; case error: HandleError(&s.events) break; case exit: } }
Это не конечный автомат. Логика конечного автомата тут нарушается, из-за того что каждый отработчик блокирует выполнение главного цикла. Это по-сути цикл управления игрой. Каждый отработчик сообщает некоторый результат своей работы, в зависимости от которого игра переходит в следующий режим. Например, отработчик MainMenu проигрывает по-сути программу для подгрузки уровней. Отработчик LoadLevel смотрит на "очередь уровней" и подгружает тот, что необходим. Отработчик GameLoop выполняет неблокирующий PollControl (запрос на обновоение ввола), затем обновляет шаг игровой логики, затем вычисляет физику на основании времени пройденного с предыдущего игрового цикла, затем вычисляет перемещения анимированных обьектов, затем рендерит все видимые объекты. Представить можно так GameLoop() { scene = InitScene(level) while(response == none) { PollControl(&c); UpdateTime(&t) UpdateGameLogic(c, &scene, t); UpdatePhysics(&scene, t); UpdateAnimation(&scene, t); RenderScene(w, scene); UpdateFrame(w) ; } } Как тут можно заметить, некоторой общей "сесией" для всех компонент игры является "сцена". Сцена - это набор "сущность". Под "сущностью" подразумевается вообще любой игровой объект, будь то это просто декорация или невидимый хитбокс. Сущности имеют свойства, которые автоматически прикрепояют их к конкретному отработчику. Например, физика: объекты могут быть проницаемыми, т. е. через них можно будет пройти. А могут быть и твердыми, что означает что через них невозможно пройти. И для тех и для других нужно рассчитывать столкновения. В случае с проницаемыми столкновениями, их нужно только высчитать, и отдать игровой логике еа отработку. В случае с колизиями, для них нужно принять чёткое решение о изменении физических параметров (например, вес объекта вызывает противоположную силу реакции опоры, или силу натяжения нити. То есть нужно рассчитать вектор этой силы, и на основании сил принимать решение об ускорении точек). А есть объекты - декорации, для которых и вовсе расчёт даже столкновений заранее не нужен. Также и с анимацией: есть статические объекты, для которых изменения за кадр считать не нужно, а есть анимированные, которые в теории могут потребовать воспроизведения анимации. Также и рендер: есть объекты которые следует ренлерить плоским шейдером, есть те к которым необходимо применять сглаживание гуро, а есть и вовсе невидимые объекты, которые рендерить и не нужно. Затем идёт отработка кадра, и повторяется все заново. Игровая логика, в данном случае, стоит "над" всеми этими системами, потому что все системы обращаются именно к ней, а она к ним. Что может делать игровая логика? Игровая логика может подавать управляющие сигналы системам: мол "я нажал кнопку, прибавь-ка силы по оси x, (или просто энергии) к точке моего персонажа, на что физическая система в дальнейшем среагирует так " точка действует силой F на стену, следовательно стена действует на точку с силой N, а значит они вычииаются и ускорение равно нулю". Игровая логика как бы формирует воздействие на системы. Например, игровая логика считывает из сессии что произошло столкновение (этот расчёт сделала физическая система например), и после этого она решила активировать анимацию, поставив соответствующий флаг для этого, и указатель на следующий кадр. Тогда система анимации начнёт анимировать этот обьект.
Вот примерно такой концепт накидал для устройства игры. В будущем надо будет его подровнять, сделать более внятным. Но пока вот так.
Короче, не понял как реализовать клиппинг трёхмерного пространства. В гугле только двухмерное выдаёт. Чатжпт говорит что у меня он уже реализован, при помощи far и cam_z < 0 у камеры (типо, чтобы не отрисовывать объекты которые из-за дальности превратились в точку и объекты которые позади камеры или паралельны камере ), а боковые подрезки это типо двухмерные клиппинг-механизмы реализует. Хз может врёт. Чатгпт может и фигню говорить.
Придумал как реализовать альфа-канал. В этом же наверное нет ничего сложного. Это нужно взять цвет из видеобуфера, и правильно его смешать с заданным цветом. Тогда можно и альфа-текстуры делать. Собсна, вот! прозрачный розовый BSD шар. Теперь при помощи этого можно делать тени, стекло и воду.
Чё там ещё? Хотел было перевести рендер на fixed-point, но потом понял, что это снова придётся таблицу корней делать. А таблицу корней нельзя никак ограничить, поэтому придётся делать большую таблицу корней. Большая таблица корней никуда не поместится, поэтому придётся делать интерполяцию... ну впизду короче, пусть на флоатах будет. Если таблицу косинусов ещё можно сделать, то таблица корней - всё, картоп.
Самое моё нелюбимое в паттернах, это что всегда когда ищешь паттерн, единственное что находится, это туполылый умник который такой: "ээээ ну эта короче вот компонент вот система... эээээ всё понятно?" и нихрена не понятно. 0 полезной информации. 0 сути. Только общие красивые слова типо "вот смотрите это "систееема"". Нужно не общие слова, нужна суть. Нужно придать свойства такому понятию как "система". Нужно придать свойства такому понятию как "компонент".
Так. Попытаюсь понять паттерн ECS при помощи размышлений. Энтити - это "сущность". Пустышка другими словами. Какие свойства есть у "энтити"? 1.Идентификатор. Все энтити имеют свой id. 2.Компоненты. Одним словом:
typedef struct entity_t{ int id; void *component[total]; } entity;
То есть энтити - это какое-то количество разных данных (свойств), с одним id.
Что же такое "компонент"? Это данные какого-нибудь типа. Например wavefront_obj, или vector, или ещё какие-нибудь. Причём, чем более "несвязные" типы данных, тем лучше. То есть "тёплое, мягкое, квадратное и координаты" - это хороший пример компонент. Важно подобрать компоненты так, чтобы каждый компонент был независим. То есть, нельзя объединять компонент "точка" и "3dмодель" в один. Почему? Потому что точка может быть без 3dмодели. А модель, в общем-то можно отрисовать и без точки (например, если он всегда стоит в нулевой координате, то есть земля например какая-нибудь). Совсем неинтуитивно, но также дела обстоят и с текстурой. Чево?! Да. Текстуру имеет смысл представить отдельным свойством. Казалось бы: "а зачем? у нас ведь кроме объекта нет ничего чтобы могло брать на себя текстуру? текстура ведь отдельно от объекта не существует". Но легко представить ситуацию, когда текстура нужна как отдельное свойство. Это, например погоревшее делево. Моделька дерева типо "сгорела", и у неё должна измениться текстура на обуглевшую. И вот на этом моменте становится понятно, что текстура тоже является независимым свойством. (это конечно в том случае, если в игре имеет место изменение текстур). Из всего этого можно представить следующий список компонент: 1.Wavefront модели 2.TGA картинки 3.Векторы (по-просту, можно сказать "точки") (этот компонент помогает явно асбтрагироваться от визуала, и понять, что в общем-то компонентом может быть даже простой int, никак не связанный ни с визуалом ни вообще с игровым процессом. Главное что это какие-то данные, т.е. свойство чего-то). 4.Анимации (свой формат) 5.Кнопки, панельки, формы ввода, клетки. Одним словом, то что описывается структурой GUI (к слову, гуй можено и не включать в компоненты. Но если игра предполагает "игровой гуй" то есть, допустим диалоги с персонажем" то лучше всё-же включить это в ECS. 6.Сессия искуственного интелекта (ну, то есть если энтити - это какой-то NPC, то у него как у конечного автомата должна быть своя "сессия"). 7.Видимость для рендера. 8.Состояние персонажа (здоровье, сила, ловкость).
Надеюсь, суть ясна. Компонент - это данные, практически как "тип данных". Как уже организовать эти данные - зависит от автора. Если игра, например, не предполагает изменения текстур, то как раз модель и текстуры тогда удобнее объединить.
Далее, "системы". Системы, это отработчки "энтити". Каждая система, отрабатывает "энтити" в зависимости от данных которые в ней вложены. Например, если в энтити есть данные "3д модель" и "объект видим для рендера" то RenderSystem отработает такой энтити. Логика построения систем также лежит на авторе, но как уже понятно, имеет смысл делать системы так, чтобы все данные играли какую-то роль. Тот же пример с текстурой: если в игре все текстуры статичны, и отрабатываются, разве что модулем для анимации, то выделять тестуру в отдельную сущность никакого смысла не имеет, ведь она ни одним отработчиком не будет задействована. Какие у нас могут быть системы. 1.RenderSystem - рендерит всё что можно отрендерить. 2.AnimationSystem - мониторит анимации, и выполняет итерацию для активных. 3.GUI - система кнопок, инвентарей, диалогов, изменяет состояние кнопок в зависимости от ввода. 4.Physics - механника точек. обнаружение столкновений. 5.AI - отдельно ведь нужно искуственный интеллект просчитывать! совсем забыл уже. 6.GameLogic - основная игровая система. 7.Аудиосистема. Система ввода лежит за пределами ECS так я понял, просто потому что у неё нет компонент. Кстати о компонентах. Компоненты - это некоторые типы данных используемые в игре.
Наверное, этот паттерн нужен уже тогда, когда все системы готовы. Пока даже и неизвестно, сколько всего типов данных получиться. Поэтому, далее нужно делать модуль анимации, а затем модуль физики.
Поспал. Мне приснился кошмар, что в моём доме живут десять скуфов и таджиков, и я не могу их выгнать, потому что выгонять их теперь незаконно.
Зато немного переварил вчерашнюю информацию. Я понял некоторые поправки относительно ECS. Во-первых: Компоненты не то чтобы должны быть "несвязными", компоненты скорее должна быть наиболее широкими и представлять некоторую характеристтику. "несвязанность и абстракция" я бы сказал. Компонент не должен подразумевать, что его как-то конкретно применяют. То есть, компонент должен передавать чистое свойство, без какого-то применения. В таком случае, компонент "текстура" это всё-таки плохой пример, ведь текстуры всё же бывают только на обхектах. Хорошим набором компанент было бы:
enum set{ mesh, image, camera, animation }
Потому что теперь, image можно использовать как свойство текстур ддля трёхмерных объектов, и как свойство битмапа для кнопок.
В это то и суть ECS как я понял, в эдакой "ориентированности на данные. Чтобы Entity полностью определялось набором компонент. Вот взять компонент (т.е. свойство) "камера" у энтити. А что это за камера? Это "указатель на принадлежность к камере на которую следует спроецировать модель" для рендеринга? Или это сама по себе камера, как независимый объект, который можно инициализировать, повернуть, удалить. Или это "камера игрока" которой он может крутить и управлять. Ответ: как угодно, хоть все три варианта сразу, как пожелает разработчик. Так, собственно и можно сделать. 1.Если в энтити нет ничего кроме камеры, то эта энтити и есть камера. К ней можно применять функции для камеры. 2.Если в энтити есть камера и 3д модель, то 3д модель тут главнее. В таком контексте, это "указатель" на камеру для трёхмерной модели, т.е. та камера на которую её нужно проецировать. Если тут стоит NULL, то модель не нужно рендерить вообще. А если стоит какая-то другая камера, то может следует отрендерить с другого ракурса. 3. Если в энтити есть камера и "игрок", то значит, это камера которой управляет игрок. Т.е. крутится она должна уже по в зависимости от того как игрок вращает мышкой. При этом 3д модели может и не быть вообще (например, если игрок невидимый). То есть, если в пункте 2, у нас модель была основной (без модели, энтити превратился бы просто в пункт 1, камера), то если присутствует компонент "игрок", то модель уже отходит на третий план, и камера теперь уже свойство игрока а не модели. Причём, игрок то может не содержать в себе камеры. В таком случае камера может быть просто статично где-то закреплена, а игрок просто ходит и рендерится как в сайлент хиле, с неподвижной камерой.
Всвязи с тем, что мы можем рассматривать камеру как "свойство", я подумал, а в чём ценность такого свойства, если сам рендерер содержит функцию "perspective projection", что как бы предполагает что рендерер заранее знает что камера именно перспективная, и использует метод камеры. Непорядочно выходит! не по-полочкам!
Тогда, решил вот такую фигню отчебучить. Projection это у нас указатель на функцию. И камера, содержит метод Capture типа "указатель на функцию Projection". Тогда получается. что можно сделать два вида проекции, допустим перспективная и ортогональная. И внутри функцию Render, сам рендер будет вызывать метод камеры. Таким образом, далёкие объекты вообще можно рендерить чисто ортогонально. Либо ещё что-нибудь. Короче, это более гибкий и логичный подход, мне больше нравиться, так как выглядит более красиво, поэтому оставлю так.
Ахаха. Я сегодня сделал функцию RescaleImage. Подумал, раз уж реализация текстурок научила меня тому, что в интерполяции ничего страшного и сложного нет, то почему бы не сделать механизм растягивания изображений.
Изначально я предполагал, что благодаря этому, можно будет взять один "шаблон" для кнопки, и задать ему разные размеры, а он будет растягивать картинку под эти размеры. Итог:пикрил. Растягиваются все картинки. И не удивительно, ведь картинка по-сути содержит просто указатель на холст. Холст не дублируется. Причём, остальные картинки например "выделенной иконки", остаются прежними в размерах, ведь картинка скалируется только когда инициализируется кнопка.
Ну и хуета. Но забавно, мне нравится когда ошибки выдают "забавный" результат.
Почему-то кнопки тормозят. Если с флагом -O3 откомпилить, то торможение уйдёт. Но всё какая-то фигня. Что-то я неоптимально сделал. Даже 3д модель не тормозила, а кнопки тормозят.
Хз. Может в вычислении "середины надписи" дело. Там цикл стоит. А цикл - всегда источник потери лишних тактов.
Пофиг, пока оставлю так. По крайней мере, я вообще не планировал делать кнопки с надписями, потому что все кнопки должны быть нарисованы в интересном стиле каком-нибудь, где "просто шрифты" явно не подойдут.
Самое нудное и скучное в разработке - это создание 3d моделек. Даже на простую лоупольку может уйти 1-2 дня (это с текстурингом). А ведь некоторые модели ещё и анимированными должны быть. Разработка игровых ресурсов наверное занимает даже больше чем разработка движка.
Сделал так называемые "дженерик" кнопки. Это кнопки которые не содержат битмапов, и отрисовываются встроеными алгоритмами, со встроенным шрифтом. Хз зачем, просто чтобы были если вдруг лень будет с битмапами таскаться. А, вот, нужно сделать чтобы их поля находились в "виджете" а не в "скине виджета" как это с обычными кнопками делается, чтобы можно было множество кнопок разных размеров создать, но с одним цветовым скином.
Тормозит похоже что ещё из-за OBS. Тесировал кнопки без OBS всё работало быстро. Вот, сделал систему GUI получается. Теперь можно под неё разрабатывать "ползунки", "инвенари", "миникарты" и другие элементы что используются в игре. GUI по-сути выполняет только отрисовку и "реакцию" свойственную определённому типу элементов. А отработчик WidgetHandler заточен под то, чтобы придать виджету функционал, т.е. отдать что-то "сесии" основываясь на внутренних полях кнопки. Тут сразу несколько паттернов используется: - стратегия - наблюдатель - синглтон.
Стратегия заключается в том, что сама по себе "система виджетов" просто хранит общие свойства "какого-то" абстрактного виджета. Каждая ячейка хранит "текст, координаты, смещение, статус, тип, скин" итд. А то, чем является виджет определяет поле "тип" которое задаётся исплючительно функцией, типо AddButton, AddMenu итд.
Наблюдатель, заключается в том, что все виджеты вызывают метод "действие" каждый цикл (либо может быть не вызывают его). Суть в том, что каждая кнопка - это "подписчик клавиш управшения" и она выполняет свои функции в зависимости от поступления новой информации.
Синглтон заключается в том, что есть такой объект как "дженерик шрифт" соостветственно для дженерик кнопки. Он создаётся единственный раз, при первой инициализации кнопки. В будущем, если создаётся кнопка с другим скином, новый скин просто "перенимает указатель" на уже созданный шрифт. Скин сам по себе отчасти выполнен как "синглтон". У скина существует "количество ссылок". И при инициализации новой кнопки, вначале проверяется, присвоен ли этот скин кому-то ещё. Если присвоен - то количество ссылок у скина увеличивается. Когда удаляется - напротив, количество ссылок уменьшается. И в случае, если удаляемый объект содержит скин с 0 ссылок, то этот скин из памяти удаляется. Тут правда есть большое неудобство: а что если мы заходим убрать все кнопки, а потом заново восстановить. А все скины уже удалили. Так что хз, может я зря это сделал. Может не нужно было такое "самоочищение" организовывать.
Весь день убил на этот гуй всратый. А толку? Всего то ещё один дополнительный вид кнопок добавил. И ведь нужно всякие другие элементы сделать. Например "ячейка" - ячейка инвентаря. Как в world of warcraft, minecraft и других играх. Часто используемый элемент гуя.
Ещё неплохо было бы сделать 2.5D объекты. Т.е 2д картинки, которые скалируются в зависимости от удалённости от камеры. и рендерятся в точке. Как в Doom короче. Такими объектами можно было бы сделать эффект "старой" графики. Плюс всякие динамические фишки типо "полоски урона".
Поправочка. Односвязный список же перебирается в обратном направлении. Значит нужно в обратном направлении и записывать кадры в файл, чтобы при воспроизведении он читал их в правильном порядке.
таааак... какие-то проблемы с анимацией. Придётся переделывать. Надо поступить по-другому. Сейчас у меня анимации рассчитываются исходя из изменения координат, т.е. dx, а нужно чтобы они явно указывали x, т.е. какая координата будет в следующем кадре.
Результат получше. Есть свои плюсы и минусы так сказать + Обратимость. При желании можно отмотать анимации сразу на несколько кадров. Не требуется "сохранительного" буфера для начального положения. + Предсказуемость. Отсутствует дифференциальная ошибка (если выражатся языком ТАУ). Анимация в любой её форме является зацикленной, так как возврат в обратное положение происходит автоматически, за счёт того что все кадры - просто указывают координаты. Таким образом, можно не беспокоится что анимация куда-то "улетит", из-за отсутствия зацикленности. - Требовательность вычислений. В отличии от анимации "по рассогласованию", для реализации ключевой анимации требуется "синтерполировать" координаты между кадрами. Если при dx достаточно было это dx поделить на количество фпс, то тут вначале нужно вычислить этот самый dx причём для каждой точки. Наверное придётся оставить этот вариант, так как предыдущий череват помехами.
Я знаю немного язык программирования С.
Нужно курить мануалы как с драйвером монитора. Предполагаю, что на FreeBSD должен быть некий набор системных вызовов для работы с монитором. Есть конечно уже готовые api типо Xlib, но у нас нет права получать зависимости на пустом месте. Xorg довольно устарел. Игра должна быть независима от какого-то всратого Xorg.
Итак, поняв как работать с драйвером монитора, мы можем выводить визуальную информацию, закрашивать пиксели. Следовательно нам нужно создать алгоритмы 3D графики. Алгоритм брезенхема, Z-буфер и тд. и тп.
Теперь самое сложное. Как, собственно, разработать игру? Для этого нужно задаться вопросом, а что такое вообще игра?
Вот персонаж в игре - это некий конечный автомат, который получает события с клавиатуры, и под них меняет своё состояние. В это же время, события могут поступить не только с клавиатуры, но и сами собой, по каким-то внутренним причинам. В конечном итоге, событие может быть просто фактом истечения некотороего времени. И игра, это некоторая сложная система, которая все эти факторы как-то организует.
Как сделать самую простую 3d игру с самой простой архитектурой, чтобы потом просто немного расширить функционал? Создание игры получается сводится к срзданию своего xorg.