一、整体的思路
既然是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.关于最后的静态手势,也是做着玩的,交叉手,就是判断线的角点书不是在四个点之间,还是很好写的。