ページ 11

弾の軌道について

Posted: 2010年3月11日(木) 01:47
by サクソ
C言語というよりは弾丸軌道の概念なんですが
サインカーブを描きつつ斜めに移動する弾はどのように考えればいいのでしょうか?
真下や真横なら進行方向の座標は定値を加算、
もう一方の座標はサイン値に応じてで変化させればいい。というのはわかったんですが、
たとえば、
自機狙いなので、射出されるまで角度は決まっていない
射出されると自機に向かって蛇行(サインカーブ)しながら進む
という軌道を再現したいと考えた時
自分のアタマでは加算用のテーブルを配列で持たせるくらいしか思いつきません。
無駄が多いように感じるし何より応用が効かないので、
色々な方法があるとは思いますが「こういう風に考えると良い」みたいなのを教えてください。

Re:弾の軌道について

Posted: 2010年3月11日(木) 03:10
by Justy
 そうですね、普通に考えるとこんな感じでしょうか。

 まず敵と自機との間に直線でも曲線でもいいのですが、仮想パスを引きます。
 このパスを常に移動の基準として考えます。
 
 パス上の前のフレームの位置と現在のフレームの位置から移動ベクトル Vを出します。

 次にそのベクトル Vから、それに対して垂直なベクトルを求め、単位化しておきます。
 これをベクトル V'とします。
 
 あとはそのベクトル V'と、サインカーブの値を元に求めた係数を掛けたベクトルを
オフセットとしてパス上の現在位置と足すと蛇行するはずです。

 纏めると

 蛇行する位置 = [パス上の位置] + sin(counter) * len * V'

 となります。

 ちなみに lenの長さが蛇行の最大振れ幅になりますので、ターゲットとの距離に比例するように
設定すると確実にターゲットに命中することになりますし、ある程度の大きさを保っているなら
ターゲットはタイミング次第で回避することができます。


 尚、この説明ではベクトルを使いましたが、移動ベクトルではなく進行方向の角度から
V'を求めても構いません。
 龍神録的なコードであればこちらの方が楽でしょう。

Re:弾の軌道について

Posted: 2010年3月11日(木) 13:47
by Tatu
sin(a×t)をtで微分するとa×cos(a×t)になることを利用した
サインカーブを描く弾を含む弾幕を作ってみました。

/*
弾発射関数
way個のknd番目の種類のcol番目の色の弾を座標(x,y)からb_angleの方向を中心に発射角度をそれぞれd_angleだけずらし、
(ただし、偶数弾などb_angleの方向に撃ちたくないときはb_angleの値をd_angle/2.0だけ大きくする)
angle方向に速度をspd,vxの値をvx,vyの値をvyに(vx,vyはそのままでは機能しない),
状態をstateにしてとばす。この弾はtillまで画面外に出ても消えず、
effが1ならば加算ブレンドして描画され、sflagが1ならば発射音がなる。
*/
void boss_bullet_on(int way,int knd,int col,int cnt,double x,double y,double b_angle,double d_angle,double spd,double vx,double vy,int state,int till,int eff=0,int sflag=1){
int i=0,k;
for(i=0;i<way;i++){
if((k=search_boss_shot())!=-1){
boss_shot.bullet[k].flag = 1;
boss_shot.bullet[k].knd = knd;//弾の種類
if(knd==7) boss_shot.bullet[k].kaiten=1;
else boss_shot.bullet[k].kaiten=0;
boss_shot.bullet[k].col = col;//弾の色
boss_shot.bullet[k].cnt = cnt;
boss_shot.bullet[k].x = x;//座標
boss_shot.bullet[k].y = y;
boss_shot.bullet[k].angle = b_angle+d_angle*(i - way/2);//角度:bが基準、dが間隔
boss_shot.bullet[k].spd = spd;//angle方向のスピード
boss_shot.bullet[k].vx = vx;//x方向の速度
boss_shot.bullet[k].vy = vy;//y方向の速度
boss_shot.bullet[k].state = state;//弾の状態
boss_shot.bullet[k].till = till;//弾が画面外に出ても消えない時間
boss_shot.bullet[k].eff = eff;
if(sflag==1)se_flag[0]=1;
}
}
}

void boss_shot_bulletT130(){
int i,t=boss_shot.cnt%540;
double angle;
if(t == 60){
boss_shot.base_angle[0]=bossatan2();
}

if(t>=60 && t<180){
angle=boss_shot.base_angle[0];
boss_bullet_on(1,6,1,0,boss.x,boss.y,angle ,0,3,0,0,1,500);
boss_bullet_on(1,6,0,0,boss.x,boss.y,angle ,0,3,0,0,2,500);
boss_bullet_on(1,6,1,0,boss.x,boss.y,angle-PI/2,0,3,0,0,3,500);
boss_bullet_on(1,6,0,0,boss.x,boss.y,angle-PI/2,0,3,0,0,4,500);
boss_bullet_on(1,6,1,0,boss.x,boss.y,angle+PI/2,0,3,0,0,3,500);
boss_bullet_on(1,6,0,0,boss.x,boss.y,angle+PI/2,0,3,0,0,4,500);
}

if(t>=240 && t<300 && t%5==0){
boss_bullet_on(1,7,2,0,boss.x,boss.y,-PI/2,0,2,0,0,5,0);
boss_bullet_on(1,7,2,0,boss.x,boss.y,-PI/2,0,2,0,0,6,0);
}

if(t==420){
boss_bullet_on(2,10,0,0,boss.x,boss.y,PI/2+PI/4,PI/2,5,0,0,7,0,1,1);
}

if(t==480){
move_boss_pos(48,48,FMX-48,156,100,60);
}

for(i=0;i<BOSS_BULLET_MAX;i++){
if(boss_shot.bullet.flag==1){
switch(boss_shot.bullet.state){
case 1:
boss_shot.bullet.x-=sin(boss_shot.bullet.angle)*PI2/2*cos(PI2/40*boss_shot.bullet.cnt);
boss_shot.bullet.y+=cos(boss_shot.bullet.angle)*PI2/2*cos(PI2/40*boss_shot.bullet.cnt);
break;
case 2:
boss_shot.bullet.x+=sin(boss_shot.bullet.angle)*PI2/2*cos(PI2/40*boss_shot.bullet[i].cnt);
boss_shot.bullet[i].y-=cos(boss_shot.bullet[i].angle)*PI2/2*cos(PI2/40*boss_shot.bullet[i].cnt);
break;
case 3:
boss_shot.bullet[i].x-=sin(boss_shot.bullet[i].angle)*PI2/2*cos(PI2/40*boss_shot.bullet[i].cnt);
boss_shot.bullet[i].y+=cos(boss_shot.bullet[i].angle)*PI2/2*cos(PI2/40*boss_shot.bullet[i].cnt);
if(boss_shot.bullet[i].cnt==40){
boss_shot.bullet[i].angle=atan2(ch.y-boss_shot.bullet[i].y,ch.x-boss_shot.bullet[i].x);
}
break;
case 4:
boss_shot.bullet[i].x+=sin(boss_shot.bullet[i].angle)*PI2/2*cos(PI2/40*boss_shot.bullet[i].cnt);
boss_shot.bullet[i].y-=cos(boss_shot.bullet[i].angle)*PI2/2*cos(PI2/40*boss_shot.bullet[i].cnt);
if(boss_shot.bullet[i].cnt==40){
boss_shot.bullet[i].angle=atan2(ch.y-boss_shot.bullet[i].y,ch.x-boss_shot.bullet[i].x);
}
break;
case 5:
if(boss_shot.bullet[i].y<10){
boss_shot.bullet[i].angle=0;
}
if(boss_shot.bullet[i].x>FMX-10){
boss_shot.bullet[i].angle=PI/2;
}
if(boss_shot.bullet[i].y>ch.y){
boss_shot.bullet[i].angle=PI;
boss_shot.bullet[i].state=0;
}
break;
case 6:
if(boss_shot.bullet[i].y<10){
boss_shot.bullet[i].angle=PI;
}
if(boss_shot.bullet[i].x<10){
boss_shot.bullet[i].angle=PI/2;
}
if(boss_shot.bullet[i].y>ch.y){
boss_shot.bullet[i].angle=0;
boss_shot.bullet[i].state=0;
}
break;
case 7:
if( (boss_shot.bullet[i].x<0 && cos(boss_shot.bullet[i].angle)<0)
|| (boss_shot.bullet[i].x>FMX && cos(boss_shot.bullet[i].angle)>0)){
boss_shot.bullet[i].angle=PI-boss_shot.bullet[i].angle;
}
if(boss_shot.bullet[i].y>FMY && sin(boss_shot.bullet[i].angle)>0){
boss_shot.bullet[i].angle=-boss_shot.bullet[i].angle;
}
boss_bullet_on(1,10,0,0,boss_shot.bullet[i].x,boss_shot.bullet[i].y,0,0,0,0,0,8,0,1,0);
break;
case 8:
if(boss_shot.bullet[i].cnt>180){
boss_shot.bullet[i].flag=0;
}
break;
}
}
}
}

Re:弾の軌道について

Posted: 2010年3月11日(木) 22:14
by サクソ
ありがとうございます
コードを読み解くのはもう少し時間が要りそうですが、
自分の解釈を簡単に言い表すと

画像も判定も無い自機狙いの弾の軌道を作り、
次にソレの座標を基準にサイン値を変化させる事のできる
画像と判定を持った蛇行弾を表示+移動させる。

ってところです。妙な曲解してたらスイマセン。

で、実際には弾を2つ作るわけではなく、
蛇行弾内に過去座標等と同様の座標保持用変数(領域)を持たせ
その座標は自機狙い弾が直進した時と同じ座標に更新、
表示用+判定用の座標はその直進した場合の座標をもとに求めれば良い。
そして、自機狙いの軌道をサインカーブや螺旋拡大の軌道に入れ替えれば、
そのバリエーションの蛇行弾が即座に再現できるというわけですね。
いろんな事が出来そうな気がしてきました。

Re:弾の軌道について

Posted: 2010年3月14日(日) 21:11
by サクソ
再現できたので報告に来ました。
言語はC#になります。
初期化メソッドの引数を変化させてやればサインカーブの振れ幅をいろいろいじくれます。
発射メソッドの引数が、射出する(基準座標の)座標、狙う角度、カウンタの初期値になってます。
カウンタの初期値にカウンタの最大値の半分を指定してやると、
初期値に0を指定した時の軌道と左右対称の軌道をたどるようにしてます。

clsTwist
{
private int Count;//サインカーブのカウンタ
private int MaxCount;//カウンタの最大値
private float Speed;//仮想パス上の進行速度
private float Radius;//サインカーブの半径
private float BaseX,BaseY;//基準座標(仮想パス上)
private float PosX,PosY;//弾の座標
private float AheadX,AheadY;//基準座標の加算値
private double ShellAngle;//軌道の角度
private bool Reality;//弾の実体フラグ

public clsTwist()//コンストラクタ
{
省略
}

public void BulletInit(float Spd,int Max,float Rad)//初期化メソッド
{
Speed=Spd;
MaxCount=Max;
Radius=Rad;
Reality=false;
}

public BulletIgnite(PointF Muzzle,double Angle,int DefaultCount)//発射メソッド
{
ShellAngle=Angle;
AheadX=(float)(Math.Sin(ShellAngle)*Speed);
AheadY=(float)(Math.Cos(ShellAngle)*Speed);
BaseX=Muzzle.X;
BaseY=Muzzle.Y;
Count=DefaultCount;
Reality=true;
}

public BulletMove()//移動メソッド
{
BaseX+=AheadX;
BaseY+=AheadY;
double Length=Math.Sin(Math.PI/(MaxCount/2)*Count);
PosX=BaseX+(float)(Math.Cos(Math.PI/2+ShellAngle)*Length);
PosY=BaseX+(float)(Math.Sin(Math.PI/2+ShellAngle)*Length);
Count++;
if(Count>=MaxCount)
{
Count=0;
}
if(PosX<0||PosX>640||PosY<0||PosY>480)
{
Reality=false;
}
}

public PointF BulletPos
{
get
{
return new PointF(PosX,PosY);
}
}

public bool BulletReality
{
get
{
return Reality;
}
}
}