なでしこを明後日の方向に

日本語プログラム言語なでしこを応援しています

なでしこでイメージ部品同士で画像のやりとり

↓マニュアル探査艦で探すと出てくるやつ。

イメージ部品の画像へ他のイメージ部品の画像を設定することもできます。その場合、部品を「イメージ名」と文字列として渡す必要があります。

Aイメージとはイメージ
Aイメージ画像「nadesiko.png」
Bイメージとはイメージ
Bイメージ画像「Aイメージ」

画像コピーとかじゃなくて名前を文字列指定でいける。
でも「~として作成」で動的生成した場合のサンプルが見当たらない。
いろいろ試したらできたので紹介。

Aイメージ[0]イメージして作成
Aイメージ[0]画像「nadesiko.png」
Bイメージとはイメージ
Bイメージ画像Aイメージ[0]名前

GUI部品含むグループには「名前」要素があるのでそれを使います。
アニメ部品に組み込む場合も同様でした。

imgとは変数
r1から20まで繰り返す
  img[r]イメージして作成
  img[r]可視オフ
  img[r]画面クリア
  img[r]r,0から0,r線

listとは変数
animeとはアニメ
anime表示間隔50
r1から20まで繰り返す
  listimg[r]名前一行追加
anime画像list
anime開始

これとは関係ないけど、2枚の画像が同一かどうかを判定する方法がほしい。

なでしこで「一行追加」するとき気を付ける

ゲームにログはつきものなので、メッセージログを追記する関数を作ったときのこと。

ログとはハッシュ
●ログ追記(項目に,内容を)
 もしログ項目==""ならば、ログ項目="" //←ここ
 ログ項目内容一行追加

よいですかなでしこさん……もし変数が空ならば、変数に空を代入するのです……
という部分は「nil(何もない)ならば""をいれてハッシュキーを作成せよ」という意味です。
別の言語でいうnilガードというやつです。多分。

ハッシュがnilだと困る例が2つあります。(なでしこバージョン:1.578)
・「追加」「一行追加」で、何も追加されない
・(nil)で「条件分岐」するとエラーを吐く

ではさっそく試してみましょう

ログとはハッシュ
(ログ「1ターン目」)「あいうえお」一行追加
(ログ「1ターン目」)表示

実行結果: (何も表示されない)


ログとはハッシュ
(ログ「1ターン目」)条件分岐
  ""ならば、OK言う

実行結果:
[エラー] com.nadesi.exe.nakopad.temp.bak1.nako(2): モジュール 'dnako.dll' のアドレス 02A083BE でアドレス 00000000 に対する読み込み違反がおきました。(条件分岐)


どちらも原因と対策がわかるまでけっこうかかりました。

なでしこでファイル名変更

パソコン変えたらログイン方法とか忘れて間が開いたよ!

・ファイル名変更とか

//なんかのミスで.txt.txtのファイルをつくっちゃった時、
//.txt.txt → .txt にひたすらファイル名変更するプログラム
「*.txt.txt」ファイル列挙
反復
  変更前 = 対象
  変更後 = 対象「」拡張子変更
  もし変更後存在==1ならば続ける
  変更前変更後ファイル名変更

ファイル名変更は危険な命令です。 変更後のファイル名がかぶると上書きされ、失われます。 テストするときも危ないのでバックアップを取りましょう。

変更後 = 対象「」拡張子変更

拡張子を消すときは「」に拡張子変更すると.xxxの部分が消えます。 拡張子の手前に日付などを入れる時に便利……か?

もし変更後存在==1ならば続ける

変更後に同一のファイル名があると容赦なく上書きされるので防止策。 変更後は変数なのですが、この判定の後に中身を変えてはいけない(1敗)

  もし変更後存在==1ならば続ける
  変更前「{変更後}.txt」ファイル名変更 //防止策の意味がない!

↑やってはいけないミス

なでしこでゲーム作る話  #6 操作の取得

マウスとかボタンを取得する話です。 キーは「キー状態」の命令で取得できます。

キー状態は1フレームに1回だけ

ゲームで同じキーを1フレーム内に複数回取得するのは得策ではありません。 1回だけ取得して変数に入れておき、操作で条件分岐するときは変数のほうを参照します。

たとえば右を押したら主人公と敵の両方が右に動くようにしたい時。

//よくない例
もしキー状態(右キー)==1ならば、主人公移動("右")
もしキー状態(右キー)==1ならば、敵移動("右")

主人公移動("右")を処理している間に右を離したら、主人公と敵は同じ動きになりません。
確実に連動させたいなら右キーの取得は1回にする必要があります。

マウスのX,Y、ボタンの取得

ボタンの状態はキー状態(1)、キー状態(2)でそれぞれ左ボタンと右ボタンです。

どうでもいいですが、マウスボタンは左利き用にするとクリック=右ボタンになります。
画面に「クリックでスタート」と表示しておいてキー状態(1)で分岐すると変な人からクレームが来ます。

キーとマウスの取得サンプル

裏イメージとはイメージ
そのサイズ「0,0,600,400」
その可視オフ
裏イメージ画面クリア
表イメージとはイメージ
そのサイズ「0,0,600,400」
表イメージ画面クリア

■マウス
 ・X
 ・Y
 ・押
 ・離
 ・右押
 ・右離

キーとはハッシュ

//毎フレーム呼ぶ
●キー操作取得
 keyとは変数
 key1から255まで繰り返す
   もしキー状態(key)==1ならば
     キーkey = キーkey+1
   違えば
     キーkey = 0
 
 //マウス関連
 マウス.離 = (マウス.押>0) && (キー1==0)
 マウス.押 = キー1
 マウス.右離 = (マウス.右押>0) && (キー2==0)
 マウス.右押 = キー2
 xyとは変数
 xy=母艦ハンドル0,0窓ハンドル画面座標計算
 マウス.X=机上マウスX - (xyから「,」まで切り取る)
 マウス.Y=机上マウスY - (xyから「,」まで切り取る)

●キー情報表示
 txtとは変数
 txt(マウスメンバ詳細列挙)一行追加
 keyとは変数
 key1から255まで繰り返す
   txt(キーkey>0)追加
   txt「 」追加
   もしkey%16==0ならば,
     txt改行追加
 裏イメージ,5,5txt文字表示

//ゲームループ
1ループ
  キー操作取得()
  キー情報表示()
  裏イメージ表イメージ0,0画像コピー
  裏イメージ画面クリア
  0.04秒待つ

●キー操作取得について。
1~255のキー状態をハッシュにいれます。 押しっぱなしにしていたらどんどん+1して、離していたら0にします。
キー@key = キー状態(key) などとすると分岐するときに困ります。(長く押している最中か押した瞬間かの判断がつかない)

マウスのほうは怪しげなことをしているので解説。
まず■マウスのグループにX,Y,押,離,右押,右離のメンバがあります。 それぞれXY座標とボタンの押している/離した瞬間であるを記録しておくところです。

ゲームではマウスボタンを離した瞬間を決定とする作りが多いです。
「前回が押している(マウス.押>0)」かつ「今回離している(キー@1==0)」が「マウスを離した」ということです。

次がマウスのXY座標の計算。
母艦ハンドルの0,0を窓ハンドル画面座標計算 で母艦の描画領域の左上が取れるみたいなので、机上マウスXとかから引けばいいみたい。
……うちの環境ではこれで動いてるよ!

●キー情報表示について

f:id:tkizzz:20190103014433j:plain

(キー@key>0)を文字表示しているので実際の値ではないです。
キーボードを適当にさわるとどこかが反応するはず。

最後がゲームループ。
0.04秒ごとにキー1~255とマウス情報を更新して描画します。

実際のゲームでは もしマウス.離==1ならば  とか もしキー90/*Z*/==1ならば とかで操作を反映していきます。

なでしこでゲーム作る話  #5 配列にグループを入れて使う

波紋と称して、大量の円が広がっていく様子を描画しようと思います。

f:id:tkizzz:20181216130530j:plain
波紋が広がったり消えたり

入れ物の用意

■波紋
 ・X{=0}
 ・Y{=0}
 ・TIME{=0}
 ・有効{=0}

波紋配列とは配列
波紋初期化
●波紋初期化
 rとは変数
 r0から100まで繰り返す
   波紋配列[r]波紋グループコピー

配列の変数を一つ用意して、要素にグループコピーしたものを用意します。
なでしこのマニュアルにある、「部品を動的に生成する」と同じです。
マニュアルでは「として作成」で例がでていますが、「グループコピー」でも動くみたいです。

とりあえず波紋配列[0]から波紋配列[100]まで用意しました。

毎フレーム更新

//毎フレーム呼ぶ
//中心がX,Y、半径がTIMEの円を描く
//100フレームで消える
●波紋更新
 xとは変数
 yとは変数
 tとは変数
 rとは変数
 線太さ1
 塗スタイル「透明」
 r0から100まで繰り返す
   もし(波紋配列[r]有効==0)ならば続ける
   x = 波紋配列[r]X
   y = 波紋配列[r]Y
   t = 波紋配列[r]TIME
   裏イメージ(x-t),(y-t)から(x+t),(y+t)円
   波紋配列[r]TIME1直接足す
   もし波紋配列[r]TIME>=100ならば
     波紋配列[r]波紋グループコピー //初期化

●波紋更新 という関数を用意します。
これを1フレームごと、今回は0.04秒ごとに1回呼び出します
呼び出されるたびに行うのは……
・有効==1なグループだけに対して以下を行う
・中心がX,Y、半径TIME(時間で増加)の円を描く
・TIMEを1増やす
・TIMEが100になったら消滅(初期化)
となります。

これだけでは有効が1の波紋がありません。
中心x,yを指定して●波紋作成(x,y)したときに有効=1を入れることにします。

//未使用の波紋配列[r]を探して中心を設定
●波紋作成(x,y)
 rとは変数
 r0から100まで繰り返す
   もし(波紋配列[r]有効==1)ならば続ける
   波紋配列[r]X=x
   波紋配列[r]Y=y
   波紋配列[r]TIME=0
   波紋配列[r]有効=1
   抜ける

配列の[0]から順にみていって、有効==1でないグループを探します。
中心点X,Yに値をコピーして、有効を1にします。
これを仮に8フレームごと、0.04秒*8=0.32秒ごとに呼びます

ゲームループに組み込む

//ゲームループ
1ループ
  もし回数%8==0ならば、波紋作成(乱数(300),乱数(300))
  波紋更新()
  裏イメージ表イメージ0,0画像コピー
  裏イメージ画面クリア
  0.04秒待つ

8フレームごとに波紋作成(x,y)
1フレームごとに波紋更新()
バックバッファから転送
0.04秒待つ
これをループさせるとこうなります。
f:id:tkizzz:20181216130530j:plain

完成品

!変数宣言必要
表イメージとはイメージ
そのサイズ「0,0,600,400」

裏イメージとはイメージ
そのサイズ「0,0,600,400」
その可視オフ


■波紋
 ・X{=0}
 ・Y{=0}
 ・TIME{=0}
 ・有効{=0}

波紋配列とは配列
波紋初期化
●波紋初期化
 rとは変数
 r0から100まで繰り返す
   波紋配列[r]波紋グループコピー  //配列にグループを入れる

//未使用の波紋配列[r]を探して中心を設定
●波紋作成(x,y)
 rとは変数
 r0から100まで繰り返す
   もし(波紋配列[r]有効==1)ならば続ける
   波紋配列[r]X=x
   波紋配列[r]Y=y
   波紋配列[r]TIME=0
   波紋配列[r]有効=1
   抜ける

//毎フレーム呼ぶ
//中心がX,Y、半径がTIMEの円を描く
//100フレームで消える
●波紋更新
 xとは変数
 yとは変数
 tとは変数
 rとは変数
 線太さ1
 塗スタイル「透明」
 r0から100まで繰り返す
   もし(波紋配列[r]有効==0)ならば続ける
   x = 波紋配列[r]X
   y = 波紋配列[r]Y
   t = 波紋配列[r]TIME
   裏イメージ(x-t),(y-t)から(x+t),(y+t)円
   波紋配列[r]TIME1直接足す
   もし波紋配列[r]TIME>=100ならば
     波紋配列[r]波紋グループコピー //初期化
 
//ゲームループ
1ループ
  もし回数%8==0ならば、波紋作成(乱数(300),乱数(300))
  波紋更新()
  裏イメージ表イメージ0,0画像コピー
  裏イメージ画面クリア
  0.04秒待つ

今回は8フレーム毎に乱数のx,y座標に波紋をつくりましたが、クリックしたところを中心にするなどするとゲームのエフェクト作りに役立つと思います。

なでしこでゲーム作る話  #4 変数にはグループを使う

グローバル変数のかわりにグループを使うのがおすすめ

グループを変数のかたまりとして使おうという話です。
ゲームにつかう変数はいくつか特徴があります。
・たびたび初期化が必要になる(面クリア時など)
・保存と呼び出しを期待される(リスタートなど)
・変数の数が膨大になる

普通にグローバル変数を宣言してこれらをやると神経を使います。
そして初期化を忘れてバグになります

グループコピーが活躍する

まずはグループの宣言です。

■プレイヤー
 ・X{=10}
 ・Y{=10}
 ・HP{=100}
 
プレイヤー初期値とはプレイヤー
主人公とはプレイヤー

グループも宣言と同時に初期値を入れることができます。
普通の変数宣言でも初期値は入れられますが、初期値を別の変数(プレイヤー初期値)にとっておくのがコツです。

ゲームが進み、ゲームオーバーになりました。 さて主人公の位置やHPを初期値にもどしましょう。

●プレイヤー初期化
 主人公プレイヤー初期値グループコピー

グループコピー一つで初期値に戻りました。
短くて楽というのもありますが、次にもっと良い点が。
ゲームを作っている途中で変数を増やすことがありますね。
メンバに「武器」を追加してみましょう。

■プレイヤー
 ・X{=10}
 ・Y{=10}
 ・HP{=100}
 ・武器{="素手"}  //追加
プレイヤー初期値とはプレイヤー
主人公とはプレイヤー

●プレイヤー初期化
 主人公プレイヤー初期値グループコピー

グローバル変数だと、ここで初期化部分もいじらなくてはなりません。
でも今回はグループコピーで行っているので初期化部分は触らなくても大丈夫です。
初期化関連でバグを起こす可能性が減りました。
このバグは見逃しやすいので減らす仕組みが大事だと思います。

なでしこでゲーム作る話  #3 イメージ部品をどう使う

画像を表示するにはイメージ部品を使うわけですが、他のプログラム言語の例を見ると「ダブルバッファリング」と「キャッシュ」を使うのがよさそうです。
これをなでしこで書いてみます。

プレイヤーに見せる「表イメージ」。
同じサイズの裏イメージ。
同じサイズのレイヤーを数枚(ここでは0~2の3枚)。
キャッシュという無駄に大きなイメージを用意します。
透過色については後で説明します。

!変数宣言必要
!レイヤー枚数=3
!画面サイズ=「0,0,600,400」
!透過色=0
黒色=$000001

//-------------部品の宣言ここから----------
表イメージとはイメージ
そのサイズ画面サイズ

裏イメージとはイメージ
そのサイズ画面サイズ
その可視オフ
裏イメージ画面クリア

レイヤーとは配列
(レイヤー枚数)回
  レイヤー[回数-1]イメージして作成
  レイヤー[回数-1]サイズ画面サイズ
  レイヤー[回数-1]可視オフ
  レイヤー[回数-1]透過色画面クリア

キャッシュとはイメージ
そのW=1000
そのH=5000//適当に大きく
その可視オフ
キャッシュ透過色画面クリア
//-------------部品の宣言ここまで----------

次は転送部分です。
プログラムからはレイヤー[0]~レイヤー[2]に描画します。
その後、●描画転送 でレイヤーを順番に裏イメージに画像合成します。
レイヤーを合成してできた裏イメージを表イメージに画像コピーしてプレイヤーに見せます。
その後レイヤーと裏イメージを画面クリアし、次の描画に備えます。
これゲームループに組み込んで毎フレーム呼びます。

//毎フレーム呼ぶ
●描画転送
 (レイヤー枚数)回
   レイヤー[回数-1]0,0透過色点描画//左上が透過色
   レイヤー[回数-1]裏イメージ0,0画像合成
 裏イメージ表イメージ,0,0画像コピー//上書き
 
 裏イメージ画面クリア
 (レイヤー枚数)回
   レイヤー[回数-1]透過色画面クリア

//キャラをレイヤーに描画する
●テスト
 x1とは変数。x2とは変数。y1とは変数。y2とは変数。
 線太さ1。線色黒色
 3回
  回数条件分岐
    1ならば、塗色赤色
    2ならば、塗色青色
    3ならば、塗色緑色
  x1=乱数(50)
  x2=x1+50
  y1=0
  y2=50
  レイヤー[回数-1]x1,y1からx2,y2円
 

//ゲームループ
1ループ
  テスト
  描画転送
  0.2秒待つ

レイヤーについて

レイヤー[0]が一番奥に描画され、レイヤー[1]、レイヤー[2]に画像合成で上書きされます。
レイヤー[0]に背景を、レイヤー[1]にキャラを、レイヤー[2]にポップアップ説明などを入れるといいんじゃないでしょうか。

例ではレイヤーに直接描画していますが、キャラなどはキャッシュから「画像部分コピー」してくるのがいいでしょう。 もちろんキャッシュには画像をあらかじめ読み込みしておきます。

透過色と黒色について

画像合成の命令の説明を読むと「左上の色を透過色として扱う」とありますが、罠があります。(なでしこver1.572時点)
透過色を0(黒)以外にすると合成後に変な色になります。
透過色が0以外の場合

!透過色=0 と黒色=1 としているのは0を透過色として、もともと0が黒だったのを似た色(=1)に置き換えています。
また、素材画像を作ってもらうときは「透過色は0でお願いします」と添えておくとよいと思います。

その他補足事項

左上の点に透過色以外が来た時のことも考えなくてはなりません。
上の例では左上に点描画してごまかしています。
画像マスクを使った例がサンプルにありますが、それも透過色が0じゃないと変な色になるので気を付けましょう。 サンプルにきちんと手を加えればどんな透過色でも大丈夫になります。

画像合成のほうが処理が早いので、無理に画像マスクを使うよりいいとおもいます。