void Robo::getViewMatrix( Matrix34* vm ) const { //まず正面方向ベクタを作成 Vector3 d( 0.0, 0.0, 1.0 ); Matrix34 m; m.setRotationY( mAngleY ); m.multiply( &d, d ); //こいつを前方にmCameraTargetDistanceZだけ伸ばす Vector3 t; t.setMul( d, mCameraTargetDistanceZ ); //ロボが高いところにいるならちょっと下を見てやる。これはパラメータにないその場工夫。 t.y -= mPosition.y * 0.12; //このへんの調整も適当 //こいつを後方にmCameraDistacneZだけ伸ばす Vector3 p; p.setMul( d, -mCameraDistanceZ ); //YにmCameraDistanceYをプラス p.y += mCameraDistanceY; //ロボが高いところにいるならちょっと高目にして下を見てやる。これはパラメータにないその場工夫。 p.y += mPosition.y * 0.12; //このへんの調整も適当 //ロボ現在位置をプラス t += mPosition; p += mPosition; //ビュー行列作成 vm->setViewTransform( p, t ); }
void Robo::update( const Vector3& enemyPos ){ Pad* pad = Pad::instance(); if ( pad->isOn( Pad::TURN, mId ) ){ if ( pad->isOn( Pad::LEFT, mId ) ){ mAngleY += 1.0; if ( mAngleY > 180.0 ){ //-PIからPIにおさめる mAngleY -= 360.0; } } if ( pad->isOn( Pad::RIGHT, mId ) ){ mAngleY -= 1.0; if ( mAngleY < -180.0 ){ //-PIからPIにおさめる mAngleY += 360.0; } } }else{ //移動処理。まず視点を考慮しない加速度を出す Vector3 move( 0.0, 0.0, 0.0 ); if ( pad->isOn( Pad::UP, mId ) ){ move.z = -1.0; } if ( pad->isOn( Pad::DOWN, mId ) ){ move.z = 1.0; } if ( pad->isOn( Pad::LEFT, mId ) ){ move.x = -1.0; } if ( pad->isOn( Pad::RIGHT, mId ) ){ move.x = 1.0; } //視線方向を加味して回転 Matrix34 m; m.setRotationY( mAngleY + 180.0 ); m.multiply( &move, move ); mPosition += move; } //武器生成 if ( pad->isOn( Pad::FIRE, mId ) ){ //空き武器を探す for ( int i = 0; i < mBulletNumber; ++i ){ if ( mBullets[ i ].isEmpty() ){ mBullets[ i ].create( mDatabase, "bullet", mPosition, 45.0, mAngleY ); break; } } } //武器更新 for ( int i = 0; i < mBulletNumber; ++i ){ if ( !mBullets[ i ].isEmpty() ){ mBullets[ i ].update( enemyPos ); } } }
void Framework::update(){ setFrameRate( 60 ); //元の頂点配列 Vector3 p[ 4 ]; unsigned c[ 4 ]; //わかりやすいように色 p[ 0 ].set( -1000.0, 0.0, -1000.0 ); p[ 1 ].set( -1000.0, 0.0, 1000.0 ); p[ 2 ].set( 1000.0, 0.0, -1000.0 ); p[ 3 ].set( 1000.0, 0.0, 1000.0 ); c[ 0 ] = 0xffff0000; //赤 c[ 1 ] = 0xff00ff00; //緑 c[ 2 ] = 0xff0000ff; //青 c[ 3 ] = 0xffffffff; //白 //ビュー行列を作ろう Matrix34 m; m.setViewTransform( gEyePosition, gEyeTarget ); //この中が本体なのでそっちを参照のこと //行列にベクタをかけて回る for ( int i = 0; i < 4; ++i ){ m.multiply( &p[ i ], p[ i ] ); } //ニアクリップとファークリップ const double nearClip = 1.0; const double farClip = 10000.0; //ニアとファーからZ範囲変換の式を作る const double zConvA = 1.0 / ( nearClip - farClip ); //1/(n-f) const double zConvB = nearClip * zConvA; //n/(n-f) //ハードウェアに渡す準備をする。4次元化 double p4[ 4 ][ 4 ]; for ( int i = 0; i < 4; ++i ){ p4[ i ][ 0 ] = p[ i ].x; //yに640/480をかけて縦横比を調整 p4[ i ][ 1 ] = p[ i ].y * 640.0 / 480.0; //wに範囲変換前のzを「マイナスにして」格納。Z軸が手前向きだとこのマイナスが必要。 p4[ i ][ 3 ] = -p[ i ].z; //z範囲変換 p4[ i ][ 2 ] = zConvA * p[ i ].z + zConvB; //範囲変換もZ軸の向きを //zにwをかけておく。 p4[ i ][ 2 ] *= p4[ i ][ 3 ]; } //四角形を描く。 drawTriangle3DH( p4[ 0 ], p4[ 1 ], p4[ 2 ], 0, 0, 0, c[ 0 ], c[ 1 ], c[ 2 ] ); drawTriangle3DH( p4[ 3 ], p4[ 1 ], p4[ 2 ], 0, 0, 0, c[ 3 ], c[ 1 ], c[ 2 ] ); ++gCount; //視点と注視点を適当にいじる gEyePosition.x = sin( gCount ) * 2000; gEyePosition.z = cos( gCount ) * 2000; gEyePosition.y = 1000.f; gEyeTarget.x = gCount % 100; gEyeTarget.y = gCount % 200; gEyeTarget.z = gCount % 300; }
void Bullet::update( const Vector3& enemyPos ){ //敵の方に向ける。 Vector3 dir; Vector3 p = *mModel->position(); Vector3 a = *mModel->angle(); dir.setSub( enemyPos, *mModel->position() ); //自分から敵へ //Y軸角度はatan2( x, z )。 double ty = atan2( dir.x, dir.z ); //180度以上差があれば+-360度して逆回し if ( ty - a.y > 180.0 ){ ty -= 360.0; }else if ( a.y - ty > 180.0 ){ ty += 360.0; } //X軸角度はY/(X,Z)。 double zxLength = sqrt( dir.x * dir.x + dir.z * dir.z ); double tx = atan2( dir.y, zxLength ); //X軸角度はそもそも範囲が(-90,90)で180度以上離れることはない。そのままで良い。 double hx = mHomingX; //別名 double hy = mHomingY; //ホーミング範囲内ならそのものに if ( tx - a.x < hx && a.x - tx < hx ){ a.x = tx; }else if ( tx < a.x ){ a.x -= hx; }else{ a.x += hx; } if ( ty - a.y < hy && a.y - ty < hy ){ a.y = ty; }else if ( ty < a.y ){ a.y -= hy; }else{ a.y += hy; } //おもろいのでz回転つけとくか a.z += 2.0; //角度更新 mModel->setAngle( a ); //位置はこの方向の回転行列で(0,0,1)を変換して足してやる Vector3 v( 0.0, 0.0, mSpeed ); Matrix34 m; m.setRotationY( a.y ); m.rotateX( -a.x ); m.multiply( &v, v ); p += v; mModel->setPosition( p ); ++mCount; if ( mCount >= mLife ){ mCount = -1; } }
void Batch::draw( const Matrix44& pvm, const Matrix34& wm, const Vector3& lightVector, const Vector3& lightColor, const Vector3& ambient, const Vector3& diffuseColor ) const { Framework f = Framework::instance(); //テクスチャセット if ( mTexture ){ mTexture->set(); }else{ f.setTexture( 0 ); //空のテクスチャ } //ブレンドモードセット f.setBlendMode( mBlendMode ); //ブレンドモードによってZバッファ書き込みのフラグをOn,Off if ( mBlendMode == Framework::BLEND_OPAQUE ){ f.enableDepthWrite( true ); }else{ f.enableDepthWrite( false ); } //ZテストはいつもOn f.enableDepthTest( true ); //頂点をワールドに変換 int vertexNumber = mVertexBuffer->size(); Vector3* wv = new Vector3[ vertexNumber ]; for ( int i = 0;i < vertexNumber; ++i ){ wm.multiply( &wv[ i ], *mVertexBuffer->position( i ) ); } //頂点を最終変換 double* fv = new double[ vertexNumber * 4 ]; //final vertices for ( int i = 0;i < vertexNumber; ++i ){ pvm.multiply( &fv[ i * 4 ], wv[ i ] ); } //法線をワールド座標に変換する。本当はワールド行列ではいけない。 Matrix34 wmNormal = wm; //ワールド行列から移動を抜いたもの wmNormal.m03 = wmNormal.m13 = wmNormal.m23 = 0.0; //前もって頂点単位で色を決めておく unsigned* colors = new unsigned[ vertexNumber ]; for ( int i = 0; i < vertexNumber; ++i ){ Vector3 wm; //world normal wmNormal.multiply( &wm, mNormals[ i ] ); //ワールド座標に変換 wm *= 1.f / wm.length(); //長さを1にして colors[ i ] = light( lightVector, lightColor, ambient, diffuseColor, wm ); } int triangleNumber = mIndexBuffer->size() / 3; for ( int i = 0; i < triangleNumber; ++i ){ unsigned i0 = mIndexBuffer->index( i * 3 + 0 ); unsigned i1 = mIndexBuffer->index( i * 3 + 1 ); unsigned i2 = mIndexBuffer->index( i * 3 + 2 ); f.drawTriangle3DH( &fv[ i0 * 4 ], &fv[ i1 * 4 ], &fv[ i2 * 4 ], &mVertexBuffer->uv( i0 )->x, &mVertexBuffer->uv( i1 )->x, &mVertexBuffer->uv( i2 )->x, colors[ i0 ], colors[ i1 ], colors[ i2 ] ); } SAFE_DELETE_ARRAY( wv ); SAFE_DELETE_ARRAY( fv ); SAFE_DELETE_ARRAY( colors ); }
void Robo::update( Robo* enemy ){ //死んでる if ( mHitPoint <= 0 ){ return; } //角度範囲補正 if ( mAngleY > 180.0 ){ mAngleY -= 360.0; }else if ( mAngleY < -180.0 ){ mAngleY += 360.0; } //AIの思考。プレイヤーの場合は入力を取得して返すだけ bool iJump; bool iFire; bool iTurn; bool iLeft; bool iRight; bool iUp; bool iDown; think( &iJump, &iFire, &iTurn, &iLeft, &iRight, &iUp, &iDown ); //以下もらった入力を使って行動 const Vector3& enemyPos = *enemy->position(); ++mCount; //ジャンプ押されてる? double t; //字句解析に似た書き方をしてみよう。コードの重複が増えるがブロック単位で見ればシンプルになる。 //普通の書き方とどちらが良いか比べてみよう。 switch ( mMode ){ case MODE_JUMP_UP: //カメラが回りきっていないならカメラ回転継続 if ( mCameraCount < mCameraDelayCount ){ mAngleY += mAngleVelocityY; ++mCameraCount; } //上昇 t = mJumpHeight / static_cast< double >( mJumpUpTime ); mVelocity.y = t; if ( !iJump ){ //ジャンプ入力がないので下降に変化 mMode = MODE_JUMP_FALL; mCount = 0; }else if ( mCount >= mJumpUpTime ){ //上昇終了 mMode = MODE_JUMP_STAY; mCount = 0; } mVelocity.x = mVelocity.z = 0.0; //X,Z移動を抹殺 break; case MODE_JUMP_STAY: //カメラが回りきっていないならカメラ回転継続 if ( mCameraCount < mCameraDelayCount ){ mAngleY += mAngleVelocityY; ++mCameraCount; } mVelocity.y = 0.0; if ( !iJump ){ //ジャンプ入力がないので下降に変化 mMode = MODE_JUMP_FALL; mCount = 0; }else if ( mCount >= mJumpStayTime ){ //下降へ mMode = MODE_JUMP_FALL; mCount = 0; } break; case MODE_JUMP_FALL: //カメラが回りきっていないならカメラ回転継続 if ( mCameraCount < mCameraDelayCount ){ mAngleY += mAngleVelocityY; ++mCameraCount; } //下降 t = mJumpHeight / static_cast< double >( mJumpFallTime ); mVelocity.y = -t; //接地判定は最終的には衝突処理でやるのでここではやらない。 break; case MODE_ON_LAND: if ( iJump ){ mMode = MODE_JUMP_UP; mCount = 0; mCameraCount = 0; //敵の方に向ける。 Vector3 dir; dir.setSub( enemyPos, mPosition ); //自分から敵へ //Y軸角度はatan2( x, z )。 t = GameLib::atan2( dir.x, dir.z ); //180度以上差があれば+-360度して逆回し if ( t - mAngleY > 180.0 ){ t -= 360.0; }else if ( mAngleY - t > 180.0 ){ t += 360.0; } mAngleVelocityY = ( t - mAngleY ) / static_cast< double >( mCameraDelayCount ); }else if ( iTurn ){ turn( iLeft, iRight ); //コードが長くなるので関数に飛ばす }else{ move( iLeft, iRight, iUp, iDown ); //コードが長くなるので関数に飛ばす } mVelocity.y = 0.0; break; } //ここから下は衝突処理が入るとその後になる。 mPosition += mVelocity; if ( mPosition.y < 0.0 ){ mPosition.y = 0.0; mMode = MODE_ON_LAND; } //武器生成 if ( iFire ){ //上昇、下降中は撃てない if ( ( mMode != MODE_JUMP_FALL ) && ( mMode != MODE_JUMP_UP ) ){ //エネルギー足りてる? if ( mEnergy >= mEnergyPerBullet ){ //空き武器を探す for ( int i = 0; i < mBulletNumber; ++i ){ if ( mBullets[ i ].isEmpty() ){ unsigned c = ( mId == 0 ) ? 0xcc0088ff : 0xccff8800; mBullets[ i ].create( mDatabase, "bullet", mPosition, 15.0, mAngleY, mLockOn, c ); mEnergy -= mEnergyPerBullet; break; } } } } } //武器更新 for ( int i = 0; i < mBulletNumber; ++i ){ if ( !mBullets[ i ].isEmpty() ){ mBullets[ i ].update( enemyPos ); //衝突処理 Vector3 t; t.setSub( *mBullets[ i ].position(), enemyPos ); if ( t.squareLength() < 4.0 ){ enemy->setDamage( 1 ); //1点減らしてみた。 mBullets[ i ].die(); //弾消えます。 } } } //武器エネルギーチャージ mEnergy += mEnergyCharge; if ( mEnergy > mMaxEnergy ){ mEnergy = mMaxEnergy; } //ロックオン処理 //まずは角度を測ってみよう。 //角度は何で測るか?そう、内積だ。 Vector3 toEnemy; toEnemy.setSub( enemyPos, mPosition ); Vector3 myDir( 0.0, 0.0, -1.0 ); Matrix34 m; m.setRotationY( mAngleY + 180.0 ); m.multiply( &myDir, myDir ); toEnemy *= 1.0 / toEnemy.length(); //長さを1に double dotProduct = toEnemy.dot( myDir ); //角度に直すと、 double angle = GameLib::acos( dotProduct ); if ( mLockOn ){ //ロックオンしてるなら外れるかどうか調べる if ( angle > mLockOnAngleOut ){ mLockOn = false; } }else{ //入るかどうか調べる if ( angle < mLockOnAngleIn ){ mLockOn = true; } } }
void Robo::move( bool left, bool right, bool up, bool down ){ //移動処理。まず視点を考慮しない加速度を出す Vector3 move( 0.0, 0.0, 0.0 ); if ( up ){ move.z = -1.0; } if ( down ){ move.z = 1.0; } if ( left ){ move.x = -1.0; } if ( right ){ move.x = 1.0; } //視線方向を加味して回転 Matrix34 m; m.setRotationY( mAngleY + 180.0 ); m.multiply( &move, move ); //今止まっているなら話は早い。適当に加速してやるだけだ if ( mVelocity.x == 0.0 && mVelocity.z == 0.0 ){ //加速にかかる時間で最大速度を割れば1フレームあたりの加速度が出る。 double accel = mMaxMoveSpeed / static_cast< double >( mMoveAccelEndCount ); mVelocity.setMul( move, accel ); }else{ //すでに動いている場合 if ( move.x == 0.0 && move.z == 0.0 ){ //移動がゼロ mVelocity.set( 0.0, 0.0, 0.0 ); //移動はとまる。 }else{ //すでに動いている場合かなり面倒である。 //45度だけ方向転換した時にゼロから加速しなおしというのはストレスだ。 //だから、「今の速度と方向が合わない成分だけをゼロからやり直し」にする。 //90度以上のターンなら一旦速度を0にする。 //慣性が働く方がいいゲームもあるが、きびきび動かしたいなら慣性は邪魔だろう。 //90度以上のターンなら現速度と加速の内積がマイナスのはずだ double dp = mVelocity.dot( move ); if ( dp <= 0.0 ){ mVelocity.set( 0.0, 0.0, 0.0 ); }else{ //90度未満 //現在の移動速度と水平な成分のみ取り出す //水平成分は、移動方向単位ベクタとの内積を、移動方向単位ベクタにかければいい。 //移動単位ベクタE、現速度ベクタVとして、新しい速度ベクタV'すなわち平行成分は //V' = dot(V,E) * E //この時、Eは移動ベクタMを使ってE=M/|M|と書けるから、 //V' = dot(V,M) * M / ( |M|^2 ) //書け、単位ベクタを作る際の平方根を除ける。|M|より|M|^2の方が計算は速いのだ。 double moveSquareLength = move.x * move.x + move.z * move.z; double dp = mVelocity.dot( move ); mVelocity.setMul( move, dp / moveSquareLength ); } //加速を加える。 //移動速度は最大速度/加速時間である。 double accel = mMaxMoveSpeed / static_cast< double >( mMoveAccelEndCount ); mVelocity.madd( move, accel ); //最大速度でストップ double speed = mVelocity.length(); if ( speed > mMaxMoveSpeed ){ mVelocity *= mMaxMoveSpeed / speed; } } } }