アドベントカレンダー2024 開幕!
・・・にもかかわらず特にやることが思いつかないので、12月だし、クリスマスだし、雪でも降らせてみようかなぁ、という。
アドベントカレンダーのページでも雪が降っていますよね。
今回は、なでしこさんの貯蔵庫エディタでキャンバスに雪を降らせてみます。
あるいは、新しく導入されたオブジェクトプロパティ構文をお試し。
なでしことは
日本語プログラミング言語「なでしこ」は、その名の通り日本語でプログラミングできる、素敵プログラミング言語です❣️
みんなが義務教育でプログラミングを勉強するというこんにち、「誰でも簡単プログラマー」と謳われているとおり、ちょっとプログラムを作って遊んだり生活が便利になったりしたいという時に、日本語なら取っつきやすいですよね✨
教育用途としてもなでしこは、中学校の教科書に採用されたり、大学入試のセンター試験で利用される疑似言語、DNCL、DNCL2の互換モードが実装されたりしています。
詳しくはホームページで👇
雪の結晶を描いてみるよ!
なでしこではHTML5のCanvasを使うことが出来、日本語で色々な描画が行えます。
👉なでしこ3 マニュアル > plugin_browser/描画
カンタンに「円描画」で丸とかでもいいのですが、少し華やかにしてみたいと思います。
現実には雪の結晶の形が降ってるの見られるわけじゃないですけれどね~。
雪の結晶は正六角形を元にした対象形なのでプログラムで描きやすいアレだと思いますが、ワタクシ能力もなければセンスも不足なので、とにかく「多角形描画」したり「線描画」したりして頑張ります。
👉なでしこ3 マニュアル > plugin_browser/多角形描画
「多角形描画」は座標配列vを指定して多角形を描画する命令で、正n角形を描きたいと思ったら、その座標を自分で計算して指定してあげなくちゃいけないんですよね~。
ユキノは、さいんこさいんのじゅもんをとなえた!
x = r cos θ
y = r sin θ
半径rの円の中心(0,0)からx軸に対し角度θの直線が円周と交わる点(x,y)の座標を求める公式。
これを使って正六角形の頂点の配列を作成し、六角形を描いたり線を引っ張って枝を付けたり・・・
// x = r cos θ y = r sin θ の呪文
●(中点から半径でnの)多角形頂点取得
変数 v=空配列。
n回
変数 角度=(360/n)*(回数-1)。
変数 ラジアン度=(角度-90)をラジアン変換。
変数 x=半径*COS(ラジアン度)+中点[0]。
変数 y=半径*SIN(ラジアン度)+中点[1]。
vへ[x,y]を配列追加。
ここまで。
vで戻る。
ここまで。
//中点は[x,y]の配列。雪径は雪の「直径」
●(中点へ雪径の)雪描画
空に塗り色設定
変数 m=雪径/10。m/4*3に線太設定。
中点からm*2で6の多角形頂点取得。
それで多角形描画。
変数 頂点は、中点からm*5で6の多角形頂点取得。
3回:
頂点[回数-1]から頂点[回数+2]まで線描画。
変数 分岐点は、中点からm*3で6の多角形頂点取得。
分岐点を反復:
変数 枝は、対象からm*2で6の多角形頂点取得。
変数 A=(対象キー-1+6)%6。
変数 B=(対象キー+1+6)%6。
枝[A]から対象まで線描画。
枝[B]から対象まで線描画。
ここまで。
[50,50]へ100の雪描画。
こんなかんじ? ちょっとそれらしくなったんじゃないでしょうか?
雪の結晶の形は無数にあるので、是非もっとかっこいい結晶を描いてみて下さい。
色々な結晶が降れば、なお華やかな感じになりますよね。
①とりあえず降らせます
今度は、この雪を降らせてみます。
雪のデータを作ります
とりあえず雪のサイズは決め打ちで、縦位置も降り始めなので一番上に決め打ちで良いかと思いますので、横位置だけ乱数で決めます。
設定項目はこれから増える予定なので、辞書型変数形式で雪一覧配列に追加します。
一片一片降ってくる感じを出したいので、とりあえず0.5秒毎に雪データが追加されるようにしておきます。
#設定
雪径=30。//雪のサイズ
#データ作成
雪一覧は空配列。
0.5秒毎には、雪追加。。。
●雪追加
# 雪位置
変数 横位置=画面幅の乱数。
変数 縦位置=雪径/2*-1。//雪が見えない所から降らせる
変数 雪データ={
"位置":[横位置,縦位置]
}
雪一覧に雪データを配列追加。
ここまで。
アニメーションします
「画面更新時実行」は、画面更新のタイミングで画面を指定した関数を実行する命令で、画面を一旦全て塗り潰し、そこへ雪の縦位置を変化させながら雪一覧の雪を次々を描画していく関数で自分を呼び出し続けることでアニメーションになります。
縦位置が画面の下に見えなくなったら、その雪は雪一覧から消すの忘れず。
👉なでしこ3 マニュアル > plugin_browser/画面更新時実行
本当は雪追加もタイムスタンプを取ってこの中で行った方がイイのかなぁ? と思いましたが、面倒なので別のタイマーを使っています。
画面を塗り潰す空描画はとりあえず四角を描いているだけです。
なでしこだと「空」は「から」と読みたくなっちゃうけど、「そら」描画です~。
●降雪アニメーション
[画面幅,画面高さ]へ空描画。
もし、(雪一覧の要素数)=0ならば、雪追加。
雪一覧を反復
No=対象キーを整数変換。
雪位置は対象@「位置」。
横位置,縦位置=雪位置。
# 描画
「snow」に線色設定。
雪位置へ雪径の雪描画。
# 次の位置を変更
縦位置を1増やす。
もし、縦位置-雪径>画面高さならば、
雪一覧のNoを配列削除。
違えば、
対象@「位置」=[横位置,縦位置]。
ここまで。
ここまで。
「降雪アニメーション」を画面更新時実行。
ここまで。
「降雪アニメーション」を画面更新時実行。
●(whへ)空描画
「midnightblue 」に塗色設定。1に線太さ設定。
[0,0,画面幅,画面高さ]へ四角描画。
ここまで。
できました。
と言っても、上から下へ全部同じ速さでスーっと移動しているだけなので、雪が降ってる感はないですね。
②設定を追加していくよ!
雪の一片ごとに大きさや落ちる速さ、風で左右に流れる感じなどを付与して、自然な感じにしていきたいと思います。
変化できる範囲は後から変更しやすいよう辞書型変数で先に設定しておきます。
変数 雪設定={このようにJSON記法でまるっと設定することが出来、設定などに利用すると個別に変数を用意するより管理しやすく大変便利です。
"最大径":50,
"最小径":10,
"最大落下速度":20,
"最小落下速度":10,
"最大風速":5,
"最小風速":1,
}
オブジェクトプロパティ構文を使ってみるよ!
先ほどはあえて対象@「位置」
という書き方をしていました。
元々辞書型変数の参照はA["キー"]
もしくはA@「キー」
でした。
ここで!
今回新たに導入された「オブジェクトプロパティ構文」です!
もともとはDOM部品の属性値へ効率的にアクセスするために導入されましたが、DOMもオブジェクトなら辞書型変数もオブジェクトです。
英語イヤンな人からすると、オブジェクトもプロパティもまあまあな呪文ですが、辞書型変数のキーと思えば分かりやすいですね(?)
この「オブジェクトプロパティ構文」ではA$キー
またはA.キー
で参照することが出来ます。
「」がなくなっただけじゃんって? いやもうそれ大事!
めちゃくちゃ書きやすくなりました。
ここが書きにくかったり見づらかったりすると、いっそもう全部バラの変数にしちゃいたくなるし!💧
そして本当はもっと、DOMの読み書きに真価を発揮できる予定の便利構文なのですが、それはまた今度頑張りましょう。
今回はお試しとゆうことで、とりあえず使ってみます。
●雪追加
変数 雪=雪設定。
変数 方向=[-1,1]。
# 雪の降り方
変数 風力=(雪$最小風速から雪$最大風速までの乱数範囲)/10。
変数 風向=方向[2の乱数]。
変数 速度=(雪$最小落下速度から雪$最大落下速度までの乱数範囲)/10
# 雪のサイズ
変数 雪径=雪$最小径から雪$最大径までの乱数範囲。
# 雪位置
変数 横位置=画面幅の乱数。
変数 縦位置=雪径/2*-1。//雪が見えない所から降らせる
# 雪色
変数 R=0xCCから0xFFまでの乱数範囲。
変数 G=0xDDから0xFFまでの乱数範囲。
変数 B=0xEEから0xFFまでの乱数範囲。
変数 A=(5から8までの乱数範囲)*0.1。
変数 雪データ={
"位置":[横位置,縦位置],
"径":雪径,
"色":「RGBA({R},{G},{B},{A})」,
"左右方向":風力*風向き,
"落下速度":速度
}
雪一覧に雪データを配列追加。
ここまで。
#雪を降らせる
●降雪アニメーション
[画面幅,画面高さ]へ空描画。
もし、(雪一覧の要素数)=0ならば、雪追加。
雪一覧を反復
変数 No=対象キーを整数変換。
変数 雪位置は対象$位置。
変数 [横位置,縦位置]=雪位置。
変数 雪径は対象$径。
変数 雪色は対象$色。
# 描画
雪色に線色設定。
雪位置へ雪径の雪描画。
# 次の位置を変更
横位置を対象$左右方向だけ増やす。
縦位置を対象$落下速度だけ増やす。
もし、縦位置-雪径>画面高さならば、
雪一覧のNoを配列削除。
違えば、
対象$位置=[横位置,縦位置]。
ここまで。
ここまで。
「降雪アニメーション」を画面更新時実行。
ここまで。
「降雪アニメーション」を画面更新時実行。
動きました! いい感じ🎶
1に決め打ちだった縦方向への移動を、最小落下速度から最大落下速度の間の乱数で決めるようにして、同様に風の影響による左右方向への移動も追加。
ちょっとは動きが自然になり、雪が降ってる感も出てきたんじゃないでしょうか?
ついでに色もうっすら幅を持たせ、透明度も設定して、自然っぽさとキラキラ感(?)がアップしました✨
③くるくるひらひら舞わせたい!
「描画変換マトリクス設定」を使って、雪の結晶をくるくる舞わせたいと思います。
👉なでしこ3 マニュアル > plugin_browser/描画変換マトリクス設定
「描画変換マトリクス設定」は、描画を変化させる命令です。
既に描かれているものではなく、これから描画するものが変化します。
使い方については2021年のアドベントカレンダーで一度やりました。
引数 [a,b,c,d,e,f] に[伸縮x, 傾斜y, 傾斜x, 伸縮y, 移動x, 移動y]を指定することで、拡大、傾斜、回転、起点設定をまとめて設定出来る、[伸縮x, 傾斜y, 傾斜x, 伸縮y]を[cos,sin,-sin,cos]とすることで回転するということでした。
でも、ただフツーに回転させたら、雪っていうより歯車に見えてきちゃいますからねー💧
伸縮の片一方を0にしてやったらどうかな?
角度=0。
●雪回転とは
全描画クリア。
角度=角度+1。
sin=SIN((角度をラジアン変換))。
cos=COS((角度をラジアン変換))。
キャンバス状態保存。
#---
※[伸縮x,傾斜y,傾斜x,伸縮y,起点x,起点y]
[0,sin,-sin,cos,150,150]だけ描画変換マトリクス設定。
[0,0]へ100の雪描画
#---
キャンバス状態復元。
「雪回転」を画面更新時実行。
ここまで。
「雪回転」を画面更新時実行。
ちょっとくるくるひらひらしてるっぽい?
伸縮を0にして一旦見えなくなると、そこを軸にして回転してるように見える説😅
変換マトリクスの行列式の意味が分かる賢い人は、もっとかっこよくひらめかすことが出来るのかも知れませんが、とりあえずこんなところで。
完成させます
回転の向きと伸縮の向きはランダムにして動作のバリエーションを増やします。
画面いっぱいに降らせるようにして、背景の空をムダにグラデーションにしてみました。
が! ムダに動作が重くなってしまったので、グラデーションの描画は一回で良いよう背景用のキャンバスを用意する始末💧
先ほどはオブジェクトプロパティ構文の「$」を使いましたが、今回は「.」を使ってみましたよ。入力のしやすさや見やすさで、どっちでも好きな方を使えばOKです。
ワタシは個人的にv1やじゃばすくりぷとさんと一緒な「.」のほうが気に入っています✨
#画面
画面幅はクライアント幅/10*9.5。
画面高はクライアント高/10*9.5。
メイン画面は、描画中キャンバス。
メイン画面.width=画面幅。
メイン画面.height=画面高さ。
背景は、[画面幅,画面高さ]のキャンバス作成。
背景.非表示=オン。
[画面幅,画面高さ]へ空グラデ描画。
メイン画面へ描画開始。
#設定
変数 雪設定={
"最大径":50,
"最小径":10,
"最大落下速度":20,
"最小落下速度":10,
"最大風速":5,
"最小風速":1,
"最大数":(画面幅+画面高)/50
}
#データ作成
雪一覧は空配列。
雪作成間隔基準値=0.5。
雪作成間隔は(雪作成間隔基準値/(画面幅/400))。
(雪作成間隔)秒毎には、もし、(雪一覧の要素数)<(雪設定.最大数)ならば、雪追加。。。
●雪追加
変数 雪=雪設定。
変数 方向=[-1,1]。
変数 伸縮=[[0,1],[1,0]]。
# 雪の降り方
変数 風力=(雪.最小風速から雪.最大風速までの乱数範囲)/10。
変数 風向=方向[2の乱数]。
変数 速度=(雪.最小落下速度から雪.最大落下速度までの乱数範囲)/10
# 雪のサイズ
変数 雪径=雪.最小径から雪.最大径までの乱数範囲。
# 雪位置
変数 横位置=画面幅の乱数。
変数 縦位置=雪径/2*-1。//雪が見えない所から降らせる
# 雪色
変数 R=0xCCから0xFFまでの乱数範囲。
変数 G=0xDDから0xFFまでの乱数範囲。
変数 B=0xEEから0xFFまでの乱数範囲。
変数 A=(5から8までの乱数範囲)*0.1。
変数 雪データ={
"位置":[横位置,縦位置],
"径":雪径,
"色":「RGBA({R},{G},{B},{A})」,
"左右方向":風力*風向き,
"落下速度":速度,
"回転方向":方向[2の乱数],
"伸縮":伸縮[2の乱数],
"角度":0
}
雪一覧に雪データを配列追加。
ここまで。
#雪を降らせる
降雪アニメーション。
●降雪アニメーション
[0,0]へ背景を画像描画。
もし、(雪一覧の要素数)=0ならば、雪追加。
雪一覧を反復
変数 No=対象キーを整数変換。
変数 雪位置は対象.位置。
変数 [横位置,縦位置]=雪位置。
変数 雪径は対象.径。
変数 雪色は対象.色。
雪色に線色設定。
変数 伸縮=対象.伸縮。
変数 角度=対象.角度+対象.回転方向。
変数 sin=SIN((角度をラジアン変換))。
変数 cos=COS((角度をラジアン変換))。
変数 [伸縮x,傾斜y,傾斜x,伸縮y]=[cos*伸縮[0],sin,-sin,cos*伸縮[1]]。
変数 [起点x,起点y]=[横位置,縦位置]。
キャンバス状態保存。
#---
[伸縮x,傾斜y,傾斜x,伸縮y,起点x,起点y]だけ描画変換マトリクス設定。
[0,0]へ雪径の雪描画。
#---
キャンバス状態復元。
横位置を対象.左右方向だけ増やす。
縦位置を対象.落下速度だけ増やす。
もし、縦位置-雪径>画面高さならば、
雪一覧のNoを配列削除。
違えば、
対象.位置=[横位置,縦位置]。
対象.角度=角度。
ここまで。
ここまで。
「降雪アニメーション」を画面更新時実行。
ここまで。
#描画
//中点は[x,y]の配列。雪径は雪の「直径」
●(中点へ雪径の)雪描画
空に塗り色設定
変数 m=雪径/10。m/4*3に線太設定。
中点からm*2で6の多角形頂点取得。
それで多角形描画。
変数 頂点は、中点からm*5で6の多角形頂点取得。
3回:
頂点[回数-1]から頂点[回数+2]まで線描画。
変数 分岐点は、中点からm*3で6の多角形頂点取得。
分岐点を反復:
変数 枝は、対象からm*2で6の多角形頂点取得。
変数 A=(対象キー-1+6)%6。
変数 B=(対象キー+1+6)%6。
枝[A]から対象まで線描画。
枝[B]から対象まで線描画。
ここまで。
// x = r cos θ y = r sin θ の呪文
●(中点から半径でnの)多角形頂点取得
変数 v=空配列。
n回
変数 角度=(360/n)*(回数-1)。
変数 ラジアン度=(角度-90)をラジアン変換。
変数 x=半径*COS(ラジアン度)+中点[0]。
変数 y=半径*SIN(ラジアン度)+中点[1]。
vへ[x,y]を配列追加。
ここまで。
vで戻る。
ここまで。
!「https://n3s.nadesi.com/plain/ImageProcessing.nako3」を取り込む。
●(whへ)空グラデ描画
変数 上空色=「#000066」
変数 地上色=「#CCCCFF」
変数 [w,h]=wh。
1に線太さ設定。上空色に塗り色設定。上空色に線色設定。
[0,0,w,h/3]へ四角描画。
h/3*2回
C=回数-1。
空色=[C,h/3*2]で上空色と地上色の色等比計算の色混ぜ。
空色に線色設定。
[0,C+h/3]から[w,C+h/3]へ線描画。
ここまで。
ここまで。
●クライアント幅
「document.documentElement.clientWidth」をJS実行。
ここまで。
●クライアント高さ
「document.documentElement.clientHeight」をJS実行。
ここまで。
降っています! ひらひらと舞っていますよ~🎶
おわります
キャンバスにお絵かきするだけじゃなく、それが動かせると楽しいですね!
アドベントカレンダー始まりました。
皆さんどうかご参加下さい~🙇♀️