Игра Змейка на С++ WinAPI Visual Studio 2019

Загрузить исходный код С++ WinAPI Visual Studio 2019 ЗДЕСЬ

Ссылка на видео на youtube о создинии игры Змейка a Создание игр на C++ : Змейка, автор видео использует С++ и OpenGL.

Директива define определяет основные параметры игрового поля (заголовочный файл MyApp.h).


#define SCALE 25 //size one square on screen (in pixels)

#define FIELD_WIDTH 30 //field width
#define FIELD_HEIGHT 20 //field height

#define WINDOW_WIDTH (FIELD_WIDTH * SCALE)
#define WINDOW_HEIGHT (FIELD_HEIGHT * SCALE)

Сначала создаем игровое поле, этим занимается функция void CMyApp::PrintField().

Теперь создадим змейку. Для описания змейки нужны переменные:


	static int Dir = 3; //направление движения от 0 до 3
	static int Num = 4; //чиcло квадратиков нашей змейки, для начала игры 4

Направление может принимать значения:


	Змейка движеться назад Dir = 0;
	Змейка движеться влево Dir = 1;
	Змейка движеться вправо Dir = 2;
	Змейка движеться вперед Dir = 3;

Число квадратиков змейки увеличивается, когда змейка съедает фрукт, и уменьшается когда змейка сама себе наскочила на хвост.

Сама змейка представляет из себя следующую структуру:


	//структура описывает змейку
	struct {
		int x;	//текущий квадрат змейки по ширине
		int y;	//текущий квадрат змейки по высоте
	} s[100];	//максимальное количество квадратиков
				//которое может быть у змейки 100

Как видим у каждого квадрата змейки есть своя позиция на игровом поле x,y. Змейка не может выходить за пределы игрового поля, т.е. координата x змейки не может быть больше FIELD_WIDTH, не может быть меньше нуля, и координата y змейки не может быть больше FIELD_HEIGHT и не может быть меньше нуля.

Напишем код который дает 1 шаг на игровом поле для нашей змейки (направление не важно).

Что означает что змейка делает 1 шаг- это значит ее головной элемент передвинулся в направлении Dir на одну координату (клетку). При движении змейки вверх координата змейки y увеличивается на 1, при движении вниз координата змейки y уменьшается на 1, при движении вправо координата змейки x увеличивается на 1, при движении влево координата змейки x уменьшается на 1.

Как движеться головной элемент змейки понятно, как движуться остальные элементы змейки? Остальные элементы просто перемещаются на место предыдущего элемента. Т.е. к примеру последний элемент станет на место предпоследнего.


void CMyApp::RenderScene()
{
	static bool BeginGame = true;

	if(BeginGame)
	{
		//инициализация змейки перед игрой
		//у змейки изначально четыре квадратика
		s[0].x = 10;
		s[0].y = 10;

		s[1].x = 10;
		s[1].y = 11;

		s[2].x = 10;
		s[2].y = 12;

		s[3].x = 10;
		s[3].y = 13;

	}

	//3)движение всех элементов змейки после головного
	for (int i = Num ; i > 0 ; --i)
	{
	s[i].x = s[i - 1].x;
	s[i].y = s[i - 1].y;
	}


	//2)движение головного элемента змейки
	if(Dir == 0)
		s[0].y += 1; //змейка движется вверх
	if(Dir == 1)
		s[0].x -= 1; //змейка движется влево
	if(Dir == 2)
		s[0].x += 1; //змейка движется вправо
	if(Dir == 3)
		s[0].y -= 1; //змейка движется вниз

	//4)рисуем нашу змейку - выводим елементы змейки на экран
	hBrush = CreateSolidBrush(RGB(0, 0, 255));
	hOldBrush = (HBRUSH)SelectObject(m_hBackBuffer, hBrush);

	for ( int i = 0; i < Num; i++)
	{
		Rectangle( m_hBackBuffer, s[i].x * SCALE, s[i].y * SCALE,
			(s[i].x + 1) * SCALE, (s[i].y + 1) * SCALE );
	}

После запуска данного кода змейка на экране движется вверх, теперь добавим управление:


	if(linput & IN_FORWARD)
	{
		Dir=3;
	}

	if(linput & IN_BACK)
	{
		Dir=0;
	}
	
	if(linput & IN_LEFT)
	{
		Dir=1;
	}
	
	if(linput & IN_RIGHT)
	{
		Dir=2;
	}

Теперь сделаем что бы когда змейка наскакивает на фрукт она увеличивалась в размере.

Создадим класс Fructs который описывает фрукты на игровом поле (MyApp.h):


	//класс описывает фрукты для нашей змейки
	class Fructs
	{
	public:
		int x,y; //позиция фрукта на игровом поле

		//функция нужно если змейка наехала на яблоко
		//тогда это яблоко появляется в новом месте
		void New ()
		{
			x = rand() % FIELD_WIDTH;
			y = rand() % FIELD_HEIGHT;
		}
	} m[10]; //всего 10 фруктов на игровом поле

Если змейка наскочила на фрукт, то фрукт появляется в новом месте - функция New() класса Fructs.

Функция которая выводит на экран все фрукты:


	//6)функция выводит фрукты на экран
	hBrush = CreateSolidBrush(RGB(0, 200, 0));
	hOldBrush = (HBRUSH)SelectObject(m_hBackBuffer, hBrush);
	for (int i = 0; i < 10; i++)
	{
		Rectangle( m_hBackBuffer, m[i].x * SCALE, m[i].y * SCALE,
			(m[i].x + 1) * SCALE, (m[i].y + 1) * SCALE );	
	}

	SelectObject(m_hBackBuffer, hOldBrush);
	DeleteObject(hBrush);

Так же во время инициализации игры нужно создать фрукты на экране:


	srand((unsigned)time( NULL ));

	//создаем 10 фруктов на поле
	for (int i=0;i<10;i++)
		m[i].New();


Теперь у нас есть 10 фруктов на игровом поле, но когда змейка наскакивает на фрукт пока ничего не происходит. Добавим код. Здесь в цикле преряем, если головной элемент змейки (координата) совпала с каким нибудь из 10 фруктов, увеличиваем змейку Num++ (например было 4 стало 5 элементов змейки), и рисуем фрукт в новом месте игрового поля.


	//7)если головной элемент змейки наехал на фрукт
	for (int i = 0; i < NUM_FRUITS; i++)
	{
		if( (s[0].x == m[i].x)  &&  (s[0].y == m[i].y) )
		{
			Num++; //увеличиваем змейку
			m[i].New(); //выводим фрукт в новом месте игрового поля
		}
	}

Добавим проверку- змейка выходит за границы игрового поля, и когда змейка наскакивает на свой хвост- она должна сокращаться. Когда змейка выходит за границы игрового поля можно так же сделать Game Over. Но в нашем случае когда змейка выходит за границы игрового поля она меняет направление движения на противоположное. Тут один момент- змейка будет наскакивать на свой хвост и сокращаться. Как делается проверка наскочила змейка на себя или нет- если головной элемент (координаты) совпал с каким нибудь хвостовым элементом змейки- то сокращается количество элементов змейки.


	//8)если змейка вышла за пределы игрового поля
	//меняем направление движеня на противоположное
	//змейка будет наскакивать на свой хвост при движении
	//в противоположном направлении (за экраном гол.эл.)
	//поэтому будет уменьшаться в размере
	if(s[0].x > FIELD_WIDTH)
		Dir = 1;
	if(s[0].x < 0)
		Dir = 2;
	if(s[0].y > FIELD_HEIGHT)
		Dir = 3;
	if(s[0].y < 0)
		Dir = 0;

	//9)если наскочила змейка на себя
	//головной элемент пересекаеться с хвостом
	for ( int i = 1; i < Num; i++ )
	{
		if(s[0].x == s[i].x && s[0].y == s[i].y)
			Num = i;
	}


Причем последовательность кода такая:


void CMyApp::RenderScene()
{
	1)управление, контроль змейки и фруктов
	2)отображение на экране линий игрового поля
	3)отображение на экране фруктов
	4)отображение на экране змейки
}