AndroidJava/sinで放物線や明滅をつくる

数字の増減を繰り返したい時のsin関数例(2018-11-04)


結論から書けば、こう、です。

   static double sin(int now, int total){
       // 引数にint を使うことの方が多いので、cast する手間を省きます
       return sin((double)now, (double)total);
   }
   static double sin(double now, double total){
       // Math.sin のラッパー
       // sin, cos は 2パイ(3.14*2, Math.PI * 2) あるいは
       // 360度(Math.toRadians) で一周するけど、
       // その周期を任意の値 total で調節したい
       // sinの場合は 0開始、1から-1 を経て、0に戻るを繰り返す
       if(total == 0){
           return Math.sin(now); // コーディング間違いへの保険として。ゼロ除算を避ける。
       }else {
           return Math.sin(Math.PI * 2 * (now / total));
       }
   }

これで、例えば
(適当なCanvas canvas を用意して*1

 canvas.drawColor(Color.BLACK);
 Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
 p.setColor(Color.WHITE);
 p.setTextSize(canvas.getWidth() / 20); 
 p.setAlpha(155 + (int)(100.0 * sin(System.currentTimeMillis(), 4000))); //ここがポイント! 
 canvas.drawText("Hello World: ", 100, 100, p);

これでHello World がじわじわ点滅します。
透過率 55 から 255 を4秒周期で行ったり来たりする感じです。


変数を適当に増やしたり減らしたりして画像の座標や色を変えることはできるのですが、
前述のような「じわじわ」変わる風にするにはひと工夫欲しいところです。
たとえばキャラクターをジャンプさせる時のY座標で、直線ではなく放物線を描くようなイメージです。

算数は苦手なので、いい感じの説明はできないのですが。
高校時代の基礎解析の参考書によると...

よくあるsinの説明の図

ポイントは、sin 関数に何かの数字を与えると、
「1 から -1 の間」の数字を永遠に返し続けてくれるらしい、
その調節には、なんだか「π (=3.14)」 つまり Math.PI を使うと良いらしい、
ということで、
紆余曲折の末に、前述のsinラッパー関数に行き着きました。
(自分が知らないだけで、探せばこういうユーティリティがあるのかもしれませんが、
見つけられなかったので作りました)

ループ内でカウントしたもの(i++ みたいなやつ)を与えるよりも、
時刻(System.currentTimeMillis()みたいなやつ)を与えたほうが、
ループの処理の早さに影響されにくいので、画面表示が安定した明滅や放物線になりますが、
その場合は中断・再開に気をつけましょう。
(例えば、キャラクターがジャンプすると同時に端末のホームボタンを押す、
再びアプリに戻ったときは、ジャンプして着地した状態になってしまっている、とか)


ご参考まで 以下は応用で、正の値だけが欲しい場合です。

 static double sin010(double now, double total){
     //
     // sinの戻り値は 0開始、1から0、(0から-1は飛ばして)ふたたび 0から1へ  
     //
     double d;
     double total2 = total * 2;
     if(total2 == 0){
         return Math.sin(now); // 0除算回避  
     }else {
         d = Math.sin(Math.PI * 2 * ((now % (total)) / total2)); // 0 -> 1.0 -> 0 を1サイクルとする  
     }
     return (d >= 0 ? d : d * -1.0);
 }
 static double sin01(double now, double total){
     //
     // sinの戻り値は 0から1のみ、(1から-1は飛ばして)ふたたび 0から1へ  
     //
     double d;
     double total4 = total * 4;
     if(total4 == 0){
         d = Math.sin(now); // 0除算回避
     }else {
         d = Math.sin(Math.PI * 2 * ((now % total) / total4)); // 0 -> 1.0 を1サイクルとする  
     }
     return (d >= 0 ? d : d * -1.0);
 }

以上です。 (でも数学は苦手なので、もし数値が間違えていたらごめんなさい)


*1 用意できない場合はAndroidJava/少し長いHelloWorld(SurfaceView)を参照。その際はループのsleep周期を短くしたほうが、じわじわ感がでると思います。