Даже не знаю, поможет ли кому данная статья или нет, но так как в момент написания своей мини-игры я не нашёл ответа на данный вопрос, решил написать решений.

Месяцев 2 назад я начал писать мини-игру, просто для оттачивания имеющихся умений и приобретения новых. Игра была задумана 2D. Полистав немножко интернет, нашёл вполне нормальную библиотеку для начинающего (ну и не только) — SFML. Идея была придумана и библиотека выбрана.

В скором времени возникла проблема с столкновениями со стенами и блоками. Ведь я не опытный, и проблема для меня была глобальной я опять начал искать в интернете готовые решения или подсказки. Нашёл библиотеку (движок) для работы с физикой — box2D. Но юзать настолько сложную библиотеку для простенькой игры — это глупо, и я решил как-то выйти из ситуации.

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

for (int i = (rect.top) / 32; i < ((rect.top + rect.height) / 32); i++)
			for (int j = rect.left / 32; j < ((rect.left + rect.width) / 32); j++)
			{
				if (Map[i][j] == '*') //Если это блок
				{
					if (dir == 1) rect.left = j * 32 + rect.width;
					if (dir == 2) rect.left = j * 32 - rect.width;
					if (dir == 3) rect.top = i * 32 + rect.height;
					if (dir == 4) rect.top = i * 32 - rect.height;
				}
			}

Как вы увидели, карта состояла из символов, и юзать TileMap я не то, чтобы не собирался, а скорее сказать не умел. В общем-то код работал, и всё было супер, если бы не одно «но»… И это «но» слишком большое: если персонаж стоял так, как показано на рисунке, то он не может пройти вперед.

image

Я не знал как с этим бороться и в итоге немножко забил на игру. Но интерес брал своё, и в один прекрасный день мне в голову пришла идея. Я решил, что юзать положение персонажа по центру спрайта будет намного легче. То есть по типу:

rect = IntRect(48, 48, 32, 32); 

Но как вы могли догадаться, это ничего нам не дало, и опять я начал думать. Ничего лучше, чем уменьшить квадрат персонажа, я не придумал. То есть если раньше мы брали размеры квадрата 32х32, то теперь я брал размер 16х16. Но этого мне показалось мало, и я решил брать 24х24. Про этот способ я и напишу.

Я избавился от цикла и решил просто брать направление нашего движения (dir) и смотреть, соприкасается ли эта точка с каким-либо блоком, и если она соприкасается, то просто давать персонажу ответный толчок от стенки. Этот способ показал результат.

void Collision(int dir) //Функция обработки столкновения
	{
		if (dir == 1)
		{
			int x = int((rect.left - rect.width)/32);
			int wy = int((rect.top + rect.height)/32);
			int ny = int((rect.top - rect.height)/32);
			if(Map[wy][x] == '+' || Map[ny][x] == '+' || Map[wy][x] == '*' || Map[ny][x] == '*')
				rect.left = x * 32 + rect.width;
		}
		else if (dir == 2)
		{
			int x = int((rect.left + rect.width) / 32);
			int wy = int((rect.top - rect.height) / 32);
			int ny = int((rect.top + rect.height) / 32);
			if (Map[wy][x] == '+' || Map[ny][x] == '+' || Map[wy][x] == '*' || Map[ny][x] == '*')
				rect.left = x  * 32 - rect.width;
		}
		else if (dir == 3)
		{
			int y = int((rect.top - rect.height) / 32);
			int lx = int((rect.left - rect.width) / 32);
			int rx = int((rect.left + rect.width) / 32);
			if (Map[y][lx] == '+' || Map[y][rx] == '+' || Map[y][lx] == '*' || Map[y][rx] == '*')
				rect.top = y * 32 + rect.height;
		}
		else if (dir == 4)
		{
			int y = int((rect.top + rect.height) / 32);
			int lx = int((rect.left - rect.width) / 32);
			int rx = int((rect.left + rect.width) / 32);
			if (Map[y][lx] == '+' || Map[y][rx] == '+' || Map[y][lx] == '*' || Map[y][rx] == '*')
				rect.top = y * 32 - rect.height;
		}
	}

Но этот код был несовершенен, так как персонаж залазил на блоки, пролетал сквозь них и просто замирал на некотором растоянии от блока. Немножко добавив математики, я просто смотрел вперед перед персонажем и принимал решение. То есть справа я смотрел на 8 пикселей вперед, и когда персонаж двигался вниз, то также смотрел вперед на 8 пикселей. Добавление +1 к движению вправо и движению вниз понадобилось, потому что моя игра имеет отступы по границам экрана, и наш персонаж должен быть на 32 пикселя дальше от границы. -1 аналогично. Код начал работать вполне хорошо.

void Collision(int dir) //Функция обработки столкновения
	{
		if (dir == 1)
		{
			int x = int((rect.left - rect.width - 8)/32);
			int wy = int((rect.top + rect.height)/32);
			int ny = int((rect.top - rect.height)/32);
			if(Map[wy][x] == '+' || Map[ny][x] == '+' || Map[wy][x] == '*' || Map[ny][x] == '*')
				rect.left = (x+1) * 32 + rect.width;
		}
		else if (dir == 2)
		{
			int x = int((rect.left + rect.width) / 32);
			int wy = int((rect.top - rect.height) / 32);
			int ny = int((rect.top + rect.height) / 32);
			if (Map[wy][x] == '+' || Map[ny][x] == '+' || Map[wy][x] == '*' || Map[ny][x] == '*')
				rect.left = x  * 32 - rect.width;
		}
		else if (dir == 3)
		{
			int y = int((rect.top - rect.height) / 32);
			int lx = int((rect.left - rect.width) / 32);
			int rx = int((rect.left + rect.width) / 32);
			if (Map[y][lx] == '+' || Map[y][rx] == '+' || Map[y][lx] == '*' || Map[y][rx] == '*')
				rect.top = (y + 1) * 32 + rect.height;
		}
		else if (dir == 4)
		{
			int y = int((rect.top + rect.height + 8) / 32);
			int lx = int((rect.left - rect.width) / 32);
			int rx = int((rect.left + rect.width) / 32);
			if (Map[y][lx] == '+' || Map[y][rx] == '+' || Map[y][lx] == '*' || Map[y][rx] == '*')
				rect.top = (y - 1) * 32 + rect.height;
		}
	}

Код по своей красоте и восприятию не очень хорош, но он работает. Скриншот рабочей можете посмотреть ниже.



Всем хорошего кода и свежих мыслей.

Комментарии (5)


  1. myxo
    15.03.2016 13:28
    +4

    Здравствуйте. Вы молодцы, что пытаетесь сделать что-то свое, учитесь… Но это не уровень хабра, не нужно писать такие статьи.

    Но раз уж написали, то несколько советов по коду.
    1) Именуйте нормально переменные. Что означают wy, ny (о боже, пока писал понял. Не говорите мне, что wy — это "вверх по y"). rect тоже как-то неинформативно.
    2) int((rect.left + rect.width) / 32) — вот так писать в Си необязательно, если все переменные int. То есть результатом (rect.left + rect.width) / 32 и так будет int.
    3) Что, если вам впоследствии захочется сделать клетку не 32, а 28? Сколько исправлений нужно делать? Не используйте такие константы в коде, завидите переменные.

    4) Чтобы не было кучи if в 2d играх используют такой прием. Например нужно передвинуть персонажа, в зависимости от направления dir — от 0 до 3. Заводим массив dx = [-1, 0, 1, 0]; dy = [0, 1, 0, -1]. Тогда new_position.x = position.x + dx[dir] * speed; для y аналогично.

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


    1. rostik_tsekhmistro
      15.03.2016 15:57

      Спасибо большое за коментарий. Учту это в своём коде и дальнейшей работе. Я понимаю что статья не для хабра, но я только учусь и захотелось написать чтото своё.


    1. domix32
      15.03.2016 16:09

      Для последнего пункта в Си++ имеются перечисления.

      Пример
      enum Direction
      {
          TOP,
          LEFT,
          DOWN,
          RIGHT,
          NONE
      }
      //и собственно обработка
      switch(dir)
      {
          case TOP:
          {
              //code 
              break;
          }
          case LEFT:
          {
              //code
              break;
          }
          case DOWN:
          {
              //code
              break;
          }
          case RIGHT:
          {
              //code 
              break;
          }
          default: {
              //обработка остальных вариантов
          }
      }


  1. AtomKrieg
    15.03.2016 19:09

    Казалось бы при чем тут SFML


    1. rostik_tsekhmistro
      15.03.2016 19:24

      Ну впринципе она сдесь не используеться. Но имеет прямое отношение. Так как с помощью нее мы описываем графический интерфес и реализация данных кусков кода смысла не имеет без графического интерфейса. Ну впринципе реализация действительна и для других библиотек для работы с графикой.