18.09.2024, 12:45. Показов 979. Ответов 0
Так ли уж сложно написать игру, имея возможности FreeBasic ?
Конечно, библиотеки DirectX и OpenGL дают высокое быстродействие
и потрясающую 3D графику, но они очень уж сложны для новичка.
Особенно, если требуется создавать собственные 3D модели.
Но можно сделать игру, которая не требует 3D, и даже не
использует сложные графические форматы, а только BMP.
Так была сделана "казуальная" игра "Охота на кур" ("Moorhuhn")
начала 2000х годов.
Моя программа на FreeBasic демонстрирует такой подход,
но графики здесь гораздо меньше, чем в Moorhuhn - у меня это просто учебный пример.
Прозрачные спрайты с альфа-каналом.
FreeBasic может самостоятельно (без дополнительных библиотек)
выводить картинки с альфа-каналом. При этом на одну точку картинки
приходится 4 байта: Blue - синий, Green - зелёный, Red - красный, Alpha - прозрачность.
Первые 3 байта задают любой реальный цвет, байт прозрачности задаёт: 0 - полностью
прозрачный, 255 - полностью непрозрачный. Это позволяет добиться красивых
эффектов огня, взрывов, тумана и т д. До сих пор многие графредакторы не
позволяют использовать слой прозрачности (mspaint точно не может).
Слои прозрачности могут сохраняться в форматах изображений BMP, PNG, TGA.
FreeBasic сохраняет такие картинки командой BSAVE, если установлен режим
32 битной цветности SCREEN.
Как сделать спрайт с прозрачностью ?
Главный герой моей игры - смайлик - был сделан рендерингом из бесплатной
3D модели смайлика в программе Poser. Картинка должна иметь размер 128*128.
Преобразование из формата png в bmp сделано программой XnView.
Анимация взрыва была сделана из готовой непрозрачной текстуры с помощью небольшой
программы на FreeBasic.
Звуки и музыка.
Классическим подходом к созданию звуков в (бесплатных и малобюджетных) играх
является использование бесплатной библиотеки bass.dll
Эта библиотека умеет проигрывать файлы форматов wav, mp3 и модульную (трекерную) музыку,
например файлы mod.
В моём примере использована bass.dll версии 2.4 FreeBasic поддерживает
bass.dll "из коробки", правда родные примеры в дистрибутиве FreeBasic довольно
неудачные.
Как сделана анимация в игре ?
(Структуры - это наше всё)
В самых первых играх персонажи довольно тупо двигались все вместе
с одинаковыми скоростями и одинаковым внешним видом (см здесь)
https://ru.wikipedia.org/wiki/Space_Invaders
Но уже игры на ZX-Spectrum поражали возможностью движения врагов-мишеней
и ракет по замысловатым траекториям, с выходом и уходом за границы экрана.
И при этом фоновая картинка игры двигалась ! На современных компьютерах
сложного движения многочисленных спрайтов достаточно легко достигнуть.
Создадим структуру spt такого вида:
| QBasic/QuickBASIC |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| ' Определение типа для работы с спрайтами
TYPE SpriteType
x AS SINGLE ' координата x
x1 AS SINGLE ' скорость x
y AS SINGLE ' координата y
y1 AS SINGLE ' скорость y
' переменные, используемые для анимации
speed AS SINGLE ' скорость анимации
speedT AS SINGLE ' таймер анимации
frb AS LONG ' начальный кадр анимации (и флаг - спрайт активен)
frend AS LONG ' конечный кадр анимации
fr AS LONG ' текущий кадр анимации
anstop AS LONG ' флаг - однократная анимация
anstopf AS LONG ' флаг - анимация закончена
END TYPE
' массив спрайтов
DIM SHARED spt(100) AS SpriteType |
|
Теперь напишем такую процедуру:
| QBasic/QuickBASIC |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| ' Функция анимации
SUB anim()
' Эта функция обрабатывает все спрайты (с 1 по 100)
' Это позволяет в дальнейшем анимировать любой спрайт в игре.
FOR i=1 TO 100
' Если поле frb (начальный кадр анимации) > 0, то идет обработка
IF spt(i).frb THEN
'...
'...
'...
' нарисовать спрайт на экране
IF spt(i).frb THEN
PUT (spt(i).x-64,spt(i).y-64),images(spt(i).fr),alpha
END IF
endif
NEXT i
END SUB |
|
Если теперь в основном цикле игры вызвать anim(),
то на экран будут выведены спрайты, причём только те, у которых spt(i).frb<>0
Захотели пристрелить врага - нет ничего проще - достаточно в любом месте программы
присвоить spt(obj).frb=0, где obj - номер спрайта.
Т е движение врагов, их появление, взрывы и т. д. целиком управляются данными
структуры spt()
При этом код движения врагов, код обработки движения мышки (уничтожение врагов)
и код рисования врагов могут находиться в разных процедурах (функциях)
См zip файл с откомпилированной игрой.
Полный текст программы:
| QBasic/QuickBASIC |
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
| 'freebasic
REM Created: 18.09.2024
REM (C) Dmitriy Korabelnikov
REM d_korabelnikov@mail.ru
REM Простая игра - спрайтовый 2D шутер
#Include "bass.bi"
DEFLNG a-z
' размер экрана
CONST screenw=1024
CONST screenh=768
' центр экрана
CONST screenw2=512
CONST screenh2=384
CONST bordery=64
' звуки и музыка
DIM SHARED AS HSAMPLE snd(20)
' Определение типа для работы с спрайтами
TYPE SpriteType
x AS SINGLE ' координата x
x1 AS SINGLE ' скорость x
y AS SINGLE ' координата y
y1 AS SINGLE ' скорость y
' переменные, используемые для анимации
speed AS SINGLE ' скорость анимации
speedT AS SINGLE ' таймер анимации
frb AS LONG ' начальный кадр анимации (и флаг - спрайт активен)
frend AS LONG ' конечный кадр анимации
fr AS LONG ' текущий кадр анимации
anstop AS LONG ' флаг - однократная анимация
anstopf AS LONG ' флаг - анимация закончена
END TYPE
' массив спрайтов
DIM SHARED spt(100) AS SpriteType
DIM SHARED mousex AS LONG,mousey AS LONG,mbutton AS LONG,kbdelay AS LONG
DIM SHARED crossimg AS ulong ptr,bgimg AS ulong ptr
DIM SHARED images(100) AS ulong ptr
DIM SHARED maxsmiles AS LONG,hits AS LONG,missed AS LONG
FUNCTION irnd(n AS LONG) AS LONG
irnd=INT(n*RND)
END FUNCTION
FUNCTION srnd(n AS SINGLE) AS SINGLE
srnd=n*RND
END FUNCTION
' Is file n$ exist ?
FUNCTION exist(n$) AS LONG
re=OPEN(n$,FOR INPUT,AS #9)
CLOSE #9
IF re=0 THEN exist=-1 ELSE exist=0
END FUNCTION
SUB bassbegin
r=BASS_GetVersion()
' Initialize BASS using the default device at 44.1 KHz.
r=BASS_Init(-1, 44100, 0, 0, 0)
END SUB
SUB bassend
r=BASS_Free()
END SUB
SUB errnofile(n$)
screenset 0,0
CLS
bassend
PRINT n$;" - file not found";
SLEEP
END
END SUB
FUNCTION LoadSound(n$) AS DWORD
DIM r AS DWORD
IF NOT exist(n$) THEN errnofile(n$)
r = BASS_MusicLoad(0, StrPtr(n$), 0, 0, BASS_SAMPLE_LOOP, 0)
IF r = 0 THEN r = BASS_StreamCreateFile(BASSFALSE,StrPtr(n$), 0, 0, 0)
LoadSound=r
END FUNCTION
SUB PlaySound(n AS LONG)
BASS_ChannelPlay(snd(n), 1)
END SUB
' Движение врагов
SUB motion()
' В цикле по номерам спрайтов
FOR i=1 TO 100
IF spt(i).frb THEN
' Если спрайт уходит за левую границу экрана, двигать его вправо
IF spt(i).x<0 THEN spt(i).x1=ABS(spt(i).x1)
' Если спрайт уходит за правую границу экрана, двигать его влево
IF spt(i).x>screenw THEN spt(i).x1=-ABS(spt(i).x1)
' Изменить координату X
spt(i).x=spt(i).x+spt(i).x1
END IF
NEXT i
END SUB
' Функция анимации
SUB anim()
' Эта функция обрабатывает все спрайты (с 1 по 100)
' Это позволяет в дальнейшем анимировать любой спрайт в игре.
FOR i=1 TO 100
' Если поле frb (начальный кадр анимации) > 0, то идет обработка
IF spt(i).frb THEN
fr=spt(i).fr:frb=spt(i).frb:frend=spt(i).frend
' Если текущий кадр вне диапазона, текущий кадр = начальный
IF fr<frb OR fr>frend THEN fr=frb
' Следующий замысловатый код плавно меняет скорость анимации
' в зависимости от значения .speed
' Если .speed=100.0, то анимация будет меняться в каждом кадре
' (учитывая, что мы задали 50 кадров в секунду)
' Если .speed=50, анимация будет меняться в каждом втором кадре и т д
' При таком способе при плавном изменении .speed анимация меняется
' плавно, а не скачками.
spt(i).speedT=spt(i).speedT-spt(i).speed
IF spt(i).speedT<=0 THEN
spt(i).speedT=spt(i).speedT+100.0
fr=fr+1
' Есть возможность делать анимацию однократной (не циклической)
' установкой флага .anstop
IF fr>=frend THEN
IF spt(i).anstop THEN
spt(i).frb=0:spt(i).anstopf=1
ELSE
fr=frb
endif
endif
spt(i).fr=fr
endif
' нарисовать спрайт на экране
IF spt(i).frb THEN
PUT (spt(i).x-64,spt(i).y-64),images(spt(i).fr),alpha
END IF
endif
NEXT i
END SUB
' Проверка попаданий
SUB testcollision(mx,my)
' line (mx-32,my-32)-step(63,63),rgb(255,0,0),"bf":sleep 500
found=0
' Для всех спрайтов врагов
FOR i=100 TO 1 STEP -1
IF spt(i).frb>0 AND spt(i).anstop=0 THEN
' Если растояние от прицела до центра врага не больше 32 точек
IF ABS(spt(i).x-mx) + ABS(spt(i).y-my)<32 THEN
found=1
' Остановить движение врага (скорость=0)
spt(i).x1=0
' Заменить анимацию "мордочки" на анимацию взрыва
spt(i).frb=16:spt(i).frend=31:spt(i).anstop=1
' Включить звук взрыва (12) и один из 10 воплей
PlaySound 1+(i MOD 10)
PlaySound 12
' Изменить счёт игры
maxsmiles=maxsmiles-1:hits=hits+1
endif
endif
IF found THEN EXIT FOR
NEXT i
' Если не попали, включить звук "свистящей мимо" пули
IF found=0 THEN PlaySound 13:missed=missed+1
END SUB
' Управлять мышью
SUB mousecontrol()
getmouse mousex,mousey,,mbutton
' Увеличим чувствительность мыши в 2 раза
' (где screenw2 и screenh2 - половина высоты и ширины экрана)
mx=screenw2+2*(mousex-screenw2)
my=screenh2+2*(mousey-screenh2)
IF mx<0 THEN mx=0
IF my<0 THEN my=0
IF mx>screenw THEN mx=screenw
IF my>screenh THEN my=screenh
mousex=mx
mousey=my
' kbdelay используется для задержки .5 сек при нажатии на кнопку мышки
IF kbdelay>0 THEN kbdelay=kbdelay-1
IF kbdelay=0 THEN
' Если нажата левая кнопка мыши, проверить попадание
IF (mbutton AND 1)<>0 THEN
kbdelay=25:testcollision(mx,my)
endif
endif
END SUB
' Создать нового врага, присвоив случайные значения координат
SUB generate(obj)
' если спрайт с номером obj активен, то выйти (ничего не делать)
IF spt(obj).frb THEN EXIT SUB
spt(obj)=spt(0)
' Начальные координаты и анимация
spt(obj).frb=1:spt(obj).frend=15
spt(obj).fr=1+irnd(14)
spt(obj).x=srnd(screenw)
spt(obj).x1=3+srnd(4):IF rnd>.5 THEN spt(obj).x1=-spt(obj).x1
spt(obj).y=bordery+irnd(screenh-2*bordery)
spt(obj).speed=30
' увеличить счётчик спрайтов
maxsmiles=maxsmiles+1
END SUB
' преобразование числа в текст
' используется при загрузке файлов и выводе счёта
FUNCTION tx$(v,n)
tx$=RIGHT$(STR$(1000000000+v),n)
END FUNCTION
' финальное сообщение игры
SUB finalwords()
screenset 0,0:CLS
WHILE INKEY$=CHR$(27):SLEEP 20:WEND
t$="THANK YOU FOR PLAYING!"
le=LEN(t$)
x=64-INT(le/2)
y=24
FOR n=1 TO le
m$=MID$(t$,n,1)
IF m$<>" " THEN
FOR i=47 TO y STEP -1
LOCATE i+1,x+n:PRINT " ";
LOCATE i,x+n:PRINT m$;
IF INKEY$=CHR$(27) THEN EXIT SUB
SLEEP 20
NEXT i
END IF
NEXT n
FOR i=1 TO 200
IF INKEY$=CHR$(27) THEN EXIT SUB
SLEEP 20
NEXT i
END SUB
' основная программа
screenres screenw,screenh,32,2,5
WIDTH screenw/8,screenh/16
' загрузка картинок
p$=exepath+"\gamedata"
ind=1
FOR i=1 TO 15
images(i)=imagecreate(128,128)
n$=p$+"\emot\emoti"+tx$(ind,2)+".bmp"
IF NOT exist(n$) THEN errnofile(n$)
BLOAD n$,images(i)
ind=ind+2
NEXT i
ind=1
FOR i=16 TO 31
images(i)=imagecreate(128,128)
n$=p$+"\explod\explod"+tx$(ind,2)+".bmp"
IF NOT exist(n$) THEN errnofile(n$)
BLOAD n$,images(i)
ind=ind+1
NEXT i
crossimg=imagecreate(64,64)
n$=p$+"\cross.bmp"
IF NOT exist(n$) THEN errnofile(n$)
BLOAD n$,crossimg
bgimg=imagecreate(screenw,screenh)
n$=p$+"\palm1024.bmp"
IF NOT exist(n$) THEN errnofile(n$)
BLOAD n$,bgimg
' поставить курсор мышки в центр и сделать его невидимым
setmouse screenw2,screenh2,0
' инит звуковую библиотеку
bassbegin
' загрузить музыку
snd(20)=LoadSound(p$+"\skogen01.mod")
' Загрузить звуки
FOR i=1 TO 10
snd(i)=LoadSound(p$+"\sound\sn"+tx$(i,2)+".wav")
NEXT i
snd(12)=LoadSound(p$+"\sound\explosion.wav")
snd(13)=LoadSound(p$+"\sound\rippler.wav")
tick=0
apage=0
FOR i=1 TO 5
IF maxsmiles<20 THEN generate(1+irnd(99))
NEXT i
' играть музыку
PlaySound 20
' цикл игры
DO
screenset apage,1-apage
apage=1-apage
PUT (0,0),bgimg,PSET
' status()
LOCATE 2,2:PRINT "SMILES ";tx$(maxsmiles,2);" HITS ";tx$(hits,6);" MISSED ";tx$(missed,6);
' выход из игры по ESC
IF INKEY$()=CHR$(27) THEN bassend:finalwords:END
' Генерировать нового врага 1 раз в 96 циклов
tick=tick+1
IF tick=96 THEN
tick=0
IF maxsmiles<20 THEN generate(1+irnd(99))
endif
' двигать спрайты
motion
' движение мышки и обработка стрельбы
mousecontrol
' анимация и вывод спрайтов
anim
' рисовать курсор мышки (прицел)
PUT (mousex-32,mousey-32),crossimg,alpha
' задержка 20 миллисекунд (50 кадров в секунду)
SLEEP 20
LOOP
END |
|