Когда мы создаем врага – мы создаем один экземпляр класса Enemy. Что если нам нужно 50 врагов? Нам что создавать разные экземпляры врагов ещё и задавать им разное имя?
Видеоверсия ( http://www.youtube.com/watch?v=AlwrUT_U_A4 )
Получается вот такие строки будут повторяться 50 раз?
1 |
Enemy easyEnemy1(easyEnemyImage, "EasyEnemy", lvl, easyEnemyObject.rect.left, easyEnemyObject.rect.top, 200, 97); |
1 |
Enemy easyEnemy2(easyEnemyImage, "EasyEnemy", lvl, easyEnemyObject.rect.left, easyEnemyObject.rect.top, 200, 97); |
И тд…
А если мы захотим удалить объект, ведь врага можно убить. В общем, нам нужно как-то это обойти и сделать всё проще и удобнее.
Будем использовать список – занесем в него объекты, а именно врагов.
Давайте расставим на карте с помощью tiled map editor ещё несколько объектов врагов с именем EasyEnemy.
Проверьте, чтобы были подключены в main.cpp:
1 2 3 4 5 6 7 8 9 |
#include <SFML/Graphics.hpp> #include "view.h" #include <iostream> //#include <sstream>//сейчас не нужно. раньше выводили текст ( в 13 ом уроке ) #include "mission.h" #include "iostream" #include "level.h" #include <vector> #include <list> |
В функции int main() создадим список ( хорошо про список здесь – https://youtu.be/KNVPFVG49Oc?t=10m25s ) entities типа данных Entity (потому что класс – пользовательский тип данных), в него будем помещать потом практически всех, кого бывает много.. (пули, ездящие платформы, враги). ну кроме игрока соответственно)
1 2 |
std::list<Entity*> entities;//создаю список, сюда буду кидать объекты.например врагов. std::list<Entity*>::iterator it;//итератор чтобы проходить по эл-там списка |
Далее забираем врагов
1 |
std::vector<Object> e = lvl.GetObjects("EasyEnemy");//все объекты врага на tmx карте хранятся в этом векторе |
Теперь закинем в наш ранее созданный список всех врагов из карты tmx:
1 2 |
for (int i = 0; i < e.size(); i++)//проходимся по элементам этого вектора(а именно по врагам) entities.push_back(new Enemy(easyEnemyImage, "EasyEnemy", lvl, e[i].rect.left, e[i].rect.top, 200, 97));//и закидываем в список всех наших врагов с карты |
Таким образом мы поместим столько врагов в нашу игру, сколько их на карте .tmx , причем у каждого элемента вектора (то есть объекта врага с карты) своя координата “нахождения” этого врага на карте. Эти координаты и передаются:
1 |
e[i].rect.left//коорд Х |
1 |
e[i].rect.top//коорд Y |
А создание одного врага надо удалить:
1 2 |
//Object easyEnemyObject = lvl.GetObject("easyEnemy"); //Enemy easyEnemy(easyEnemyImage, "EasyEnemy", lvl, easyEnemyObject.rect.left, easyEnemyObject.rect.top, 200, 97); |
Осталось только вызвать функцию update для всех элементов списка.
Но у нас сейчас не получится – этой функции вообще нет в Entity, а список у нас типа Entity. Поэтому – в классе Entity добавим:
1 |
virtual void update(float time) = 0; |
Виртуальная функция — это функция, объявленная с ключевым словом virtual в базовом классе и переопределенная в одном или в нескольких производных классах.
Всё, теперь можем юзать ф-цию update для элементов списка entities , и врубаться будет именно update класса Enemy, поскольку именно объекты Enemy хранятся в списке типа Entity.
Таким образом можно будет вызывать ф-цию update и для платформ и для пуль и тд.
Вызываем ф-цию update:
1 2 3 |
for (it = entities.begin(); it != entities.end(); it++) { (*it)->update(time);}//для всех элементов списка(пока это только враги,но могут быть и пули к примеру) активируем ф-цию update //easyEnemy.update(time);//старый вариант update врага |
Осталось нарисовать наши объекты из списка:
1 2 3 4 5 6 7 8 9 10 11 |
window.setView(view); window.clear(Color(77,83,140)); lvl.Draw(window); for (it = entities.begin(); it != entities.end(); it++){ window.draw((*it)->sprite); //рисуем entities объекты (сейчас это только враги) } //window.draw(easyEnemy.sprite);//старый вариант рисования одного врага window.draw(p.sprite); window.display(); |
В списке в каждом элементы есть данные и указатель на следующий элемент списка. Давайте предположим, что у нас есть список из трёх врагов (на картинке ниже односвязный список. в уроке двусвязный, но пока его используем как односвязный, так что смысл тот же):
При удалении элемента посередине указатель первого элемента будет указывать на третий и всё.
Вобще элементы списка разбросаны по памяти, в отличие от вектора – там они идут последовательно и для них выделяется память целыми блоками.
Вектор , например, последовательно хранит блоками данные в памяти, поэтому в векторе можно очень быстро получать произвольный доступ к элементу вектора, зато удаление из середины происходит дольше, поскольку все ячейки должны будут сдвинуться влево. Зато в векторе быстро удаляются элементы из конца, например. Кстати , при удалении элемента из вектора память может не освободиться, это потому, что вектор хавает блоки памяти.
Вобще вот тут написано, вкладка “за и против”:
https://ru.wikipedia.org/wiki/Vector_(C%2B%2B)
Вобщем для разных задач нужны разные контейнеры. Вектор хорошо лепится к полоске здоровья персонажа-последний элемент добавляем или удаляем в зависимости от урона по персонажу или пополнению здоровья.
У меня 5 методов инициализации моего объекта (LoadMassAnim(SkeletAnim); setAnimPropers(seconds(0.2), true, false); SetSpeed(5); SetPos(900, 750, 100); LoadObjectMass(ZombieMass, i, 10);)// последние это передача силки на массив элементу массива.
Как это сделать через список ?
создай список типа(тип данных в смысле) класса этого объекта и кидай в него элементы (экземпляры класса)
Ради справедливости хотелось бы отметить, что std::list является двунаправленным списком (иначе как бы мы могли использовать обратные итераторы rbegin и rback?). Пруф (второй абзац). Но это никак не влияет на качество урока, ибо как всегда пять с плюсом!
Единственное, что хотелось бы уточнить, почему автор избегает использования “нового” for’a для обхода контейнеров? На это есть конкретные причины или это просто такой олд скул?
ууу, точняк. спасибо) исправлю)
ты про foreach? просто так наглядней, кажется) и все 100% это знают:-) хотя надо как нибудь сказать об этом.
В общем, да. Но foreach в шарпе Я говорил об этом:
Вот на Вики. И по-моему с итераторами менее наглядно, хотя более полезно В любом случае, это не так уж и важно, я из любопытства. Некоторые избегают этой записи, чтобы с C++ 11 не связываться.
Просто на данный момент это может работать ещё не у всех..
Есть ли смысл для однотипных врагов каждый раз загружать картинку, создавать текстуру ( про спрайт фз, может быть потом будет проблемой проверить столкновение для враг / враг … хотя можно написать свою проверку на столкновение учитывая ток координаты).
Не экономичнее хранить только координаты для однотипных врагов.
Но пока не решил вопрос: где и как.
мы не загружаем картинку каждый раз для однотипных врагов. мы загрузили картинку один раз и передали по ссылке. если бы передали по значению – создавалась бы копия этой картинки для каждого врага и нерационально раздувалась бы память
Хорошо бы код добавить main.cpp.
Почему то на данном этапе
for (it = entities.begin(); it != entities.end(); it++);
{
(*it)->update(time);
}
Выдаёт ошибку и выкидывает.
Куда пропал анимация персонажа?
выпилилась и не переделывалась под этот урок, т.к планировалось сделать потом отдельный класс для работы с анимацией.
Решил с анимаций, а теперь как анимировать врага?
А как реализовать двунаправленный список с врагами, не прибегая к использованию Tiled Map Editor ?