|
На 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.
Скачать исходный текст Скачать игры для телефона Автор: Александр Ледков
|