g
Суббота
20.04.2024
09:59
 
Все для редактирования и создания игр
 
Приветствую Вас Новичок | RSS|Главная | Каталог статей | Регистрация | Вход

Каталог статей

Главная » Статьи » Разное » Разное

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

image

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



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

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


Box2D

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

SFML

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

Tiled Map Editor

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

Скрытый текст


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

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

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


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

image

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

Тайлы

Затем идем в «Карта->Новый набор тайлов...», загружаем наш тайлсет:

image

В итоге у вас получится что-то вроде этого:

image

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

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

imageimage

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, и потом их можно получить любой из первых трех функций. 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 возвращает первый объект с указанным именем, 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)


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

Object Level::GetObject(std::string name)
{
 // Только первый объект с заданным именем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();
 }

 return0;
}


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

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

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();
 }

 return0;
}


Результат:

image

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

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


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

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

#include "level.h"#include <Box2D\Box2D.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/60 секунд), а также количество velocityIterations и positionIterations. Чем выше значение последних двух аргументов, тем реальнее получается физика игры. Так как у нас нету никаких сложных фигур, как в 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();


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

 }

 return0;
}


Готово!

Примерная карта:

image

Исходники


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

Благодарности


Авторам SFML
Авторам Box2D
Авторам TinyXml

Источник: http://gameshaker.ucoz.ru
Категория: Разное | Добавил: gameshaker (03.11.2013) | Автор: rgs E
Просмотров: 6763 | Теги: SFML, используя, Платформер, редактор, минут, Создаем, Box2D, 30, за, C++
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Последние файлы
UnWorld (Unreal Engine 1.5-2)(редактор уровней Harry Potter)
(Комментариев:0)
(Загрузок:2277)
Серия игр:Гарри Поттер " Harry Potter"
(Комментариев:0)
(Загрузок:1471)
Top 10 Flash Games(флеш игр) по версии GamesHaker.ucoz.ru pack10
(Комментариев:0)
(Загрузок:1564)
Top 10 Flash Games(флеш игр) по версии GamesHaker.ucoz.ru pack9
(Комментариев:0)
(Загрузок:1063)
Top 10 Flash Games(флеш игр) по версии GamesHaker.ucoz.ru pack8
(Комментариев:0)
(Загрузок:572)
Top 10 Flash Games(флеш игр) по версии GamesHaker.ucoz.ru pack5
(Комментариев:0)
(Загрузок:587)
Top 10 Flash Games(флеш игр) по версии GamesHaker.ucoz.ru pack6
(Комментариев:0)
(Загрузок:520)
Категории раздела
Разное [22]
Наш опрос
Есть ли у вас свой сайт?
Всего ответов: 79
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Зарег. на сайте
Всего: 3673
Новых за месяц: 2
Новых за неделю: 1
Новых вчера: 1
Новых сегодня: 0
Из них
Админинистраторов:1
Модераторов:0
Проверенных:0
Обычных юзеров:3672
Из них
Парней:3193
Девушек:480
Статистика файлов
Новостей:178
Файлов:494
Статьи:198
Фото:450
Коментариев:162
Форма входа
Поиск



Социум
Друзья сайта
продвижение сайта в поисковых системах GanGstA Game's TOP-100 CounterRambler's Top100 TOP Яндекс.Метрика
продвижение самостоятельно самостоятельное продвижение сайтоврегистрация сайта в каталогах Все для редактирования и создания игр с нуля.Игрострой.Новости моддинга и игростроя.Уроки рисования. Рейтинг SIMPLETOP.NET раскрутка сайта

Бесплатная регистрация в поисковых системах

Locations of visitors to this page

Graffiti Decorations(R) Studio (TM) Site Promoter ТОП GTA-GAME
О сайте Новое в галерее

Здравствуйте уважаемые любители игростроя! Этот сайт создан специально для вас! Здесь есть всё что нужно для редактирования и созданию игр, в том числе и без программирования... На этом сайте вы найдёте очень много файлов статей для редактирования игор. Также на сайте есть очень много различных файлов к играм, программ, патчей, трейнеров и др., для таких известных игор как GTASA, NFS, CS1.6, Crysis и др. Также есть различные программы связаны с играми, для создания 3D-моделей, для извлечения ресурсов из разных игор. На сайте очень много картинок, скриншотов и обоев на рабочий стол из игор. Здесь вы найдёте всё необходимое для создания модификаций к вашим любимым играм, а так же создания собственных игр. Для игроделов на сайте можно скачать необходимые для этого инструменты (конструкторы игр, движки), изучить необходимую литературу (статьи, журналы, книги) и т.д. На нашем форуме вы всегда сможете спросить совет или поделиться своими наработками. Если у вас имеются интересные файлы, то вы так же можете разместить их на нашем сайте.Ващей фантизии нет границ? Тогда вперёд, покажите всем на, что вы способны! А наш сайт вам в этом поможет. Играйте, создавайте, модифицируйте, удивляйте людей! И может быть через какое то время весь мир узнает о Вас!
Подробно о сайте >>>

Ubisoft транспорт GTA IV 3d модели GTA SA Crytek nfs_shift моделирование Новый pc играть Как обзор правильно статьи Туториал Как создать журнала редактор photoshop игровые Моды новое Демо стиле красивый научиться TDU смотреть иностранный локацию pdf интернет персонаж Рисуем персонажа game программирования фон Фотошоп GTA flash ADOBE уроки спрайт графика мод игра FPS журнал 2013 игр GDC программы Выдиралки данных моддинг рисование конструктор версия игры для 3D моделей studio Tools создания скачать новости вышел Android на Nvidia демонстрация CryENGINE анонс Unity Игровая движка текстур выпуск игрового Видео игрострой движок 2D SDK игровой Нарисовать графику GameDev урок 3ds Max sa создание авто Grand Theft Auto из рисовать Флеш Top



Гостевая книга                   Последние комментарии
Администрация не несет ответственности за корректность и достоверность информации в статьях и надежность всех упоминаемых URL-адресов. Все названия и логотипы в файлах и статьях являются собственностью их законных владельцев и не используются в качестве рекламы продуктов или услуг. Материалы портала распространяются согласно условиям лицензии
CreativeCommons.
© 2024
Рейтинг@Mail.ru