Вот, человек понимающий как нужно делать. Честно, такой подход напрашивается. Но игроки en masse, к сожалению, плохие тактики, с реакцией у них не очень, да и интуиция времени хромает, не говоря уж о топографическом кретинизме. Вроде бы всё это просто и даже полезно развивать, но им, видите ли, неинтересно. Но они, собаки такие, массовые. Вывод — делать шутеры разных сортов. Годные — для ценителей. CoD'ы — для хомячков.
Интересно, однако как-то мыльновато получается и с наклонными линиями плохо справляется. Подойдёт для имитации плохого качества картинок в старых игровых журналах. А смысл пиксель-арта всё-тики в чётких линиях (без мыльности) и красивых кластерах пикселей (без бандинга и пр.) т.е. для качественно pixel art super resolution нужна особым образом натренированная нейросеть ^^
Пятиминутка придирок конкретно к коду. Какой-то C++ тут не очень.
1) Зачем передавать объекты в функции копированием, а не, скажем, константной ссылкой? см. float Distance( Vec2 a, Vec2 b ) и void ResolveCollision( Object A, Object B )
2) Зачем копировать объекты из «многообразия», опять же вместо создания ссылки? см. AABB abox = A->aabb
3) Разве в С++ уже есть операция возведения в степень через ^? см. (a.x — b.x)^2 + (a.y — b.y)^2
4) Проверять floating-point значения на равенство вместо допуска (epsilon) плохая идея. см. if(d != 0) или if(n == closest) (тут как минимум отсутствует указание допуска)
5) Зачем копировать вектор если значения потом затираются, а до этого не используются? см. Vec2 closest = n
6) Что за тип real? см. real d =
7) Зачем дважды вычислять сумму квадратов компонентов вектора (dot product с собой), первый раз при проверке LengthSquared(), а второй сразу же после этого при взятии Length()? см. if(n.LengthSquared( ) > r) и float d = n.Length( )
Согласен, тень можно сделать нужной чёткости и на правильном расстоянии, если это поможет в восприятии текста. Также часто в играх используется обводка текста, опять же правильно подобранной толщины и прозрачности. Так что всё может быть полезно, если в меру и с умом.
Плавно перематывать назад по записанным данным ввода можно будет только дискретные действия, например, четкое передвижение на клетку вверх/влево/вправо/вниз, или простое ожидание. А вот всякие цепочки последствий от, в общем случае, необратимых взаимодействий таким образом перемотать сложно т.к. нужно тогда делать ключевой кадр (snapshot) в прошлом и постоянно доматывать от него до нужного кадра в будущее, что может быть слишком медленно. Это как играть видео назад в видеоплеере из упакованного инкрементального формата.
Ввод хорошо использовать для реплеев при детерминированной механике в т… ч. идентично настроенных генераторах случайных чисел. Скажем, реплеи для игр типа Clash of Clans занимают минимум места, там просто указано в какой момент какие команды задействовал игрок, но они привязаны к версии механики игры, которую нужно тем или иным образом стараться не сломать в новых версиях (записывать версию в реплей и уметь играть механику старых версий, либо просто не играть старые реплеи).
На Unity тоже можно сделать общий объект-менеджер состояния игры (который в т.ч. может переживать перезагрузку сцены, см. примеры в Интернете), к которому будут обращаться создаваемые на сцене объекты и связывать своё состояние с нужными данными в модели через некоторые ID'шки.
Если объект сцены создаётся динамически, либо если объект загружается из сохраненной игры, то ему прописывают ID из менеджера в одну из компонент при его создании. Если объект загружается вместе с Unity-сценой, то ID у него будет не прописан и он может автоматически зарегистрироваться в менеджере, который присвоит ему новый ID.
Также можно дополнительно прописывать некоторые PersistentID'шки в объектах на сцене Unity, чтобы они связывали своё состояние всегда с определенными записями в БД (скажем, для онлайн игр или игр вроде Dark Souls без нормальных сейвов).
Положения и вращения это хорошо, но в Настоящих (TM) играх у объектов ещё есть состояния. Плюс события создания и удаления самих объектов.
Для состояний т.к. их может быть много (жизни, предметы и пр.), но при этом обычно на новом «кадре» меняются лишь некоторые (а восстанавливать хочется точно), то без выборочного дельта-кодирования в битовый/байтовый поток оказывается не обойтись т.е. кодировать для каждого «кадра» только те состояния, которые изменились и тратить на это минимум памяти в зависимости от величины изменения.
Ну и следующий логичный шаг — чтобы не заниматься сравнением конечных значений легче будет писать изменения в «поток изменений» в том же месте кода, где значение меняется. А тут уже и до сериализации в виде do/undo потоков недалеко, чтобы вообще не делать изменений напрямую.
Любая однопоточная программа уже является автоматом. Когда программист пишет код, он мыслит категориями вычислений, чтений, вывода, проверок и переходов.
Дополнительный уровень абстракции (FSM/FSA-либы, ну или просто переменные кодирующие состояния) нужен только в случае асинхронных потоку исполнения входных данных т.е. ввода от пользователя, из сети, из другого потока и абстрагированному, а значит в общем случае не-синхронному, вводу от других компонент в составе того же потока.
Растеризация текстов обычно к таким не относится, поэтому ожидать здесь наличие таблицы состояний странно.
Топ-херачеры? Бизнесмены вообще ничего сами не меняют, только берут, вынужденно сколько-то отдавая, причём стараясь уменьшить «издержки», обычно в ущерб общему будущему, зато грея свои любимые амбиции-иллюзии и хотелки тех кто «над ними». Они вообще продукт порочной капиталистической парадигмы. А меняют те лучшие, кто на них работает, причём обычно не благодаря, а вопреки. Это они топ-херачеры?
Итого: имя НЕ хранится в пароле, а лишь используется для частичной валидации пароля. О чём и был мой вопрос, на который вы напрямую так и не ответили. Но всё равно спасибо.
Небольшая неточность в описании формата пароля для Super Castlevania IV — «эти биты сохраняют данные игровые параметры… имя игрока из 8 символов» — в 16 битах (N и C т.е. совмещенных с контрольной суммой) нельзя сохранить значения 8 символов по 36 вариантов каждого. Тут нужно как минимум 42 бита, если без сжатия. Таким образом получается, что имя вводится в игре отдельно и используется для проверки пароля, но не хранится в нём. Верно?
Да хочется чего-то элементарного и простого, без магий, с автоматической поддержкой только для тех типов к которым может быть обращение и при этом полностью отдельно от самих данных. При этом шаблоны тоже нужно поддерживать через определение списка типов при инстансировании шаблонов. Либо для явно указанных типов при динамическом способе доступа (по имени), если таковой поддерживать.
for ( auto field : AnyType:::Fields ) // AnyType:::Fields.size(), etc.
{
// field.offset, field.size, field.type, field.ToString(obj), field.FromString(obj)
}
for ( auto method : AnyType:::Methods ) // ...
{
// method.Call(obj), method.ToString(), method.FromString()
}
Можно вместо ::: любой другой подходящий символ для доступа к метаданным. Синтаксис для добавления поддержки для динамических типов тоже может другой, конечно же.
Зато можно сделать генератор небольших рассказов-сценариев по шаблонам, но с высокой комбинаторикой и вариативностью суб-сценариев (шагов) и кросс-сценариев (параллельных арок), после чего скормить получаемые сценарии подобной ассоциативной системе с подбором рифмы и архивированием смыслов в минимальные наборы слов. И тем самым скрыть аляповатости исходных генерированных сценариев за плотным слогом =)
Вопрос в том стоит ли переходить на другой язык только ради некоторых улучшений?
Лично моё мнение что скорее стоит доработать C++, добавив механизмов для описания и автоматического или полу-автоматического соблюдения важных контрактов разного уровня (от указателей до схем работы модулей). И тут получается, что Rust выступает как полигон для обкатки подобных механизмов.
Ну ещё очень хочется нормальной поддержки сериализации =)
Все три пункта зависят от включенности мозга в работу. Не просто памяти, логики и интуиции, но также и критического мышления. Есть такой подход — осторожное разумное исследование неизвестной территории с опорой на самые надёжные методы и постоянной перепроверкой их надёжности. Требует адаптировать ассоциативную память под как можно более быстрые и полные (с учётом деталей) выборки и синтез вариантов решения для каждого случая. Делается выборка/синтез из N разносторонних вариантов, далее мысленно проверяется насколько каждый подходит для решения. Отбраковываются неподходящие, адаптируются подходящие, выбирается лучший. Далее ищется M подтверждений что этот вариант действительно лучший из всех N. Замечу, что именно разносторонний N даёт качественный эффект. Постоянно вижу как программисты используют 1 вариант который привычен или первым пришёл в голову. Но ведь задача программиста не сделать «как-нибудь», а сделать в т.ч. качественно т.е. способ решения тоже имеет значение.
Rust делает больше, чем C/C+, позволяя автоматизировать соблюдение некоторых контрактов. Не идеально, конечно, но вполне удобно. Только вот чего такого сложного в самостоятельном понимании и соблюдении контрактов? По опыту работы с программистами на C++ считаю, что они либо забивают на качество и продуманность своего кода, либо недопонимают что должен делать или делает их код. «Ленятся лениться.» Пропускают состояния, путаются в вариантах, недообрабатывают частные случаи. И таких много, очень много. Корректный код на любом языке умеют писать, похоже, считанные единицы, которых действительно можно назвать программистами. Остальные постоянно фейлят. Почему так?
Проблемы с указателями, как и с целостным кодированием, всегда по недомыслию. И в плохом порядке кодирования. Если писать код строго придерживаясь простых правил, вытекающих друг из друга, то проблем исчезающе мало. Каких правил? Разнообразных контрактов использования и владения структурами данных. Они как бы неявные, но если приложить мысль и абстрактное мышление, то выводятся на счёт раз и далее используются на-автомате. Только поняв эти особенности можно и следует писать код. И это так для любого языка программирования.
Если кратко — следует ПОЛНОЦЕННО соблюдать ОО т.е. область определения аргументов и собственных данных при кодировании любой функции (в т.ч. с учётом контракта) и блоков кода, а также постоянно учитывать ОДЗ т.е. область допустимых значений данных после вычислений и преобразований. Указатель лишь частный случай. Если «программист» не способен полноценно соблюдать ОО и учитывать ОДЗ, но какой язык ему не дай, будет лишь обезьяна с гранатой.
В общем, закончим. Предлагаю Firefox'у не прогибаться под DRM. Пользователей, которым так нужен DRM-контент не больше, чем пользователей которые прекрасно без него обойдутся. Особенно если вести пропаганду вреда от DRM — уж этого они запретить не могут.
1) Зачем передавать объекты в функции копированием, а не, скажем, константной ссылкой? см. float Distance( Vec2 a, Vec2 b ) и void ResolveCollision( Object A, Object B )
2) Зачем копировать объекты из «многообразия», опять же вместо создания ссылки? см. AABB abox = A->aabb
3) Разве в С++ уже есть операция возведения в степень через ^? см. (a.x — b.x)^2 + (a.y — b.y)^2
4) Проверять floating-point значения на равенство вместо допуска (epsilon) плохая идея. см. if(d != 0) или if(n == closest) (тут как минимум отсутствует указание допуска)
5) Зачем копировать вектор если значения потом затираются, а до этого не используются? см. Vec2 closest = n
6) Что за тип real? см. real d =
7) Зачем дважды вычислять сумму квадратов компонентов вектора (dot product с собой), первый раз при проверке LengthSquared(), а второй сразу же после этого при взятии Length()? см. if(n.LengthSquared( ) > r) и float d = n.Length( )
Плавно перематывать назад по записанным данным ввода можно будет только дискретные действия, например, четкое передвижение на клетку вверх/влево/вправо/вниз, или простое ожидание. А вот всякие цепочки последствий от, в общем случае, необратимых взаимодействий таким образом перемотать сложно т.к. нужно тогда делать ключевой кадр (snapshot) в прошлом и постоянно доматывать от него до нужного кадра в будущее, что может быть слишком медленно. Это как играть видео назад в видеоплеере из упакованного инкрементального формата.
Ввод хорошо использовать для реплеев при детерминированной механике в т… ч. идентично настроенных генераторах случайных чисел. Скажем, реплеи для игр типа Clash of Clans занимают минимум места, там просто указано в какой момент какие команды задействовал игрок, но они привязаны к версии механики игры, которую нужно тем или иным образом стараться не сломать в новых версиях (записывать версию в реплей и уметь играть механику старых версий, либо просто не играть старые реплеи).
Если объект сцены создаётся динамически, либо если объект загружается из сохраненной игры, то ему прописывают ID из менеджера в одну из компонент при его создании. Если объект загружается вместе с Unity-сценой, то ID у него будет не прописан и он может автоматически зарегистрироваться в менеджере, который присвоит ему новый ID.
Также можно дополнительно прописывать некоторые PersistentID'шки в объектах на сцене Unity, чтобы они связывали своё состояние всегда с определенными записями в БД (скажем, для онлайн игр или игр вроде Dark Souls без нормальных сейвов).
Для состояний т.к. их может быть много (жизни, предметы и пр.), но при этом обычно на новом «кадре» меняются лишь некоторые (а восстанавливать хочется точно), то без выборочного дельта-кодирования в битовый/байтовый поток оказывается не обойтись т.е. кодировать для каждого «кадра» только те состояния, которые изменились и тратить на это минимум памяти в зависимости от величины изменения.
Ну и следующий логичный шаг — чтобы не заниматься сравнением конечных значений легче будет писать изменения в «поток изменений» в том же месте кода, где значение меняется. А тут уже и до сериализации в виде do/undo потоков недалеко, чтобы вообще не делать изменений напрямую.
Дополнительный уровень абстракции (FSM/FSA-либы, ну или просто переменные кодирующие состояния) нужен только в случае асинхронных потоку исполнения входных данных т.е. ввода от пользователя, из сети, из другого потока и абстрагированному, а значит в общем случае не-синхронному, вводу от других компонент в составе того же потока.
Растеризация текстов обычно к таким не относится, поэтому ожидать здесь наличие таблицы состояний странно.
for ( auto field : AnyType:::Fields ) // AnyType:::Fields.size(), etc.
{
// field.offset, field.size, field.type, field.ToString(obj), field.FromString(obj)
}
for ( auto method : AnyType:::Methods ) // ...
{
// method.Call(obj), method.ToString(), method.FromString()
}
DynType:::BuildMetaData;
void foo()
{
BaseType:::DynamicDerivedType derivedType( "DynType" );
// derivedType.Fields(), derivedType.Methods()
}
Можно вместо ::: любой другой подходящий символ для доступа к метаданным. Синтаксис для добавления поддержки для динамических типов тоже может другой, конечно же.
Лично моё мнение что скорее стоит доработать C++, добавив механизмов для описания и автоматического или полу-автоматического соблюдения важных контрактов разного уровня (от указателей до схем работы модулей). И тут получается, что Rust выступает как полигон для обкатки подобных механизмов.
Ну ещё очень хочется нормальной поддержки сериализации =)
Если кратко — следует ПОЛНОЦЕННО соблюдать ОО т.е. область определения аргументов и собственных данных при кодировании любой функции (в т.ч. с учётом контракта) и блоков кода, а также постоянно учитывать ОДЗ т.е. область допустимых значений данных после вычислений и преобразований. Указатель лишь частный случай. Если «программист» не способен полноценно соблюдать ОО и учитывать ОДЗ, но какой язык ему не дай, будет лишь обезьяна с гранатой.