Войти
ПрограммированиеСтатьиОбщее

MMO на Unreal Engine #2 (3 стр)

Внимание! Этот документ ещё не опубликован.

Автор:

Ещё один способ отладки это прицепиться к процессу, в принципе первый запуск также пройдет медленно. Делается это так:
Изображение
Там выбираем UE4Editor, ну и цепляемся.

Но перед тем как мы расширим функционал чтения карты, нам нужно обязательно создать класс блока, его мы назовем BP_Wall. Идем в наши блупринты (где уже лежит BP_Map), и там создаем новый блупринт. Также выбираем Actor, а имя даем ему BP_Wall (стена).
В Content есть кнопка Save All, когда есть значек на блупринтах или файлах, то лучше её нажать.

Снова переходим в корень, создаем там две папки: Models и Materials. Открываем Models и кидаем туда файлы из архива https://drive.google.com/open?id=0Bzl8pH3p1mHRcFBseWxBekg5cFU (которые предварительно лучше куда то распаковать).
В сплывающем окне жмем Import All. Файлы появились, пока шейдеры не скомспилировались, они будут серым, потом примут установленные мной цвета.
Выделяем файлы материалов, это все что начинаются с буквы M_, и вам советую так именовать. И перетаскиваем их в папку Materials. UE спросит вас Copy или Move, выбираем Move.
Ну и жмем на Save All.

+ Fix Up Redirectors in Folder

Теперь открываем BP_Wall и добавляем новую переменную, BlockID.
Изображение
Обратите внимание на правую панель Details, там надо выбрать что тип Integer (зелененький такой), ну а первоначальное значение появится лишь после компиляции. Плюс надо поставить галку на Editable, это даст возможность редактировать значение прямо в редакторе (без запуска или в процессе запуска игры). Компилим, и видим что появился 0 в Default Value, мы его менять не будем, нас он устраивает.
Изображение

Помните я упомянул про первую вкладку Viewport, нам в принципе сейчас именно туда. Можно её не открывать, но так сложнее будет выставлять значения. У нашего Actor, слева есть такое поле Components
Изображение
Именно там и будут располагаться все визуальные составляющие такие как модель. Так что давайте туда и добавим. Жмём на Add Component и набираем слово Static, выбираем Static Mesh.
Получилось добавить? Отлично, всё таки текстом куда дольше передавать данные, но точнее (да и всегда можно перечитать 8))
Когда у нас выбран Static Mesh, в Details (справа) у нас теперь совсем другие вещи. Жмякаем на второй None (который со стрелкой вниз, то есть выпадающий список). Выбираем там наш BlockWall. И.. о чудо, у нас появилась наша модель. Но мы слишком близко к ней, можно скроллингом мыши находясь в Viewport откатиться назад. Также при зажатой ПКМ можно использовать традиционные WASD, Space, Ctrl. На первое время этого должно хватить для изменения вида.
Теперь давайте добавим второй Static Mesh, как они располагаются в иерархии - это очень важно, если второй будет внутри первого, то при перемещении ... потом распишу. Они так и легли сейчас, нам пока это не важно.
появился StaticMesh1, переименуйте его сразу во MeshFloor (то бишь пол, по которому ходим). А верхний, давайте в просто Mesh. Теперь MeshFloor даем модель BlockWall_plate. Если обратите внимание, он находится в том же месте по координатам (0,0,0) но при этом расположен ниже их. Специальный блок для тех участков карты где нет никакого блока.
Снова выбираем StaticMesh, и вместо BlockWall, пропишите BlockWall_02_glass. Это я щас для проверки, что вы всё сделали правильно. Это должно выглядеть вот так:
Изображение
Если выглядит по другому, имеется ввиду что позиция блоков расположена по другому, или их нет вообще, перечитайте снова. Достаточно удалить и тот и другой, и начать заново этот абзац.
Если всё хорошо, то выберите верхнему блоку Clear в списке моделей. Останется лишь нижний маленький кубик.

Теперь переходим во вкладку Constuct Sript, видим там один единственный блок. Перетягиваем нашу переменную слева BlockID поближе и выбираем GET. От BlockID тянем и набираем switch to int. Соединяем блок конструктора и Switch, Default оставим пустым. А вот ниже есть плюсик, жмем на него начинаем ответвлять. На 0 нам ничего не надо делать, жмешь ещё раз плюсег. Гуд. Теперь из окна Компоненты (где у нас Mesh и MeshFloor), тянем Mesh. От блока Mesh тянем и пишем set static mesh. И соединяем этот блок с единицей у Switch
Изображение
Теперь в блоке set static mesh, в переменной New Mesh выбираем BlockWall. Тем самым мы говорим что при значении BlockID = 1, модель первого блока сделать BlockWall.

Теперь, снова от Mesh делаем ещё один set static mesh, и в New Mesh выбираем BlockWall_02_glass
А теперь выберем set static mesh и при его выделенным нажмем Ctrl+W, должен появится ещё один блок set static mesh. Если этого не произошло (такое бывает), выберите другой блок и снова set static mesh, и нажимаем Ctrl+W. Блок появится именно в том месте где стоит курсор мыши. Обратите внимание у этого блока Target будет self, в данном случае это не совсем правильно. Протяните линию от Mesh до SetStaticMesh->Target. В третьем блоке New Mesh надо выбрать BlockWall_03_door. Доделайте до этого варианта сами:
Изображение
Это в принципе окончательная его форма. Нажимаем Compile, ну и всё, закрываем этот блупринт.

Для проверки, перетащите блупринт на сцену и попробуйте выставить разные значения BlockID. Блок должен мгновенно менять свою модель.
Изображение
Как наиграетесь, не забудьте удалить его со сцены (уровня, карты), он нам там пока ни к чему.

Ну и давайте пока мы тут, сделаем вот что, откроем блупринт блока BP_Wall. Зайдем в конструктор, выделим мышкой всё, кроме блоков: конструктора и BlockID, жмем ПКМ, Collapse to Function, и слева переименуем её в UpdateBlockID.
Правило #2: Если у Вас есть набор комманд который вы используете дважды, не надо копировать кусок текста и выставлять, просто создайте метод и вызывайте через него. Это избавит Вас от необходимости править код как минимум в двух местах если что-то поменяется, а если этих мест не два а больше? Представили. Ок
Эту функцию мы будем вызывать из CMap, передавая в неё ID нашего блока, делать это будем уже когда блок на карте, это позволит нам изменять ID уже не на этапе построения, а в любой момент времени.
Так же откройте эту функцию, нам надо кое что подправить. Потому что есть ещё одно значение блока, это -1. Сейчас мы должны добавить состояние для 0 и -1. В Switch нет -1, но есть Default, им мы и воспользуемся.
Сделайте от Default ещё один блок SetStaticMesh от Mesh, а от него ещё один SetStaticMesh от MeshFloor. New Mesh, оставьте None у обоих блоков.
ну и добавьте от 0 тоже два SetStaticMesh, также от Mesh и от MeshFloor. У SetTaticMesh от MeshFloor->New Mesh сделайте BlockWall_plate. Должно получится вот так.
Изображение
Компилим. Мы ещё сюда вернемся, когда добавим возможность получать из карты: "а какой блок стоит в таких то координатах?" (GetBlockID).

Ну а теперь опять вернемся в студию и посмотрим что там нам "боженька принес". В начале откроем CMap.h и добавим две переменные. Одна будет иметь ссылку на наш блупринт BP_Wall, чтобы мы смогли создавать такой объект внутри CMap, а вторая это переменная Map, в которой мы будем хранить нашу карту ID блоков в виде целых чисел.
Ах да, мыж договорились ещё добавить метод GetBlockID(), добавляем его сразу видимым из блупринтов.
Ну и будет ещё две переменных, которые нам очень нужны, это map_width и map_height. Одна из них будет хранить ширину карты, другая высоту.
Открываем CMap.h, ищем public и добавляем в конец:

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = References)
  TSubclassOf<AActor> bpwallClass;

  TArray<int32> Map;

  UFUNCTION(BlueprintCallable, Category = Game)
  int32 GetBlockID(int32 X, int32 Y);

  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = References)
  int32 map_width;
  
  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = References)
  int32 map_height;
Открываем CMap.cpp, ползём в метод LoadMap и пишем там следующий код, в этот раз весь выложу и просто объясню чё там происходит.
void ACMap::LoadMap(FString filename)
{
  UE_LOG(LogTemp, Warning, TEXT("Loading Map"));
  TArray<FString> arr;
  LoadStringFromFile(("Content/Levels/"+filename), arr);

  map_height = map_width = 0;
  Map.Reset(0);
  if (arr.Num() > 0)
  {

    TArray<FString> blocks;
    
    //количество строк в файле и есть высота карты
    map_height = arr.Num();

    // я сделал чтобы один блок от другого разделялся запятой (откройте level_01.smap), 
    // можете сделать любой разделитель там и поправить здесь.
    // разделитель на анг. будет delimiter (De limit er)
    for (FString line : arr)
    {
      // этот метод ParseIntoArray, превращает строку в массив строк, работает также как 
      // explode в PHP, или split в Java и в C#
      line.ParseIntoArray(blocks, TEXT(","), true);

      // устанавливаем ширину карты по количеству ID блоков в первой строке
      if (map_width == 0)
      {
        map_width = blocks.Num();
        // когда мы узнали ширину и высоту карты, нам надо переписать размер массива карты
        Map.Reset(map_width*map_height);
      }

      for (FString block : blocks)
      {
        // Map.Add() - добавляет новое значение в конец массив
        // FCString::Atoi - перевод строки в число, если эта строка содержит целое число
        // Если же там float, пишите FCString::Atof
        // А вообще можно тупо почитать доки по FString и FCString
        Map.Add(FCString::Atoi(*block));
      }
    }
    // UE_LOG, это вывод в консольку, 
    // в данном случае хочу видеть какая стала ширина и высота карты после загрузки из файла
    UE_LOG(LogTemp, Warning, TEXT("Map is loading: width=%d, height=%d"), map_width, map_height);
  }
  else
    UE_LOG(LogTemp, Error, TEXT("Map is empty"));
}

Ну и добавим метод GetBlockID. На самом деле разница не большая где описывать метод в .h или .cpp. Но проще описывать именно в .cpp
Никуда пока не пишем, просто смотрим. Стоит обратить внимание на вот это

int32 ACMap::GetBlockID(int32 X, int32 Y)
обычно мы везде писали void в самом начале, а тут мы написали int32. О чем это говорит? А говорит это о том, что этот метод не просто может, а уже должен вовзвращать значение такого типа (то бишь, целое число). И если вы его забудете вернуть, компилятор будет ругаться. А теперь пишем этот метод в самый конец CMap.cpp
int32 ACMap::GetBlockID(int32 X, int32 Y)
{
  // всё довольно просто, я так давно работаю с одномерными массивами (вместо двухмерных или трехмерных) что уже привык
  // надеюсь привыкните и вы.
  // допустим у нас карта 10 в ширину и 20 в высоту
  // первое значение всегда хранится в нуле [0]
  // 1,1,1,1,1, 1,1,1,1,1
  // 10 значений, последнее значение будет лежать в [9]
  // добавляем ещё одно, что у нас получается?
  // 1,1,1,1,1, 1,1,1,1,1, 1
  // последнее значение где будет лежать? правильно в [10]
  // а теперь давайте проверим index, чему будет равен index если X=0, Y=1, map_width=10
  // правильно 10. Может поймете не сразу, но со временем, это у вас будет на автомате
  int index = Y*map_width + X;
  // мы бы могли просто вернуть, написав return Map[index];
  // но что если значения X и Y лежат за пределами карты, например когда X=-10? будет ошибка
  // так что дальше я просто исключу все ошибки, и если X и Y за пределами значений, вернем -1
  if (X >= 0 && X < map_width && Y >= 0 && Y < map_height)
    return Map[index];
  else
    return -1;

}

Не забываем компилить в студии Ctr+Shift+B и ползём на следующую страницу, осталось совсем чуть чуть ребят.

Страницы: 1 2 3 4 Следующая »

#Blockmap, #debug, #MMO, #Tilemap, #Unreal Engine

23 мая 2017 (Обновление: 28 мая 2020)

Комментарии [5]