Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Создаем платформер за 30 минут

Anna | 24.06.2014 | нет комментариев

Здравствуйте! Сегодня мы будем писать платформер, применяя C , Box2D и SFML, а также редактор 2D карт для игр Tiled Map Editor

image

Вот итог (карта создавалась 5 минут во время сьемки игра тормозила экран не так растянут — недостаток Bandicam :) )

Исходники и exe — внизу статьи

Что, где, когда?

Box2D

Эту библиотеку мы будем применять для симуляции физики в платформере (соударение с блоками, гравитация). Допустимо, не стоило для одних только блоков юзать эту библиотеку, но прекрасно жить не запретишь ;)
Отчего именно Box2D? Потому что это самая распространенная и бесплатная физическая библиотека

SFML

Отчего SFML? Сначала я хотел применять библиотеку SDL, но она крепко ограничена в вероятностях по сопоставлению с SFML, многое пришлось бы дописывать самому. Спасибо автору SFML за сэкономленное время!
Ее используем для отрисовки графики

Tiled Map Editor

Что делает здесь Tiled Map Editor?
Вы когда-нибудь пробовали создавать карты для игр? Спорим, что вашей первой картой было что-то подобно такого.

Спрятанный текст

1111111
1000001
1001001
1000011
1000111
1111111

Это достаточно неэффективное решение! Значительно отменнее написать что-то как бы редактора карт, но задним числом мы понимаем, что это не делается за 5 минут, а приведенная выше «карта» — абсолютно.

Tiled Map Editor — один из таких редакторов карт. Он отменен тем, что карту, сделанную в этом редакторе (состоит из объектов, тайлов, их слоев) дозволено сберечь в XML-сходственном файле .tmx и потом с поддержкой особой библиотеки на C считать ее. Но обо каждому по порядку.

Создание карты

Скачиваем TME с официального сайта
Создаем новую карту «Файл->Сделать…»

image

Ориентация должна быть ортогональной (если вы не делаете изометрический платформер :) ), а формат слоя XML, мы будем считывать именно данный формат
Кстати, ни формат слоя, ни размер тайлов невозможно будет поменять в сделанной карте

Тайлы

После этого идем в «Карта->Новейший комплект тайлов…», загружаем наш тайлсет

image

В результате у вас получится что-то как бы этого

image

В чем толк слоев тайлов?
Примерно в всякой игре есть многослойные карты. 1-й слой — земля (лед, чернозем, etc), 2-й слой — здания (казармы, форт, etc, причем фон прозрачен), 3-й — деревья (ель, пихта, etc, фон тоже прозрачен). То есть рисуется вначале 1-й слой, поверх него накладывается 2-й слой, а потом теснее 3-й.

Процесс создания слоев запечатлен на следующих 4 скриншотах

image
image
image

Список слоев

image

Объекты

Что такое объект в TME?
Объект имеет свое имя, тип, а также параметры со значениями.
За объекты отвечает эта панель

image

Вы абсолютно можете узнать, что делает всякая из кнопок, сами.
Сейчас испробуем сделать объект.
Удаляем слой «Колобоша», взамен него создаем слой объектов, возможен, с тем же наименованием «Колобоша». Выбираем «Вставить тайл-объект» из панели для объектов (либо можете предпочесть всякую фигуру — Shape), нажимаем на тайл Колобоши и легко ставим объект в какое-нибудь место.
Позже чего нажимаем правой кнопкой мыши на объект и нажимаем на «Свойства объекта…». Измените имя объекта на Kolobosha.

Позже чего сбережете карту.

В всеобщем, ничего архисложного в редакторах карт нет. Пора переходить к считыванию карты.

Считывание карты

Для считывания XML файлов сделана чудесная библиотека TinyXML, скачайте ее исходники.

Сделайте план Visual Studio. Подключите файлы TinyXML (либо легко запихайте все эти файлы в план, за исключением xmltest.cpp :) )
Сейчас подключаем includ’ы и lib’ы SFML в «План->Свойства». Если не знаете, как это делать — добросердечно пожаловать в Гугл

Создаем Level.h для карт

#ifndef LEVEL_H
#define LEVEL_H

#pragma comment(lib,"Box2D.lib")
#pragma comment(lib,"sfml-graphics.lib")
#pragma comment(lib,"sfml-window.lib")
#pragma comment(lib,"sfml-system.lib")

#include <string>
#include <vector>
#include <map>
#include <SFML/Graphics.hpp>

Это предисловие файла.

Дальше идет конструкция объекта

struct Object
{
    int GetPropertyInt(std::string name);
    float GetPropertyFloat(std::string name);
    std::string GetPropertyString(std::string name);

    std::string name;
    std::string type;
    sf::Rect<int> rect;
    std::map<std::string, std::string> properties;

	sf::Sprite sprite;
};

Разберем её.
Как теснее говорилось, в TME всякий объект может иметь параметры. Параметры берутся из XML файла, записываются в properties, и потом их дозволено получить всякий из первых 3 функций. name — имя объекта, type — его тип, rect — прямоугольник, описывающий объект. И наконец, sprite — спрайт (изображение) — часть тайлсета, взятая для объекта. Спрайта может и не быть.

Сейчас идет конструкция слоя — она дюже примитивна

struct Layer
{
    int opacity;
    std::vector<sf::Sprite> tiles;
};

В слое есть прозрачность (да, да, мы можем делать полупрозрачные слои!) и список из тайлов.

Дальше идет класс Level

class Level
{
public:
    bool LoadFromFile(std::string filename);
    Object GetObject(std::string name);
    std::vector<Object> GetObjects(std::string name);
    void Draw(sf::RenderWindow &window);
	sf::Vector2i GetTileSize();

private:
    int width, height, tileWidth, tileHeight;
    int firstTileID;
    sf::Rect<float> drawingBounds;
    sf::Texture tilesetImage;
    std::vector<Object> objects;
    std::vector<Layer> layers;
};

#endif

LoadFromFile загружает карту из указанного файла. Это сердце класса Level
GetObject возвращает 1-й объект с указанным именем, GetObjects возвращает список объектов с указанным именем. Вообще-то, по-отличному, следовало применять тип (type) объекта, но мне было комфортнее вылавливать блоки и игрока через имя, так как в редакторе имя показывается сверху объекта, а тип — нет
Draw рисует все тайлы (не объекты!), беря себе экземпляр RenderWindow

Сейчас создаем Level.cpp

#include "level.h"

#include <iostream>
#include "tinyxml.h"

Первым мы обрабатываем конструкцию объектов

int Object::GetPropertyInt(std::string name)
{
    return atoi(properties[name].c_str());
}

float Object::GetPropertyFloat(std::string name)
{
    return strtod(properties[name].c_str(), NULL);
}

std::string Object::GetPropertyString(std::string name)
{
    return properties[name];
}

Для Layer реализация не необходима, переходим к Level

bool Level::LoadFromFile(std::string filename)

bool Level::LoadFromFile(std::string filename)
{
    TiXmlDocument levelFile(filename.c_str());

	// Загружаем XML-карту
    if(!levelFile.LoadFile())
    {
        std::cout << "Loading level "" << filename << "" failed." << std::endl;
        return false;
    }

	// Трудимся с контейнером map
    TiXmlElement *map;
    map = levelFile.FirstChildElement("map");

	// Пример карты: <map version="1.0" orientation="orthogonal"
	// width="10" height="10" tilewidth="34" tileheight="34">
    width = atoi(map->Attribute("width"));
    height = atoi(map->Attribute("height"));
    tileWidth = atoi(map->Attribute("tilewidth"));
    tileHeight = atoi(map->Attribute("tileheight"));

	// Берем изложение тайлсета и идентификатор первого тайла
    TiXmlElement *tilesetElement;
    tilesetElement = map->FirstChildElement("tileset");
    firstTileID = atoi(tilesetElement->Attribute("firstgid"));

	// source - путь до картинки в контейнере image
    TiXmlElement *image;
    image = tilesetElement->FirstChildElement("image");
    std::string imagepath = image->Attribute("source");

	// Пытаемся загрузить тайлсет
	sf::Image img;

    if(!img.loadFromFile(imagepath))
    {
        std::cout << "Failed to load tile sheet." << std::endl;
        return false;
    }

	// Очищаем карту от света (109, 159, 185)
	// Вообще-то в тайлсете может быть фон всякого цвета, но я не обнаружил решения, как 16-ричную строку
	// как бы "6d9fb9" преобразовать в цвет
    img.createMaskFromColor(sf::Color(109, 159, 185));
	// Грузим текстуру из изображения
	tilesetImage.loadFromImage(img);
	// Расплывчатость запрещена
    tilesetImage.setSmooth(false);

	// Получаем число столбцов и строк тайлсета
	int columns = tilesetImage.getSize().x / tileWidth;
    int rows = tilesetImage.getSize().y / tileHeight;

	// Вектор из прямоугольников изображений (TextureRect)
    std::vector<sf::Rect<int>> subRects;

	for(int y = 0; y < rows; y  )
	for(int x = 0; x < columns; x  )
	{
		sf::Rect<int> rect;

		rect.top = y * tileHeight;
		rect.height = tileHeight;
		rect.left = x * tileWidth;
		rect.width = tileWidth;

		subRects.push_back(rect);
	}

	// Работа со слоями
    TiXmlElement *layerElement;
    layerElement = map->FirstChildElement("layer");
    while(layerElement)
    {
        Layer layer;

		// Если присутствует opacity, то задаем прозрачность слоя, напротив он всецело непрозрачен
        if (layerElement->Attribute("opacity") != NULL)
        {
            float opacity = strtod(layerElement->Attribute("opacity"), NULL);
            layer.opacity = 255 * opacity;
        }
        else
        {
            layer.opacity = 255;
        }

		// Контейнер <data>
        TiXmlElement *layerDataElement;
        layerDataElement = layerElement->FirstChildElement("data");

        if(layerDataElement == NULL)
        {
            std::cout << "Bad map. No layer information found." << std::endl;
        }

		// Контейнер <tile> - изложение тайлов всякого слоя
        TiXmlElement *tileElement;
        tileElement = layerDataElement->FirstChildElement("tile");

        if(tileElement == NULL)
        {
            std::cout << "Bad map. No tile information found." << std::endl;
            return false;
        }

        int x = 0;
        int y = 0;

        while(tileElement)
        {
            int tileGID = atoi(tileElement->Attribute("gid"));
            int subRectToUse = tileGID - firstTileID;

			// Устанавливаем TextureRect всякого тайла
            if (subRectToUse >= 0)
            {
                sf::Sprite sprite;
                sprite.setTexture(tilesetImage);
				sprite.setTextureRect(subRects[subRectToUse]);
                sprite.setPosition(x * tileWidth, y * tileHeight);
                sprite.setColor(sf::Color(255, 255, 255, layer.opacity));

                layer.tiles.push_back(sprite);
            }

            tileElement = tileElement->NextSiblingElement("tile");

            x  ;
            if (x >= width)
            {
                x = 0;
                y  ;
                if(y >= height)
                    y = 0;
            }
        }

        layers.push_back(layer);

        layerElement = layerElement->NextSiblingElement("layer");
    }

    // Работа с объектами
    TiXmlElement *objectGroupElement;

	// Если есть слои объектов
    if (map->FirstChildElement("objectgroup") != NULL)
    {
        objectGroupElement = map->FirstChildElement("objectgroup");
        while (objectGroupElement)
        {
			// Контейнер <object>
            TiXmlElement *objectElement;
            objectElement = objectGroupElement->FirstChildElement("object");

			while(objectElement)
            {
				// Получаем все данные - тип, имя, позиция, etc
                std::string objectType;
                if (objectElement->Attribute("type") != NULL)
                {
                    objectType = objectElement->Attribute("type");
                }
                std::string objectName;
                if (objectElement->Attribute("name") != NULL)
                {
objectName = objectElement->Attribute("name");
                }
                int x = atoi(objectElement->Attribute("x"));
                int y = atoi(objectElement->Attribute("y"));

				int width, height;

				sf::Sprite sprite;
                sprite.setTexture(tilesetImage);
				sprite.setTextureRect(sf::Rect<int>(0,0,0,0));
                sprite.setPosition(x, y);

				if (objectElement->Attribute("width") != NULL)
				{
					width = atoi(objectElement->Attribute("width"));
					height = atoi(objectElement->Attribute("height"));
				}
				else
				{
					width = subRects[atoi(objectElement->Attribute("gid")) - firstTileID].width;
					height = subRects[atoi(objectElement->Attribute("gid")) - firstTileID].height;
					sprite.setTextureRect(subRects[atoi(objectElement->Attribute("gid")) - firstTileID]);
				}

				// Экземпляр объекта
                Object object;
                object.name = objectName;
                object.type = objectType;
				object.sprite = sprite;

                sf::Rect <int> objectRect;
                objectRect.top = y;
                objectRect.left = x;
				objectRect.height = height;
				objectRect.width = width;
                object.rect = objectRect;

				// "Переменные" объекта
                TiXmlElement *properties;
                properties = objectElement->FirstChildElement("properties");
                if (properties != NULL)
                {
                    TiXmlElement *prop;
                    prop = properties->FirstChildElement("property");
                    if (prop != NULL)
                    {
                        while(prop)
                        {
                            std::string propertyName = prop->Attribute("name");
                            std::string propertyValue = prop->Attribute("value");

                            object.properties[propertyName] = propertyValue;

                            prop = prop->NextSiblingElement("property");
                        }
                    }
                }

				// Пихаем объект в вектор
                objects.push_back(object);

                objectElement = objectElement->NextSiblingElement("object");
            }
            objectGroupElement = objectGroupElement->NextSiblingElement("objectgroup");
        }
    }
    else
    {
        std::cout << "No object layers found..." << std::endl;
    }

    return true;
}

Остальные функции Level

Object Level::GetObject(std::string name)
{
	// Только 1-й объект с заданным именем
    for (int i = 0; i < objects.size(); i  )
        if (objects[i].name == name)
            return objects[i];
}

std::vector<Object> Level::GetObjects(std::string name)
{
	// Все объекты с заданным именем
	std::vector<Object> vec;
    for(int i = 0; i < objects.size(); i  )
        if(objects[i].name == name)
			vec.push_back(objects[i]);

	return vec;
}

sf::Vector2i Level::GetTileSize()
{
	return sf::Vector2i(tileWidth, tileHeight);
}

void Level::Draw(sf::RenderWindow &window)
{
	// Рисуем все тайлы (объекты НЕ рисуем!)
	for(int layer = 0; layer < layers.size(); layer  )
		for(int tile = 0; tile < layers[layer].tiles.size(); tile  )
			window.draw(layers[layer].tiles[tile]);
}

С Level.h окончено!

Протестируем его.
Создаем main.cpp и пишем

#include "level.h"

int main()
{
	Level level;
	level.LoadFromFile("test.tmx");

	sf::RenderWindow window;
	window.create(sf::VideoMode(800, 600), "Level.h test");

    while(window.isOpen())
    {
        sf::Event event;

        while(window.pollEvent(event))
        {
            if(event.type == sf::Event::Closed)
                window.close();
        }

        window.clear();
		level.Draw(window);
        window.display();
    }

    return 0;
}

Карта может выглядеть как желательно!

Можете поиграться с объектами

image

main.cpp

#include "level.h"
#include <iostream>

int main()
{
	Level level;
	level.LoadFromFile("test.tmx");

	Object kolobosha = level.GetObject("Kolobosha");
	std::cout << kolobosha.name << std::endl;
	std::cout << kolobosha.type << std::endl;
	std::cout << kolobosha.GetPropertyInt("health") << std::endl;
	std::cout << kolobosha.GetPropertyString("mood") << std::endl;

	sf::RenderWindow window;
	window.create(sf::VideoMode(800, 600), "Kolobosha adventures");

    while(window.isOpen())
    {
        sf::Event event;

        while(window.pollEvent(event))
        {
            if(event.type == sf::Event::Closed)
                window.close();
        }

        window.clear();
		level.Draw(window);
        window.display();
    }

    return 0;
}

Итог
image

Когда вы наиграетесь с объектами, наступит пора Box2D

Коробки-коробочки

Мы хотим сделать 3D-экшон платформер, суть такова…
На карте расположены объекты — с наименованиями player — игрок, enemy — недруг, block — блок, money — монетки.
Мы загружаем игрока, принуждаем его подчиняться нажатиям клавиши и силе Ньютона.
Недруги ходят туда-сюда, отталкивают слишком близко находящегося игрока и умирают, если игрок прыгает на них
Блоки закрепляются «в воздухе» как неподвижные объекты, на них игрок может прыгать
Монеты ничего не дают, легко исчезают при соударении с игроком

Открываем main.h, стираем то, что там было написано, и пишем

#include "level.h"
#include <Box2DBox2D.h>

#include <iostream>
#include <random>

Object player;
b2Body* playerBody;

std::vector<Object> coin;
std::vector<b2Body*> coinBody;

std::vector<Object> enemy;
std::vector<b2Body*> enemyBody;

Тут у нас подключены level.h и Box2D.h. iostream необходим для итога в консоль, random — для генерации направления движения недруга.
Дальше идут игрок и векторы, всякому недругу, монетке, игроку полагается свой Object и b2Body (тело в Box2D)
Внимание — блокам этого не полагается, так как они взаимодействуют с игроком только на ярусе физики Box2D, а не в игровой логике

Дальше:

int main()
{
	srand(time(NULL));

	Level lvl;
	lvl.LoadFromFile("platformer.tmx");

    b2Vec2 gravity(0.0f, 1.0f);
    b2World world(gravity);

	sf::Vector2i tileSize = lvl.GetTileSize();

srand(time(NULL)) необходим для рандома.
Загружаем карту, создаем b2World, передавая ей гравитацию. Кстати, гравитация может исходить из какого желательно направления, и гравитация из (0,10) действует крепче (0,1). Потом мы берем необходимый нам размер тайлов

Дальше создаем тела блоков

	std::vector<Object> block = lvl.GetObjects("block");
	for(int i = 0; i < block.size(); i  )
	{
		b2BodyDef bodyDef;
		bodyDef.type = b2_staticBody;
		bodyDef.position.Set(block[i].rect.left   tileSize.x / 2 * (block[i].rect.width / tileSize.x - 1),
			block[i].rect.top   tileSize.y / 2 * (block[i].rect.height / tileSize.y - 1));
		b2Body* body = world.CreateBody(&bodyDef);
		b2PolygonShape shape;
		shape.SetAsBox(block[i].rect.width / 2, block[i].rect.height / 2);
		body->CreateFixture(&shape,1.0f);
	}
bodyDef.type = b2_staticBody;

Блоки — статические тела, они не имеют массы и висят в воздухе

bodyDef.position.Set(block[i].rect.left   tileSize.x / 2 * (block[i].rect.width / tileSize.x - 1),
			block[i].rect.top   tileSize.y / 2 * (block[i].rect.height / tileSize.y - 1));

Здесь мы устанавливаем позицию блоков. Дело в том, что если легко указать позицию такую же, как у объекта, нас будет ожидать хитрая оплошность

b2Body* body = world.CreateBody(&bodyDef);

Создаем тело блока в world. Дальше мы с телом не трудимся (в смысле, нигде не бережем)

b2PolygonShape shape;
		shape.SetAsBox(block[i].rect.width / 2, block[i].rect.height / 2);

Всякому телу принадлежит несколько shape — фигур. Я не буду детально разбирать эту тему, так как блокам (и остальным телам) хватает каждого-то одного прямоугольника.

body->CreateFixture(&shape,1.0f);

Объединяем фигуру с телом.

После этого мы делаем то же самое с недругами, монетами и игроком, за небольшими отличиями

	coin = lvl.GetObjects("coin");
	for(int i = 0; i < coin.size(); i  )
	{
		b2BodyDef bodyDef;
		bodyDef.type = b2_dynamicBody;
		bodyDef.position.Set(coin[i].rect.left   tileSize.x / 2 * (coin[i].rect.width / tileSize.x - 1),
			coin[i].rect.top   tileSize.y / 2 * (coin[i].rect.height / tileSize.y - 1));
		bodyDef.fixedRotation = true;
		b2Body* body = world.CreateBody(&bodyDef);
		b2PolygonShape shape;
		shape.SetAsBox(coin[i].rect.width / 2, coin[i].rect.height / 2);
		body->CreateFixture(&shape,1.0f);
		coinBody.push_back(body);
	}

	enemy = lvl.GetObjects("enemy");
	for(int i = 0; i < enemy.size(); i  )
	{
		b2BodyDef bodyDef;
		bodyDef.type = b2_dynamicBody;
		bodyDef.position.Set(enemy[i].rect.left  
			tileSize.x / 2 * (enemy[i].rect.width / tileSize.x - 1),
enemy[i].rect.top   tileSize.y / 2 * (enemy[i].rect.height / tileSize.y - 1));
		bodyDef.fixedRotation = true;
		b2Body* body = world.CreateBody(&bodyDef);
		b2PolygonShape shape;
		shape.SetAsBox(enemy[i].rect.width / 2, enemy[i].rect.height / 2);
		body->CreateFixture(&shape,1.0f);
		enemyBody.push_back(body);
	}

	player = lvl.GetObject("player");
	b2BodyDef bodyDef;
	bodyDef.type = b2_dynamicBody;
	bodyDef.position.Set(player.rect.left, player.rect.top);
	bodyDef.fixedRotation = true;
	playerBody = world.CreateBody(&bodyDef);
	b2PolygonShape shape; shape.SetAsBox(player.rect.width / 2, player.rect.height / 2);
	b2FixtureDef fixtureDef;
	fixtureDef.shape = &shape;
	fixtureDef.density = 1.0f; fixtureDef.friction = 0.3f;
	playerBody->CreateFixture(&fixtureDef);
bodyDef.fixedRotation = true;

Обозначает, что тело не может вращаться

Все тела сделаны, осталось инициализировать графику!

	sf::Vector2i screenSize(800, 600);

	sf::RenderWindow window;
	window.create(sf::VideoMode(screenSize.x, screenSize.y), "Game");

Отлично внятный код, создает окно с указанным размером и заголовком

	sf::View view;
	view.reset(sf::FloatRect(0.0f, 0.0f, screenSize.x, screenSize.y));
	view.setViewport(sf::FloatRect(0.0f, 0.0f, 2.0f, 2.0f));

Здесь мы создаем вид (View) для окна.
Для чего это нужно? Для того, Дабы придать игре пиксельный жанр, мы умножаем размер экрана на 2 с применением sf::View и все картинки рисуются в 2 раза выше и шире.

    while(window.isOpen())
    {
        sf::Event evt;

        while(window.pollEvent(evt))
        {
			switch(evt.type)
			{
			case sf::Event::Closed:
                window.close();
				break;

Окно закрывается по нажатию на алый крестик. Такой код был ранее

			case sf::Event::KeyPressed:
				if(evt.key.code == sf::Keyboard::W)
					playerBody->SetLinearVelocity(b2Vec2(0.0f, -15.0f));

				if(evt.key.code == sf::Keyboard::D)
					playerBody->SetLinearVelocity(b2Vec2(5.0f, 0.0f));

				if(evt.key.code == sf::Keyboard::A)
					playerBody->SetLinearVelocity(b2Vec2(-5.0f, 0.0f));
				break;

Здесь теснее увлекательнее! Мы добавляем скорость игроку по нажатию клавиш WAD

world.Step(1.0f / 60.0f, 1, 1);

Здесь мы обновляем физический мир Box2D. 1-й довод принимает частоту обновления мира (раз в 1/60 секунд), а также число velocityIterations и positionIterations. Чем выше значение последних 2-х доводов, тем реальнее получается физика игры. Так как у нас нет никаких трудных фигур, как в AngryBirds, а только прямоугольники, то нам довольно по разу.

		for(b2ContactEdge* ce = playerBody->GetContactList(); ce; ce = ce->next)
		{
			b2Contact* c = ce->contact;

Тут мы обрабатываем соударение игрока с другими телами

			for(int i = 0; i < coinBody.size(); i  )
				if(c->GetFixtureA() == coinBody[i]->GetFixtureList())
				{
					coinBody[i]->DestroyFixture(coinBody[i]->GetFixtureList());
					coin.erase(coin.begin()   i);
					coinBody.erase(coinBody.begin()   i);
				}

Обработка соударения с монетами.
Если какая монета столкнулась с игроком, она легко уничтожается и стирается из векторов

			for(int i = 0; i < enemyBody.size(); i  )
				if(c->GetFixtureA() == enemyBody[i]->GetFixtureList())
				{
					if(playerBody->GetPosition().y < enemyBody[i]->GetPosition().y)
					{
						playerBody->SetLinearVelocity(b2Vec2(0.0f, -10.0f));

						enemyBody[i]->DestroyFixture(enemyBody[i]->GetFixtureList());
						enemy.erase(enemy.begin()   i);
						enemyBody.erase(enemyBody.begin()   i);
					}

Если недруг сталкивается с игроком, проверяется, выше игрок недруга либо нет. Если игрок выше недруга, то он стирается, а игрок подскакивает вверх.
Если напротив, то игрок отскакивает от недруга

					else
					{
						int tmp = (playerBody->GetPosition().x < enemyBody[i]->GetPosition().x)
							? -1 : 1;
						playerBody->SetLinearVelocity(b2Vec2(10.0f * tmp, 0.0f));
					}
				}
		}

Игрок движется направо либо налево в соотвествии с его нынешним расположением касательно недруга.

		for(int i = 0; i < enemyBody.size(); i  )
		{
			if(enemyBody[i]->GetLinearVelocity() == b2Vec2_zero)
			{
				int tmp = (rand() % 2 == 1) ? 1 : -1;
				enemyBody[i]->SetLinearVelocity(b2Vec2(5.0f * tmp, 0.0f));
			}
		}

Если скорость недруга равна 0, то ему скорость придается опять — он движется либо направо, либо налево. Визуально это выглядит как движение рывками.

		b2Vec2 pos = playerBody->GetPosition();
		view.setCenter(pos.x   screenSize.x / 4, pos.y   screenSize.y / 4);
		window.setView(view);

Работа с графикой. Берем позицию игрока, изменяем центр вида и используем наш вид

		player.sprite.setPosition(pos.x, pos.y);

		for(int i = 0; i < coin.size(); i  )
			coin[i].sprite.setPosition(coinBody[i]->GetPosition().x, coinBody[i]->GetPosition().y);

		for(int i = 0; i < enemy.size(); i  )
			enemy[i].sprite.setPosition(enemyBody[i]->GetPosition().x, enemyBody[i]->GetPosition().y);

Устанавливаем спрайтам игрока, монет и недругов позиции, полученные из b2Body

        window.clear();

		lvl.Draw(window);

		window.draw(player.sprite);

		for(int i = 0; i < coin.size(); i  )
			window.draw(coin[i].sprite);

		for(int i = 0; i < enemy.size(); i  )
			window.draw(enemy[i].sprite);

		window.display();

Очищаем окна, рисуем тайлы карты, потом игрока, монеты и недругов, позже чего представляем окно.

    }

    return 0;
}

Готово!

Приблизительная карта

image

Исходники

image
https://github.com/Izaron/Platformer

Вопросы пишите здесь либо в ЛС, либо, если вам не повезло быть зарегистрированным на Прогре — на izarizar@mail.ru

Источник: programmingmaster.ru

Оставить комментарий
БАЗА ЗНАНИЙ
СЛУЧАЙНАЯ СТАТЬЯ
СЛУЧАЙНЫЙ БЛОГ
СЛУЧАЙНЫЙ МОД
СЛУЧАЙНЫЙ СКИН
НОВЫЕ МОДЫ
НОВЫЕ СКИНЫ
НАКОПЛЕННЫЙ ОПЫТ
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB