Microsoft Visual Studio .NET 2003 NVIDIA PhysX SDK v2.8.3 Microsoft DirectX 9.0 SDK (December 2004) |
■PhysXで衝突イベント通知 | Prev Top Next |
関連ページ:PhysX リファレンス |
ちょっと書籍の紹介。「物理エンジン PhysX プログラミング」ってやつ。 今、参考にしてます。あんまり詳しいところまで解説はしてないですが、自分のような初心者にはいい本だと思います。
では本題。ゲーム作成するときは、モデル同士が衝突した瞬間を把握する必要があります。 例えばプレイヤーが撃った弾が敵にヒットしたとき効果音を出すようなプログラムがあったとします。 この場合、弾が敵にヒットした瞬間がわからなければ効果音を鳴らすタイミングがわかりません。 今回はこれに対応するために衝突イベントをPhysX側から通知してもらうためのプログラムを作成します。
実際には三角ポリゴンメッシュと地面が衝突したときだけ、画面上に文字を表示するプログラムを作成します。
詳細はソースを見ながら解説していきます。
---PhysX.h---
// アクターで使用するユーザーデータの構造体 typedef struct _USERDATA { NxU32 ContactPairFlag; }USERDATA; class ContactCallBack : public NxUserContactReport { void onContactNotify( NxContactPair& pair, NxU32 events ); };イベントを通知するためのクラスをヘッダファイルで定義します。 このクラスは NxUserContactReport クラスを継承しています。 NxUserContactReport クラスは仮想クラスで、 onContactNotify メソッドはオーバーライドしたものです。 ですので、onContactNotify メソッド名やこのメソッドのパラメータは必ず同じにしてください。
USERDATA 構造体についてはのちほど
---PhysX.cpp---
void ContactCallBack::onContactNotify( NxContactPair& pair, NxU32 events ) { // アクターで使用するユーザーデータ USERDATA* pUserData1 = NULL; USERDATA* pUserData2 = NULL; switch( events ) { // 衝突開始イベント通知 case NX_NOTIFY_ON_START_TOUCH: // 衝突対象の2つのアクターのユーザーデータのポインタを取得する。 pUserData1 = (USERDATA*)pair.actors[0]->userData; pUserData2 = (USERDATA*)pair.actors[1]->userData; // ユーザーデータにイベント情報を設定する pUserData1->ContactPairFlag = events; pUserData2->ContactPairFlag = events; break; // 衝突終了イベント通知 case NX_NOTIFY_ON_END_TOUCH: // 衝突対象の2つのアクターのユーザーデータのポインタを取得する。 pUserData1 = (USERDATA*)pair.actors[0]->userData; pUserData2 = (USERDATA*)pair.actors[1]->userData; // ユーザーデータを 0 に初期化する pUserData1->ContactPairFlag = 0; pUserData2->ContactPairFlag = 0; break; } }イベントを通知するためのクラスの本体です。 今回のプログラムは PhysX 側で管理するアクター同士で衝突した瞬間、または離れた瞬間を 通知してもらうようにします。でそのイベント情報を NxActor::userData 変数に 格納しています。この変数はあとで使用します。
---Main.cpp---
//Direct3D static LPDIRECT3D9 m_pdirect3d9 = NULL; static LPDIRECT3DDEVICE9 m_pd3dDevice = NULL; static D3DPRESENT_PARAMETERS m_d3dParameters; //PhysX static NxPhysicsSDK* m_pNxPhysicsSDK = NULL; static NxScene* m_pNxScene = NULL; // PhysXデバッグ用クラス static DEBUG_RENDERER* m_pDebugRenderer = NULL; // 地面モデル static LPD3DXMESH m_pPlaneMesh = NULL; static TCHAR* m_pPlaneMeshName = _T("Plane"); static NxActor* m_pActorPlane = NULL; static USERDATA m_UserDataPlane; // 三角メッシュモデル static LPD3DXMESH m_pTriangleMesh = NULL; static TCHAR* m_pTriangleMeshName = _T("TriangleMesh"); static NxActor* m_pActorTriangleMesh = NULL; static USERDATA m_UserDataTriangleMesh; static UINT nWidth = 1024; //ウィンドウの横幅 static UINT nHeight = 768; //ウィンドウの縦幅 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmpLine*/, INT /*nCmdShow*/) { HRESULT hr; MSG msg; ZeroMemory( &msg, sizeof(MSG) ); HWND hWnd = NULL; BOOL Windowed = TRUE; TCHAR* AppName = _T("PhysX Tutrial 01"); //**************************************************************************** // ウィンドウ作成 //**************************************************************************** // 省略。。。 //**************************************************************************** // Direct3Dの初期化 //**************************************************************************** // 省略。。。 //**************************************************************************** // 3Dメッシュをロードする //**************************************************************************** D3DXLoadMeshFromX( _T("Plane.x"), D3DXMESH_MANAGED, m_pd3dDevice, NULL, NULL, NULL, NULL, &m_pPlaneMesh ); D3DXLoadMeshFromX( _T("Triangle.x"), D3DXMESH_MANAGED, m_pd3dDevice, NULL, NULL, NULL, NULL, &m_pMesh ); //固定機能パイプラインライティングを設定する D3DLIGHT9 Light; ZeroMemory(&Light, sizeof(D3DLIGHT9)); Light.Type = D3DLIGHT_DIRECTIONAL; Light.Direction = D3DXVECTOR3( -1.0f, -1.0f, 1.0f ); Light.Ambient = D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f ); Light.Diffuse = D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f ); Light.Specular = D3DXCOLOR( 0.0f, 0.0f, 0.0f, 0.0f ); m_pd3dDevice->SetLight(0, &Light); m_pd3dDevice->LightEnable(0, TRUE); //**************************************************************************** // PhysXの初期化 //**************************************************************************** // この関数については「PhysXをとりあえず使ってみる」ページを参照すること hr = InitNxPhysX( &m_pNxPhysicsSDK, &m_pNxScene, &m_pDebugRenderer ); if( FAILED( hr ) ) { ::DestroyWindow( hWnd ); return 0; } // マテリアル関連 NxMaterial * defaultMaterial = m_pNxScene->getMaterialFromIndex(0); defaultMaterial->setRestitution(0.5f); // 反発係数 defaultMaterial->setDynamicFriction(0.1f); // 動摩擦係数 defaultMaterial->setStaticFriction(0.1f); // 静止摩擦係数 NxActorDesc actorDesc; //**************************************************************************** // 静的な衝突判定用オブジェクトを作成する //**************************************************************************** // 初期化 actorDesc.setToDefault(); // 地面のアクターを作成する NxPlaneShapeDesc planeDesc; // 初期化 planeDesc.setToDefault(); // 原点からの距離. 法線ベクトルと一緒に設定し、地面の位置を決定する planeDesc.d = -5.0f; // アクターにシェイプを追加 actorDesc.shapes.pushBack(&planeDesc); //アクターに名称を設定する actorDesc.name = m_pPlaneMeshName; //ユーザーデータを設定する m_UserDataPlane.ContactPairFlag = 0; actorDesc.userData = &m_UserDataPlane; // シーンにアクターを追加 m_pActorPlane = m_pNxScene->createActor(actorDesc); //**************************************************************************** // 動的な衝突判定用オブジェクトを作成する //**************************************************************************** // 初期化 actorDesc.setToDefault(); actorDesc.density = 1.0f; // 質量密度 // 三角ポリゴンメッシュを作成する // この関数については「PhysXで三角ポリゴンメッシュを使用してみる」ページを参照すること NxTriangleMeshShapeDesc shapeDesc = CreateTriangleMesh( m_pNxPhysicsSDK, m_pTriangleMesh ); // ローカル座標系上でのシェイプの位置 shapeDesc.localPose.t = NxVec3( 0, 0, 0 ); // アクターにシェイプを追加 actorDesc.shapes.pushBack(&shapeDesc); // アクターに名称を設定する actorDesc.name = m_pTriangleMeshName; // ユーザーデータを設定する m_UserDataTriangleMesh.ContactPairFlag = 0; actorDesc.userData = &m_UserDataTriangleMesh; // 動的な衝突判定用モデルの設定 NxBodyDesc bodyDesc; // 初期化 bodyDesc.setToDefault(); // 回転による減衰係数 bodyDesc.angularDamping = 0.5f; // ボディに設定する. なおNxScene::createActorをコールした時点でコピーされるっぽいので NxBodyDesc の内容を保持しておく必要はない actorDesc.body = &bodyDesc; // シーン上の位置 actorDesc.globalPose.t = NxVec3(0.0f, 20.0f, 50.0f); // シーンにアクターを追加 m_pActorTriangleMesh = m_pNxScene->createActor(actorDesc); // NxSceneにイベント追加 m_pNxScene->setUserContactReport( new ContactCallBack() ); // 2つのアクターを関連づけて衝突開始と衝突終了イベントを有効にする m_pNxScene->setActorPairFlags( *m_pActorPlane, *m_pActorTriangleMesh, NX_NOTIFY_ON_START_TOUCH | NX_NOTIFY_ON_END_TOUCH ); ::ShowWindow(hWnd, SW_SHOW); ::UpdateWindow(hWnd); //**************************************************************************** // メッセージループ //**************************************************************************** do { if( ::PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } else { if( MainLoop(hWnd) == FALSE ) ::DestroyWindow( hWnd ); } }while( msg.message != WM_QUIT ); ::UnregisterClass( AppName, hInstance ); return msg.wParam; } LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, UINT wParam, LONG lParam ) { switch (msg) { case WM_KEYDOWN: //U キーを押すと三角ポリゴンメッシュに対し、上方向に力を加える if( wParam == 0x55 ) m_pActorTriangleMesh->addForce( NxVec3( 0, 10000, 0 ) ); break; case WM_KEYUP: if( wParam == VK_ESCAPE ) ::DestroyWindow( hWnd ); break; // 終了処理 case WM_DESTROY: //PhysX SafeDelete( m_pDebugRenderer ); if( m_pNxPhysicsSDK != NULL ) { if( m_pNxScene != NULL ) { NxU32 actorCount = m_pNxScene->getNbActors(); for( NxU32 i=0; i<actorCount; i++ ) { NxActor* actor = m_pNxScene->getActors()[i]; m_pNxScene->releaseActor( *actor ); } m_pNxPhysicsSDK->releaseScene( *m_pNxScene ); m_pNxScene = NULL; } NxReleasePhysicsSDK( m_pNxPhysicsSDK ); m_pNxPhysicsSDK = NULL; } //Direct3D SafeRelease( m_pTriangleMesh ); SafeRelease( m_pPlaneMesh ); SafeRelease( m_pd3dDevice ); SafeRelease( m_pdirect3d9 ); ::PostQuitMessage(0); break; default: return ::DefWindowProc( hWnd, msg, wParam, lParam ); } return 0L; } BOOL MainLoop( HWND hWnd ) { D3DXMATRIX matWVP, matWorld, matView, matProj; //ワールド行列 D3DXMatrixIdentity( &matWorld ); m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); //ビュー行列 D3DXMatrixTranslation( &matView, 0.0f, 0.0f, 0.0f ); m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ); //遠近射影行列 //クリップ面はアプリケーションごとに調整すること D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4.0f, 4.0f / 3.0f, 0.01f, 150.0f ); m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj ); if( SUCCEEDED( m_pd3dDevice->BeginScene() ) ) { m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x0, 1.0f, 0L ); //**************************************************************************** // 3Dメッシュをレンダリング //**************************************************************************** D3DMATERIAL9 Material; NxU32 ContactPairFlag = 0; // アクター数を取得 NxU32 actorCount = m_pNxScene->getNbActors(); for( NxU32 i=0; i<actorCount; i++ ) { //シーンに登録されているアクターのポインタを取得する NxActor* actor = m_pNxScene->getActors()[i]; if( !ContactPairFlag ) { // ユーザーデータを取得する USERDATA* UserData = (USERDATA*)actor->userData; ContactPairFlag = UserData->ContactPairFlag; } //アクターに登録されているシェイプ数を取得 NxU32 shapeCount = actor->getNbShapes(); //アクターの名称を取得する const TCHAR* name = actor->getName(); if( name != NULL ) { // 三角ポリゴンメッシュ if( _tcscmp( name, m_pTriangleMeshName ) == 0 ) { for( NxU32 j=0; j<shapeCount; j++ ) { NxShape* shape = actor->getShapes()[j]; //DirectXと異なりPhysXは右手座標系であることに注意する。 NxF32 m[16]; //単位行列 ZeroMemory( m, sizeof( D3DXMATRIXA16 ) ); m[0] = m[5] = m[10] = m[15] = 1.0f; actor->getGlobalPose().getColumnMajor44( m ); CopyMemory( &matWorld, m, sizeof( D3DXMATRIXA16 ) ); m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); //マテリアル設定 ZeroMemory( &Material, sizeof( Material ) ); Material.Ambient.r = 0.1f; Material.Ambient.g = 0.1f; Material.Ambient.b = 0.1f; Material.Ambient.a = 1.0f; Material.Diffuse.r = 0.0f; Material.Diffuse.g = 0.0f; Material.Diffuse.b = 1.0f; Material.Diffuse.a = 1.0f; m_pd3dDevice->SetMaterial( &Material ); //動的モデルをレンダリングする switch( shape->getType() ) { case NX_SHAPE_MESH: m_pTriangleMesh->DrawSubset(0); break; } } } //地面メッシュ else if( _tcscmp( name, m_pPlaneMeshName ) == 0 ) { for( NxU32 j=0; j<shapeCount; j++ ) { NxShape* shape = actor->getShapes()[j]; D3DXMatrixIdentity( &matWorld ); //平行移動情報を取得し、Direct3D側に設定する NxPlane plane = shape->isPlane()->getPlane(); matWorld._41 = 0.0f; matWorld._42 = plane.d; matWorld._43 = 0.0f; m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); //マテリアル設定 ZeroMemory( &Material, sizeof( Material ) ); Material.Ambient.r = 0.1f; Material.Ambient.g = 0.1f; Material.Ambient.b = 0.1f; Material.Ambient.a = 1.0f; Material.Diffuse.r = 0.2f; Material.Diffuse.g = 1.0f; Material.Diffuse.b = 0.0f; Material.Diffuse.a = 1.0f; m_pd3dDevice->SetMaterial( &Material ); //Planeメッシュをレンダリングする m_pPlaneMesh->DrawSubset(0); } } } } //**************************************************************************** // デバッグ用のワイヤーフレームを描画 //**************************************************************************** if(m_pDebugRenderer) m_pDebugRenderer->RenderData( m_pd3dDevice, m_pNxScene->getDebugRenderable() ); if( ContactPairFlag == NX_NOTIFY_ON_START_TOUCH ) { // この辺で適当にテキストをレンダリング } m_pd3dDevice->EndScene(); // 1FPSの時間を指定 // 遅延が発生した場合を考慮しないとだめだろう m_pNxScene->simulate( 1.0f / 60.0f ); //よくわからんが下記の2関数をコールして実際に時間を進めるらしい m_pNxScene->flushStream(); m_pNxScene->fetchResults( NX_RIGID_BODY_FINISHED, true ); } m_pd3dDevice->Present( NULL, NULL, NULL, NULL ); return TRUE; }ポイントは NxScene::setUserContactReport メソッドと NxScene::setActorPairFlags です。
こんな感じで衝突イベント処理は実装していくわけですが、多対多の場合どうするんだろ。