>>13418> текстурные координаты
Ты не понял. Тем, что выдает художник, пользоваться в программе вообще невозможно. Изначально это было мое предположение, но после
>>13417-куна это стало утверждением. А раз так, то выхлоп художника в любом случае надо будет переводить в формат, удобный программистам. А раз так, то мы (программисты) можем сделать из этого выхлопа всё, что захотим. Хотим небо - сделаем. Хотим Аллаха - сделаем. Хотим набор из пары атласов 8192x8192 и мапы из ID спрайта в кортеж из имени атласа и координат на этом атласе (
Map: SpriteID -> {AtlasName, Matrix2}
) - сделаем. И все эти навороты, которые я описал: 3 способа задания спрайтов, 2 вида конфигов - это все делается для удобства художников, не кодеров. Сами мы этим пользоваться (по крайней мере, в игровом коде) не будем.
То есть, если кратко: от художника нам нужны будут текстурные атласы и мапа из ID спрайта в структуру "имя атласа; координаты этого спрайта на атласе". Всё. А уж будет ли художник сам эти атласы и мапы составлять, через нашу тулзу (то есть, используя конфиги) или если даже они будут собираться в рантайме - это уже дело тулмейкера. Сами мы этими конфигами пользоваться не будем.
> Шейдеры
> Для шейдера же нужны еще и юниформы, которые кто-то должен установить и обновлять,
Они будут выставляться перед отправкой всех координат спрайтов на видяху. То есть:
GL.АктивизироватьШейдер(YobaEffect);
GL.УстановитьЮниформ("Имя", Значение);
GL.УстановитьЮниформ("Имя", Значение);
...
GL.УстановитьЮниформ("Имя", Значение);
GL.ОтправитьВертексБуфферИТексельБуффер(ВертексБуфферСоВсейСценой, ТексельБуферСоВсейСценой);
> а для обшейдеренного объекта еще и дополнительные параметры для вершин спрайта, причем уникальные для каждого объекта. Т.е. для эфекта шума одинаковые текстурные координаты карты шума еще покатат, но одинаковая фаза для всех мигающих(например) объектов будет бросаться в глаза и выглядеть странно. Значит для каждого пообъектного шейдера нужны уникальные структура данных, сигнатура у функции добавления спрайта и я хз что еще.
Именно.
Можно обойтись без дополнительных параметров в функции Render(...), просто добавить функции ActivateEffect(...), DeactivateEffect(...); то есть, сделать эдакую машину состояний. То есть, в рендер-менеджере:
GL.АктивизироватьШейдер(YobaEffect);
GL.УстановитьЮниформ("Имя", Значение);
GL.УстановитьЮниформ("Имя", Значение);
GL.УстановитьЮниформ("Имя", Значение);
...
GL.УстановитьЮниформ("Имя", Значение);
GL.ОтправитьВертексБуфферИТексельБуффер(ВертексБуфферПочтиСоВсейСценой, ТексельБуферПочтиСоВсейСценой);
GL.ДеактивироватьШейдер();
GL.АктивироватьШейдер("Мигание");
GL.УстановитьЮниформ("Имя", МигающийОбъект.ФазаМигания);
ВертексБуфер = ЗаполнитьВертексБуферИзОбъектов(new GameObject[] { МигающийОбъект });
ТексельБуфер = ЗаполнитьТексельБуферИзОбъектов(new GameObject[] { МигающийОбъект });
GL.ОтправитьВертексБуфферИТексельБуффер(ВертексБуфер, ТексельБуфер);
GL.ДеактивироватьШейдер();
GL.АктивироватьШейдер("МиганиеСиним");
GL.УстановитьЮниформ("Имя", ДругойМигающийОбъект.ФазаМигания);
ВертексБуфер = ЗаполнитьВертексБуферИзОбъектов(new GameObject[] { ДругойМигающийОбъект });
ТексельБуфер = ЗаполнитьТексельБуферИзОбъектов(new GameObject[] { ДругойМигающийОбъект });
GL.ОтправитьВертексБуфферИТексельБуффер(ВертексБуфер, ТексельБуфер);
GL.ДеактивироватьШейдер();
GL.АктивироватьШейдер("МиганиеВсемиЦветамиРадуги");
GL.УстановитьЮниформ("Имя", ТретийМигающийОбъект.ФазаМигания);
ВертексБуфер = ЗаполнитьВертексБуферИзОбъектов(new GameObject[] { ТретийМигающийОбъект });
ТексельБуфер = ЗаполнитьТексельБуферИзОбъектов(new GameObject[] { ТретийМигающийОбъект });
GL.ОтправитьВертексБуфферИТексельБуффер(ВертексБуфер, ТексельБуфер);
GL.ДеактивироватьШейдер();
... // И так для всех мигающих объектов. Да-да, каждый зашейдеренный объект рендерится отдельно от всех.
Я на то отвечаю?> по коду
> елды
class MovingObject : GameObject
{
/// <summary>
/// Сдвигает данный объект к указанной точке с указанной скоростью.
/// </summary>
protected void MoveTo(Vector2D pos, float pixelsPerSecond)
{
...
}
}
class ЖнецИзЛунарии : MovingObject
{
public ЖнецИзЛунарии()
{
self.Pos = Vector2D(-100, 400);
Existence = Logic();
}
private IEnumerator Logic()
{
// Вылетаем сзади.
var newPos = Vector2D(1000, 400); // Середина правого края экрана.
while (self.Pos != newPos)
{
MoveTo(newPos, 1000/2 /* прописью: тысяча пикселей за две секунды */);
yield return null;
}
// Пока не сдохнем...
while (true)
{
// Поднимаемся...
newPos = Vector2D(1000, 50); // Верхний правый край.
while (self.Pos != newPos)
{
MoveTo(newPos, 700/1);
yield return null;
}
// Летаем вверх-вниз.
for (int i = 0; i < 10; i++) // 10 раз.
{
newPos = Vector2D(1000, 750); // Нижний правый край.
while (self.Pos != newPos)
{
MoveTo(newPos, 700/1);
yield return null;
}
newPos = Vector2D(1000, 50); // Верхний правый край.
while (self.Pos != newPos)
{
MoveTo(newPos, 700/1);
yield return null;
}
}
// Готовимся к удару...
newPos = Vector2D(1000, 400); // Середина.
while (self.Pos != newPos)
{
MoveTo(newPos, 700/1);
yield return null;
}
// Ебошим!
newPos = Vector2D(50, 400); // Левый край экрана.
while (self.Pos != newPos)
{
MoveTo(newPos, 1000/1);
yield return null;
}
// Точно ебошим!
УдаритьКосой();
// Летим обратно.
newPos = Vector2D(1000, 400); // Правый край экрана.
while (self.Pos != newPos)
{
MoveTo(newPos, 1000/2);
yield return null;
}
// Повторяем все с начала.
}
}
private void УдаритьКосой()
{
...
}
}
Разные способы описания подходят под разные задачи. С этим, думаю, никто спорить не будет. Декларативщина подходит под описание гуёв, функциональщина - под математические вещи, машина состояний - под я не знаю что, а под описание логики бота лучше всего подходит императивщина вида "
ПодойдиК(Игрок.Позиция, я.Скорость); Отпизди(Игрок);
".
INB4 "а если жнеца таки замочили?": некто делает "
Жнец.Existence = DisappearImmediately();
" - и все, жнец исчезает на следующем кадре.
> public const IEnumerator StayStill = null; // Вместо null будет что-то. Это ещё что за хрень? Какой конст?
Ой, я забыл, что в C# с const-ом говно какое-то.
Правильно здесь написать надо было так:
public IEnumerator StayStill = ... // Комментарий для программиста: НЕ ВЗДУМАЙ ПРИСВАИВАТЬ ЭТОМУ ПОЛЮ НИЧЕГО, СУКА!!!!11
> GLScreen
Обертка над OpenGL. Типа всё равно все объекты рендерятся на экран - вот вам экран.
> Мне не нравиться сортировка в рендер-менеджере.
> Чем тебе не нравится прошлый вариант с сортировкой при добавлении объекта?
Я именно так и предлагаю, только сортируются при добавлении не объекты, а спрайты. Когда я говорю: "
RenderSprite(TheSprite)
" - этот спрайт просто добавляется в очередь, соответствующую: 1) текущему приоритету; 2) атласу, которому этот спрайт принадлежит (контрактам, которые я указал, это не противоречит). В результате за порядком объектов в GameWorld следить не надо (у объектов могут поменяться спрайты так, что может понадобиться другой атлас; плюс какой-нибудь из объектов может вылезти на передний план или уйти на задний), а RenderSprite(...) будет отрабатывать за амортизированное O(1). То есть:
class GameWorld
{
private List<GameObject> Objects;
public void Add(GameObject obj)
{
self.Objects.Add(obj); // Тупо.
}
}
class GLScreen
{
private Map<Priority, Map<AtlasID, VertexAndTexelBuffer>> VertexAndTexelBuffers;
public void RenderSprite(Vector2D pos, int spriteID)
{
var spriteDescription = TextureManager.FindSpriteDescription(spriteID);
var buffer = VertexAndTexelBuffers[CurrentPriority][spriteDescription.AtlasID];
buffer.AddCoordinates(
// Vertices
pos.x, pos.y, pos.x + spriteDescription.width, pos.y + spriteDesccription.height,
// Texels
spriteDescription.x, spriteDescription.y, spriteDescription.x + spriteDescription.width,
spriteDescription.y + spriteDescription.height
);
}
}
> Если подразумевается, что "тут будет что-то написано", то оставляй лучше пустое место или коментарии, а не get {return null;}
Я ошибки компилятором искал. Больше так не буду. Да, "
return null
" везде надо заменить на "
...
".
> Ну ёпт так и пиши:
Ты неправильно написал. Я не хочу выносить World в конструктор, иначе получится, что можно создать объект, но не добавить его в мир. Впрочем, это решается, и это на самом деле технические детали уже. Суть и так ясна: игрообъекты знают об игромире, в который они добавлены. Вот и все.
>>13433> И мы таки остановились на XML?
Да, до появления варианта получше. Например, до няшного API для YAML.
> И мы же вроде без depth-буфера, не?
Без. Я периодически буду на него оглядываться, вдруг пригодиться (все-таки мощный инструмент, как ни крути), но пока без него.
>>13456 Ну, делал, на Бейсике.
10 DIM X(300) : DIM Y(300) : DIM BULLETX(50) : DIM BULLETY(50)
А чтоб вот так серьезно, с проектированием, с архитектурой - это первый раз.
--
> <tank>
> <animation>
> <idle><sprite id="Tank" col="1" row="0"></idle>
> <move><sprite id="Tank" col="2" row="0"></move>
> …
> </animation>
> …
> </tank>
Рефлекторно послал нахуй и в пизду, потому что попахивает скриптингом, а в моих задумках скриптинг - это оверкилл. Но здравое зерно в этом XML-нике есть. Сейчас объясню.
Я провел ряд мысленных экспериментов и обнаружил, что анимация - это тоже часто встречающаяся задача, и было бы неплохо ее тоже автоматизировать. Посему вбрасываю идею: отправлять рендер-менеджеру не спрайты, а целые анимации (какие-нибудь объекты класса Animation, там, я не знаю). А игрообъекты со своей стороны будут рулить этими анимациями: запускать, тормозить, проигрывать в обратном порядке, перематывать, зацикливать и прочее.
<мечты>
А вообще, я еще в отрочестве хотел писать игры на потоках: нужен игрок - создаешь поток, рулящий игроком; нужен враг - создаешь поток, рулящий врагом; нужно убить игрока, если здоровья стало мало - создаешь поток, следящий за здоровьем игрока. И рендерер - тоже поток, и реакция на контроллеры - тоже поток. И все объекты-потоки не ждут очередного кадра, а работают все одновременно. И когда им надо изменить картинку (то есть, свое отображение на экране) - они просто скидывают изменения в определенный глобальный буфер. А поток-рендерер просто 60 раз в секунду рендерит этот глобальный буфер на экран. И анимации - это тоже потоки: создался поток-вертолетик, а внутри себя создал поток-анимацию, которая быстро-быстро меняет его спрайт и складывает эти изменения в глобальный буфер. И потоки могут друг с другом взаимодействовать - суспендить, убивать, запускать заново и т. д. Причем любой поток может поиметь любой другой поток, какой хочет. Естесственно, это не голые потоки, а некие объекты, с состоянием и шлюхами. Но засуспендить или вообще убить их все равно можно. И вот так вот живут все потоки сами по себе, рожают, убивают друг друга, а один маленький поток рендерит всё это безобразие каждую 1/60-ую секунды.
Понятно, что эта идея бредова, но что если бы потоки были бы легковесными, как в Эрланге, и работали бы по-настоящему одновременно? Я вижу прототип таких потоков в корутинах, например.
</мечты>
c:дорога дизайнер зима отделяет читал