게임엔진 제 17 강지형충돌처리 이대현교수 한국산업기술대학교게임공학과
학습목차 광선을이용한지형충돌검사와이를이용한지형위의카메라이동구현. 간단한 world editor 의구현 닌자및로봇을지형위에배치, 이동및선택 쿼리마스크 기능 사용법 다중쿼리마스크의사용방법
실습 CamearTerrainMove 지형위를이동하는카메라
구현기능 카메라를이동할때, 지형을통과하는일이일어나지않음.
구현방법 카메라와지형사이의거리를구하여, 그값이아주작으면카메라의위치를재조정한다. 거리를구하는방법 1. 카메라로부터지형까지광선을발사. 2. 광선과지형간의교점을구한다. 3. 카메라와교점사이의거리를구한다.
PlayState.cpp void PlayState::enter() { 중략 실습 mrayscenequery = mscenemgr->createrayquery( Ray() ); 중략 bool PlayState::frameStarted(GameManager* game, const FrameEvent& evt) { 중략 Vector3 campos = mcamera->getposition( ); Ray cameraray( campos, Vector3::NEGATIVE_UNIT_Y ); mrayscenequery->setray( cameraray ); RaySceneQueryResult result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin( ); if ( itr!= result.end() && itr->worldfragment ) { Real terrainheight = itr->worldfragment->singleintersection.y; if ((terrainheight + 10.0f) > campos.y) mcamera->setposition(campos.x, terrainheight+10.0f, campos.z); 중략
bool PlayState::mousePressed(GameManager* game, const OIS::MouseEvent &e, OIS::MouseButtonID id) { if (id == OIS::MB_Right) { CEGUI::MouseCursor::getSingleton().hide(); mrmousedown = true; return true; bool PlayState::mouseMoved(GameManager* game, const OIS::MouseEvent &e) { if (mrmousedown) { mcamera->yaw(degree(-e.state.x.rel)); mcamera->pitch(degree(-e.state.y.rel)); else { CEGUI::System::getSingleton().injectMouseMove(e.state.X.rel, e.state.y.rel); if (e.state.z.rel < 0) { mtransvector = Vector3(0.0f, 0.0f, -1.0f); else if (e.state.z.rel > 0) { mtransvector = Vector3(0.0f, 0.0f, 1.0f); 실습 return true;
실행결과 : 지형위의카메라이동
마우스입력처리 :mousemoved bool PlayState::mouseMoved(GameManager* game, const OIS::MouseEvent &e) { if (mrmousedown) { mcamera->yaw(degree(-e.state.x.rel)); mcamera->pitch(degree(-e.state.y.rel)); else { CEGUI::System::getSingleton().injectMouseMove(e.state.X.rel, e.state.y.rel); if (e.state.z.rel < 0) { mtransvector = Vector3(0.0f, 0.0f, -1.0f); else if (e.state.z.rel > 0) { mtransvector = Vector3(0.0f, 0.0f, 1.0f); return true;
마우스입력처리 :mousepressed, mousereleased bool PlayState::mousePressed(GameManager* game, const OIS::MouseEvent &e, OIS::MouseButtonID id) { if (id == OIS::MB_Right) { CEGUI::MouseCursor::getSingleton().hide(); mrmousedown = true; return true; 오른쪽버튼이눌리면 마우스보기 모드가되므로, 커서를없앤다. bool PlayState::mouseReleased(GameManager* game, const OIS::MouseEvent &e, OIS::MouseButtonID id { if (id == OIS::MB_Right) { CEGUI::MouseCursor::getSingleton().show(); mrmousedown = false; return true; 오른쪽버튼이떼어지면, 마우스보기 모드가해제되므로, 커서를다시표시한다.
광선의설정과발사 Vector3 campos = mcamera->getposition( ); Ray cameraray( campos, Vector3::NEGATIVE_UNIT_Y ); mrayscenequery->setray( cameraray ); RaySceneQueryResult result = mrayscenequery->execute(); 발사할광선을설정. 광선의발사에따른결과들 ( 지형과의교점 ) 을저장하는객체. 광선을발사함.
거리계산과카메라위치조정 RaySceneQueryResult::iterator itr = result.begin( ); 광선발사의결과는반복자객체 itr 에저장됨. 광선발사의결과는 worldfragment( 여기는지형 ) 와 movable 의리스트임. if ( itr!= result.end() && itr->worldfragment ) { Real terrainheight = itr->worldfragment->singleintersection.y; 교점의 y 좌표가결국은지형의높이가된다. if ((terrainheight + 10.0f) > campos.y) mcamera->setposition(campos.x, terrainheight+10.0f, campos.z); 카메라높이가지형의높이가 10 이하이면, 강제로카메라를지형보다 10 만큼높게설정한다.
RaySceneQueryResult 자세히살펴보기 RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin( ); RaySceneQueryResultEntry 의리스트 Real distance; MovableObject* movable; SceneQuery::WorldFragment* worldfragment; distance: 광선시작점으로부터, 교차되는오브젝트까지의거리 movable: MovableObject 엔터티, 광원등과같이장면노드에소속되는개체들. NULL 이면, MovableObject 가아니라는뜻임. worldfragement: 장면관리자에의해생성된 world geometry 의일부분과, 광선이교차된경우. worldframgement 의 type 는 WFT_SINGLE_INTERSECTION 이며 (RaySceneQuery 의결과는항상 ), worldfragment->singleinetersection 은교점을나타내는 Vector3 타입의값임.
실습 PlaceNinja 지형위에닌자들을배치
구현내용 마우스왼쪽버튼을누르면닌자가지형위에만들어진다.
PlayState.cpp 실습 bool PlayState::mousePressed(GameManager* game, const OIS::MouseEvent &e, OIS::MouseButtonID id) { 중략 CEGUI::MouseCursor::getSingleton().hide(); CEGUI::Point mousepos = CEGUI::MouseCursor::getSingleton().getPosition(); Ray mouseray = mcamera->getcameratoviewportray(mousepos.d_x/mwindow->getwidth(), mouse Pos.d_y/mWindow->getHeight()); mrayscenequery->setray(mouseray); RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin( ); if (itr!= result.end() && itr->worldfragment) { char name[16]; sprintf( name, "ninja%d", mcount++ ); Entity *ent = mscenemgr->createentity( name, "ninja.mesh" ); mcurrentobject = mscenemgr->getrootscenenode( )->createchildscenenode( String(name) + "Node", itr->worldfragment->singleintersection ); mcurrentobject->attachobject( ent ); mcurrentobject->setscale( 0.1f, 0.1f, 0.1f ); mlmousedown = true; 중략
실습 bool PlayState::mouseMoved(GameManager* game, const OIS::MouseEvent &e) { 중략 CEGUI::System::getSingleton().injectMouseMove(e.state.X.rel, e.state.y.rel); CEGUI::Point mousepos = CEGUI::MouseCursor::getSingleton().getPosition(); Ray mouseray = mcamera->getcameratoviewportray(mousepos.d_x/mwindow->getwidth( ), mousepos.d_y/mwindow->getheight()); mrayscenequery->setray(mouseray); RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin(); if (itr!= result.end() && itr->worldfragment) mcurrentobject->setposition(itr->worldfragment->singleintersection); 후략
실행결과 왼쪽마우스버튼을누르면, 닌자가만들어지고, 마우스버튼을누른채로이동시키면닌자가이동된다. 마우스버튼을놓는위치에닌자가배치된다.
mousepressed: 마우스클릭위치에닌자를생성하고위치시킴 CEGUI::Point mousepos = CEGUI::MouseCursor::getSingleton().getPosition(); Ray mouseray = mcamera->getcameratoviewportray(mousepos.d_x/mwindow->getwidth(), mousepos.d_y/mwindow->getheight()); mrayscenequery->setray(mouseray); RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin( ); if (itr!= result.end() && itr->worldfragment) { char name[16]; sprintf( name, "ninja%d", mcount++ ); Entity *ent = mscenemgr->createentity( name, "ninja.mesh" ); mcurrentobject = mscenemgr->getrootscenenode( )->createchildscenenode( String(name) + "Node", itr->worldfragment->singleintersection ); mcurrentobject->attachobject( ent ); mcurrentobject->setscale( 0.1f, 0.1f, 0.1f );
mousemoved: 왼쪽마우스버튼을이용한로봇의드래깅 bool PlayState::mouseMoved(GameManager* game, const OIS::MouseEvent &e) { 중략 CEGUI::System::getSingleton().injectMouseMove(e.state.X.rel, e.state.y.rel); CEGUI::Point mousepos = CEGUI::MouseCursor::getSingleton().getPosition(); Ray mouseray = mcamera->getcameratoviewportray(mousepos.d_x/mwindow->getwidth( ), mousepos.d_y/mwindow->getheight()); mrayscenequery->setray(mouseray); RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin(); if (itr!= result.end() && itr->worldfragment) mcurrentobject->setposition(itr->worldfragment->singleintersection); 후략
바운딩박스의표시 bool PlayState::mousePressed(GameManager* game, const OIS::MouseEvent &e, OIS::MouseButtonID id) { 중략 CEGUI::MouseCursor::getSingleton().hide(); CEGUI::Point mousepos = CEGUI::MouseCursor::getSingleton().getPosition(); Ray mouseray = mcamera->getcameratoviewportray(mousepos.d_x/mwindow->getwidth(), mouse Pos.d_y/mWindow->getHeight()); mrayscenequery->setray(mouseray); RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin( ); if (itr!= result.end() && itr->worldfragment) { char name[16]; sprintf( name, "ninja%d", mcount++ ); Entity *ent = mscenemgr->createentity( name, "ninja.mesh" ); mcurrentobject = mscenemgr->getrootscenenode( )->createchildscenenode( String(name) + "Node", itr->worldfragment->singleintersection ); mcurrentobject->attachobject( ent ); mcurrentobject->setscale( 0.1f, 0.1f, 0.1f ); if (mcurrentobject) mcurrentobject->showboundingbox(true); mlmousedown = true; 중략 실습
바운딩박스의해제 bool PlayState::mouseReleased(GameManager* game, const OIS::MouseEvent &e, OIS::MouseButtonID id) { if (id == OIS::MB_Right) { CEGUI::MouseCursor::getSingleton().show(); mrmousedown = false; else if (id == OIS::MB_Left) { CEGUI::MouseCursor::getSingleton().show(); mlmousedown = false; if (mcurrentobject) mcurrentobject->showboundingbox(false); return true; 실습
실습과제 #10 지형위를걷는닌자의구현 화살표키이를이용해닌자를이동시킴 ( 걷기 ) 닌자의발이지형에빠지지않게함.
실습 SelectNinja 지형위의닌자를선택및이동
구현방법 카메라로부터닌자를향해광선을발사해서, 그교차점을구한다.
PlayState.cpp bool PlayState::mousePressed(GameManager* game, const OIS::MouseEvent &e, OIS::MouseButtonID id) { 중략 for ( itr = result.begin(); itr!= result.end(); itr++ ) { if (itr->movable && itr->movable->getname().substr(0, 5)!= "tile[") { mcurrentobject = itr->movable->getparentscenenode(); break; else if (itr->worldfragment) { char name[16]; sprintf( name, "ninja%d", mcount++ ); Entity *ent = mscenemgr->createentity( name, "ninja.mesh" ); 실습 중략 mcurrentobject = mscenemgr->getrootscenenode( )->createchildscenenode( String(name) + "Node", itr->worldfragment->singleintersection ); mcurrentobject->attachobject( ent ); mcurrentobject->setscale( 0.1f, 0.1f, 0.1f ); break;
bool PlayState::mouseMoved(GameManager* game, const OIS::MouseEvent &e) { 중략 mrayscenequery->setsortbydistance( false ); 실습 후략 RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin(); for ( itr = result.begin( ); itr!= result.end(); itr++ ) { if (itr->worldfragment) { mcurrentobject->setposition(itr->worldfragment->singleintersection); break;
실행결과
mousepressed() 이벤트처리 if (id == OIS::MB_Right) { CEGUI::MouseCursor::getSingleton().hide(); mrmousedown = true; else if (id == OIS::MB_Left) { CEGUI::MouseCursor::getSingleton().hide(); CEGUI::Point mousepos = CEGUI::MouseCursor::getSingleton().getPosition(); mcurrentobject = NULL; Ray mouseray = mcamera->getcameratoviewportray(mousepos.d_x/mwindow->getwidth(), mousepos.d_y/mwindow->getheight()); mrayscenequery->setray(mouseray); mrayscenequery->setsortbydistance( true ); 광선을발사한결과, 만들어진교점들을거리순으로정렬하도록설정. 반복자를통해서얻게되는맨처음교점은, 광선의시작점 ( 즉, 카메라 ) 으로부터가장가까운교점이됨.
for ( itr = result.begin( ); itr!= result.end(); itr++) { for 문안에서 itr 를반복함으로써, 광선과교차된오브젝트들을거리가가까운것부터확인해나감. if (itr->movable && itr->movable->getname().substr(0, 5)!= "tile[" ) { 오브젝트가 MovableObject 이지만, 지형오브젝트가아닐경우 결과적으로엔터티일경우에 참고 : 지형장면관리자는지형을여러개의타일로구성된 MovableObject 들을생성해서만든다. 이때오브젝트의이름은 tile[ 로시작된다. mcurrentobject = itr->movable->getparentscenenode( ); break; 엔터티에해당되는노드가얻어지면, 그노드를선택하면되므로, 더이상다른교차되는오브젝트들은처리할필요가없다. else if (itr->worldfragment) {
mousemoved() 이벤트처리 bool PlayState::mouseMoved(GameManager* game, const OIS::MouseEvent &e) { if (mrmousedown) { mcamera->yaw(degree(-e.state.x.rel)); mcamera->pitch(degree(-e.state.y.rel)); else if ( mlmousedown ) { CEGUI::System::getSingleton().injectMouseMove(e.state.X.rel, e.state.y.rel); CEGUI::Point mousepos = CEGUI::MouseCursor::getSingleton().getPosition(); Ray mouseray = mcamera->getcameratoviewportray(mousepos.d_x/mwindow->getwidth(), mousepos.d_y/mwindow->getheight()); mrayscenequery->setray(mouseray); dragged 상태에서는지형상의교점만을찾으면됨. 거리를기준으로정렬하면, 지형이맨뒤로오게되므로, 다음 for 루프에서쓸데없이다른오브젝트들을처리하게된다. 따라서, 거리기준정렬을 false 로설정. RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr; for ( itr = result.begin( ); itr!= result.end(); itr++ ) { if ( itr->worldfragment ) { mcurrentobject->setposition( itr->worldfragment->singleintersection ); break;
엔터티의선택시고려할점 닌자와로보트가카메라의시선에겹쳐있는경우 광선을발사하면, 닌자와로봇중앞에있는것과먼저교차하게된다. 만약, 닌자가앞에있고, 로봇이뒤에있는상황에서, 로봇을선택하려면? 엔터티의이름을차례로살펴보면서, 로봇이름을찾으면된다. 문제점은? 교차되는엔터티가매우많을경우, 특정종류의엔터티를찾으려면, 교차되는모든엔터티를일일이검사해야한다. 결국시간이많이걸린다 성능이떨어진다.
쿼리마스크 (Query Mask) 란? Query Mask 모든 MovableObject 에고유한마스크값을설정 RaySceneQuery 에서, 광선을발사하기전에, 교차점을찾고자하는엔터티들의마스크값을지정. 지정된마스크값을가지는엔터티들에대해서교차점을찾는동작이수행됨.
실습 QueryMask 닌자와로봇을효과적으로선택
PlayState.h class PlayState : public GameState { 중략 실습 enum QueryFlags { NINJA_MASK = 1 << 0, ROBOT_MASK = 1 << 1 ; 중략
PlayState.cpp bool PlayState::mousePressed(GameManager* game, const OIS::MouseEvent &e, OIS::Mouse ButtonID id) { 중략 mrayscenequery->setquerymask(mninjamode? NINJA_MASK : ROBOT_MASK); 중략 ent->setqueryflags(ninja_mask); 중략 ent->setqueryflags(robot_mask); 후략 실습
실행결과 닌자모드에서로봇을클릭하면, 뒤에서있는닌자가선택된다.
마스크값기본값정의 class PlayState : public GameState { 중략 각엔터티마다구별될수있는마스크값을부여. enum QueryFlags { NINJA_MASK = 1 << 0, ROBOT_MASK = 1 << 1 ; 중략
엔티티가생성될때, 마스크값을부여 Entity *ent; char name[16]; if ( mninjamode ){ sprintf( name, "Ninja%d", mcount++ ); ent = mscenemgr->createentity( name, "ninja.mesh" ); ent->setqueryflags( NINJA_MASK ); else { sprintf( name, "Robot%d", mcount++ ); ent = mscenemgr->createentity( name, "robot.mesh" ); ent->setqueryflags( ROBOT_MASK );
광선발사할때, 마스크값을설정 Ray mouseray = mcamera->getcameratoviewportray(mousepos.d_x/mwindow- >getwidth(), mousepos.d_y/mwindow->getheight()); mrayscenequery->setray(mouseray); mrayscenequery->setsortbydistance(true); mrayscenequery-> setquerymask(mninjamode? NINJA_MASK : ROBOT_MASK); 광선을발사하기전에 (execute() 함수실행전에 ), query mask 를현재모드에따라, ROBOT_MASK 또는 NINJA_MASK 로설정한다. RaySceneQueryResult &result = mrayscenequery->execute(); 마스크값으로미리설정된엔터티들에대해서만, 교차점검사가이루어진다. RaySceneQueryResult::iterator itr = result.begin( );
다중쿼리마스크값의정의 각각의쿼리마스크는이진수로표현했을때, 반드시한개의비트 1 만을가져야하며, 그자리수가각각달라야한다. 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000
다중쿼리마스크값의정의 각각의쿼리마스크는이진수로표현했을때, 반드시한개의비트 1 만을가져야하며, 그자리수가각각달라야한다. 최대 32 개의쿼리마스크값을만들수있다. 00000001 = 1<<0 00000010 = 1<<1 00000100 = 1<<2 00001000 = 1<<3 00010000 = 1<<4 00100000 = 1<<5 01000000 = 1<<6 10000000 = 1<<7
다중쿼리마스크의사용예 (1) enum QueryFlags { FRIENDLY_CHARACTERS = 1<<0, ENEMY_CHARACTERS = 1<<1, STATIONARY_OBJECTS = 1<<2 ; FRIENDLY_CHARACTERS 타입의경우에만, RaySceneQuery 의실행. mrayscenequery->setquerymask( FRIENDLY_CHARACTERS ); ENEMY_CHARACTERS 또는 STATIONARY_OBJECTS 의경우에, RaySceneQuery 실행 mrayscenequery->setquerymask(enemy_characters STATIONARY_OBJECTS);
다중쿼리마스크의사용예 (2) FRIENDLY_CHARACTERS 타입이아닌경우에만, RaySceneQuery 의실행. mrayscenequery->setquerymask( ~FRIENDLY_CHARACTERS ) FRIENDLY_CHARACTERS 또는 STATIONARY_OBJECTS 가아닌경우에만 RaySceneQuery 실행 mrayscenequery ->setquerymask(~(friendly_characters STATIONARY_OBJECTS));
다중쿼리마스크의사용예 (3) mrayscenequery->setquerymask( 0 ); 지형과만나는점만을얻고자할때는, MovableObject 와의교점을찾을필요가없다. 0 으로쿼리마스크를설정하면, WorldFragment 오브젝트만을얻어낼수있다. SetQueryMask() Basically MovableObject instances will only be returned from this query if a bitwise AND operation between this mask value and the MovableObject::getQueryFlags value is non-zero.
QueryTypeMask 사용자가정의하는쿼리마스크외에 SceneManager 에서미리정의된또한종류의쿼리마스크로써 QueryTypeMask 가있음. QueryMask 는오브젝트인스턴스하나하나에마스크를부여. QueryTypeMask 는오브젝트그룹에마스크를부여. 모두 6 종류의마스크가있음. 설정된기본값은 ENTITY_TYPE_MASK 사용법 : mrayscenequery->setquerytypemask(scenemanager::fx_type_mask); WORLD_GEOMETRY_TYPE_MASK //Returns world geometry. ENTITY_TYPE_MASK //Returns entities. FX_TYPE_MASK //Returns billboardsets / particle systems. STATICGEOMETRY_TYPE_MASK //Returns static geometry. LIGHT_TYPE_MASK //Returns lights. USER_TYPE_MASK_LIMIT //User type mask limit.