资源下载地址
http://download.csdn.net/detail/zhoupeng39/7204925
一、整体的思路
既然是Kinect开发,首先解决的就是识别的问题,其实网上切水果视频里面的主要就是有后面黑色背影,手部运动形成的水果刀,黑色背影就是深度图像的获取,然后根据它的ID编号,判断人体的部分。移动的水果刀,是利用骨骼图像找出手的位置,然后确定连线,画出水果刀。当然,这些资源的获取在程序里面都是在一个单独的线程里面执行的。
然后才是切水果游戏的开发,关于这个游戏,我没有想太多,其实就是水果的移动,然后连线判断是否经过矩形(程序里面,我只是用一个点来判断),然后后面没有写切成两半的效果,用了一个粒子模型画出切开之后的画面,其中切中之后出现短暂的到刀锋效果,就是一个四边形,然后具体的就是判断水果的类型,然后对应的加分惩罚等等,这些细节的东西我写的比较少,毕竟主要是Kinect的使用。
二. 程序的细节和代码
1.这里直接贴出获取手的骨骼位置的代码,都是些基础的代码,至于深度图像的,看看这个人的博客 http://blog.csdn.net/zouxy09/article/category/1273380
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; }
2.其实整个代码的过程中纠结最久的就是手的坐标的存储,使用队列存储坐标点,限制里面点的个数,在指针的使用上一开始总是出现访问的错误,下面是代码。
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; }
至于画刀的效果,我就简化了很多,直接通过设定线条的宽度变化,实现粗细的变化,当然细节大家可以自己填充。
3.关于水果的移动和简单的粒子系统,当然我只是初步的接触了下游戏的编程,上面的也只是基础的内容,大家可以学习一下大神的博客 http://blog.csdn.net/crocodile__
//增加到下一帧
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.关于最后的静态手势,也是做着玩的,交叉手,就是判断线的角点书不是在四个点之间,还是很好写的。
三.自己的一些感悟
其实之前是学习OpenCV的,但是当中的数学知识真的很难,不是一时半会儿就能搞定的,后来就偷闲搞搞Kinect,现在有不得不接触下C#,花个两三天谢谢这个小游戏,没什么,就是对以前只是的反复的使用,防止自己忘记,当然代码很挫,毕竟都是自己瞎折腾的,非计算机专业,也没什么训练,就这样写写被,希望大家指证代码里面的错误,当然有问题可以交流下。我们是靠着网络资源成长的,总归要给别人留点什么,是不是!!
下面粘出我很敬佩的大神的一篇博客里面的内容,共勉!!!
这句话一直写在我C++笔记本的扉页上。
每当我对前路迷茫的时候,就会翻开扉页,看着这段文字淡淡的笔迹发一会儿呆,然后就渐渐释然了。
今天我把它留在自己的博客里,希望它也能帮助到那些迷茫的朋友们。
总有一天你将破蛹而出,成长得比人们期待的还要美丽。
但这个过程会很痛,会很辛苦,有时候还会觉得灰心。
面对着汹涌而来的现实,觉得自己渺小无力。
但这,也是生命的一部分。做好现在你能做的,然后,一切都会好的。
我们都将孤独地长大,不要害怕。