Вторник, 21.05.2024, 21:12
Personal dimanche13 site
Главная | Регистрация | Вход Приветствую Вас Гость | RSS
Категории каталога
Мои статьи [22]
BlitzMax [17]
раздел содержит статьи относящиеся к языку программирования BlitzMax
SDL [9]
раздел содержит статьи на тему SDL
Code::Blocks [3]
в этом разделе рассказывается как прикрутить движок к интегрированной среде разработки(IDE) Code::Blocks
Форма входа
Поиск
Друзья сайта
Главная » Статьи » Мои статьи

Картинки, тайлсеты и карты в Warcraft I,II (1)
Введение.
Снова мне не дает покоя старина Warcraft. В предыдущих двух статьях мы узнали как хранятся данные в архивах war и файлах карт для WarII. На этот раз статья будет посвещана игре WARI, которая была предтечей(на равне с Dune 2), всех Real-Time Strategy (RTS). К сожалению уже мало кто помнит о ее существовании, а некоторые даже и не знают что такая игра была. Тем не менее мне кажется(поправьте если это не так), что это была первая игра BLizzard-ов по теме противостояния орков и людей, которую они развивают и по сей день, но уже он-лайн. Игра оказалась незаслуженно забыта, после выхода второй части. Да и что говорить, WARII была очень продуманной игрой и стала хитом на многие годы и надо признать - заслуженно. А возвращаясь к первой части, могу сказать, что она мне сильнее запала в душу, хотя бы потому что была первой моей RTS-игрой. А первая любовь, говорят, никогда не забывается.
 
Недра.
Ковырясь в недрах этой игрушки, уже по прошлым статьям, было замечено как серьезно разработчики подошли к своему делу. Раздобыть ресурсы этой игры оказалось не так то и просто. Blizzard-ы потратили уйму времени, чтобы скрыть всё в архивах и музыку, и звуки, картинки и тайлсеты, карты и анимации. Возможно архивирование этого добра в один файл диктовалось ограниченностью в ресурсах компов того времени(игры должны были быть маленькими), а возможно они не хотели чтоб весь внутренний контент игры разошелся по другим играм(юр. база по играм, была крайне мала). Но как бы там ни было, ресурсы вскоре стали вполне доступны окружающим, благодаря таким программам как WarDraft и проектам WarGus, Stratlas и другим. Таким образом следующие проекты от Blizzard имели уже другой формат архивов mpq-архивы. Но это уже другая история, а возможно еще одна тема для статей :)
 
Ближе к делу. О картинках.
Теперь ближе к делу. С помощью программы из предыдущей части, мы распаковали наш архивный файл от WARI на составные части. Все они носят названия с порядковым номером секции, которая была у них в архиве. То есть файлы от data.war.0 до data.war.582 и того 582 файла. Теперь неплохо было бы узнать что же содержится в каждом из них. Мы узнаем как посмотреть картинки, как узнать где карты из игры и как найти тайлсеты к картам. Начнем пожалуй с картинок. Как я уже мельком объявил, в предыдущей статье, некоторые картинки найти достаточно просто, по размеру. Компы в те времена были слабыми, мониторы тоже. VGA -мониторы могли отображать до 256 цветов на экране. А режим VGA мог отображать картинку размером 320 на 200 пикселей, то есть 64000 пикселей (320*200).  Ха, посмотрите ка на файлы с номерами 216,254, 261 и другие- они имеют размер 64004 байта. Что это как не полноэкранная картинка? конечно, а первые 2 слова(4 байта) это ее размеры! Вот так мы и обнаружили полноэкранные картинки из игры. Хранятся они просто кучей байтов. А как же так? один байт обозначает цвет? А очень легко - палитрой. Сейчас объясню. Как вы знаете любой цвет можно представить сочетанием 3 основных цветов: красного, зеленого и синего. Степеь их насыщенности определяется их значением. Так для желтого цвета: R:255 G:255 B:000. То есть на 1 цвет приходятся 3 байта. Существовала так называемая системная палитра, это таблица из 256 элементов, за каждым ее индексом хранились те три байта, определяющие цвет. Таким образом байт определял не цвет пикселя, а указатель в палитре, за которым был закреплен тот или иной цвет. Можно заполнить таблицу палитры всеми 256 оттенками красного или только синего, можно хоть всю ее заполнить черным цветом. Сейчас мониторы могут отображать такое кол-во цветов, которое старым мониторам даже и не снилось ;) Итак для того чтобы отобразить рисунок на экране, надо еще знать где лежит палитра этого рисунка. Узнать это можно так же по размеру файла, логично предположить, что 256 цветов по 3 компоненты будут занимать 768 байт (256*3). Ха, и такие файлы тоже есть! Как правило палитра лежит прямо рядом с картинкой, и имеет номер либо на 1 больше либо меньше. Вот пример - картинка data.war.458 а палитра к ней data.war.459 давайте и помучаем эту картинку:
 
SuperStrict
Global width:Int
Global height:Int
Global raw_picture:Byte Ptr  ' указатель на картинку
 
' эта функция загружает секцию как картинку
Function load_raw_picture( file_name:String )
 
 Local raw_stream:TStream = OpenFile( file_name )
 If Not raw_stream Then Print "raw_stream Ooops!"; End
 
 width = raw_stream.ReadShort()
 height = raw_stream.ReadShort()
 
 raw_picture = MemAlloc( width * height )   
 
 For Local y:Int = 0 Until height
  For Local x:Int = 0 Until width
   raw_picture[ y * width + x ] = raw_stream.ReadByte()
  Next
 Next
   
 raw_stream.close()
 
End Function
 
' загружает палитру из файла
Function load_pallete:TBank( file_name:String )
 Return LoadBank( file_name )
End Function
 
' преобразует палитру
Function recalck_pallete( buffer:TBank )
 For Local i:Int = 0 Until buffer.size()
  buffer._buf[ i ] :Shl 2
 Next  
End Function 
 
 
Global pall:TBank = load_pallete( "data.war.459" )
recalck_pallete( pall )
load_raw_picture( "data.war.458" )
 
Global pixmap:TPixmap = create_pixmap_from_raw(width, height, raw_picture, pall.buf() )
If Not pixmap Then Print "oops"; End
'----------------------------- main loop -----------------------------
Const ScreenWidth:Float = 640, ScreenHeight:Float = 480, FullScreen:Int = 0
Graphics(ScreenWidth, ScreenHeight, FullScreen)
Print "width : " + width
Print "height : " + height
Local mtx:Int, mty:Int
 Repeat
 
  mtx = MouseX()
  mty = MouseY()
  
  Cls()
 
   DrawPixmap( pixmap , 20 , 20 )
   
   SetColor( $FF, $FF, $FF )
   
   DrawText("mx : " + mtx, 0, 360)
   DrawText("my : " + mty , 0 , 380)
        
  Flip()
  
 Until( KeyDown(KEY_ESCAPE) Or AppTerminate() )
End
 
' просто выводит число в 16-ричной манере
Function show_hex:String( str_int:Int, bytes:Int = 4)
 Local hex_str:String = Hex( str_int )
 Local end_of_string:Int = hex_str.length
 Local from:Int = end_of_string - bytes * 2
 Return "0x" + hex_str[ from .. ]
End Function
 
' здесь как раз и создается пиксмап из указателя на картинку
Function create_pixmap_from_raw:TPixmap( img_width:Int, img_height:Int, raw_image:Byte Ptr, pal:Byte Ptr )
 Local out_pixmap:TPixmap = CreatePixmap( img_width, img_height, PF_RGB888 )
 
  For Local x:Int = 0 Until img_width
   For Local y:Int = 0 Until img_height
    Local  r:Byte, g:Byte, b:Byte, a:Byte = $FF
    Local index:Byte = raw_image[ y * img_width + x ]
    
    r = pal[ index * 3 + 0]'Shl 2
    g = pal[ index * 3 + 1]'Shl 2
    b = pal[ index * 3 + 2]'Shl 2
    
    Local color:Int = ( a Shl 24 ) + ( r Shl 16 ) + ( g Shl 8 ) + b
    out_pixmap.WritePixel( x , y , color )
   Next
  Next
  
 Return out_pixmap  
End Function 

 
код досаточно простой, здесь 3 важные функции: загрузить картинку как массив байтов, загрузить палитру и преобразование массива байтов в пиксмап. За это отвечают функции:  load_raw_picture(), load_pallete() и create_pixmap_from_raw().
После выполнения этой программы вы увидите заставку для игры орками. Тут все понятно и без комментариев. Для тех кто не знает, что такое тип TBank, могут задержаться, я сейчас о нем расскажу. 
 
Коротко о TBank.
Очень часто необходимо динамически выделить некоторое количество памяти, для этого нужно как минимум знать: где находится выделенная память и ее размер. Как правило делается это очень просто: берется указатель (byte ptr) и размер памяти(int) потом выделяется память этого размера. В коде это выглядит так:
 
global bptr:byte ptr        ' указатель на будущий кусок памяти
global size:int                ' размер который хотим выделить для этого
bptr = memalloc( size )  ' выделяем кусок памяти и указываем на его начало
memset_(bptr,0,size)     ' заполнить 0-ями кусок этой памяти
memfree(bptr)               ' а в конце надо освободить эту память
 
То есть система 5-и ступенчатая. К тому же имеет ряд недостатков, таких как: надо всегда хранить размер блока памяти, перед использованием очистить кусочек, а по завершении освобождать память вручную. При этом если вы захотите ее расширить, то надо будет еще пройти несколько шагов, чтобы оставить то что сейчас в ней хранится: выделить новый кусок памяти, переписать старый кусочек памяти в новый, очистить старый, вобщем мутота еще та...  Есть решение более изящное по своему исполнению: это тип TBank, всю грязную работу с памятью он выполнит за вас. Создается он очень просто:
 
global my_mem:TBank = CreateBank( 100 )
 
так мы создали банк памяти(не крови и слава Богу) и задали ему размер 100 байт. Он выделил, запомнил размер и очистил ее для вас. И все это одной строкой. Хорошо, а как узнать указатель на эту память и ее размер? Проще некуда: указатель вернет метод buf(), а размер метод size() вот так:
 
global bptr:byte ptr = my_mem.buf()
global size:int = my_mem.size()
 
то есть оба эти параметра содержатся внутри объекта, что значительно облегчает работу с памятью как с компьютерной так и с вашей(не надо запоминать какому указателю какой размер соответствует). "Растянуть" память можно так же просто методом Resize( new_size ). Помимо этого объекты TBank-а могут напрямую записывать или считывать информацию в поток или в файл. Работа с заполнением банка и изъятием данных из него, тоже сводится к простым методам Peek/Poke , что куда как проще чем преобразование указателей к нужному типу данных, как это надо было бы сделать не пользуйся мы банком. Так же можно указать значение/размер преодолев который, банк сам динамически расширит свой размер до необходимого. Как я уже говорил, банк может работать в связке с потоками(есть все необходимые методы), для того он в принципе и создавался.
Для удобства работы, зная указатель на кусок памяти с данными и размер этой памяти, можно преобразовать их в банк функцией CreateStatic(). Стоит еще сказать об одном обстоятельстве, что методы Peek/Poke иногда не очень удобно использовать, потому как им надо передавать позицию из которой необходимо читать данные. Что чаще всего используется в циклах с заранее известным или расчитанным количеством итераций. Но есть способ сделать, так сказать, банковский поток(к финансам это не имеет никакого отношения) по англицки это звучит более приемлимо, а главное, объяняет саму суть - TBankStream. То есть можно будет пользоваться истинно потоковыми командами при работе с банком, такими как read/write, size(),eof() и другими, что в некторых случаях очень облегчает жизнь.
Категория: Мои статьи | Добавил: dimanche (06.03.2009)
Просмотров: 984 | Комментарии: 1 | Рейтинг: 0.0/0 |
Всего комментариев: 0
Имя *:
Email *:
Код *:
Copyright MyCorp © 2024