Программирование Android и iPhone
Вконтакте Twitter Google+ Facebook RSS
Главная Старые статьи
Разработка игры для мобильного телефона на Mobile Basic

На Mobile Basic я наткнулся случайно. Мне всегда хотелось написать что-нибудь для мобильного телефона, но все как-то не хватало времени разбираться с Java. Mobile Basic предельно простой язык программирования. Конечно, он значительно уступает по своим возможностям Java, но, тем не менее, вполне пригоден для написания серьезных приложений.

Mobile Basic заточен под создание игр. В него встроена поддержка работы со спрайтами, тайлами и картами. Вам не придется ломать голову и бороться с миганием картинок при отрисовке. Все отображение производится в буфер. Для того чтобы вывести его содержимое на экран, используется команда REPAINT.

Чтобы показать всю простоту языка, давайте напишем игрушку. Идея игры проста: космический корабль летит, уворачиваясь от астероидов и вражеских кораблей. Поскольку у меня Siemens c65, я буду ориентироваться на экран 128x128 пикселей.

Структура игры очень проста. Есть основной цикл, где опрашиваются клавиши и рисуется кораблик, есть четыре подпрограммы: отображение заднего плана, отображение выстрелов, отображение врагов, проверка столкновений. Я признаю, что структура не идеальна, но моя цель не создать великую игру, а показать вам основные принципы программирования в Mobile Basic-е.

СРЕДИ ЗВЕЗД

Для начала нарисуем звездное небо. Выведем на экране 15 точек. Для хранения их координат будем использовать два массива: St1X% и St1Y%. Объявление массива производится с помощью оператора DIM.

        DIM St1X%(15)
        DIM St1Y%(15)

Присвоим элементам массива случайные координаты. Для этого используем функцию RND(), возвращающую случайное 32 битное число. Для того, чтобы получить целочисленные координаты в пределах 0 до 128, используем выражение: 64+MOD(RND(),64).

        FOR i% = 0 TO 14
            St1X%(i%) = 64+MOD(RND(),64)
            St1Y%(i%) = 60+MOD(RND(),60)
        NEXT i%

Для перебора элементов массива используется цикл FOR. Обратите внимание, нумерация начинается не с первого, а с нулевого элемента.
Итак, у нас есть координаты. Теперь нам надо закрасить экран телефона черным цветом и нарисовать белые точки.

        REM Устанавливаем черный цвет (RGB)
        SETCOLOR 0,0,0
        REM Рисуем черный прямоугольник
        FILLRECT 0,0,128,120
        REM Устанавливаем белый цвет
        SETCOLOR 255,255,255
        FOR i% = 0 TO 14
            REM Рисуем точку
            PLOT St1X%(i%),St1Y%(i%)
        NEXT i%
        REM Выводим содержимое буфера на экран
        REPAIINT

Заставим наши точки двигаться вниз. Организуем бесконечный цикл, внутри которого будем увеличивать значения элементов массива St1Y% на единицу. Если значение кокгог-либо элемента превысит 120, обнулим его. Для того чтобы регулировать скорость движения звезд, используем функцию SLEEP T%, где T% - задержка в миллисекундах. Кроме того, вынесем все, что связано с отрисовкой звезд в отдельную процедуру и назовем ее MoveStars. Для вызова этой процедуры используется выражение GOSUB MoveStar. В конце процедуры стоит оператор RETURN, который передает управление команде, следующей за GOSUB.

SUB MAIN
    DIM St1X%(15)
    DIM St1Y%(15)
        FOR i% = 0 TO 14
            St1X%(i%) = 64+MOD(RND(),64)
            St1Y%(i%) = 60+MOD(RND(),60)
        NEXT i%
 
    REM *****************************
    REM *  Основной цикл программа  *
    REM *****************************
    a1:
        SETCOLOR 0,0,0
        FILLRECT 0,0,128,120
        GOSUB MoveStars
        REPAIINT
        SLEEP 30
    GOTO a1
 
    REM ***********************************
    REM *  Подпрограмма, рисующая звезды  *
    REM ***********************************
 
    MoveStars:
        SETCOLOR 255,255,255
        FOR i% = 0 TO 14
            St1Y%(i%) = St1Y%(i%) + 1
            IF St1Y%(i%) > 120 THEN St1Y%(i%) = 0
            PLOT St1X%(i%),St1Y%(i%)
        NEXT i%
    RETURN
    ENDSUB

Чтобы придать картинке объемность, нарисуем еще два ряда звезд, летящих со скоростью два и три пикселя за такт. Чтобы движение звезд не казалось "рваным", нарисуем за ними шлейф более темного цвета.

        REM Рисуем белые звезды
        SETCOLOR 255,255,255
        PLOT St1X%(i%),St1Y%(i%)
        PLOT St2X%(i%),St2Y%(i%)
        PLOT St3X%(i%),St3Y%(i%)
        REM Рисуем серый шлейф за быстрыми звездами
        SETCOLOR 205,205,205
        PLOT St3X%(i%),St3Y%(i%)-1
        REM Рисуем темно-серый шлейф
        SETCOLOR 150,150,150
        PLOT St3X%(i%),St3Y%(i%)-2
        PLOT St2X%(i%),St2Y%(i%)-1

РИСУЕМ КОСМИЧЕСКИЙ КОРАБЛЬ

Для вывода космического корабля будем пользоваться спрайтами. Спрайт это изображение какого-либо объекта в игре. Нарисуем в редакторе Paint Brash три картинки размером 10x10: вид сверху, вид при правом и левом повороте. Переведем их в формат PNG (именно этот формат поддерживается всеми телефонами. Имена файлов: plane.png, plane1.png и plane2.png.) и добавим в наш проект.
При работе со спрайтами сначала необходимо загрузить "графический элемент" (GRL) из файла. Это реализуется с помощью команды GELLOAD "Имя GEL", "Имя файла". Затем надо указать спрайту, какой графический элемент отображать: SPRITEGEL "Имя спрайта", "Имя GEL". Положение спрайта на экране задается с помощью оператора SPRITEMOVE "Имя спрайта", x%, y%. Здесь x% и y% - переменные, определяющие положение корабля.

Для организации управления будем использовать функции LEFT(), RIGHT(), UP(), DOWN(), FIRE(), которые возвращают не ноль, если нажата соответствующая клавиша. Кроме того, воспользуемся функцией INKEYS(), которая возвращает код нажатой клавиши или 0, если клавиша не нажата. Ниже приводится основной цикл программы, где реализовано управление кораблем.

        ...
        GELLOAD "Plane_GEL1","plane.png"
        GELLOAD "Plane_GEL2","plane2.png"
        GELLOAD "Plane_GEL3","plane3.png"
        SPRITEGEL "Plane", "Plane_GEL1"
        x% = 60
        y% = 109
        a1:
            setcolor 0,0,0
            FILLRECT 0,0,132,120
            GOSUB MoveStars
 
            REM Опрос клавиш
            if INKEY()=0 then SPRITEGEL "Plane", "Plane_GEL1"
            IF Left()<>0 THEN
               x% = x% - 4
               SPRITEGEL "Plane", "Plane_GEL3"
            ENDIF
            IF Right()<>0 THEN
               x% = x% + 4
               SPRITEGEL "Plane", "Plane_GEL2"
            ENDIF
            IF Up()<>0 THEN y% = y% - 3
            IF Down()<>0 THEN y% = y% + 3
 
        REM Ограничиваем движение корабля пределами экрана
        IF x% > 117 THEN x% = 117
        IF x% < 0 THEN x% = 0
        IF y% > 110 THEN y% = 110
        IF y% < 0 THEN y% = 0
 
        REM Двигаем спрайт
        SPRITEMOVE "Plane",x%,y%
 
        REM Рисуем снизу панельку
        SETCOLOR 0,0,255
        FILLRECT 0,120,132,128
        REPAINT
        Sleep 30
      GOTO a1
     ...

РИСУЕМ ВЫСТРЕЛЫ

Давайте научим наш корабль стрелять. Для описания выстрелов создадим четыре массива: координаты снаряда, его тип (ракета или огненный шар) и скорость

DIM FireX%(FireBufNum%)
DIM FireY%(FireBufNum%)
DIM FireT$(FireBufNum%)
DIM FireV%(FireBufNum%)

Переменная FireBufNum% определяет максимальное число снарядов, одновременно отображаемых на экране. В дальнейшем мы не будем делать различий между нашими снарядами и снарядами врагов. Все они будут храниться в этих массивах.
Введем переменную FireNum%, которая определяет, какое число снарядов в данный момент рисуется на экране. Добавим обработку кнопки FIRE в основной цикл программы.

     ...
        ActiveShortType$="1"
        localtime%=0
        lasttimeshot%=0
        a1:
        localtime%=localtime%+1
        ...
        REM Произвести выстрел
        IF Fire()<>0 THEN
          IF localtime% - lasttimeshot% > 5 THEN
                 IF FireNum% < FireBufNum% - 1 THEN
                          lasttimeshot% = localtime%
                          FireNum% = FireNum% + 1
                          FireX%(FireNum%) = x% + 5
                          FireY%(FireNum%) = y%
                          FireT$(FireNum%) = ActiveShortType$
                          FireV%(FireNum%) = 4
                 ENDIF
          ENDIF
        ENDIF
 
        REM Сменить оружие
        IF Game_B() THEN
           IF ActiveShortType$="1" then
                 ActiveShortType$="2"
                 ELSE
                 ActiveShortType$="1"
         ENDIF
        ENDIF
 
        ...
        GOTO a1
        ...

Здесь мы ввели три новых переменных localtime% - счетчик игровых циклов. Использование этой переменной совместно с lasttimeshot% (в которой хранится номер цикла последнего выстрела) позволяет задать частоту стрельбы. В рассматриваемом случае мы можем стрелять не чаще чем раз в пять тактов.

Функция GAME_B() опрашивает клавишу 9. Если она нажата, меняем оружие. Номер активного оружия хранится в текстовой переменной ActiveShortType$.

Отрисовку выстрелов и удаление покинувших экран снарядов из массива будем производить в подпрограмме FirePoint. Для отрисовки будем пользоваться функцией DRAWGEL "Имя GEL", x%, y%, которая рисует на заднем фоне графический элемент.

...
 REM Загружаем из файлов рисунки снарядов. Размер 3x5
 GELLOAD "fire1","fire.png"
 GELLOAD "fire2","fire1.png"
 ...
 FirePoint:
  FOR i% = 0 TO FireNum%
  REM Изменяем положение снаряда
  FireY%(i%) = FireY%(i%) - FireV%(i%)
  REM Проверяем, не покинул ли снаряд пределов экрана
  IF (FireY%(i%) < 0) OR (FireY%(i%) > 120) THEN
    REM Если покинул, то обнуляем его тип и смещаем все ненулевые выстрелы влево
    REM Таким образом, в нашем массиве нет "дыр"
    FireT$(i%) = "0"
    j% = i%
    FireNum% = FireNum% + 1
    WHILE j% < FireNum%
        REM Двигаемся с конца массива, пытаясь отыскать ненулевой элемент
        WHILE (FireT$(FireNum% - 1) = "0")
                FireNum% = FireNum% - 1
                REM  Если мы дошли с конца до нашего элемента, покидаем цикл
                IF j% = FireNum% THEN GOTO exitWhile
        ENDWHILE
        REM Заменяем нулевой элемент ненулевым элементом с конца
        IF FireT$(j%) = "0" THEN
                     FireT$(j%) = FireT$(FireNum% - 1)
                     FireT$(FireNum% - 1) = "0"
                     FireX%(j%) = FireX%(FireNum% - 1)
                     FireY%(j%) = FireY%(FireNum% - 1)
                     FireV%(j%) = FireV%(FireNum% - 1)
                     FireNum% = FireNum% - 1
        ENDIF
        j% = j% + 1
    ENDWHILE
    exitWhile:
    FireNum% = FireNum% - 1
  ENDIF
  REM Рисуем снаряд
  DRAWGEL "fire"+FireT$(i%), FireX%(i%),FireY%(i%)
  NEXT i%
 RETURN


РИСУЕМ ВРАГОВ

Для отображения врагов на экране будем использовать пять массивов: два массива координат, два массива скоростей и массив типов кораблей.

DIM EnX%(EnBufNum%)
DIM EnY%(EnBufNum%)
DIM EnVX%(EnBufNum%)
DIM EnVY%(EnBufNum%)
DIM EnT$(EnBufNum%)

Переменная EnBufNum% определяет максимальное число врагов, отображаемых на экране, а переменная EnNum% - число врагов, отображаемых в данный момент. Процесс отрисовки и хранения данных о врагах практически не отличается от схожих операций над снарядами. Все действия связанные с врагами вынесены в подпрограмму MoveEnemy. Думаю, вы сами разберетесь что к чему.

...
MoveEnemy:
 FOR i% = 0 TO EnNum%
  EnX%(i%) = EnX%(i%) + EnVX%(i%)
  EnY%(i%) = EnY%(i%) + EnVY%(i%)
  IF (EnY%(i%) > 120) THEN
        EnT$(i%) = "0"
        j% = i%+1
        EnNum% = EnNum% + 1
        WHILE j% < EnNum%
          WHILE (EnT$(EnNum% - 1) = "0")
                EnNum% = EnNum% - 1
                IF j% = EnNum% THEN GOTO exitWhile2
          ENDWHILE
          IF EnT$(j%) = "0" THEN
                     EnT$(j%) = EnT$(EnNum% - 1)
                     EnT$(EnNum% - 1) = "0"
                     EnX%(j%) = EnX%(EnNum% - 1)
                     EnY%(j%) = EnY%(EnNum% - 1)
                     EnVX%(j%) = EnVX%(EnNum% - 1)
                     EnVY%(j%) = EnVY%(EnNum% - 1)
                     EnNum% = EnNum% - 1
          ENDIF
          j% = j% + 1
        ENDWHILE
        exitWhile2:
        EnNum% = EnNum% - 1
  ENDIF
  REM Блок отрисовки врагов
  DRAWGEL "Enemy"+EnT$(i%), EnX%(i%), EnY%(i%)
  REM Рисуем взрыв
  IF EnT$(i%) = "V3" THEN
     EnY%(i%) = 130
  ENDIF
  IF EnT$(i%) = "V2" THEN
     EnT$(i%) = "V3"
  ENDIF
  IF EnT$(i%) = "V1" THEN
     EnT$(i%) = "V2"
     EnVY%(i%)=0
  ENDIF
NEXT i%
RETURN
...

В игре будем использовать врагов двух типов: астероиды и корабли. Их изображения загрузим из файлов rock.png и vrag1.png. Также загрузим изображения взрыва. Наш взрыв будет состоять из трех изображений V1.png, V2.png и V3.png.

GELLOAD "Enemy1","rock.png"
GELLOAD "Enemy2","vrag1.png"
GELLOAD "EnemyV1","V1.png"
GELLOAD "EnemyV2","V2.png"
GELLOAD "EnemyV3","V3.png"

Для того чтобы взорвать врага, достаточно просто заменим его тип на "V1". Это приведет к тому, что при следующем обращении к подпрограмме MoveEnemy нарисуется первая фаза взрыва, затем его тип поменяется на "V2". После того как будет достигнута третья фаза, объекту присваиваются координаты за пределами экрана, что приведет к его удалению при следующем вызове процедуры.
Если сейчас откомпилировать и запустить программу, то враги будут лететь, не обращая на нас никакого внимания. Наделим их неким подобием искусственного интеллекта. Пусть астероиды отскакивают от границ экрана, а корабли производят выстрел при достижении определенной координаты. Вставьте нижеприведенный код в подпрограмму перед командой DRAWGEL.  

   ...
      IF EnT$(i%) = "1" THEN
        REM Если достигнута граница, меняем горизонтальную скорость полета на противоположную
        IF (EnX%(i%) < 0) OR (EnX%(i%) > 119) THEN EnVX%(i%) = -EnVX%(i%)
      ENDIF
      IF (EnT$(i%) = "2") AND (EnY%(i%) = 20) THEN
         FireNum% = FireNum% + 1
         FireX%(FireNum%) = EnX%(i%) + EnW2%
         FireY%(FireNum%) = EnY%(i%) + EnH% + 2
         FireT$(FireNum%) = "1"
         FireV%(FireNum%) = -EnVY%(i%) - 2
      ENDIF
      ...

Все почти готово для того, чтобы начать использовать врагов в игре. Осталось только правильно расставить их на нашем пути. Для хранения карты врагов будем использовать массивы: MEnTime%() - для хранения времени, в которое вражеский объект должен появиться на экране, MEnX%() - для хранения координаты X, MEnVX%(), MEnVY%() - для хранения скоростей; MEnT$()- для типов врагов. Для загрузки Значений в массив будем использовать операторы RESTORE, READ и DATA. Первый из них говорит, где находятся данные, которые необходимо загрузить. READ считывает очередное значение из последовательности, указанной в команде DATA.

        mapsize%=20
        DIM MEnTime%(mapsize%)
        RESTORE EnTyme1
         For i%=0 to mapsize%-1
        READ MEnTime%(i%)
        Next i%
 
        DIM MEnT$(mapsize%)
        RESTORE EnType1
        For i%=0 to mapsize%-1
           READ MEnT$(i%)
        Next i%
 
        DIM MEnX%(mapsize%)
        RESTORE EnX1
        For i%=0 to mapsize%-1
          READ MEnX%(i%)
        Next i%
 
        DIM MEnVX%(mapsize%)
        RESTORE EnVX1
        For i%=0 to mapsize%-1
          READ MEnVX%(i%)
        Next i%
 
        DIM MEnVY%(mapsize%)
        RESTORE EnVY1
        For i%=0 to mapsize%-1
          READ MEnVY%(i%)
        Next i%
 
        REM Данные для Первого уровня
        REM Время появления врагов на экране
        EnTyme1:
          DATA 0,0,50,50,60,80,80,150,160,170,180,190,200,250,260,270,300,390,400,450
        REM Тип врагов
        EnType1:
          DATA "1","1","2","1","2","1","2","2","2","1","2","1","1","1","2","1","1","1","2","Q"
        REM Координаты X
        EnX1:
          DATA 10,120,40,100,30,60,90,120,0,40,80,100,120,10,35,17,10,145,10,6
        REM Горизонтальные скорости
        EnVX1:
     DATA 4,-1,0,2,0,0,2,0,0,-2,-1,1,0,3,-2,1,2,0,1,0
        REM Вертикальные скорости
        EnVY1:
          DATA 3,4,4,4,3,4,4,3,3,2,4,3,4,4,3,2,2,3,3,2

Здесь mapsize% - число вражеских объектов на уровне.
Как вы помните, у нас есть переменная localtime%, которая содержит номер такта основного цикла. Логика следующая: как только элемент массива MEnTyme% становится равным текущему значению переменной localtime%, Добавляем в массивы En новый элемент и присваиваем соответствующие значения из массивов MEn. Нижеприведенный код реализует эту идею. Его надо вставить в самое начало подпрограммы MoveEnemy.

   WHILE MEnTime%(MEnNum%)<=localtime%
      EnNum%=EnNum%+1
      EnT$(EnNum%)=MEnT$(MEnNum%)
      EnX%(EnNum%)=MEnX%(MEnNum%)
      EnY%(EnNum%)=-10
      EnVX%(EnNum%)=MEnVX%(MEnNum%)
      EnVY%(EnNum%)=MEnVY%(MEnNum%)
      if EnT$(EnNum%)="Q" Then Goto GameWin
      MEnNum%=MEnNum%+1
   ENDWHILE

ПРОВЕРКА СТОЛКНОВЕНИЙ

В данный момент у нас имеется нечто похожее на игру: все летает, все стреляет, Но никто не хочет умирать. Устраним этот недостаток. Проверим, не врезался ли в нас враг и не задело ли нас шальной пулей. Эту проверку реализуем в подпрограмме CheckBoom:
CheckBoom:
 

REM Проверяем, не попал ли выстрел в врага. Для этого перебираем все
 REM отображаемые на экране снаряды и всех врагов
 FOR j% = 0 TO FireNum%
   FOR i% = 0 TO EnNum%
     RastX%=FireX%(j%)-EnX%(i%)
     RastY%=FireY%(j%)-EnY%(i%)
      if RastX%<0 then goto m1
      if RastY%<0 then Goto m1
      IF (RastX% <= EnW%) AND (RastY% <= EnH%) THEN
               FireY%(j%) = -10
               EnT$(i%) = "V1"
     ENDIF
     m1:
   NEXT i%
   REM Проверяем, не попал ли в нас снаряд
   RastX%=FireX%(j%)-x%
   RastY%=FireY%(j%)-y%
   if RastX%<0 then goto m2
   if RastY%<0 then goto m2
   IF (RastX% <= ShipW%) AND (RastY <= ShipH%) THEN
               FireY%(j%) = -10
               Lives% = Lives% - 1
               IF Lives% = 0 THEN GOTO GameOver
   ENDIF
   m2:
 NEXT j%
 REM Проверяем всех врагов и смотрим, не врезался ли в нас враг
 FOR i% = 0 TO EnNum%
   RastX%=EnX%(i%)-x%
   RastY%=EnY%(i%)-y%
   if RastX%<0 then RastX%=-RastX%
   if RastY%<0 then RastY%=-RastY%
   IF (RastX% <= EnW%) AND (RastY% <= EnH%) THEN GOTO GameOver
 Next i%
RETURN

В приведенном коде проверяется расстояние между центрами объектов по горизонтали и вертикали (RastX% и RastY%). Таким образом проверяется столкновение прямоугольников, ограничивающих наши спрайты.

ДОБАВИМ ЗВУК

Как ни странно, звук для телефона больная тема. Нет единого формата поддерживаемого всеми телефонами, поэтому при разработке игры приходится ориентироваться на конкретного производителя. Mobile Basic позволяет проигрывать звук в форматах AU, OTT и WAV. Для того чтобы проиграть звуковой файл, добавьте его в свой проект и выполните одну из команд

 PLAYAU "Файл.au"
 PLAYOTT "Файл.ott"
 PLAYWAV "Файл.wav"
 

Думаю, вы без труда самостоятельно озвучите взрывы и выстрелы.

ЗАКЛЮЧЕНИЕ

В принципе наш игровой движок готов. Осталось сделать вразумительное меню, добавить пару десятков уровней и новых врагов. Со всем этим Вы вполне справитесь и сами. Можете использовать код игры в любых своих программах, единственное, о чем я прошу, укажите, что при их разработке вы пользовались материалами сайта www.mobilab.ru.

Скачать исходный текст
Скачать игры для телефона
Автор:
Александр Ледков


 


Баннер

ИНТЕРЕСНОЕ



Беременность и роды: миома матки лечение. Операция по удалению миомы матки. . предоставляем услуги переводчика . керамзит в мешках
Новости [1] [2] [3]... / Программинг ( Android/ iOS/ J2ME[1] [2] [3]) / Безопасность / Статьи / Софт ( Android / iOS) / Форум / Архив ( Symbian/ Статьи)
Рейтинг@Mail.ru

MobiLab.ru © 2005-2012
При использовании материалов сайта ссылка на www.mobilab.ru обязательна