SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|

2022-11-20(Sun) アクセラのバッテリを交換

  現状、特段の問題は出ていないが、これまでの経験からも、バッテリが弱ってエンジンが始動できない現象は突然にやってくるので、本格的な冬を目の前に交換を検討し始めていた。

  アクセラは中古で買ってもうすぐ4年になるが、2016年の秋に登録のクルマなので、もう6年以上も使っている計算になる。まだバッテリの電圧は12Vチョイあるので、もう少し粘れそうではあるが、10年に対して折返し点は過ぎているし、予防交換しても悪くはないタイミングだろう。

  最近の車は電装品が多く、バッテリを外してしまうと、いろいろな設定が飛んでしまう。時刻やラジオ局くらいならいいのだが、ステアリングの切れ角やパワーウィンドウの停止位置、挙げ句にはバッテリの状態みたいなものも記憶しているらしく、それらを飛ばしてしまうと再設定が面倒らしい。

  というわけで、だいぶ以前にこんなものを製作していたのであった。メモリバックアップ用電源装置(ラベルプリンタ型)である。

  これ系の製品は多くの種類が市販されているが、バッテリターミナル、OBDコネクタ、アクセサリソケットなどに接続するようになっている。しかし、バッテリターミナルだと作業の邪魔だし、OBDコネクタだと対応コネクタがいるし、アクセサリソケットだとナビが動くから電源容量が不足するはずだ。ヒューズ電源から供給するのが一番だと思うがそういう製品は見当たらない。というわけで自作したのであった。ラベルプリンタを使ったのは、単に12Vの電池ボックスとして格好だったから、である。

  画像の説明

  アクセラの場合、室内ヒューズボックスの5番が常時供給なので、そこに12Vを供給するヒューズを挿し、マイナス側のミノムシを適当な金属部分に噛ませる。これでメモリバックアップ状態のハズである。

  あとはバッテリを交換する。用意したバッテリはTuflong STANDARDという製品の75D23L。本来はアイドリングストップ車なのでQ-85指定なのであるが、i-stopはうっとおしいので、長らく走り出すとすぐにボタンでオフにしてしまっており、遂には先日よく知られているウラ技によって強制オフ状態にしてしまったので、めでたく充電制御車対応のバッテリにダウングレードしてしまうのである。

  画像の説明

  バッテリの端子が外れにくかったり、外した端子に軍手を被せて短絡の保護をしたり、重くて取り出しに手間取ったり、通り一遍の苦労をしつつ、無事に交換完了。エンジンを始動し、設定が残っているっぽい様子を確認。メモリバックアップにも成功したようだ。ちょうどそこにあるラベルプリンタで交換日を印刷して貼っておくかw。

  このアクセラ、特段の不具合を起こすこともなく、MT車であることもあって普通に気分良く乗れている。これからも長らく活躍していただきたい。次は、ぼちぼち5年経つ、ロードスターのバッテリも交換だなぁ。


2022-11-12(Sat) ラリージャパン、サファリドライブ

  何度か観に行っているF1に対して、これまでほとんど興味のなかったラリーだが、近所で開催されるとなればちょっと話が違ってくる。

  さすがにチケットを買って観戦するつもりまではないものの、ちょっと調べたら、なんとあの旧伊勢神トンネルがコースに含まれている。高専時代に心霊スポットとして知り、何度か通ったこともあるが、あそこをブッ放すってマジか。

  そもそも、コースを調べたら、だいぶガチで「酷道」レベルのコースを走るようだ。「酷道」好きで、豊田の多くを踏破しているオイラでも、まだ攻めていない道が含まれている。ラリーって、もう少し広い道を走る印象だったので、これにはだいぶ驚いた。

  ラリーに対する知識のあまり多くないオイラだが、全力でタイムアタックするSS区間と、一般車に混じって規定時刻ピッタリの到着を目指すリエゾン区間があることくらいは知っている。じゃ、ドライブがてら、時間に合わせてリエゾンを逆走すれば、猛獣見物を兼ねられて、一石二鳥ではないか。

  ラリージャパンの公式ではリエゾンは非公開とあったが、トヨタ系のサイトには、ほぼ完璧に読み取れるコースマップがあったので、タイムテーブルと突き合わせてドライブコースを組み立てる。いつもとちょっと違ったドライブ計画。ある意味、オレラリーの開催である。

  で、いつもより少し早い、昼前に出発。豊田の街中を抜け、イベントの中心地である豊田スタジアムにさしかかる。結構な人出だが、渋滞と言うほど混んではいない。そのまま301号に向かうと、後ろからラリーカーが。きたきたッ!しかし、左折車線を直進したり、ゼブラを使って追い越したり、ちょっとお行儀の悪い運転である。まぁ、リエゾンとはいえ、完璧に日本の交通事情に沿うのは難しいわな。

  画像の説明

  ちょうどヒョンデのマシンが後ろからきたので、前に入れて見物する。ヒョンデのラリー2のマシンだ。そこそこウルサく、排気も少しニオう。なるほど。

  そのまま301号を進み、473号に入り、SS8/11 Nukata Forestのスタート地点付近から、リエゾン区間の逆走を始め、向かってくるラリーカーを迎撃する。おー、大漁大漁。次々にすれ違う。記念撮影はドラレコにお任せである。なんちゃってラリーカーも走っているようだが、プライベータも含まれているので、ナンバーが付いていることで、それを識別することはできない。まさかと思ったコペンは競技車であった。中にはカニでキジでマツダのディスリで有名な国沢氏のクルマも含まれていた。

  画像の説明

  少しコンビニで小休止して岡崎方面へ。少し早いので、ワザと遠回りしてから、再びリエゾン区間の逆走を始め、SS13/14 Okazaki Cityに向かうラリーカーを迎撃する。それでもあまりラリーカーが来ないので、退避車線を見つけて小休止すると近づいてくる爆音が。2枚だけX100Sで撮影。1枚目はブレたが、2枚目は割とちゃんと撮れた。と、なんとそれはトップクラスであるカーナンバー1のオジエ選手だった。ちょっとラッキー。その後には、カーナンバー18の勝田選手ともすれ違っていた。まぁ、そこそこ計画したとはいえ、十分すぎる豊作であった。

  画像の説明 画像の説明

  その後、再び三河湖の南を301号で進みSS9/12 Lake Mikawakoのスタート地点付近を抜け、県道35号へ。趣味である通行止めどん詰りチェックをこなしてから、420号で帰路につく……が、紅葉の香嵐渓がオンシーズンであることを忘れていた。渋滞に巻き込まれ、少し回避して帰宅。

  画像の説明

  実は、10月末に左リアがスローパンクチャを起こしていることが判明しており。その原因はおそらく、10月中旬に発見し抜去した金属片なのだが、継続的に空気圧をチェックすることで、スローパンクチャの進行速度は確認済みなので、それを知りつつも、ちょっとドキドキを隠せない200km超ドライブなのであった。帰宅しても190/200kPaは確保されていたが、早く直さなきゃな。


2022-10-26(Wed) 腕をブン回すボス

  画像の説明


2022-09-29(Thu) ウォッチをウオッチ

  このブログでも何度取り上げているが、長らくナビホークというシチズンの航空腕時計を愛用している。前回は日記エントリにしなかったが、2020年9月に5度目の電池交換をしたところだ。購入日は1999年6月26日とあるので……え、23年も使ってんのか。

  しかし、最近は出勤しないので腕に巻く機会が激減している。カラオケの練習の時に3時間を計測したり、ジョギングしている時間を計測したり、どっちもストップウォッチとしての利用だな。

  が、ここ数ヶ月、右上のボタン、ストップウォッチのスタート、ストップに使うボタンが徐々に渋くなり、とうとう戻ってこなくなってしまった。えー、電池交換までまだ何年もあるし、それだけのためにメンテに出すのもなぁ。結構、お金が掛かるし……。

  ストップウォッチはあったほうがいいのだが、ストップウォッチもなぁ……というワケで、ちょっと見栄えのする安い腕時計を探すことにした。アリエクでいいかな。

  ところが、ちょっと見栄えのするのはアリエクでも2000円くらいする。信頼性の低いものにそんなに出すなら、カシオのがいいわ。というわけで、カシオで探す。G-SHOCKにコダわりはないが、アナログが好きなので、ストップウォッチ付きとなると、ほぼアナデジコンビタイプに絞られる。

  ゴチャっとしているのもイヤじゃないが、針の視認性が低いのはイヤである。黒地に白は悪くないが、液晶部分が灰色だと浮いている感じで違和感。ナビホークは全体に灰色地なのでよかったのだが……と、最近は液晶部分が反転しているものもある。液晶表示は見やすいとはいえないが、デザインとしては悪くない。

  画像の説明 画像の説明

  と、結局、散々に迷った挙げ句、AEQ-110W-1BJHという製品にした。4230円。地味だが、パッと見G-SHOCKで、それほど安っぽくは見えない、と思う。

  最後まで迷った製品は、似たような感じでソーラー稼働だったのだが、ストップウォッチが1時間までという制約と、ソーラーも充電池の交換が必要な場合があると聞いたことがあるので外した。

  画像の説明

  で、26日に届き、フムン、実物も悪くないな、と思っていたところで、偶然にG-SHOCKの新作に関するニュースを目にした。値段は5倍くらいするが、なんだか、デザインコンセプトがすごく似ている気がするな。


2022-09-12(Mon) 結局、効果音生成ツールを改良する

  一応、効果音生成ツールは一応の完成を見たのだが、やりだすと、そう簡単には止まらないのである。

  ゲームの効果音にノイズは欠かせない。そのため、ホワイトノイズ、ブラウンノイズ、ピンクノイズ、パープルノイズが使えるようになっているが、それだけでは足りなすぎるのである。バリエーションが欲しい。

  で、ノイズに音程があるかというと、微妙である。ノイズを音程に分解すると、その分布によって上記の「色」が決まるのだが、それなりに広く分布しているので、ノイズには音程がないと言える。

  んが、一方で、3,8,5というノイズを、3,3,8,8,5,5と加工すれば、低音側に遷移することに疑いはないのである。まぁ、厳密な定義はともかく、指定の方法はトーンに合わせて、440.0を標準とし、詰めたり、間引いたりすることで、ノイズに音程変化が起こせるようにしてみた。

  なんだかんだコードを書いているうち、不具合があることに気付き、割と大きな追加/修正になってしまったが、表現力が大きく上がった気がする。

 @length = 1.0
 
 mod1 = it = {}
 it[:device] = 'generate'
 it[:length] = @length
 it[:type] = 'square'
 it[:freq] = [8]
 it[:amp] = <<AMP
 #--------------25--------------50--------------75-------------100
 ................................................................/
 AMP
 
 noise1 = it = {}
 it[:device] = 'fm'
 it[:length] = @length
 it[:type] = 'pink_noise'
 it[:freq] = [20]
 it[:amp] = <<AMP
 #--------------25--------------50--------------75-------------100
 ................................................/
 ................................................................/
 /
 AMP
 it[:index] = 1.5
 
 @connection = [ mod1, noise1 ]

  こんな記述で、ロケット噴射音っぽいの<聴いてみる>を作ることができた。これは使えるな、そろそろシューティングの製作に戻ろう。


2022-09-10(Sat) 結局、効果音生成ツールを自作する

  先日、ノイズを生成できるようになってから、効果音生成プログラムに組み込んでアレコレしていたのだが、アレコレしているうち、効果音とは、基本となる波形に様々な変化を繰り返し重ねていくことによって生成するものだということがわかってきた。

  主には、正弦波、矩形波などの生成、加算、変調、音量変化などである。しかし、その方法を汎化して記述するにはどうすればいいのか。そもそも変調とは何なのか。

  考えれば考えるほどわからなくなり、コードを書いては試し、何度も書き直した。コードの追加ではなく、こんなに何度も、構造ごと変えては、頭から書き直したのは初めてだ。変調を追求していくうちに、FM音源に行き当たり、周波数変調、位相変調、YM2151(OPM)の仕組みにまで踏み込んでしまい、オペレータのコネクションを再現するために、RPNっぽい手法まで実装することになった。

  結局、ほぼ、シンセサイザを自作した、というレベルではないだろうか。ヤクの毛刈りどころか、3頭はバーベキューにして食い尽くした気分だ。

  最終的には、以下のような記述で音を作る。以下は、soxのサンプルにあるパイプオルガンによるAm7を再現するもの。typeで正弦波(sine)を指定、freqで周波数(A3, C4, E4, G4)を指定、envでエンベロープ(音量変化)を指定。最後にそれらをRPN記法で重ねていく。

 @length = 1
 
 car1 = it = {}
 it[:device] = 'generate'
 it[:length] = @length
 it[:type] = 'sine'
 it[:freq] = <<FREQ
 #+BC+D+EF+G+A+BC+D+EF+G+A+BC+D+EF+G+A+BC+D+EF+G+A+BC+D+EF+G+A+BC+D+EF+G+A+BC+D+EF+G+A+BC+D+EF+G+A
 #--1----(55)1--2---(110)2--3---(220)3--4---(440)4--5---(880)5--6--(1760)6--7--(3520)7--8--(7040)8
 ..................................../
 FREQ
 it[:amp] = <<AMP
 #--------------25--------------50--------------75-------------100
 ................/
 AMP
 
 car2 = it = {}
 <略、C4 の正弦波>
 
 car3 = it = {}
 <略、E4 の正弦波>
 
 car4 = it = {}
 <略、G4 の正弦波>
 
 add = it = {}
 it[:device] = 'add'
 
 env1 = it = {}
 it[:device] = 'env'
 it[:length] = @length
 it[:amp] = <<AMP
 #--------------25--------------50--------------75-------------100
 /
 ................................................................/
 ................................................................/
 ................................................................/
 ................................................................/
 ................................................................/
 /
 AMP
 
 @connection = [ car1, car2, add, car3, add, car4, add, env1 ]

  これでwavファイルが生成される<聴いてみる>。

  もちろん、加算はもっとも基本的な演算で、真骨頂は変調である。インベーダのUFOの音っぽいの<聴いてみる>とか、パックマンのモンスタの音っぽいの<聴いてみる>とか、R-TYPEのショット音っぽいの<聴いてみる>とか、ダライアスのレーザ音っぽいの<聴いてみる>とか、割と直感的な記述により生成することができる。

  今回、FM音源のことを調べていて、ものすごく少ないパラメータ(バイト数)で音色が作れることが、当時のPCの性能事情に即していたこと。反面、音色作りがものすごく難しかったことを、改めて知った。実際、自分もX1のFM音源をアレコレしていたが、ちっともイメージしたような音色が作れなかった。

  とはいえ、アレよりはだいぶマシだと思うが、このツールでもイメージ通りの音に近づけていくのは難しい。近づけようとしても、意外な音ができて、その音が気に入ってしまったりする。ま、それはそれでいいんだが。


2022-08-26(Fri) 色々なノイズをジェネレートする

  シツコく飽きずにオリジナルのシューティングの製作をポチポチと進めているのだが、いくつか敵キャラクタを用意したところで、そろそろ効果音が欲しくなってきた。

  以前に「sox」というコマンドラインツールを使って効果音を作ったことがあり、少しイジり始めたのだが、音に対する自分の知識が少ないのに対して、ツールが多機能すぎるので、どうにも勝手がわからない。このパターンは……あー、いかんて、いかんて、次はサウンドエディタ作り始めちま……で、またもや「ヤクの毛刈り」の始まりである。

  で、正弦波、矩形波、三角波、鋸歯状波あたりを生成、合成や変調、周波数、音量を変化させるアルゴリズムを組んだ辺りで、ノイズの実装が必要になってきた。ん? ノイズって、種類があったよな。ピンクノイズとか。フムン。ブラウンノイズなんてのもあるのか。このパターンは……あー、いかんて、いかんて、次はノイズ生成エンジン作り始めちま……と、さらなる「ヤクの毛刈り」の始まりである。

  ホワイトノイズは簡単だ。ブラウンノイズは「ホワイトノイズの積分」らしい。基本、数学が苦手なオイラだが、試行錯誤しているうちにそれっぽいのがデキた。フタを開けてみればこれも単純だ。

  問題はピンクノイズ。1/fゆらぎの実装が必要らしい。1/fゆらぎといえば、このブログを始める前にUSB扇風機にPICマイコンを使ってそれっぽい制御を組み込んだことがあったな……などと、思い出す。弱い変化は頻繁、強い変化は稀、というヤツだ。が、結局よくわからず。ググッてどこかで見つけたサイコロ理論をアレンジして組み込んだ。

  答え合わせは、生成したノイズを「Audacity」に食わせ、周波数解析することで行う。まずはホワイトノイズ。

  画像の説明

  だいぶバラついているように見えるが、左の軸が自動調整により拡大されすぎているからだ。-21dB±1dBの範囲に収まっており、ホワイトノイズの特徴である「平坦なパワースペクトル」が実現できている。次はブラウンノイズ。

  画像の説明

  ブラウンノイズの特徴は「1オクターヴあたりパワーが6dB降下」である。言い換えると「周波数が倍になる都度パワーが6dB降下」。理想的な降下ラインを赤で示してみたが、ほぼピッタリであり、実現できたといえよう。最後はピンクノイズ。

  画像の説明

  ピンクノイズの特徴は「1オクターヴあたりパワーが3dB降下」である。言い換えると「周波数が倍になる都度パワーが3dB降下」。同じく、理想的な降下ラインを赤で示してみたが、ほぼピッタリであり、実現できたといえよう。

  というわけで、調べると他にも「ブルーノイズ」「パープルノイズ」などがあるようであるが、やりだすとキリがないので、今回はここまで。コードを置いておく。ノイズ生成のキモは「when」に続く各数行のみだ。ピンクはちょっとアクロバチックかな。

 #!/usr/bin/env ruby
 # coding: utf-8
 
 # create base.wav
 #   sox -n -r 44100 -b 16 -c 2 base.wav trim 0 1
 
 begin
     $LOAD_PATH.unshift('/usr/local/lib/ruby')
     require 'libwav'
 rescue LoadError
     raise
 end
 
 include Math
 
 ARGV.size < 2 and abort(<<USAGE % $0)
 Usage:
   $ %s base.wav white|brown|pink [sec]
 USAGE
 
 wav = NewWavFile.new(ARGV[0])
 wav.get_info[0].each {|l|
     puts(l)
 }
 type = ARGV[1]
 sec = (it = ARGV[2]) ? it.to_i : 1
 
 case(type)
   when('white')
     gain = Proc.new {
         (rand(0) - 0.5) * 65536
     }
 
   when('brown')
     gain = Proc.new {|g|
         g + (rand(0) - 0.5) * 1024
     }
 
   when('pink')
     gain = Proc.new {|g, pbuf|
         (9 - ('%b' % (rand(255) + 1)).length).times {|n|
             g -= pbuf[n]
             g += (pbuf[n] = (rand(0) - 0.5) * 8192)
         }
         g
     }
 
   else
     raise('Unexpected noise type.')
 end
 
 srand; g = 0; pbuf = [0] * 8
 (n_sample = wav.freq * sec).times {|n|
     wav[n] = [g, g]
     g = gain.call(g, pbuf)
     g < -32768 and g = -32768
     g >  32767 and g =  32767
 }
 
 wav.save_phrase(0, n_sample, '%s_noise.wav' % type)
 
 __END__

  プログラミングの歓喜を無限に味わうために。次のヤクの毛刈りのために。次の次のヤクの毛刈りのために……。


2022-08-24(Wed) パックマンのナイトライトを修正

  家族から贈られたパックマンのナイトライトだが、なんとなくググったら「買ったけど破損した状態で届いた」という報告を見つけた。併せて、一匹のモンスタがハズれた状態の写真が掲載されている。

  と、そこでヒラメいた。そんなに簡単に外れる仕様ならば、順序を入れ替えてしまえばイイじゃないか。特段パックマンに興味がない人にはどうでもいいことかもしれないが、パックマン好きとしてはこの隊列の順序に、なんともいえない違和感を拭えないのだ。

  画像の説明

  つうわけで、裏のネジを外し、各モンスタを固定しているツメを押し込んで外し、順序を変更した。すると、なんということでしょう。見るたびにイヤというほど違和感に満ち溢れていたアイテムが、心にシミ入る感じに変わっているではありませんか。まさに匠の技。

  ピンキーがいないのはどうしようもないが、そもそも彼女は待ち伏せる性格だから、きっと前方に居るのだろう。去年のアイテムから出張を願おう。よし。と。

  画像の説明

  しかし、こんどは右向きなのが気になりはじめてしまった。コーヒーブレイクの時もそうだが、追いかけられて逃げる方向は左と決まっているのだが……。


2022-08-06(Sat) 生誕51周年記念のパックマン

  昨年に引き続き、誕生日プレゼントにパックマングッズを贈られた。いや、嬉しいんだが、オレはパックマニア(?)ではないぞ。ナムコファンだが、ダライアス教に入信しているし、グラディウス派に属しているし、ファンタジーゾーンの住人だぞ。

  置物のように見えて、USBから電源を与えると、ナイトライトになるのだ。なかなか美しく光る。

  画像の説明 画像の説明

  が! なんで3匹なんだ。なんでピンキーがいないんだ。なんでアカベイが最後尾なんだ。パックマニアとしては気になって仕方ない。

  画像の説明

  以前に、パックマン柄の2種類のトランクスを買って、喜んで履いているのだが、その片方の柄は、アカベイ、アオスケ、ピンキーで、グズタがいない。もう片方の柄は、隊列を組んでいないのだが、ピンキーがいない。誰か! ちゃんと監修してやってくれ!

  というか、よく見るとこのライト「namco」のマークないな……まー、そういうことか。

  画像の説明

  念のため、オリジナルのドットパターンをオーバレイしてみたら、モンスタは1ドットの違いもなく正確であった。パックマンは口角の1ドットのみ違う。うぅーむ……うぅーむ!!


2022-08-04(Thu) CoffeeScriptにて回転機能をmixinにより実装す

  意外と飽きずにオリジナルのシューティングの製作をポチポチと進めているのだが、考えれば考えるほど、既存のシューティングに対する気付きが増えていく。

  例えば、R-TYPEは初見でも1面はクリアさせたい作りなんだな、とか、ダライアスはボス以外の作りは適当だな、とかである。反面、ゼビウスは「最初は偵察機が登場する」とか「有人機は自殺的攻撃をしない」とか考えてあることに驚く。そうだよな。それは世界感を感じさせるために有効だよな。

  今回、自分は「ストーリ」を強く感じさせるゲームにしたいので、単に思いつきで敵を作るのではなく、それなりの理由に基づいて登場、行動させたい。そうなると、最初は偵察機だし、その後はミサイル攻撃かな、とか思うわけだ。

  で、ミサイルを作ったのだが、誘導したくなってしまい、斜めに飛ばしたくなってしまった。すると、回転機能が必要になる。先日のテトランはプリレンダだったが、汎用性を考えればリアルタイムレンダを実装するべきだ。しかもキャッシュ付きで、できるだけ軽く。

  とりあえず試験的にCoffeeScriptで実装してみたら、処理はだいぶ軽く、負荷的には問題なさそうだ。後は、どういうパラダイムで実装するか。現状、以下のようなオブジェクト構成になっている。

 class Plane					# 描画面
 class Obj extends Plane			# オブジェクト(スプライト)
 class Ship extends Obj				# 自機
 class Enemy extends Obj			# 敵
 class MissileA extends Enemy			# ミサイル
 class Fixed extends Enemy			# 地形に固定された敵
 class ReconA extends Fixed			# 偵察機

  回転するのは敵に限らず、あらゆるオブジェクトが回転可能にできるべきだ。しかし、そうすると多重継承が必要になってしまう。それを避けるには、一番の根っこであるObjクラスに実装してしまう方法も考えられるが、それは気が進まない。必要に応じて回転機能が追加できるのが望ましい。

  そういう場合、Rubyならミックスイン(mix-in)があるが、CoffeeScriptにはあるのかしらん……と、調べたらあるんだな。というか、正確には「後付けできる」といったところか。

 #---------------------------------------------------------------
 #
 #   クラスの mixin を可能にする
 #
 #       https://coffeescript-cookbook.github.io/chapters/classes_and_objects/mixins
 #
 mixOf = (base, mixins...) ->
     class Mixed extends base
     for mixin in mixins by -1
         for name, method of mixin::
             Mixed::[name] = method
     Mixed

  上記を冒頭に記述しておいて、

 class Rotnscl					# 回転拡大縮小機能
 class RmissileA extends mixOf Enemy, Rotnscl	# 敵+回転拡大縮小機能

  上記のようなオブジェクト構成にする。それにより、RmissileAは、敵であり、回転機能を持つ、という定義となる。これにて一件落着。

  次は、回転パターンのキャッシュについて考える。処理負荷的にリアルタイムレンダは可能な見通しだが、毎回、回転パターンを作るのは無駄なので、一度作った回転パターンは残しておき、二度目からは勝手にそれを使うようにしたい。つまりはキャッシュだ。

  キャッシュはどう実装されるべきか、といえば、そりゃオブジェクトタイプ、パターン、角度毎に残しておくべきで、個体(インスタンス)毎に行うべきではない。つまり、クラス変数に残しておくべきだ。クラス変数はCoffeeScriptにはあるのかしらん……と、思ったら、ちょっと微妙ではあるが、ある。

  クラスの中、メソッドの外に「@xxx」を定義しておき、メソッド内からは「@constructor.xxx」でアクセスする。ただし、利用はそのクラスのみに閉じ、継承したクラスからはアクセスできない。今回の場合、その仕様で問題ないが。

  画像の説明

  つうわけで、こんな感じで気持ちよくグルグルと回転している。いや、canvas要素には、ネイティブな回転機能はあるようなのだが、それはそれとして、実装するのが楽しいのだから、これでいいのだ。コードはこんな感じ。

 #---------------------------------------------------------------
 #
 #   回転拡大縮小可能オブジェクト(mixin用)
 #
 class Rotnscl
 
     rotnscl: (pats, n, v, t, w = null, h = null, dx = null, dy = null) ->
         pat = pats[n]
         w ||= pat.width;  dx ||= -(w >> 1)
         h ||= pat.height; dy ||= -(h >> 1)
 
         return(rpat) if(rpat = @constructor.rpats[rsym = "#{n}_#{t}_#{v}_#{w}_#{h}_#{dx}_#{dy}"])
 
         unless(pdat = @constructor.pdats[psym = "#{n}"])
             src_canvas = document.createElement('canvas')
             src_canvas.width  = pat.width
             src_canvas.height = pat.height
             src_context = src_canvas.getContext('2d')
             src_context.drawImage(pat, 0, 0)
             pdat = @constructor.pdats[psym] = src_context.getImageData(0, 0, w, h).data
 
         rot_canvas = document.createElement('canvas')
         rot_canvas.width  = w
         rot_canvas.height = h
         rot_context = rot_canvas.getContext('2d')
 
         rxys = Vec.v2vxy(v, t); hxys = Vec.v2vxy(v + 32 & 0x3F, t); vxys = Vec.v2vxy(v + 48 & 0x3F, t)
         _vx8 =   dx * rxys[1] - dy * rxys[0]  - (dx << 8)
         _vy8 = -(dx * rxys[0] + dy * rxys[1]) - (dy << 8)
         w8 = w << 8; pw = pat.width
         for y in [0...h]
             _hx8 = _vx8; _hy8 = _vy8
             for x in [0...w]
                 _hx8 += hxys[0]; _hy8 += hxys[1]                # 横方向加算
                 continue if(_hx8 < 0 or _hx8 > w8)
                 p = ((_hy8 >> 8) * pw + (_hx8 >> 8)) << 2
                 continue unless(pdat[p] + pdat[p + 1] + pdat[p + 2])    # 黒は透過
                 rot_context.fillStyle = "rgb(#{pdat[p]}, #{pdat[p + 1]}, #{pdat[p + 2]})"
                 rot_context.fillRect(x, y, 1, 1)
             _vx8 += vxys[0]; _vy8 += vxys[1]                    # 縦方向加算
         return(@constructor.rpats[rsym] = rot_canvas)
 
     cache_status: ->
         console.log([@constructor.name, Object.keys(@constructor.pdats).length, Object.keys(@constructor.rpats).length].toString()) if(tsc % 60 == 0)
 
 #---------------------------------------------------------------
 #
 #   回転オブジェクトサンプル
 #
 class Rsample extends mixOf Obj, Rotnscl
 
     Plane.image_src(@pats = [
         'images/rsample0.png'                   #  0:  46 x  46
     ])
     @pdats = {}; @rpats = {}
 
     constructor: (_s, x, y) ->
         super(_s, x, y)
 
     draw: ->
         n = 0                                   # パターンナンバ
         v = (tsc >> 2) % 64                     # 角度
         t = 0                                   # 倍率
         rpat = @rotnscl(@constructor.pats, n, v, t)
         @context.drawImage(rpat, @pos_x, @pos_y)
 
     d0: ->
         @cache_status()

  さて、別にミサイルはグルグル回したかったわけじゃない。意図した動きをプログラミングしていくことにしよう。