2024/01/31(水) [n年前の日記]
#1 [basic] FreeBASICでTIC-80風のHelloWorld
Windows10 x64 22H2 + FreeBASIC 1.10.1 を触ってるけれど、せっかくだから TIC-80風の Hello World を書いてみようかなと思いついた。
TIC-80 は Fantsy Console (Fatntasy Computer)の一種。テキストエディタ、ドットエディタ、マップエディタ、サウンドエディタ、ミュージックエディタが、1ファイルの中に全部入っているので、すぐにゲーム制作が始められる。
このジャンルのアプリでは、PICO-8(有償アプリ)が有名だけど、TIC-80は無料で利用できる。(有償版もある。)
_TIC-80 tiny computer
その TIC-80 は、起動直後にサンプルファイルが既にロードされてる状態で始まるのだけど。そのサンプルと似たような処理を書けるなら、ひとまず FreeBASIC でも、2Dピコピコゲーム程度なら書けそうだ、と分かるのではないかなと…。
TIC-80 は Fantsy Console (Fatntasy Computer)の一種。テキストエディタ、ドットエディタ、マップエディタ、サウンドエディタ、ミュージックエディタが、1ファイルの中に全部入っているので、すぐにゲーム制作が始められる。
このジャンルのアプリでは、PICO-8(有償アプリ)が有名だけど、TIC-80は無料で利用できる。(有償版もある。)
_TIC-80 tiny computer
その TIC-80 は、起動直後にサンプルファイルが既にロードされてる状態で始まるのだけど。そのサンプルと似たような処理を書けるなら、ひとまず FreeBASIC でも、2Dピコピコゲーム程度なら書けそうだ、と分かるのではないかなと…。
◎ サンプルソース :
そんなわけで書いてみた。
_helloliketic80.bas
使っている画像は以下。
_obj.bmp
_obj.png
_helloliketic80.bas
' game main loop
#ifdef __FB_WIN32__
' Windowsの場合、mmsystemを利用
#include "windows.bi"
#include "win/mmsystem.bi"
#endif
' 画像読み込み用ライブラリ FBImage を使う場合は以下のコメントアウトを外す
' #include once "FBImage.bi"
' fbgfxモードを使う
#Include "fbgfx.bi"
Using fb
' 時間計測用の変数
Dim As Double start_time, prev_time, now_time, delta, one_frame, next_time
Dim As Integer frame_count
Dim As String fps_text = "FPS"
Dim As Integer scrw, scrh ' ウインドウサイズ
Dim As Integer imgw, imgh ' 画像サイズ
' カレントディレクトリを exeファイルのある場所にする
chdir exepath()
' ウインドウサイズと色深度を指定
scrw = 512
scrh = 288
Screenres scrw, scrh, 32
' 画像読み込み
#ifdef __FBImage_bi__
' FBImageを利用して読み込む場合
var img = LoadRGBAFile("obj.png")
#Else
' FreeBASIC標準のbmp読み込みを使う場合
Dim img As any ptr = ImageCreate(128, 64)
Bload "obj.bmp", img
#endif
' 画像の幅と高さを取得
imageinfo img, imgw, imgh
#ifdef __FB_WIN32__
' タイマー精度を1msecに向上
timeBeginPeriod(1)
#endif
' 1フレームあたりの本来の時間
Dim As Double MAX_FPS = 60.0
one_frame = 1.0 / MAX_FPS
' 開始時間を取得
start_time = Timer
prev_time = start_time
frame_count = 0
Dim As Boolean running = True
Dim As Double x, y
x = scrw / 2
y = scrh / 2
Dim As Double anime_t = 0.0
' メインループ
While (running)
' 前回フレームから何秒経過したか取得。単位は秒(小数点以下有り)
now_time = Timer
If now_time >= prev_time Then
delta = now_time - prev_time
Else
delta = one_frame
End If
prev_time = now_time
next_time = now_time + one_frame
If now_time >= start_time Then
If (now_time - start_time) >= 1.0 Then
' 1秒経過したのでFPSを取得
fps_text = "FPS: " & frame_count
start_time += 1.0
frame_count = 0
End If
Else
start_time = now_time
End If
' ESCキー、qキー、ウインドウの閉じるボタンを検出
Dim As String k = inkey$
If k = Chr$(27) Or k = "q" Or k = Chr$(255) + "k" Then
' メインループ終了
running = False
End If
' キャラを移動
Dim As Double spd = 4.0 * MAX_FPS * delta
If MultiKey(SC_LEFT ) Then x -= spd
If MultiKey(SC_RIGHT) Then x += spd
If MultiKey(SC_UP ) Then y -= spd
If MultiKey(SC_DOWN ) Then y += spd
' アニメ表示用カウンタを更新
anime_t += delta
' 描画開始
ScreenLock
' 画面クリア
color RGB(255, 255, 255), RGB(52, 164, 255)
cls
' 画像を描画
Dim As Integer n, sx, sy, sw, sh
n = Int(anime_t / 0.3) Mod 2 ' 0 or 1
sw = (imgw / 2) ' 幅
sh = imgh ' 高さ
sx = sw * n ' 描画元 x
sy = 0 ' 描画元 y
Put (x - (sw / 2), y - (sh / 2)), img, (sx, sy) - Step(sw, sh), TRANS
' 文字列を描画
Draw String (10, 10), fps_text
Draw String (scrw / 2 - (8 * 6), scrh * 0.8), "HELLO WORLD"
' 描画終了
ScreenUnlock
If Timer < next_time Then
' 本来の1フレーム時間がまだ経過してないので sleep させる
Dim As Double wait_ms = (next_time - Timer) * 1000.0
If wait_ms > 0.0 Then sleep wait_ms
End If
frame_count += 1
Wend
' キーバッファを空にする
While Inkey <> "": Wend
#ifdef __FB_WIN32__
' タイマー精度を本来のスペックに戻す
timeEndPeriod(1)
#endif
' 画像を使い終わったので破棄
ImageDestroy img
使っている画像は以下。
_obj.bmp
_obj.png
◎ 少し解説 :
TIC-80 のサンプルと比べたら、無駄にソースが長くなるなと…。やってることは同じなのに…。ゲーム制作に特化した環境と、どんなアプリも作れる汎用性を重視している環境では、こういうところで違いが出てくるのかもしれない。
さておき。FreeBASIC でキー入力を検出するためには、一般的には Inkey を使うけれど、カーソルキー押しっぱなしでキャラを移動させたりする時は MultiKey() を使う。この MultiKey() を使う時は、fbgfxモードとやらを有効にしないといけないらしい。
FreeBASICで時間を取得したい時は、Timer を使う。単位は秒で、小数点以下の値も入ってくるから、ミリ秒も測定できる。ほとんどの場合、PCが起動してからの時間を返してくるけれど、動かしてるOSによってどのあたりが開始時間になるかは異なる。
Windows上で FreeBASIC を動かした際、標準状態だと sleep の精度が荒いけれど、1ミリ秒の精度にしたいときは、マルチメディア関連の機能を使う。Linux の場合は標準状態で1ミリ秒の精度なので気にしなくていい。
FreeBASIC は標準状態だと bmp画像の読み込みしかできない。png画像も読み込みたい時は、何かしらの画像ライブラリを使うことになる。今回は FBImage という画像ライブラリを使って実験していた。
FBImage のインストールは、以前の日記を参考に。
_FreeBASICで画像描画 - mieki256's diary
画像描画は Put を使う。文字の描画は Draw String を使う。
描画開始時に ScreenLock を呼んで、描画が終わったら ScreenUnlock を呼べば、画面のちらつきを防止できる、という話を見かけたので一応やっている。
さておき。FreeBASIC でキー入力を検出するためには、一般的には Inkey を使うけれど、カーソルキー押しっぱなしでキャラを移動させたりする時は MultiKey() を使う。この MultiKey() を使う時は、fbgfxモードとやらを有効にしないといけないらしい。
' fbgfxモードを使う
#Include "fbgfx.bi"
Using fb
' ...
' キャラを移動
Dim As Double spd = 4.0 * MAX_FPS * delta
If MultiKey(SC_LEFT ) Then x -= spd
If MultiKey(SC_RIGHT) Then x += spd
If MultiKey(SC_UP ) Then y -= spd
If MultiKey(SC_DOWN ) Then y += spd
FreeBASICで時間を取得したい時は、Timer を使う。単位は秒で、小数点以下の値も入ってくるから、ミリ秒も測定できる。ほとんどの場合、PCが起動してからの時間を返してくるけれど、動かしてるOSによってどのあたりが開始時間になるかは異なる。
' 前回フレームから何秒経過したか取得。単位は秒(小数点以下有り)
now_time = Timer
If now_time >= prev_time Then
delta = now_time - prev_time
Else
delta = one_frame
End If
prev_time = now_time
next_time = now_time + one_frame
Windows上で FreeBASIC を動かした際、標準状態だと sleep の精度が荒いけれど、1ミリ秒の精度にしたいときは、マルチメディア関連の機能を使う。Linux の場合は標準状態で1ミリ秒の精度なので気にしなくていい。
#ifdef __FB_WIN32__ ' Windowsの場合、mmsystemを利用 #include "windows.bi" #include "win/mmsystem.bi" #endif ' ... #ifdef __FB_WIN32__ ' タイマー精度を1msecに向上 timeBeginPeriod(1) #endif ' ... #ifdef __FB_WIN32__ ' タイマー精度を本来のスペックに戻す timeEndPeriod(1) #endif
FreeBASIC は標準状態だと bmp画像の読み込みしかできない。png画像も読み込みたい時は、何かしらの画像ライブラリを使うことになる。今回は FBImage という画像ライブラリを使って実験していた。
' 画像読み込み用ライブラリ FBImage を使う場合は以下のコメントアウトを外す
' #include once "FBImage.bi"
' ...
' 画像読み込み
#ifdef __FBImage_bi__
' FBImageを利用して読み込む場合
var img = LoadRGBAFile("obj.png")
#Else
' FreeBASIC標準のbmp読み込みを使う場合
Dim img As any ptr = ImageCreate(128, 64)
Bload "obj.bmp", img
#endif
' 画像の幅と高さを取得
imageinfo img, imgw, imgh
' ...
' 画像を使い終わったので破棄
ImageDestroy img
FBImage のインストールは、以前の日記を参考に。
_FreeBASICで画像描画 - mieki256's diary
画像描画は Put を使う。文字の描画は Draw String を使う。
描画開始時に ScreenLock を呼んで、描画が終わったら ScreenUnlock を呼べば、画面のちらつきを防止できる、という話を見かけたので一応やっている。
◎ 昔のBASICらしい画面にしてみた :
前述のように、一応書けたのだけど。これってどうも、「BASICで書いてますよー」感が弱いなと…。
もうちょっと手直ししてみた。
_helloliketic80ascii.bas
よし。見た目がBASICらしくなった。
いや、自分、最初に使ってたPCが、グラフィック画面が無くて文字しか表示できない MZ-700 だったもんで…。PC-8801 や FM-7 や X1 使ってた人の「BASIC感」は、たぶん違うんだろうなあ…。
もうちょっと手直ししてみた。
_helloliketic80ascii.bas
' game main loop
#ifdef __FB_WIN32__
' Windowsの場合、mmsystemを利用
#include "windows.bi"
#include "win/mmsystem.bi"
#endif
' fbgfxモードを使う
#Include "fbgfx.bi"
Using fb
' 時間計測用の変数
Dim As Double start_time, prev_time, now_time, delta, one_frame, next_time
Dim As Integer frame_count
Dim As String fps_text = "FPS"
Dim As Integer scrw, scrh ' ウインドウサイズ
' ウインドウサイズと色深度を指定
scrw = 512
scrh = 288
Screenres scrw, scrh, 32
#ifdef __FB_WIN32__
timeBeginPeriod(1) ' タイマー精度を1msecに向上
#endif
' 1フレームあたりの本来の時間
Dim As Double MAX_FPS = 60.0
one_frame = 1.0 / MAX_FPS
' 開始時間を取得
start_time = Timer
prev_time = start_time
frame_count = 0
Dim As Boolean running = True
Dim As Double x, y
x = scrw / 2
y = scrh / 2
Dim As Double anime_t = 0.0
' メインループ
While (running)
' 前回フレームから何秒経過したか取得。単位は秒(小数点以下有り)
now_time = Timer
If now_time >= prev_time Then
delta = now_time - prev_time
Else
delta = one_frame
End If
prev_time = now_time
next_time = now_time + one_frame
If now_time >= start_time Then
If (now_time - start_time) >= 1.0 Then
' 1秒経過したのでFPSを取得
fps_text = "FPS: " & frame_count
start_time += 1.0
frame_count = 0
End If
Else
start_time = now_time
End If
' ESCキー、qキー、ウインドウの閉じるボタンを検出
Dim As String k = inkey$
If k = Chr$(27) Or k = "q" Or k = Chr$(255) + "k" Then
' メインループ終了
running = False
End If
' キャラを移動
Dim As Double spd = 4.0 * MAX_FPS * delta
If MultiKey(SC_LEFT ) Then x -= spd
If MultiKey(SC_RIGHT) Then x += spd
If MultiKey(SC_UP ) Then y -= spd
If MultiKey(SC_DOWN ) Then y += spd
' アニメ表示用カウンタを更新
anime_t += delta
' 描画開始
ScreenLock
' 画面クリア
color RGB(255, 255, 255), RGB(52, 164, 255)
cls
' キャラクターを描画
Dim As Integer n = Int(anime_t / 0.5) Mod 2 ' 0 or 1
If n = 0 Then
Draw String (x, y + 8 * 0), "+-----+"
Draw String (x, y + 8 * 1), ":O O:"
Draw String (x, y + 8 * 2), ": :"
Draw String (x, y + 8 * 3), ": --- :"
Draw String (x, y + 8 * 4), "+-----+"
Draw String (x, y + 8 * 5), " H H "
Else
Draw String (x, y + 8 * 0), "+-----+"
Draw String (x, y + 8 * 1), ":- -:"
Draw String (x, y + 8 * 2), ": :"
Draw String (x, y + 8 * 3), ": - :"
Draw String (x, y + 8 * 4), "+-----+"
Draw String (x, y + 8 * 5), " H H "
End If
' 文字列を描画
Draw String (10, 10), fps_text
Draw String (scrw / 2 - (8 * 6), scrh * 0.8), "HELLO WORLD"
' 描画終了
ScreenUnlock
If Timer < next_time Then
' 本来の1フレーム時間がまだ経過してないので sleep させる
Dim As Double wait_ms = (next_time - Timer) * 1000.0
If wait_ms > 0.0 Then sleep wait_ms
End If
frame_count += 1
Wend
' キーバッファを空にする
While Inkey <> "": Wend
#ifdef __FB_WIN32__
timeEndPeriod(1) ' タイマー精度を本来のスペックに戻す
#endif
よし。見た目がBASICらしくなった。
いや、自分、最初に使ってたPCが、グラフィック画面が無くて文字しか表示できない MZ-700 だったもんで…。PC-8801 や FM-7 や X1 使ってた人の「BASIC感」は、たぶん違うんだろうなあ…。
[ ツッコむ ]
以上です。