こちらのページは「すとーむすきーアドベントカレンダー 令和5年12月23」の投稿物になります。
このページは後編です。「中編はこちらから飛べます」
※注意事項
以下にStormworksにおけるECDISの作り方を記載していきますが
このECDISのプログラム(Lua)設計は、プログラマーではなく無線技士が行っています。(人手不足)
コーディング規約等が守られていない確率が高いです。(Stormworksの仕様上、守ることが難しいのもある)
また初心者の人の作りやすさを重視した設計にしてあるので
一部の計算や補正を簡易的なものに意図的に置き換えてます。
上記の点につきましては生暖かい目で見守っていただければ幸いです。
完成品がsteam workshopに公開されていますので
不明な点がある際はこちらのリンクから完成品をダウンロードしてみてください(*^_^*)
4.ECDISのLuaを作ろう!
ECDISを動かすのに必要なLuaを作っていきましょう。
まずは今回作りたいECDISの画面を確認しましょう。
ECDISに表示したい情報を確認したので、次はどの様なプログラムが必要か考えていきます。
今回、必要なプログラムの機能は次の通りです。
①地図を表示
②自艦の向きを表示
③レーダーの向きを表示
④探知目標を表示
⑤探知していない時に目標を消す
⑥自艦の座標・速力・真方位を表示
これを踏まえた全体のプログラムは下記の通りになります。
以降のページで順番に解説していきます!
(ここから下部分は、Lua Scriptです。インデントがズレている部分がありますがストワのLuaに貼ると適合します。)
Z=100000
TARGET_X={Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z}
TARGET_Y={Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z}
TIMER={0}
function onTick()
DETECTION = input.getBool(1)
RANGE = input.getNumber(1)
ROTATION = input.getNumber(2)*math.pi*2
COMPASS = input.getNumber(3)*-math.pi*2
GPSX = input.getNumber(4)
GPSY = input.getNumber(5)
ZOOM = input.getNumber(6)
KNOT = input.getNumber(7)*1.94384
BEARING = ROTATION+COMPASS
if input.getNumber(3) < 0 then
SHIP_HEAD = 360-(1+input.getNumber(3))*360
else
SHIP_HEAD = 360-input.getNumber(3)*360
end
if DETECTION then
YM=RANGE*-math.cos(BEARING)
XM=RANGE*math.sin(BEARING)
table.insert(TARGET_X,1,XM)
table.insert(TARGET_Y,1,YM)
end
if #TARGET_X>16then
table.remove(TARGET_X)
table.remove(TARGET_Y)
end
COUNT=TIMER[1]+1
TIMER[1]=COUNT
if TIMER[1] > 240 then
table.insert(TARGET_X,1,Z)
table.insert(TARGET_Y,1,Z)
TIMER[1] = 0
end
end
function onDraw()
w = screen.getWidth()
h = screen.getHeight()
WHscale = h/w
screen.drawMap(GPSX,GPSY,ZOOM)
screen.setColor(255,255,255,50)
screen.drawLine(w/2,h/2,w/2+(w*math.sin(BEARING)*WHscale),h/2-(h*math.cos(BEARING))) --RADAR SWEEP
screen.setColor(0,255,0)
screen.drawLine(w/2,h/2,w/2+(w*math.sin(COMPASS)*WHscale),h/2-(h*math.cos(COMPASS))) --SHIP HEADING MARKER
screen.setColor(200,200,0)
for i = 1, 4 do
screen.drawCircleF(w/2+TARGET_X[i]/(5*ZOOM),h/2+TARGET_Y[i]/(5*ZOOM),2)
end
screen.setColor(200,200,0,70)
for i = 5, 8 do
screen.drawCircleF(w/2+TARGET_X[i]/(5*ZOOM),h/2+TARGET_Y[i]/(5*ZOOM),2)
end
screen.setColor(200,200,0,20)
for i = 9, 16 do
screen.drawCircleF(w/2+TARGET_X[i]/(5*ZOOM),h/2+TARGET_Y[i]/(5*ZOOM),2)
end
screen.setColor(0,255,0)
screen.drawText(12*w/16, h/16, "GPSX")
screen.drawText(12*w/16, 2*h/16, "GPSY")
screen.drawText(13.5*w/16, h/16, math.floor(GPSX))
screen.drawText(13.5*w/16, 2*h/16, math.floor(GPSY))
screen.drawText(14*w/16, 15*h/16, "BER")
screen.drawText(13*w/16, 15*h/16, math.floor(SHIP_HEAD))
screen.drawText(14*w/16, 14*h/16, "KNOT")
screen.drawText(13*w/16, 14*h/16, math.floor(KNOT))
end
(上記まではLua Script、ここから文章)
上記のプログラムは複雑そうに見えてかなり単純です。
一つずつ解説するので一緒に頑張りましょう。
⓪Lua scriptのコーディング画面を開こう
まずはプログラムを書き込むLua scriptのコーディング画面を開きます。
開くためには、「Lua script」をクリックし表示される「Edit Script」を選択します。
Lua scriptのコーディング画面が開かれました。
不要な例文や説明文がいろいろ書かれているので大事な部分以外を消します。
プログラムを入れる関数部分のみを残して分かりやすくしてみました。
上記のプログラムの説明
function onTick() <--毎回処理される関数 基本的なプログラムを動かす場所
~ここにプログラム(条件文、入出力、計算等)を入れる~
end <--function onTick()の終わり
function onDraw() <--描写時に処理される関数 描写に関するプログラムを動かす場所
~ここに描写に関するプログラム(線を引く、文字を表示等)を入れる~
end <--function onDraw()の終わり
上記の空っぽの状態でECDISを起動してみます。
関数が空っぽなので何も表示されません。
①地図を表示しよう
Lua scriptのコーディング画面に地図を表示するプログラムを構築します。
上記のプログラムの説明
function onTick()
GPSX = input.getNumber(4) <--コンポジット信号の4番を「GPSX」という引数にする
GPSY = input.getNumber(5) <--コンポジット信号の5番を「GPSY」という引数にする
ZOOM = input.getNumber(6) <--コンポジット信号の6番を「ZOOM」という引数にする
end
function onDraw()
screen.drawMap(GPSX,GPSY,ZOOM) <--座標(X軸=GPSX,Y軸=GPSY)を中心に、ZOOM倍の地図を表示する
end
上記のプログラムでECDISを起動してみます。
自分のいる場所を中心とした地図が表示されましたね。
②自艦の向き(SHM)を表示しよう
Lua scriptのコーディング画面に自艦の向き(艦首方位線:Ship Heading Marker)を表示するプログラムを追加します。
上記のプログラムの説明
function onTick()
COMPASS = input.getNumber(3)*-math.pi*2 <--コンポジット信号3番に-2πを乗算後「COMPASS」という引数にする
GPSX = input.getNumber(4)
GPSY = input.getNumber(5)
ZOOM = input.getNumber(6)
end
function onDraw()
w = screen.getWidth() <--接続したディスプレイの横幅を確認して「w」という引数にする
h = screen.getHeight() <--接続したディスプレイの縦幅を確認して「h」という引数にする
WHscale = h/w <--ディスプレイの縦横比を「WHscale」という引数にする
(正方形のディスプレイには不要な引数)
screen.drawMap(GPSX,GPSY,ZOOM)
screen.setColor(0,255,0) <--画面に描写する際の色を指定(赤=0、緑=255、青=0)
screen.drawLine(w/2,h/2,w/2+(w*math.sin(COMPASS)*WHscale),h/2-(h*math.cos(COMPASS)))
↑ディスプレイにSHMを引くプログラム
画面中心からディスプレイ縦横比「WHscale」により角度差を修正して線を引く
ディスプレイの縦軸は、上方向がマイナスなので縦軸の描写は中心からマイナス計算
end
上記のプログラムでECDISを起動してみます。
自分のいる場所から向いている方向へ緑の線が表示されましたね。
③レーダーの向き(スイープ)を表示しよう
Lua scriptのコーディング画面にレーダーの向き(PPIスコープ上におけるレーダースイープ)を表示するプログラムを追加します。
上記のプログラムの説明
function onTick()
ROTATION = input.getNumber(2)*math.pi*2 <--コンポジット信号2番に2πを乗算後「ROTATION」という引数にする
COMPASS = input.getNumber(3)*-math.pi*2
GPSX = input.getNumber(4)
GPSY = input.getNumber(5)
ZOOM = input.getNumber(6)
BEARING = ROTATION+COMPASS <--レーダーの向きと船の向きを合成して「BEARING」という引数にする
end
function onDraw()
w = screen.getWidth()
h = screen.getHeight()
WHscale = h/w
screen.drawMap(GPSX,GPSY,ZOOM)
screen.setColor(255,255,255,50) <--画面に描写する際の色を指定(赤=255、緑=255、青=255、透明度=50)
screen.drawLine(w/2,h/2,w/2+(w*math.sin(BEARING)*WHscale),h/2-(h*math.cos(BEARING)))
↑ディスプレイにレーダースイープを引くプログラム
画面中心からディスプレイ縦横比「WHscale」により角度差を修正して線を引く
ディスプレイの縦軸は、上方向がマイナスなので縦軸の描写は中心からマイナス計算
screen.setColor(0,255,0)
screen.drawLine(w/2,h/2,w/2+(w*math.sin(COMPASS)*WHscale),h/2-(h*math.cos(COMPASS)))
end
上記のプログラムでECDISを起動してみます。
自分のいる場所からレーダーが向いている方向へ白い少し透明な線が表示され回り始めましたね。
④探知目標を表示しよう
Lua scriptのコーディング画面に探知した目標(目標ビデオ)を表示するプログラムを追加します。
ここから少し難しくなります。(下図は2ページ合成しています。)
上記のプログラムの説明
Z=100000 <--目標を探知していないときは、画面外に表示しておく座標
TARGET_X={Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z}<--探知した目標のX座標を保存するテーブル
TARGET_Y={Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z}<--探知した目標のY座標を保存するテーブル
(最初は全て画面外に探知目標が表示されている=画面に映らない)
(関数の外にあるプログラムは、最初に一度だけ実行される)
function onTick()
DETECTION = input.getBool(1) <--コンポジットBool1番の状態を「DETECTION」という真偽値名にする
RANGE = input.getNumber(1) <--コンポジット信号1番を「RANGE」という引数にする
ROTATION = input.getNumber(2)*math.pi*2
COMPASS = input.getNumber(3)*-math.pi*2
GPSX = input.getNumber(4)
GPSY = input.getNumber(5)
ZOOM = input.getNumber(6)
BEARING = ROTATION+COMPASS
if DETECTION then <--レーダーが探知状態(DETECTION=true)の時に実行する。
YM=RANGE*-math.cos(BEARING) <--探知距離とレーダーの向きから目標のY座標を計算し「YM」という引数にする
XM=RANGE*math.sin(BEARING) <--探知距離とレーダーの向きから目標のX座標を計算し「XM」という引数にする
table.insert(TARGET_X,1,XM) <--目標のX座標をテーブルTARGET_Xに新規保存する
table.insert(TARGET_Y,1,YM) <--目標のY座標をテーブルTARGET_Yに新規保存する
table.insertしたデータは、1番に入って元からあったデータは
1→2、2→3、3→4 ・・・と大きい番号へ押し出される。
end <--if DETECTION thenの終わり
if #TARGET_X>16 then <--TARGET_Xに保存されたデータ数が16を上回ると実行
table.remove(TARGET_X) <--TARGET_Xに保存された一番古い(番号の大きい)データを削除
table.remove(TARGET_Y) <--TARGET_Yに保存された一番古い(番号の大きい)データを削除
end <--if #TARGET_X>16 thenの終わり
end
function onDraw()
w = screen.getWidth()
h = screen.getHeight()
WHscale = h/w
screen.drawMap(GPSX,GPSY,ZOOM)
screen.setColor(255,255,255,50)
screen.drawLine(w/2,h/2,w/2+(w*math.sin(BEARING)*WHscale),h/2-(h*math.cos(BEARING)))
screen.setColor(0,255,0)
screen.drawLine(w/2,h/2,w/2+(w*math.sin(COMPASS)*WHscale),h/2-(h*math.cos(COMPASS)))
screen.setColor(200,200,0) <--目標の色(黄色)
for i = 1, 4 do <--iが1~4に1ずつ変化した際の処理を行う
screen.drawCircleF(w/2+TARGET_X[i]/(5*ZOOM),h/2+TARGET_Y[i]/(5*ZOOM),2)
end <--for i = 1, 4 doの終わり ↑テーブル番号1~4の座標にいる目標を塗りつぶした丸で画面に表示
screen.setColor(200,200,0,70) <--目標の色(黄色、少し薄く)
for i = 5, 8 do <--iが5~8に1ずつ変化した際の処理を行う
screen.drawCircleF(w/2+TARGET_X[i]/(5*ZOOM),h/2+TARGET_Y[i]/(5*ZOOM),2)
end <--for i = 5, 8 doの終わり ↑テーブル番号5~8の座標にいる目標を塗りつぶした丸で画面に表示
screen.setColor(200,200,0,20) <--目標の色(黄色、かなり薄く)
for i = 9, 16 do <--iが9~16に1ずつ変化した際の処理を行う
screen.drawCircleF(w/2+TARGET_X[i]/(5*ZOOM),h/2+TARGET_Y[i]/(5*ZOOM),2)
end <--for i = 9, 16 doの終わり ↑テーブル番号9~16の座標にいる目標を塗りつぶした丸で画面に表示
end
上記のプログラムでECDISを起動してみます。
探知した目標が黄色のビデオで表示されましたね
⑤探知目標を時間経過で消去しよう
Lua scriptのコーディング画面に探知した目標(目標ビデオ)を時間で消去するプログラムを追加します。
周りに目標が居ないのに目標ビデオだけ残っている状態を解消できます。
Luaのプログラムが実行されるたびに、テーブルの保存値に+1することで時間を計測できます。
なおstormworksの世界では、1回の処理を1/60秒で行うため60回の処理で1秒を計測できる。
上記のプログラムの説明
Z=100000
TARGET_X={Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z}
TARGET_Y={Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z}
TIMER={0} <--経過時間を記録するテーブル
function onTick()
DETECTION = input.getBool(1)
RANGE = input.getNumber(1)
ROTATION = input.getNumber(2)*math.pi*2
COMPASS = input.getNumber(3)*-math.pi*2
GPSX = input.getNumber(4)
GPSY = input.getNumber(5)
ZOOM = input.getNumber(6)
BEARING = ROTATION+COMPASS
if DETECTION then
YM=RANGE*-math.cos(BEARING)
XM=RANGE*math.sin(BEARING)
table.insert(TARGET_X,1,XM)
table.insert(TARGET_Y,1,YM)
end
if #TARGET_X>16then
table.remove(TARGET_X)
table.remove(TARGET_Y)
end
COUNT=TIMER[1]+1 <--現在の経過時間に+1を行う
TIMER[1]=COUNT <--テーブルにカウントされた時間を記録する
if TIMER[1] > 240 then <--カウント数が240回(3秒)を超えたら実行
table.insert(TARGET_X,1,Z) <--X軸テーブルに探知していない時の座標を新規保存する
table.insert(TARGET_Y,1,Z) <--Y軸テーブルに探知していない時の座標を新規保存する
TIMER[1] = 0 <--テーブルの経過時間をリセットする
end
end
function onDraw()
w = screen.getWidth()
h = screen.getHeight()
WHscale = h/w
screen.drawMap(GPSX,GPSY,ZOOM)
screen.setColor(255,255,255,50)
screen.drawLine(w/2,h/2,w/2+(w*math.sin(BEARING)*WHscale),h/2-(h*math.cos(BEARING)))
screen.setColor(0,255,0)
screen.drawLine(w/2,h/2,w/2+(w*math.sin(COMPASS)*WHscale),h/2-(h*math.cos(COMPASS)))
screen.setColor(200,200,0)
for i = 1, 4 do
screen.drawCircleF(w/2+TARGET_X[i]/(5*ZOOM),h/2+TARGET_Y[i]/(5*ZOOM),2)
end
screen.setColor(200,200,0,70)
for i = 5, 8 do
screen.drawCircleF(w/2+TARGET_X[i]/(5*ZOOM),h/2+TARGET_Y[i]/(5*ZOOM),2)
end
screen.setColor(200,200,0,20)
for i = 9, 16 do
screen.drawCircleF(w/2+TARGET_X[i]/(5*ZOOM),h/2+TARGET_Y[i]/(5*ZOOM),2)
end
end
上記のプログラムでECDISを起動してみます。
探知した目標いなくなった際は、時間経過で黄色のビデオで消去されるようになりました。
(※静止画だとあんまり分からないという・・・)
⑥自艦の座標・速力・真方位を表示しよう
Lua scriptのコーディング画面にいろんな情報を表示するプログラムを追加します。
これでECDISが完成します!
上記のプログラムの説明
Z=100000
TARGET_X={Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z}
TARGET_Y={Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z}
TIMER={0}
function onTick()
DETECTION = input.getBool(1)
RANGE = input.getNumber(1)
ROTATION = input.getNumber(2)*math.pi*2
COMPASS = input.getNumber(3)*-math.pi*2
GPSX = input.getNumber(4)
GPSY = input.getNumber(5)
ZOOM = input.getNumber(6)
KNOT = input.getNumber(7)*1.94384 <--コンポジット信号7番に1.94384を乗算後「KNOT」という引数にする
BEARING = ROTATION+COMPASS (センサーの出力速度がm/sなので1.94384を乗算しノットに変換する)
if input.getNumber(3) < 0 then <--コンポジット信号3番(コンパスからの値)を
SHIP_HEAD = 360-(1+input.getNumber(3))*360 真方位(0度~359度の表現)に変換するプログラム
else
SHIP_HEAD = 360-input.getNumber(3)*360
end
if DETECTION then
YM=RANGE*-math.cos(BEARING)
XM=RANGE*math.sin(BEARING)
table.insert(TARGET_X,1,XM)
table.insert(TARGET_Y,1,YM)
end
if #TARGET_X>16then
table.remove(TARGET_X)
table.remove(TARGET_Y)
end
COUNT=TIMER[1]+1
TIMER[1]=COUNT
if TIMER[1] > 240 then
table.insert(TARGET_X,1,Z)
table.insert(TARGET_Y,1,Z)
TIMER[1] = 0
end
end
function onDraw()
w = screen.getWidth()
h = screen.getHeight()
WHscale = h/w
screen.drawMap(GPSX,GPSY,ZOOM)
screen.setColor(255,255,255,50)
screen.drawLine(w/2,h/2,w/2+(w*math.sin(BEARING)*WHscale),h/2-(h*math.cos(BEARING)))
screen.setColor(0,255,0)
screen.drawLine(w/2,h/2,w/2+(w*math.sin(COMPASS)*WHscale),h/2-(h*math.cos(COMPASS)))
screen.setColor(200,200,0)
for i = 1, 4 do
screen.drawCircleF(w/2+TARGET_X[i]/(5*ZOOM),h/2+TARGET_Y[i]/(5*ZOOM),2)
end
screen.setColor(200,200,0,70)
for i = 5, 8 do
screen.drawCircleF(w/2+TARGET_X[i]/(5*ZOOM),h/2+TARGET_Y[i]/(5*ZOOM),2)
end
screen.setColor(200,200,0,20)
for i = 9, 16 do
screen.drawCircleF(w/2+TARGET_X[i]/(5*ZOOM),h/2+TARGET_Y[i]/(5*ZOOM),2)
end
screen.setColor(0,255,0) <--画面に描写する際の色を指定(赤=0、緑=255、青=0)
screen.drawText(12*w/16, h/16, "GPSX") <--画面の左から12/16、上から1/16の位置にGPSXという文字を表示
screen.drawText(12*w/16, 2*h/16, "GPSY") <--画面の左から12/16、上から2/16の位置にGPSYという文字を表示
"〇〇〇"で指定するとそれは文字として表示できる。
screen.drawText(13.5*w/16, h/16, math.floor(GPSX)) <--画面の左から13.5/16、上から1/16の位置に
引数GPSXの小数点以下を切り捨て表示
screen.drawText(13.5*w/16, 2*h/16, math.floor(GPSY)) <--画面の左から13.5/16、上から2/16の位置に
引数GPSYの小数点以下を切り捨て表示
screen.drawText(14*w/16, 15*h/16, "BER") <--画面の左から14/16、上から15/16の位置にBERという文字を表示
screen.drawText(13*w/16, 15*h/16, math.floor(SHIP_HEAD))<--画面の左から13/16、上から15/16の位置に
引数SHIP_HEADの小数点以下を切り捨て表示
screen.drawText(14*w/16, 14*h/16, "KNOT") <--画面の左から14/16、上から14/16の位置にKNOTという文字を表示
screen.drawText(13*w/16, 14*h/16, math.floor(KNOT)) <--画面の左から13/16、上から14/16の位置に
引数KNOTの小数点以下を切り捨て表示
end
上記のプログラムでECDISを起動してみます。
自艦の座標や方位、速度が表示されるようになりました。
これで基本的な機能を持ったECDISが完成しました!(*^-^*)