一、整体的思路

既然是Kinect开发,首先解决的就是识别的问题,其实网上切水果视频里面的主要就是有后面黑色背影,手部运动形成的水果刀,黑色背影就是深度图像的获取,然后根据它的ID编号,判断人体的部分。移动的水果刀,是利用骨骼图像找出手的位置,然后确定连线,画出水果刀。当然,这些资源的获取在程序里面都是在一个单独的线程里面执行的。

   然后才是切水果游戏的开发,关于这个游戏,我没有想太多,其实就是水果的移动,然后连线判断是否经过矩形(程序里面,我只是用一个点来判断),然后后面没有写切成两半的效果,用了一个粒子模型画出切开之后的画面,其中切中之后出现短暂的到刀锋效果,就是一个四边形,然后具体的就是判断水果的类型,然后对应的加分惩罚等等,这些细节的东西我写的比较少,毕竟主要是Kinect的使用。

 二. 程序的细节和代码

 

int GetHandsPoints()
{   
    HANDLE skeletonEvent=CreateEvent(NULL,true,false,NULL);
    HRESULT hr=NuiInitialize(NUI_INITIALIZE_FLAG_USES_SKELETON);
    if (FAILED(hr))
    {
        MessageBox(AfxGetMainWnd()->m_hWnd,"初始化失败","错误",MB_OK);
        return -1;
    }
    //骨骼数据
    hr=NuiSkeletonTrackingEnable(skeletonEvent,0);
    if (FAILED(hr))
    {   
        MessageBox(AfxGetMainWnd()->m_hWnd,"打开骨骼失败","错误",MB_OK);
        NuiShutdown();
        return -1;
    }
    while(1)
    {   
       if (WaitForSingleObject(skeletonEvent,INFINITE)==0)
       {
         NUI_SKELETON_FRAME skeletonFrame = {0};  
         bool bFoundSkeleton = false;    
         if(NuiSkeletonGetNextFrame( 0, &skeletonFrame ) == S_OK )  
         {
           if( skeletonFrame.SkeletonData[0].eTrackingState == NUI_SKELETON_TRACKED )      
                  bFoundSkeleton = true;     
         }
        if (bFoundSkeleton)
       {
         NuiTransformSmooth(&skeletonFrame, NULL);
         float fx,fy; 
         if( skeletonFrame.SkeletonData[0].eTrackingState == NUI_SKELETON_TRACKED &&     
                  skeletonFrame.SkeletonData[0].eSkeletonPositionTrackingState[NUI_SKELETON_POSITION_SHOULDER_CENTER] != NUI_SKELETON_POSITION_NOT_TRACKED)  
            { 
                //左手
                if (skeletonFrame.SkeletonData[0].eSkeletonPositionTrackingState[NUI_SKELETON_POSITION_HAND_LEFT] != NUI_SKELETON_POSITION_NOT_TRACKED)
                      {
                      NuiTransformSkeletonToDepthImage(skeletonFrame.SkeletonData[0].SkeletonPositions[NUI_SKELETON_POSITION_HAND_LEFT],&fx,&fy,NUI_IMAGE_RESOLUTION_640x480);
                      HandsPoints[0].x=(int)fx;
                      HandsPoints[0].y=(int)fy; 
                      if (IsRunGame)
                      {
                      IsEndofXC1=FALSE;
//  利用队列存储坐标点
                      m_pt1.AddPt(HandsPoints[0],IsEndofXC1);
                      }
                      else
                      {
                         PanDuan(HandsPoints[0]);
                      }
                      }
                // 右手
                if (skeletonFrame.SkeletonData[0].eSkeletonPositionTrackingState[NUI_SKELETON_POSITION_HAND_RIGHT] != NUI_SKELETON_POSITION_NOT_TRACKED)
                      {
                      NuiTransformSkeletonToDepthImage(skeletonFrame.SkeletonData[0].SkeletonPositions[NUI_SKELETON_POSITION_HAND_RIGHT],&fx,&fy,NUI_IMAGE_RESOLUTION_640x480);
                      HandsPoints[1].x=(int)fx;
                      HandsPoints[1].y=(int)fy; 
                      if (IsRunGame)
                      {
                      IsEndofXC2=FALSE;
                      m_pt2.AddPt(HandsPoints[1],IsEndofXC2);
                      }
                      else
                       { 
                          PanDuan(HandsPoints[1]);
                          AfxGetMainWnd()->Invalidate(FALSE);
                       }
                      }
//结束时叉行手势的识别 
                  GetElbowPoints(skeletonFrame);
           }   
    }
    }
    }
    NuiShutdown();
    return 0;
}

 

 

  

void CLinePt::AddPt(CPoint point,BOOL& IsEnd)
{
    NewPoint* tmpPoint;
    tmpPoint=(NewPoint*)malloc(sizeof(NewPoint));
    tmpPoint->pt=point;
    tmpPoint->next=NULL;
    if (TotalNum<=Max_Point) TotalNum++;
    if (TotalNum==1)
    {
        Header=tmpPoint;
        End=tmpPoint;
    }
    else
    {
      if (TotalNum>Max_Point)
      {  
          //这里本来是要释放头结点的资源,但是Delete之后一直出现访问错误
          Header=Header->next;
          End->next=tmpPoint;
          //更新尾节点
          End=tmpPoint;
          TotalNum=Max_Point;
       }
      else
      {
          End->next=tmpPoint;
          End=tmpPoint;
      }
    }
    IsEnd=TRUE;
}

 

   至于画刀的效果,我就简化了很多,直接通过设定线条的宽度变化,实现粗细的变化,当然细节大家可以自己填充。

 

 

//增加到下一帧
void CGoods::AddFrame()
{
    Current_Frame++;
    if (Current_Frame>14)
        Current_Frame=0;
}

void CGoods::Move()
{ 
  Current_X+=XSpeed;
  Current_Y+=YSpeed;
  if (Current_X>=Width||Current_X<=(-m_goodsImg.GetWidth())||(Current_Y>=Height&&YSpeed>0)) IsExist=FALSE;
  if (IsExist)
  Current_Rect.SetRect(Current_X,Current_Y,Current_X+m_goodsImg.GetWidth(),Current_Y+m_goodsImg.GetHeight()/15);
}

//改变速度 正负变化
void CGoods::ChangeSpeed()
{
  if (YSpeed<0)
  {
      YSpeed+=4;
      if (YSpeed>=0) YSpeed=0;
  }
  else
  {
      YSpeed+=4;
  }
}
//画图
void CGoods::Draw(CDC* pDC)
{    
    if (IsExist)
    {  
        CRect srcRect;
        srcRect.SetRect(0,Current_Frame*m_goodsImg.GetHeight()/15,m_goodsImg.GetWidth(),(Current_Frame+1)*m_goodsImg.GetHeight()/15);
        m_goodsImg.TransparentBlt(pDC->m_hDC,Current_Rect,srcRect,RGB(255,0,255));
    }
    else
    { 
      if (IsCut)
      { 
        if (!IsShowKnife)
        {    
            //画刀锋的效果
            GetKnifePoints();
            CPen tmp(PS_SOLID,3,RGB(0,255,0));
            CPen* oldPen=pDC->SelectObject(&tmp);
            pDC->Polygon(pt,4);
            pDC->SelectObject(oldPen);
            IsShowKnife=TRUE;
        }
//开启粒子的效果
        if (!GoodsLizi.IsStart)
        {
            GoodsLizi.InitBall(Current_X+m_goodsImg.GetWidth()/2,Current_Y+m_goodsImg.GetHeight()/30);
            GoodsLizi.IsStart=TRUE;
            GoodsLizi.IsEnd=FALSE;

            //建立定时器
            AfxBeginThread(PlayCutMusic,NULL);
            AfxGetMainWnd()->SetTimer(GOODS_LIZI_CHANGE,30,NULL);
        }
        if (!GoodsLizi.IsEnd)
        GoodsLizi.DrawBall(pDC);
      }
    }
}

 

至于粒子的效果,其实也很简单,就是从同一个点分别坐标轴的四个象限生成不同的速度,然后变化移动直至消失,具体的可以看我里面的代码

void CLizi::MoveBall()
{
  if(BallsCount>0) 
  {
    for(int i=0;i<30;i++)
    {
      if (Balls[i].IsExist)
      {
          Balls[i].x+=Balls[i].cx;
          Balls[i].y+=Balls[i].cy;
          Balls[i].lasted++;
          Balls[i].CurrentTh-=1;
          if (Balls[i].CurrentTh<=0) Balls[i].CurrentTh=1;
          if (Balls[i].lasted>20||Balls[i].x<-10||Balls[i].x>(Width+10)||Balls[i].y<-10||Balls[i].y>(Height+10))
          {
              Balls[i].IsExist=FALSE;
              BallsCount--;
          }
      }
    }
  }
  else
      if (IsStart&&!IsEnd) IsEnd=TRUE;
}

void CLizi::DrawBall(CDC* pDC)
{
     for(int i=0;i<30;i++)
     {
         if (Balls[i].IsExist)
         {   
             m_lizi.TransparentBlt(pDC->m_hDC,Balls[i].x,Balls[i].y,Balls[i].CurrentTh,Balls[i].CurrentTh,RGB(255,0,255));
         }
     }
}

 

    3.其实上面也就差不多了,至于其中的什么碰撞检测,时间定时器的设置,都不是很难,对于游戏现在我感触最深的就是并发的重要性,就是多线程的问题,如何同步,在我的代码里用的很浅,我自己用的也不是很熟。

    4.至于开始界面填充球的效果,首先要说悬浮的手的效果,微软称之为磁性移动的手,它相当于给人一个直观的移动的提示,我临时写的,只是模仿一下,用此来检测手的骨骼的获取,然后填充球的效果,我本来想的就是通过这个来动态的调整人的位置,不过C++的资料太少,关于Kinect基本都是C#的资料,有很多内容获取不到,下面是这一部分的代码

double ridus=cirCleRect.Width()/2.0;
  int centerx=cirCleRect.left+ridus;
  int centery=cirCleRect.top+ridus;
  if (Angle>0)
  {
   CRgn testRgn;
   HRGN tRgn;
   pDC->BeginPath();
   pDC->SetBkMode(TRANSPARENT);
   double angle=Angle/180.0*PI;
   int x1=centerx-ridus*sin(angle);
   int y1=centery+ridus*cos(angle);
   int x2=centerx+ridus*sin(angle);
   int y2=centery+ridus*cos(angle);
   pDC->MoveTo(CPoint(x1,y1));
   pDC->LineTo(CPoint(x2,y2));
   pDC->Arc(cirCleRect,CPoint(x1,y1),CPoint(x2,y2));
   pDC->EndPath();
   tRgn=::PathToRegion(pDC->m_hDC);
   testRgn.Attach(tRgn);
   CBrush* red=new CBrush();
   red->CreateSolidBrush(RGB(255,0,0));
   pDC->FillRgn(&testRgn,red);
   int progress=Angle/180.0*100.0;
   CString tmpp;
   char c='%';
   tmpp.Format("%d %c",progress,c);
   pDC->SetBkMode(TRANSPARENT);
   pDC->SetTextColor(RGB(0,255,0));
   pDC->TextOut(centerx-50,centery-40,tmpp);

 

    5.关于最后的静态手势,也是做着玩的,交叉手,就是判断线的角点书不是在四个点之间,还是很好写的。