アニメーションプログラミング 報告者:HAL [目的] アニメーションを行うプログラムの仕組みを考察し実装する。 [作成するサンプルプログラム] 1.画面の左端にボールがある。 2.一定の速さで右に進む。 [プログラム1(失敗)] #define WIDTH 128 /*画面の横幅*/ /*ボール構造体*/ struct _ball{ int x,y;/*座標*/ int speed;/*速度*/ }ball; int main(){ /*初期化*/ ball.x=0; ball.y=10; ball.speed=4; while(TRUE){ ball.x += ball.speed; DispBall(ball.x,ball.y);/*ボール描画関数(引数のx,y座標にボールを描画)*/ if(ball.x > WIDTH)break;/*ボールが画面の端に行くとループを抜ける*/ } return 0; } [失敗の理由] 1.このプログラムを実行した場合、ボールのX座標は、 0,4,8,12,・・・と飛び飛びの値となり、滑らかなアニメーションにならない。 2.さらに、CPUの処理速度が速いと、ボールの移動が一瞬で完了してしまう。 つまり、動作させる機種によってアニメーション速度が変わってしまう。 (注:ゲーム機のように機種が定まっている場合はこのままでもよい) [プログラム2(失敗)] #define WIDTH 128 struct _ball{ float x,y;/*float型にした*/ float speed; }ball; int main(){ ball.x=0; ball.y=10; ball.speed=0.01f; while(TRUE){ ball.x += ball.speed; DispBall((int)ball.x,(int)ball.y); if(ball.x > WIDTH)break; Sleep(10);/*10ミリ秒停止する*/ } return 0; } [解説] ・ボールの座標、速度をfloat型とした。 ・ボールのX座標は0,1,2,3,・・・と更新されるようになり、滑らかなアニメ ーションとなった。 ・Sleep(10);というところで、どの機種でも10ミリ秒停止させられるようになった。 [失敗の理由] Sleep(10);というところで10ミリ秒停止するが、その前の ball.x += ball.speed; DispBall((int)ball.x,(int)ball.y); if(ball.x > WIDTH)break; という処理の実行速度は機種によって異なる。結果、whileループを一周する時間が機種 によって変わってしまう。 [プログラム3(成功)] struct _ball{ float x,y; float speed; }ball; int main(){ unsigned long frame_time,wait_time; ball.x=0; ball.y=10; ball.speed=0.01f; wait_time = GetTime();/*現在の経過時間(ミリ秒)を取得*/ while(TRUE){ frame_time = GetTime() - wait_time;/*1ループするミリ秒を計算*/ wait_time = GetTime();/*現在の経過時間(ミリ秒)を保持*/ if(frame_time == 0){frame_time=1;}/*0の場合だと処理が行われなくなるので1に強制*/ ball.x += frame_time * ball.speed;/*frame_timeを掛ける*/ DispBall((int)ball.x,(int)ball.y); if(ball.x > WIDTH)break; } return 0; } [解説] frame_timeにはwhile文を一周する時間が入る。処理内容によってframe_timeの値は毎回異なる。 ボール移動の文 ball.x += frame_time * ball.speed;/*frame_timeを掛ける*/ について説明する。 仮にframe_timeが4とする。この時、while文を一周した時間は4ミリ秒である。 1000ミリ秒=1秒だから、この文は1秒間で1000÷4=250回実行される。 ボールの移動量は1周あたりframe_time*ball.speed = 4×0.01=0.04である。 1座標移動するには1÷0.04=25周分である。 1秒間で250周するので25÷250=0.1。つまり、ボールは大体0.1秒で1座標分を移動することになる。 同様に仮にframe_timeが2とする。この時、while文を一周した時間は2ミリ秒である。 1000ミリ秒=1秒だから、この文は1秒間で1000÷2=500回実行される。 ボールの移動量は1周あたりframe_time*ball.speed = 2×0.01=0.02である。 1座標移動するには1÷0.02=50周分である。 1秒間で500周するので50÷500=0.1。つまり、ボールは大体0.1秒で1座標分を移動することになる。 よって、ボールの移動速度は機種に依存しないことが証明された。 [プログラム4(処理速度考慮)] 前回はボールの座標、速度共にfloat型を用いたが、float型はint型に比べて処理速度 が遥かに遅い(参考1参照)。また、float型が使えない機種もある。(例:iアプリ) よって、int型のみで実装したプログラムを作成する。 #define WIDTH 128 #define DIVIDE 1024 /*2の累乗にするほうが速い*/ struct _ball{ int x,y; int speed; }ball; int main(){ unsigned long frame_time,wait_time; ball.x=0; ball.y=10*DIVIDE; ball.speed=5; wait_time = GetTime(); while(TRUE){ frame_time = GetTime() - wait_time; wait_time = GetTime(); if(frame_time == 0){frame_time=1;} ball.x += frame_time * ball.speed; DispBall(ball.x/DIVIDE,ball.y/DIVIDE); if(ball.x/DIVIDE > WIDTH)break; } return 0; } [解説] frame_timeが4であった場合、ball.xは20,40,80,120,..と増えていく。 その値をそのまま表示させずに、ある値で除算して表示させる。 例えば、100で除算した場合は、0.2,0.4,0.8,1.2,...となり、 整数部分のみ適応されるので、0,0,0,1,1,1,2,...と増えていくことになる。 よってアニメーションとなる。 また、除算する数は2の累乗の数(64、128等)のほうが処理速度が遥かに速い(参考1参照)。 補足:シフト演算子を使えば、除算よりも速度は速くなるが(参考1参照)、 ball.xが負の場合に、シフト演算の結果は処理系に依存するので省略した。 [参考1.処理速度の比較をするプログラム(C言語)] #define WAIT 50 #include #include /* 補足:switchで分岐すれば数行のプログラムになるのだが、 switch文で処理時間がかかるのでうまく測定できない。 */ /* int型とfloat型 */ void result1(){ int i; float f; unsigned long count; DWORD t1,t2; count=0; t1 = GetTickCount(); while(TRUE){ t2 = GetTickCount(); if(t2 > t1 + WAIT)break; for(i=0;i<10000;i++){} count++; } printf("int=%ld,",count); count=0; t1 = GetTickCount(); while(TRUE){ t2 = GetTickCount(); if(t2 > t1 + WAIT)break; for(f=0;f<10000;f++){} count++; } printf("float=%ld\n",count); } /* 10で割る場合、64(2の累乗)で割る場合、シフト演算 */ void result2(){ int i; int temp; unsigned long count; DWORD t1,t2; count=0; t1 = GetTickCount(); while(TRUE){ t2 = GetTickCount(); if(t2 > t1 + WAIT)break; for(i=0;i<10000;i++){temp=i/10;} count++; } printf("/10=%ld,",count); count=0; t1 = GetTickCount(); while(TRUE){ t2 = GetTickCount(); if(t2 > t1 + WAIT)break; for(i=0;i<10000;i++){temp=i/100;} count++; } printf("/100=%ld,",count); count=0; t1 = GetTickCount(); while(TRUE){ t2 = GetTickCount(); if(t2 > t1 + WAIT)break; for(i=0;i<10000;i++){temp=i/64;} count++; } printf("/64=%ld,",count); count=0; t1 = GetTickCount(); while(TRUE){ t2 = GetTickCount(); if(t2 > t1 + WAIT)break; for(i=0;i<10000;i++){temp=i/256;} count++; } printf("/256=%ld,",count); count=0; t1 = GetTickCount(); while(TRUE){ t2 = GetTickCount(); if(t2 > t1 + WAIT)break; for(i=0;i<10000;i++){temp=i>>6;} count++; } printf(">>6=%ld,",count); count=0; t1 = GetTickCount(); while(TRUE){ t2 = GetTickCount(); if(t2 > t1 + WAIT)break; for(i=0;i<10000;i++){temp=i>>10;} count++; } printf(">>10=%ld\n",count); } int main(){ int i; for(i=0;i<10;i++){ result1(); } for(i=0;i<10;i++){ result2(); } return 0; } [実行結果] int=834,float=455 int=968,float=396 int=891,float=467 int=896,float=382 int=912,float=476 int=1011,float=448 int=1002,float=445 int=933,float=484 int=911,float=485 int=931,float=439 /10=140,/100=127,/64=644,/256=665,>>6=724,>>10=788 /10=130,/100=140,/64=606,/256=668,>>6=658,>>10=743 /10=140,/100=128,/64=654,/256=652,>>6=723,>>10=803 /10=129,/100=138,/64=569,/256=653,>>6=669,>>10=743 /10=137,/100=128,/64=608,/256=715,>>6=738,>>10=801 /10=130,/100=137,/64=656,/256=651,>>6=785,>>10=710 /10=130,/100=116,/64=525,/256=675,>>6=723,>>10=742 /10=140,/100=126,/64=658,/256=546,>>6=759,>>10=782 /10=131,/100=139,/64=592,/256=687,>>6=765,>>10=729 /10=132,/100=124,/64=622,/256=568,>>6=742,>>10=801