Форум: "Игры";
Текущий архив: 2003.12.16;
Скачать: [xml.tar.bz2];
ВнизПредставление о структуре игры (часть 1) Найти похожие ветки
← →
Michael Makushev (2003-05-19 12:03) [0]У меня такое представление: Объекты в игре и их графическое представление разделены и обрабатываются независимо друг от друга. Что я подразумеваю под объектом?
Грубо говоря в программном представлении это некий элемент ООП (игровой объект - ИО) с свойствами, управляемый пользователем (детали опустим). Что под графическим представлением? Это так же некий объект (он же спрайт), в котором наборы картинок. Причем для каждого состояния ИО свой набор и свои правила последовательности смены активной картинки.
Теперь сам игровой процесс: Действия всех игровых объектов управляются событиями каждое из которых вызывает скажем маленкую скриптовую програмку, которая и будет менять параметры объектов, вызывать новые события устанавливать/сбрасывать фолаги триггеры и пр. и пр. В том числе (Это я вывел самым слабым моментом моей структуры) и указывать объекту-спрайту, о том какое в данный момент у ИО состояние и соотв. какой набор картинок ( какой последовательности и пр.) надо активизировать.
Сам графический вывод осуществляется отдельным потоком, который будет брать все активные спрайты и выводить их в некоторый графический контекст.
В общем виде так.
Теперь глобальные вопросы: Неувязки, недостатки и пр. просьба помочь проанализировать? И если у кого есть (ну то есть кому не жалко поделиться тайной) подобные структуры, раскажите?
← →
cyborg (2003-05-19 12:51) [1]Есть такое дело и на проблемы уже нарвался.
Вот эта проблема отдельных потоков обработки и вывода:
http://delphimaster.net/view/9-1052897810/
То, что ты сказал про игровые объекты у меня уже есть, я тут где-то писал о том, что решил раздать исходник, даже один желающий посмотреть нашёлся, ему отправил, но тот с теми глюками которые в ссылке выше, и многое уже там изменено и добавлено.
Вся игра строится на таких самодостаточных объектах:
TYPE
PSurface=^TSurface; //Спрайт
TSurface=record
W,H : LongInt; //высота и ширина
BltFlags : Cardinal;
ColorKeyR : Byte;
ColorKeyG : Byte;
ColorKeyB : Byte;
Videomemory : Boolean;
DD7Surface : IDirectDrawSurface7;
FileName : String; //Имя файла со спрайтами
SpriteNum : Cardinal; //Номер спрайта в файле FileName
end;
TLookTo = (lDown,lUp,lLeft,lRight); //Направление объекта
PChain = ^TChain;
PSprite = ^TSprite;
TSprite = record //Данные спрайта, он же любой объект в игре
Pred,Next : PSprite; //Указатели на предыдущую и следующую запись в списке (не изменять!)
Chain : PChain; //Адрес цепочки, к которой принадлежит спрайт (не изменять!)
ID : Cardinal; //Идентификационный номер спрайта (для поиска по ID) (не изменять!)
SpriteType : Cardinal;//Тип спрайта, монстр, игрок, эффект и т.д. (конатанты stXXX)
Timer : Cardinal; //Таймер спрайта
AnimTimer : LongInt; //Таймер анимации
AnimSpeed : LongInt; //Скорость анимации, как AnimTimer=AnimSpeed тогда следующий кадр
Anim : Boolean; //Анимируется или нет
X,Y : LongInt; //Координаты
W,H : LongInt; //Высота и ширина
Speed : Integer; //Скорость объекта
LookTo : TLookTo; //Куда смотрит
Moved : Boolean; //Флаг движения
DataType : Cardinal; //Тип дополнительных данных
DataSize : Cardinal; //Размер дополнительных данных
Data : Pointer; //Указатель на дополнительные данные
ObjRect : TRect; //Прямоугольник коллизии (реальный объём объекта) от Sprite X Y
SrcRect : TRect; //Координаты копирования картинки из Surface
Surface : PSurface;
end;
TChain = record //Координаты первой и последней записи в цепочке
Start,Finish : PSprite;
end;
PChains = ^TChains;
TChains = array[1..65535] of TChain; //Тип указателей на цепочки
Вот, разбирай :)
← →
Michael Makushev (2003-05-19 13:48) [2]2cyborg:
Я прочитал указанную тобой тему. У меня на этот счет уже есть несколько соображений.
Первое: Потоку отображения совсем не обязятельно укладываться в определенные рамки кадрирования. Все равно определенного числа кадров в сек. вряд ли достигнет. Задача потока отображения в получении некоторого списка спрайтов предназначенных для отображения. Этот список на каждом игровом такте обновляется основным игроывм потоком. Преимущества данного подхода-можно с легкостью под данное игровое ядро реализовать любое графическое наполнение. Недостаток - при очень медленном графическом обновлении, если специально не притормаживать игровой поток, могут пропускаться кадры. И второй недостаток это то, что скажем скрипт-программа ИП (ну скажем сненарный триггер или триггер на таймер, что в принципе одно и то же), будет отягощена доп. функциями, для задания состояния спрайта, номера цепочки и пр.
Вопросы синхронизации потоков трограть не будем, так как в этом пункте достаточно просто запретить ИП (игровому потоку) обнослять список состояний спрайтов на отображение если этот список ещё не выведен.
Второе: Поток графического вывода спрайтов (ПГВС) сам "лезет" ко всем игровым объектам и сканирует их состояние. После чего на исновании этих состояний состовляет карту отображения.
Недостаток - То же саоме. Здесь придется искуственно и уже в обязательном порядке притормаживать ИП, дожидаясь конца обновления. Достоинства - ИП и все его объекты полностью не зависимы (не знают) и том как (где, что, когда и пр.) реализованно на графическом уровне.
Теперь в общем виде: Чисто технически действительно проблем с синхронизацией не вижу. Проблемы идут логического плана. То есть что должно быть первично? Игровой поток, или графическое ядро?
Ещё раз повторю, это для ситуации когда мы их максимально разделили...
Может есть ещё какие мысли?
← →
cyborg (2003-05-19 14:15) [3]По идее первичным должен быть вывод графики, он должен быть постоянен, тоейсть в цикле отображается. Это если нужен максимальный FPS.
А изменения объектов, т.е. состояния игры уже должно быть фиксированным, чтобы игра шла с одинаковой скоростью на любой машине, один из вариантов - таймер.
>>Задача потока отображения в получении некоторого списка спрайтов
Этот вариант годится, когда для отображения мало спрайтов. Если например спрайтов, допустим, 1000, каждое обновление придётся удалять старый список и создавать новый, известно, что в вин9Х не очен организована работа с памятью, в этом случае она быстро засорится, т.е. дефрагментируется, несложно подсчитать, например на 1 спрайт в списке идёт 10 байт, в итоге на 1000 спрайтов уходит 10000 байт, например у нас игра идёт 33 кадра\с. = 330000, гоняется более 300 кило в секунду. С такими темпами, думаю система вин9Х очень скоро, если не накроется, то будет тормозить.
Хотя возможно и ошибаюсь, не знаток я внутренностей системы.
В моём случае, когда возникла проблема, объекты, в основном, создаются в самом начале, это объекты карты, монстры и герой.
При этом они никуда не гоняются по памяти, сидят в одном месте, изменяются только указатели в списках цепочек спрайтов и данные самих объектов. Появляются и исчезают только спрайты взрывов и бомб. При этом, при удалении взрыва, ренедринг иногда попадал именно на этот взрыв и программа вылетала :(. Теперь я сделал фиксированный FPS 33 кадра, вначале считаются объекты, затем они выводятся, при таком одходе один недостаток, если компьютер слабый, и не может тянуть 33 кадра, то игра тормозит, именно не выводом графики, а логикой, так как просчёты идут до рендеринга и зависят от него.
← →
Michael Makushev (2003-05-19 14:36) [4]"По идее..."
Но это только по идее. Например на очень тормозных машинах тот же StarCraft - видно как "пролетают" отдельные ренды экранов. И при этом просто заметно что программа не приостанавливала процесс вычисления положений игрока и его противника.
"...А изменения объектов..." - согласен. Но здесь как я уже сказал мы сталкиваемся с копромисом между скоростью графики и стабильностью процесса игры. И того и другого в равной степени идеально добиться маловереятно. Поэтому надо думать на что делать больше смещение акцентов.
"...каждое обновление придётся удалять старый список и создавать новый, известно..." - просветите меня-темного (уже не в логическом, а чисто техническом плане), если очищать список так и будет как вы сказали? Я вроде слышал что в Дельфи очень неплохо организованна работа с кучей.
Что касается "гоняния" по памяти, то здесь наверное уже идет конкретная реализация.
Просто меня в начале интересоваля более логическая структура, а уже потом физическая.
← →
NailMan (2003-05-19 14:54) [5]У меня реализовано так:
Все ИО(игровые объекты) представлены в виде
OBJ:Array[0..MaxObjectCount-1] of TGameObject;
у объекта сцены есть список
RenderObj:Array[0..MaxObjectCount-1] of Integer;
игровой цикл:
Repeat
...ля-ля-ля с postmessage
UTEngine.ReadUserData;//считываем устройства ввода и изменяем
//(если нужно) параметры камеры.
UTEngine.ProcessMovements; //Просчет предвижений актеров,
//которые двигают за собой свои
// подчиненные списки ИО
UTScene.Render; //Рендер ИО. Только с теми индексами,
//которые записаны в RenderObj
Until Status=EC_ExitCode;
Procedure TScene.MakeRenderList;//приблизительный вариант
var i,j:integer;
begin
zeromemory(@RenderObj,sizeof(RenderObj));
j:=0;
for i:=0 to MaxObjCount-1 do
if assigned(OBJ[i]) and
(OBJ[i].Enabled and OBJ[i].Visible) then
begin
RenderObj[j]:=i;
inc(j);
end;
UTScene.SortRenderList;
end;
//процедура передвижения актеров(и следовательно ИО)
Procedure TEngine.ProcessMovements;
var i:integer;begin
for i:=0 to UTScene.ActorCount-1 do
if (GetTickCount-Actor[i].LactTick)>=15 then
begin
If not Actor[i].CheckCollision then
Actor[i].MoveSelf else Actor[i].ProcessCollision;
Actor[i].LactTick:=GetTickCount;
end;
UTScene.MakeRenderList; //создаем список объектов
//(+сортировка) для последующего
( по сферам) [5]У меня реализовано так:
Все ИО(игровые объекты) представлены в виде
OBJ:Array[0..MaxObjectCount-1] of TGameObject;
у объекта сцены есть список
RenderObj:Array[0..MaxObjectCount-1] of Integer;
игровой цикл:
Repeat
...ля-ля-ля с postmessage
UTEngine.ReadUserData;//считываем устройства ввода и изменяем
//(если нужно) параметры камеры.
UTEngine.ProcessMovements; //Просчет предвижений актеров,
//которые двигают за собой свои
// подчиненные списки ИО
UTScene.Render; //Рендер ИО. Только с теми индексами,
//которые записаны в RenderObj
Until Status=EC_ExitCode;
Procedure TScene.MakeRenderList;//приблизительный вариант
var i,j:integer;
begin
zeromemory(@RenderObj,sizeof(RenderObj));
j:=0;
for i:=0 to MaxObjCount-1 do
if assigned(OBJ[i]) and
(OBJ[i].Enabled and OBJ[i].Visible) then
begin
RenderObj[j]:=i;
inc(j);
end;
UTScene.SortRenderList;
end;
//процедура передвижения актеров(и следовательно ИО)
Procedure TEngine.ProcessMovements;
var i:integer;begin
for i:=0 to UTScene.ActorCount-1 do
if (GetTickCount-Actor[i].LactTick)>=15 then
begin
If not Actor[i].CheckCollision then
Actor[i].MoveSelf else Actor[i].ProcessCollision;
Actor[i].LactTick:=GetTickCount;
end;
UTScene.MakeRenderList; //создаем список объектов
//(+сортировка) для последующего
//рендера
end;
Собсно здесь реализована привязка к таймеру, и вычисления положения объектов и коллизий(по сферам) фактически не импульсны, а равномерные по времени, что благотворно для ФПС-а
← →
cyborg (2003-05-19 15:14) [6]NailMan Есть места для оптимизации:
1. Вместо zeromemory делай FillChar, zeromemory именно его и делает - избавление от лишнего call.
2. Вместо if assigned(OBJ[i]) делай if OBJ[i]<>nil - полное избавление от call@assigned и как следствие в несколько раз быстрее.
3. Зачем в цикле постоянно вызываешь GetTickCount, создай переменную и вызови один раз перед циклом.
← →
Michael Makushev (2003-06-02 11:38) [7]Извините. Но вернусь все таки в тему.
2NailMan: ваш вариант предусматривает
все что я написал ниже.
Но вместе с тем предположим некоторую ситуацию...
1.Есть некоторый объект (скажем квадратной формы 10 на 10) и его графическое представление в виде краного квадрата.
2. Есть некоторое замкнутое пространство (скажем 100 на 100). На этом пространстве перемещается данный объект.
Вопрос в том, что объект и его графическое представление (это только на мой взгляд) не одно и тоже. Ведь данный Об. может быть представлен спрайтом в виде облака, или спрайтом в виде 500-т точек, или ещё много чем. Так следует ли напрямую отождествлять Об. и его спрайт(спрайты, граф. представление и пр.)?
И поэтому объект будет меняя свои некоторые параметры (скажем координаты, угол поворота, температуру, ветер дождь и пр...:) )
Если их логически отделить друг от друга (чтобы не загромождать код ), то значит каждая из этих программных частей может обрабатываться отдельно.
Это значит что для графического отображения спрайта, тот должен "знать" в каком состоянии находится его объект "представленец", чтобы корректно отобразить ту или иную картинку.
Ещё раз повторю, это только мое видение вопроса! На универсальность и истину не претендующее? Мне интересно было бы узнать кто ещё и как решает подобные задачи. И вообще сталкивался ли с этими вопросами?
← →
NailMan (2003-06-02 17:04) [8]To -> cyborg ©
[skipped]
3. Зачем в цикле постоянно вызываешь GetTickCount, создай переменную и вызови один раз перед циклом.
Согласен - переделал, пару фпс-ов выиграл :-)
Страницы: 1 вся ветка
Форум: "Игры";
Текущий архив: 2003.12.16;
Скачать: [xml.tar.bz2];
Память: 0.5 MB
Время: 0.007 c