Показать сообщение отдельно
Старый 31.03.2009, 03:21   #1
HolyDel
 
Регистрация: 26.09.2006
Сообщений: 6,035
Написано 1,474 полезных сообщений
(для 2,707 пользователей)
Sigel Tutorial 1. Свой Crimsoland.

писать буду в несколько заходов.
приветствуется критика и вопросы.






Описание статьи и «игры»:

Разработка игры на Sigel. На примере небольшой 2д стрелялки. Аля свой crimsoland на С++ и Sigel. Будут рассматриваться сам язык С++ (вкратце, предполагаю что читатель уже слышал о таком и писал что то стоящее на каком нибудь другом), движок Sigel (архитектура, основы, нюансы), немного glsl (шейдеры). Сама игра будет предельно проста, но все же в меру технологична (освещение на шейдерах, запеченная в текстуру кровища, MRT и отложенное освещение).
Будем в ней бегать и стрелять. Вид сверху. 2д. Освещаться будет зона вокруг игрока, и будет свет от пуль, возможно еще какие нибудь «светлячки». Но в целом мир будет темным. Поэтому освещение будет отложенным (т.е сначала формируем screen-space текстуры с диффуз-картой и нормал-картой и потом уже освещаем). Также будут «моря крови», естественно с записью в текстуру.
Игрок будет спрайтом, свет тоже будет спрайтом, и враги будут спрайтом, и пули
Также будут звуки и музыка. Меню не будет (не думаю что с этим могут возникнуть сложности).
Также будет синхронизация по времени (delta time).
Далеко не все в туториале сделано оптимально. Часто можно былобы сделать попроще или побыстрее, но так как цель этого туториала показать движек. То и решаются задачи по разному.

Немного терминологии движка:

Entity (ентити) — некий абстрактный объект. Обладает ориентацией и позицией в пространстве. Можно к нему прикреплять текстуры, шейдеры и прочую муть.
Surface (сюрфейс) — некая сетка геометрии. Рендерится за один батч (дип), с настройками объекта.

Ну, с описанием покончено, перейдем собственно к уроку. Предполагаю что MSVS 2005, Sigel и драйвера уже установлены и настроены. Создаем новый проект:
Нажмите на изображение для увеличения
Название: pic1.png
Просмотров: 1223
Размер:	59.3 Кб
ID:	5718

выбираем тип проекта — SigelApp, и вводим имя проекта (я его назвал в честь игрушки _Ish, написанной еще на блице).
Нажмите на изображение для увеличения
Название: pic2.png
Просмотров: 1218
Размер:	23.9 Кб
ID:	5719

Далее, выбираем какие компоненты будут использоваться. Я решил что будут использоваться Devil и bass. Первый — для загрузки разных форматов текстур. (в первую очередь — ради png, его ядро движка не держит на данный момент). Второй — для звука и музыки.
Физика, скрипты и сеть не нужны для данной игры. Да и не сделаны они еще на должном уровне.
Нажмите на изображение для увеличения
Название: pic3.png
Просмотров: 1180
Размер:	13.7 Кб
ID:	5720

После нажатия на Finish, мастер сам создаст и скопирует нужные файлы, и простым щелчком на исходнике — мы переходим к его редактированию:
Нажмите на изображение для увеличения
Название: pic4.png
Просмотров: 1177
Размер:	47.7 Кб
ID:	5721

В итоге у нас получается исходник:
#define SIGEL_USE_ONLY

#define SIGEL_DEVIL
#define SIGEL_SOUND
//#define SIGEL_SCRIPT
//#define SIGEL_PHYSX
//#define SIGEL_NETWORK

#include <sigel/ISigel.h>

sGAME
{
	debug::startlog();
	InitDemo();
	AppTitle("_ISh2");

	VWait(0);
	CameraHelper ch;

	TFont *fnt = new TFont("tahoma.txt");

	while(!KeyDown(VK_ESCAPE))
	{
		//3d
		ch.Update();
		Render();
		//2d

		fnt->Text(GetFPS(),10,10);
		Flip();
	}

	DeInit();
	debug::endlog();
};
Во-первых меняем InitDemo() на Init() - это инициализирует полноэкранный режим, размером с рабочий стол. Что нам и нужно. Так как у большинства сейчас стоят жк мониторы, а на них картинка размером неравная матрице — выглядит смазаной.

Убираем отсюда все что связано с CameraHelper (ch). CameraHelper – это специальный объект, которым можно управлять с мыши и клавиатуры, он бывает полезен для отладки сцен, а камеру создавать и писать управление ею — лениво. Создаем свою камеру. Тут важно заметить, что объекты в сижеле могут быть двух типов — сами объекты и так называемые «инструменты». Если первые могут иметь позицию, шейдер, цвет, текстуру и т.д., то вторые всего этого не имеют, но у них есть ссылка на так называемые entity-hoster. Как правило это пивот. Но это не обязательно. Например для зеркал это обычно сюрфейс.
Camera *eye = new Camera();
Entity *cam = new Entity(EC_PIVOT);
eye->BindEntity(cam);
cam->Rotate(-90.0f,0,0);  //поворачиваем камеру вниз. Игра то с видом сверху.
Таким образом управлять настройками камеры (например цветом отчистки, Fov-ом или дистанцией прорисовки) нужно через собственно камеру (в нашем случае eye), а позицией и прочими параметрами — через cam. Грубо говоря мы возим и поворачиваем пивот, и каждый проход рендера камера ставится на его позицию.

Объявим entity игрока. Этот объект явно будет много где еще использоваться, поэтому он должен быть глобальным. т.е. Объявлен до точки вход sGAME по умолчанию. Проще всего объявить так: Entity *player = 0;
Заметьте, сам объект мы будем создавать уже после инициализации!

Сам игрок будет квадом. Впрочем много чего будет квадами, пожалуй даже все.

Пишем до sGAME:
Surface *bb = 0;
Entity *player = 0;
и после инициализации:
bb = new Surface();
bb->MakeQuad(1.0f);
player = new Entity(bb);
также нужен пол.
Для этого надо создать квад, загрузить текстуру, создать entity и затекстурить его. Вообще надо четко разделять геометрию и ее представление. Все это — сюрфейсы, террайны, биллбоарды и партикловые системы — это геометрия, которая рендерится каким то особым способом. Вот например куб — это сюрфейс из 12 трисов и 24 вершин. У нас может быть 1000 таких кубов. Это будет тысяча entity, но только один сюрфейс, который, тем ни менее, будет рендерится 1000 раз за кадр. Но в разных позициях, с разными материалами и шейдерами. Создаем сюрфейс, грузим текстуру и создаем ентити.
Surface *sfloor = new Surface();
sfloor->MakeQuad(256.0f,32.0f); //квад размером 256 на 256. и uv координатами от 0 до 32.

Entity *efloor = new Entity(sfloor);

Texture *fdiffus = new Texture("media/Fieldstone.png",TF_MIPMAP | TF_ANISOTROPIC);
efloor->BindTex(fdiffus);
Ставим игрока на середину карты:
player -> Position(128.0f,0.5f,128.0f); //ставим игрока на середину. (чуть выше земли).
В игровом цикле обновляем положение камеры. Она будет смотреть прямо на игрока сверху.
cam->Position(player); \\ставит камеру в позицию игрока
cam->PositionY(32.0f); \\ставит Y позицию камеры в 32.
В итоге у нас получается такой код:
#define SIGEL_USE_ONLY
  #define SIGEL_DEVIL
  #define SIGEL_SOUND

#include <sigel/ISigel.h>

Surface *bb = 0;
Entity *player = 0;

sGAME
{
	debug::startlog();
	Init();
	AppTitle("_ISh2");

	Camera *eye = new Camera();
	Entity *cam = new Entity(EC_PIVOT);
	eye->BindEntity(cam);
	cam->Rotate(-90.0f,0,0);  //поворачиваем камеру вниз. Игра то с видом сверху.

	bb = new Surface();
	bb->MakeQuad(1.0f);

	player = new Entity(bb);
	
	Surface *sfloor = new Surface();
	sfloor->MakeQuad(256.0f,32.0f);

	Entity *efloor = new Entity(sfloor);

	Texture *fdiffus = new Texture("media/Fieldstone.png",TF_MIPMAP | TF_ANISOTROPIC);
	efloor->BindTex(fdiffus);

	VWait(0);

	TFont *fnt = new TFont("tahoma.txt");

	player -> Position(128.0f,0.5f,128.0f); //ставим игрока на середину. (чуть выше земли).

	while(!KeyDown(VK_ESCAPE))
	{
		//3d

		cam->Position(player);
		cam->PositionY(32.0f);

		Render();
		//2d


		fnt->Text(GetFPS(),10,10);
		Flip();
	}

	DeInit();
	debug::endlog();
};
и вот такой скрин:
Нажмите на изображение для увеличения
Название: pic5.jpg
Просмотров: 1254
Размер:	125.3 Кб
ID:	5723

Следующим шагом загрузим текстуры игрока и курсора, также заставим игрока двигаться и поворачиваться в сторону курсора.
Для перемещения придется ввести dt. Это переменная тоже должна быть глобальной, значит надо ее объявить где? Правильно, в начале (не в функции точки входа по крайней мере). Узнать текущее время кадра можно функцией GetFrameTime().
float dt = 0;

в цикле:
ft = GetFrameTime();
также нужно загрузить текстуры. Делается это так:
Texture *playerdiffus[19];

for(int i=0;i<19;++i)
{
	char buff[80];
	sprintf_s(buff,80,"media/solider/%d.png",i+1);
	playerdiffus[i] = new Texture(buff);
}
цифра в квадратных скобках после имени переменной при ее объявлении означанет количество элементов в массиве. Например int a[4], a – это массив из 4 интов.
Так в нашем случае Texture *playerdiffus[19]; - это массив из 19 указателей на текстуры. Заметьте — что индексация происходит в ранге 0..18. т.е. На 1 меньше.
Циклы for имеют следующий синтаксис — у них есть три выражения, они разделены точкой с запятой. Первое — инициализация, второе — условие ПОВТОРЕНИЯ, последнее — что делать с переменной.
sprintf_s – эта команда пишет в массив char-ов некую строку. Сама строка задается третьим параметром, вторым идет макс число элементов в массиве. В этой строке "media/solider/%d.png", %d заменяется на следующий параметр (целочисленный), так например %s для строки, а %f для float.

Также нам нужны будут переменные для текущего кадра и флаг движется игрок или нет — чтобы его анимировать.
Конечно, игрока нужно затекстурить:
int move = 0;
float frame = 0;
player->BindTex(playerdiffus[(int)frame]);
Далее, в главном цикле нужно сделать управление игроком. Оно будет заключаться в движении на WASD и повороте на указатель мышки.
move = 0;
Vector2D mdir(0,0);

if(KeyDown('W'))
{
	move = 1;
	mdir.y -= 1;
}

if(KeyDown('S'))
{
	move = 1;
	mdir.y += 1;
}

if(KeyDown('A'))
{
	move = 1;
	mdir.x -= 1;
}

if(KeyDown('D'))
{
	move = 1;
	mdir.x += 1;
}


if(move)
{
	mdir.normalize();
	mdir *= (dt*0.01f);

	player->Translate(mdir.x,0,mdir.y);

	frame += dt * 0.03f;
	if(frame>18.0f)
		frame -= 18.0f;
	player->BindTex(playerdiffus[(int)frame]);
}
тут стоит описать подробнее. В данном участке — мы определяем вектор направления. Далее, если нажата одна из клавиш WASD, то мы говорим что игрок двигается. И добавляем в вектор направление клавиши. В результате у нас получится вектор показывающий направление. Далее, мы его нормализируем (приводим вектор к единичному) и домножим на скорость помноженную на dt.
Потом просто перемещаем наш объект. Translate перемещает объект не обращая внимание на его направление, в отличие от Move.
С анимцией тоже не сложно. Просто прибавляем к кадру некую цифру помноженную на dt (я ее вывел методом проб и ошибок )) )
Если кадр выходит за максимальный, то мы просто отнимаем от него это число. Это нужно, чтобы анимация выглядела более плавной.
Ну и прикрепляем текстуру с нужным индексом.
float a = atan2f((float)((ScreenWidth()/2)-MouseX()),(float)((ScreenHeight()/2)-MouseY()));
player->RotateY(a*57.1f);
первая строчка считает угол. Между центром экрана и координатой курсора. Она выглядит страшной только потому, что пришлось приводить int к float чтобы компилятор не кидал warning-и. Это делается так, если кто не знает — (float)42. Например.
Заметьте в Sigel-е углы задаются в градусах, а с++ считает в радианах. Поэтому надо домножить на (180 / pi) — примерно 57.1f. Кстати, f на конце числа означает что это число float, а если оно не указано, то компилятор считает что ето double. Так как все расчеты внутри движка ведутся в float-то и в игре удобно оперировать ими.


Грузим курсор в картинку.
Image *cur = new Image(“media/cur.jpg”);
эту же картинку нужно отрисовать с аддиативным блендингом перед самым flip-ом.
Blend(BM_ADD);
cur->Draw(MouseX()-16,MouseY()-16);
Blend(BM_ALPHA);
обратите внимание картинка рисуется в оконных координатах. Указывается позиция ее верхнего левого угла, поэтому и нужно было вычесть 16 из каждой координаты (размер картинки — 32 на 32)
ну и наконец — последний стришок — прячем стандартный курсор. HidePointer(), незабываем его показать при выходе из программы — ShowPointer():
вот такой исходник:
#define SIGEL_USE_ONLY
  #define SIGEL_DEVIL
  #define SIGEL_SOUND

#include <sigel/ISigel.h>

Surface *bb = 0;
Entity *player = 0;
float dt = 0;

sGAME
{
	debug::startlog();
	Init();
	AppTitle("_ISh2");

	Image *cur = new Image("media/cur.jpg");

	Camera *eye = new Camera();
	Entity *cam = new Entity(EC_PIVOT);
	eye->BindEntity(cam);
	cam->Rotate(-90.0f,0,0);  //поворачиваем камеру вниз. Игра то с видом сверху.

	bb = new Surface();
	bb->MakeQuad(2.0f);

	player = new Entity(bb);
	player->Blend(BM_ALPHA);

	Texture *playerdiffus[19];

	for(int i=0;i<19;++i)
	{
		char buff[80];
		sprintf_s(buff,80,"media/solider/%d.png",i+1);
		playerdiffus[i] = new Texture(buff);
	}

	Surface *sfloor = new Surface();
	sfloor->MakeQuad(64.0f,16.0f);

	Entity *efloor = new Entity(sfloor);

	Texture *fdiffus = new Texture("media/Fieldstone.png",TF_MIPMAP | TF_ANISOTROPIC);
	efloor->BindTex(fdiffus);

	VWait(0);

	TFont *fnt = new TFont("tahoma.txt");

	player -> Position(32.0f,0.5f,32.0f); //ставим игрока на середину. (чуть выше земли).

	int move = 0;
	float frame = 0;
	player->BindTex(playerdiffus[(int)frame]);

	HidePointer();
	while(!KeyDown(VK_ESCAPE))
	{
		dt = GetFrameTime();
		//3d
		move = 0;
		Vector2D mdir(0,0);

		if(KeyDown('W'))
		{
			move = 1;
			mdir.y -= 1;
		}

		if(KeyDown('S'))
		{
			move = 1;
			mdir.y += 1;
		}

		if(KeyDown('A'))
		{
			move = 1;
			mdir.x -= 1;
		}

		if(KeyDown('D'))
		{
			move = 1;
			mdir.x += 1;
		}


		if(move)
		{
			if(mdir.length()>0.1f)
				mdir.normalize();

			mdir *= (dt*0.01f);

			player->Translate(mdir.x,0,mdir.y);

			frame += dt * 0.03f;
			if(frame>18.0f)
				frame -= 18.0f;

			player->BindTex(playerdiffus[(int)frame]);
		}

		float a = atan2f((float)((ScreenWidth()/2)-MouseX()),(float)((ScreenHeight()/2)-MouseY()));
		player->RotateY(a*57.1f);

		cam->Position(player);
		cam->PositionY(24.0f);

		Render();
		//2d


		fnt->Text(GetFPS(),10,10);

		Blend(BM_ADD);
		cur->Draw(MouseX()-16,MouseY()-16);
		Blend(BM_ALPHA);
		Flip();
	}
	ShowPointer();
	DeInit();
	debug::endlog();
};
вот такой скриншот:
Нажмите на изображение для увеличения
Название: pic6.jpg
Просмотров: 1203
Размер:	115.6 Кб
ID:	5724

скачать исходник и "игру" можно тут:
http://rghost.ru/177603

Последний раз редактировалось HolyDel, 31.03.2009 в 03:30.
(Offline)
 
Ответить с цитированием
Эти 7 пользователя(ей) сказали Спасибо HolyDel за это полезное сообщение:
-=SCiP=- (31.03.2009), ABTOMAT (31.03.2009), falcon (31.03.2009), h1dd3n (31.03.2009), H@NON (31.03.2009), impersonalis (31.03.2009), moka (31.03.2009)