Главная » 2D графика » Создание редактора карт в стратегиях типа WarCraft

RSS

Создание редактора карт в стратегиях типа WarCraft

Не нравитсяНравится   Рейтинг +2

Модуль Culculate(X,Y : Integer; BrushIndex : Integer);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
procedure TMatrix5.Culculate(X,Y : Integer ; BrushIndex : Integer );
var
  i:Integer;
  BaseIndex, AdditionalIndex : Integer;
Begin
  // Заполнить матрицу считав значения с карты
  Self.Fill(X,Y);
  if BrushIndex = 3 then // Если рисуем переходной землей
  begin
    Vector[12] := 15;// Заносим центральный элемент
    for i := 0 to 24 do
    begin
      // Получить тип земли в виде индекса(0,1,2)
      BaseIndex := GetBaseIndex(Vector[i]);
      // и прежний номер переходной текстуры
      AdditionalIndex := GetAdditionalIndex(Vector[i]);
      // Если число в таблице BasicTable не равно 16 то,
      // к индексу типа земли умноженному на 16
      // прибавляем новое смещение
      // и заносим в Vector
      // ,иначе ничего не меняется
      if BasicTable[i,AdditionalIndex] <> 16 then Vector[i] := BaseIndex*16 +       BasicTable[i,AdditionalIndex];
    end;
  end { Конец обработки варианта "Переходная земля"}
  else // Иначе, если рисуем не переходной землей
    begin
    Vector[12] := BrushIndex*16;// Заносим центральный элемент
    for i := 0 to 24 do
      begin // Получить тип земли в виде индекса(0,1,2)
      BaseIndex := GetBaseIndex(Vector[i]);
      // и прежний номер переходной текстуры
      AdditionalIndex := GetAdditionalIndex(Vector[i]);
      // Если прежняя земля имеет тот же тип, что и активная
      if BaseIndex = BrushIndex then
      begin
        // Если число в таблице EqualTable не равно 16 то,
        // к индексу типа земли умноженному на 16
        // прибавляем новое смещение
        // и заносим в Vector
        // ,иначе ничего не меняется
        if EqualTable[i,AdditionalIndex] <> 16 then Vector[i] := BaseIndex*16 +         EqualTable[i,AdditionalIndex];
      end
      else // Если заменяемая и замещающая земля имеют разные типы
      begin
       // Если число в таблице NotEqualTable не равно 16 то,
       // к индексу типа земли умноженному на 16
       // прибавляем новое смещение
       // и заносим в Vector
       // ,иначе ничего не меняется
       if NotEqualTable[i,AdditionalIndex] < 16 then Vector[i] := BaseIndex*16 + NotEqualTable[i,AdditionalIndex] else        if NotEqualTable[i,AdditionalIndex] > 16 then Vector[i] := BrushIndex*16+ NotEqualTable[i,AdditionalIndex] - 16;
     end;
   end;
end;

Разберем все по полочкам: Первая строчка Self.Fill(X,Y); заполняет матрицу 5х5 значениями считанными с карты. Дальше следует такой кусок кода:

1
2
3
4
5
6
7
8
9
10
if BrushIndex = 3 then
begin
  Vector[12] := 15;
  for i := 0 to 24 do
  begin
    BaseIndex := GetBaseIndex(Vector[i]);
    AdditionalIndex := GetAdditionalIndex(Vector[i]);
    if BasicTable[i,AdditionalIndex] <> 16 then Vector[i] := BaseIndex*16 +     BasicTable[i,AdditionalIndex];
  end;
end;

В нем мы рассчитываем случай, когда рисуем переходным типом земли – ЗЕМЛЯ(if BrushIndex = 3 then). Строка Vector[12] := 15; заносит в центральный элемент №12 цельную текстуру активной земли, для нашего случая это могут быть числа 15,31,47. Как мы помним именно под этими номерами в нашем ImageListe находятся цельные текстуры ЗЕМЛИ. Далее в цикле, для каждого элемента взятого с карты и положенного в матрицу ( в данном виде – в вектор, для упрощения организации цикла) получаем индекс типа земли(BaseIndex := GetBaseIndex(Vector[i]);) , получаем номер переходной текстуры (AdditionalIndex := GetAdditionalIndex(Vector[i]);), и лезем в соответсвующую таблицу ( входные параметры которой это номер ячейки i и номер переходной текстуры AdditionalIndex). Если на выходе получим число 16, то ничего не меняем, если другое число, то индекс типа земли умножаем на 16 – это номер цельной текстуры данного типа земли, и прибавляем число полученное из таблицы – это новый номер переходной текстуры.

Расчёт

Рисунок 8

Как видно из рисунка 8, если в матрице 5х5 лежит в некоторой ячейке число 20, то индекс переходной текстуры будет равен 4 ( 20 mod 16), индекс типа земли равен 1 (20 div 16), а индекс цельной текстуры земли равен 16 ( Индекс типа земли * 16 ). Номер ячейки, где лежит число 20, и индекс переходной текстуры (4) – входные параметры в таблицу BaseTable. Если мы на выходе получим, к примеру число 8, то нужно к индексу цельной текстуры прибавить 8, чтобы получить индекс новой переходной текстуры. ( Индекс типа земли * 16 + 8 = 24 ) Это будет новое число, которое мы поместим на карту.

Следующий кусок кода:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
else
   begin
     Vector[12] := BrushIndex*16;
     for i := 0 to 24 do
     begin
       BaseIndex := GetBaseIndex(Vector[i]);
       AdditionalIndex := GetAdditionalIndex(Vector[i]);
       if BaseIndex = BrushIndex then
       begin
         if EqualTable[i,AdditionalIndex] <> 16 then Vector[i] := BaseIndex*16 + EqualTable[i,AdditionalIndex];
       end
       else
       begin
         if NotEqualTable[i,AdditionalIndex] < 16 then Vector[i] := BaseIndex*16 + NotEqualTable[i,AdditionalIndex]          else            if NotEqualTable[i,AdditionalIndex] > 16 then Vector[i] := BrushIndex*16+ NotEqualTable[i,AdditionalIndex] - 16;
      end;
    end;
  end;
end;

Делает все то же самое, для двух оставшихся случаев. Голубым выделены те строчки, которые по моему мнению можно удалить, но при этом исправить в таблице NotEqualTable числа больше 16 на эти же числа минус 16. Все, с технологией покончено!!!

Следующие страницы я посвящу некоторым особенностям вывода карты на экран в моем примере. Кого интересовала только технология расчета плавных перетеканий текстур, дальше, если нет желания, могут не читать.

Как я уже говорил, в примере я использовал компоненты для DirectX, написанные каким-то хорошим китайцем. Имя у него соответственно самое что ни на есть китайское, по этому я его не помню.

Конкретно для вывода карты на экран использовались компоненты TDXDraw, TDXImageList и TDXTimer.

TDXDraw – в основном используется для переключения страниц видеопамяти. Что это такое объяснять не буду.

TDXImageList – хранит в качестве элементов файлы со спрайтами выстроенными в одну цепочку. Соответственно к конкретному спрайту можно обратится по имени файла и номеру спрайта в нем. Также в этом компоненте есть две переменные PatternWidth, PatternHeight для указания ширины и высоты спрайтов, и переменная TransparentColor для указание прозрачного цвета.

TDXTimer – используется для генерации события DXTimerTimer с частотой заданной или рассчитанной в ходе выполнения программы.

Итак, текстуры выполнены в виде одного файла внутри которого выстроены в цепочку в соответствии с принципами изложенными выше и помещены в TDXImageList под именем «West». ( TDXImageList позволяет находить файлы внутри себя по их имени)

Нам нужно вывести на экран некоторую часть карты, причем карта наша состоит из кусочков и нам нужно вывести только те кусочки, которые видны в данный момент.

Можно сделать окно вывода кратным размеру текстур, а скроллинг организовать потекстурно с шагом равным ширине/высоте текстуры, тогда нет проблем, но это смотрится не очень красиво. Наша задача состоит в том, чтобы организовать скроллинг попиксельно и дать возможность задать окно вывода любого размера. Для того, чтобы это сделать нужно рассчитать сколько текстур по горизонтали и сколько текстур по вертикали мы должны отрисовать в окне вывода, включая и те текстуры которые в данный момент времени видны только частично.

Рисунок 9

На рисунке 9 клеточками изображена карта. Черным контуром показано окно вывода. Как видно – не все ячейки карты целиком влезли в окно, но их тоже надо отрисовать. Положение окна вывода на карте определяется координатами его левого верхнего угла относительно карты.( TopLeftCorner.x, TopLeftCorner.y) Их величины в пикселях(Нам же надо сделать попиксельный скроллинг) При создании новой карты они приравниваются нулям, и в дальнейшем определяются положением полос прокрутки. Вот часть кода:

1
2
3
4
5
6
7
8
9
10
11
12
13
procedure TMainForm.RedrawMap;
Var
  OffsPoint : TPoint;
  TopLeftElem : TPoint;
  ElemCount : TPoint;
  HelpVar1 : Integer;
  HelpVar2 : Integer;
  i,j : Integer;
  x,y : Integer;
  Index : Integer;
begin
  OffsPoint.x := TopLeftCorner.x mod ElemWidth;
  OffsPoint.y := TopLeftCorner.y mod ElemHeight;

Данные две строчки позволяют получить смешение левого верхнего угла экрана внутри левой верхней ячейки(См. рисунок 9). Глобальные переменные ElemWidth,ElemHeight это высота и ширина ячейки(текстуры). Теперь нам необходимо получить номер строки и столбца ячейки где находится левый верхний угол окна вывода:

1
2
TopLeftElem.x := TopLeftCorner.x div ElemWidth;
TopLeftElem.y := TopLeftCorner.y div ElemHeight;

Далее необходимо рассчитать сколько у нас целых текстур влезает в окно вывода по вертикали и горизонтали:

1
2
3
4
HelpVar1 := DXDraw.Width - (ElemWidth - OffsPoint.x );
HelpVar2 := DXDraw.Height - (ElemHeight - OffsPoint.y );
ElemCount.x := HelpVar1 div ElemWidth;
ElemCount.y := HelpVar2 div Elemheight;

Где DXDraw.Width, DXDraw.Height – это ширина и высота окна вывода. Если у нас есть нецелые текстуры снизу и справа окна вывода, то добавляем к ElemCount.x, ElemCount.y по единице:

1
2
if (HelpVar1 mod ElemWidth) > 0 Then Inc( ElemCount.x );
if (HelpVar2 mod ElemHeight) > 0 Then Inc( ElemCount.y );

Далее следует вывод на экран:

1
2
3
4
5
6
7
8
9
10
11
12
13
For j := 0 to ElemCount.y do
  For i := 0 to ElemCount.x do
  Begin
    // Вычислить координаты куда выводить
    X := i * ElemWidth - OffsPoint.x;
    Y := j * ElemHeight - OffsPoint.y;
    // Вычислить номер текстуры
    Index := GetElement(TopLeftElem.X + i,TopLeftElem.Y + j);
    // Вывести текстуру на экран
    // Учтите что LandType это не тип земли, а тип мира
    // Snow,West и т.д.
    ImageList.Items.Find(LandType).Draw(DXDraw.Surface,x,y,Index);
  end;

Строка :

1
Index := GetElement(TopLeftElem.X + i,TopLeftElem.Y + j);

обращается к матрице карты и считывает оттуда номер текстуры, следующая строка выводит ее на экран.

Возможно вы спросите: А как же нецелые текстуры слева и сверху окна вывода? Их-то ты не учел? Посмотрите на кусок кода отвечающий за вывод на экран. Циклическая переменная инициализируется от 0 до ElemCount.(x,y). Это значит, что всегда выводится на одну текстуру больше, чем в ElemCount, а если слева и сверху нет нецелых текстур, то переменная OffsPoint.(x,y) будет равна размерам ячейки. Переменные HelpVar(1,2) станут на размер ячейки меньше, и следовательно переменные ElemCount.(x,y) станут на единицу меньше. Все. Смотрите исходники в модуле Main.pas.

В программе не отловлены все баги. Например определен только один тип мира «West», да и текстуры нарисованы чисто схематически.

Если данный материал оказался чем-нибудь полезен для Вас, то благодарности отсылайте Дмитрию Мироводину. Если бы не он я бы никогда не написал эту статью. Если возникнут какие- нибудь вопросы пишите мне по адресу, указанному в copyright.

Исходные тексты Вы можете скачать тут, а библиотеку DelphiX найдете в разделе Lib.

Copyright © 2001 Иван Дышленко

Страницы : 1 2 3 4