这个效果起初是在一篇blog看见的,效果很赞,是网页版的,然后想在Cocos2d上实现下,然后就去做了。
blog地址:http://huangwei.pro/2015-08/game-sight-light/
我的github:https://github.com/pepsigit/Cocos2d/tree/master/2DLighgt
首先,在 .h 里面定义一下需要的结构体:
typedef struct LIGHTLINE { Point segA; // 光线的起始点 Point segB; // 光线的终止点 float t1; }LightLine;
typedefstruct POINTANDANGLE { Point point; // 光源打在墙上的点 float angle; // 相对于光源的角度 }PointAndAngle;
是的,我们只需要这些。再来 .cpp 里面,现在初始化下多边形的顶点:
boolHelloWorld::InitSegments() { int subX =60; int subY =0; LightLine tmp; // Border tmp.segA = Vec2( origin.x,origin.y); tmp.segB = Vec2( origin.x,origin.y +visibleSize.height ); m_segments.push_back(tmp); tmp.segA = Point( origin.x,origin.y ); tmp.segB = Point( origin.x +visibleSize.width,origin.y ); m_segments.push_back(tmp); tmp.segA = Point( origin.x,origin.y +visibleSize.height ); tmp.segB = Point( origin.x +visibleSize.width,origin.y + visibleSize.height ); m_segments.push_back(tmp); tmp.segA = Point( origin.x +visibleSize.width,origin.y + visibleSize.height ); m_segments.push_back(tmp); // polygon #1 tmp.segA =Point(100-subX,150-subY ); tmp.segB =Point(120-subX,50-subY ); m_segments.push_back(tmp); tmp.segA =Point(120-subX,50-subY ); tmp.segB =Point(200-subX,80-subY ); m_segments.push_back(tmp); tmp.segA =Point(200-subX,80-subY ); tmp.segB =Point(140-subX,210-subY ); m_segments.push_back(tmp); tmp.segA =Point(140-subX,210-subY ); tmp.segB =Point(100-subX,150-subY ); m_segments.push_back(tmp); // polygon #2 tmp.segA =Point(100-subX,200-subY ); tmp.segB =Point(120-subX,250-subY ); m_segments.push_back(tmp); tmp.segA =Point(120-subX,250-subY ); tmp.segB =Point( 60-subX,300-subY ); m_segments.push_back(tmp); tmp.segA =Point(60-subX,300-subY ); tmp.segB =Point(100-subX,200-subY ); m_segments.push_back(tmp); // polygon #3 tmp.segA =Point(200-subX,260-subY ); tmp.segB =Point(220-subX,150-subY ); m_segments.push_back(tmp); tmp.segA =Point(220-subX,150-subY ); tmp.segB =Point(300-subX,200-subY ); m_segments.push_back(tmp); tmp.segA =Point(300-subX,200-subY ); tmp.segB =Point(350-subX,320-subY ); m_segments.push_back(tmp); tmp.segA =Point(350-subX,320-subY ); tmp.segB =Point(200-subX,260-subY ); m_segments.push_back(tmp); // polygon #4 tmp.segA =Point(340-subX,60-subY ); tmp.segB =Point(360-subX,40-subY ); m_segments.push_back(tmp); tmp.segA =Point(360-subX,40-subY ); tmp.segB =Point(370-subX,70-subY ); m_segments.push_back(tmp); tmp.segA =Point(370-subX,70-subY ); tmp.segB =Point(340-subX,60-subY ); m_segments.push_back(tmp); // polygon #5 tmp.segA =Point(450-subX,190-subY ); tmp.segB =Point(560-subX,170-subY ); m_segments.push_back(tmp); tmp.segA =Point(560-subX,170-subY ); tmp.segB =Point(540-subX,270-subY ); m_segments.push_back(tmp); tmp.segA =Point(540-subX,270-subY ); tmp.segB =Point(430-subX,290-subY ); m_segments.push_back(tmp); tmp.segA =Point(430-subX,290-subY ); tmp.segB =Point(450-subX,190-subY ); m_segments.push_back(tmp); // polygon #6 tmp.segA =Point(400-subX,95-subY ); tmp.segB =Point(580-subX,50-subY ); m_segments.push_back(tmp); tmp.segA =Point(580-subX,50-subY ); tmp.segB =Point(480-subX,150-subY ); m_segments.push_back(tmp); tmp.segA =Point(480-subX,150-subY ); tmp.segB =Point(400-subX,95-subY ); m_segments.push_back(tmp); if(0 < m_segments.size() ) { vector<LightLine>::iterator it =m_segments.begin(); for( ; it !=m_segments.end(); it++ ) { drawNode->drawLine(it->segA,it->segB,Color4F(1,1,1) ); } } return true; }
接下来添加光源,其实就是添加一个随便的精灵即可,这里我使用了自带的资源,CloseNormal.png,然后添加下触摸事件,你就可以移动这个光源了,这部分省略吧。
刚才初始化多边形的时候,已经将所有的线段保存在类成员m_segments里面了,下面要去除下重复的点,因为一个点连接2条线段:
void HelloWorld::GetEachPointAngle() { vector<Point> tmpPoint; vector<LightLine>::iterator it =m_segments.begin(); vector<Point>::iterator isFind; // 获得线段唯一点 for( ; it !=m_segments.end(); it++ ) { isFind =find( tmpPoint.begin(),tmpPoint.end(),it->segA); if( isFind == tmpPoint.end() ) { tmpPoint.push_back(it->segA); } isFind =find( tmpPoint.begin(),it->segB); if( isFind == tmpPoint.end() ) { tmpPoint.push_back(it->segB); } } // ...... }
现在获得了唯一点,不需要了很多重复的计算,接下来计算当前光源静止的位置到各个点的角度,并且额外增加两条 0.00001 角度的线,为了能让光源打在后面的墙上。
void HelloWorld::GetEachPointAngle() { // ....... //获得唯一点的角度,额外增加 2条射线 vector<Point>::iterator it_point = tmpPoint.begin(); for( ; it_point != tmpPoint.end(); it_point++ ) { float angle =atan2(it_point->y -light->getPositionY(),it_point->x -light->getPositionX()); m_angle.push_back(angle -0.00001); m_angle.push_back(angle); m_angle.push_back(angle +0.00001); } // ....... }
接下来开始计算打到墙上的点。将光源点分别与线段计算.公式看下原文。
void HelloWorld::GetEachPointAngle() { // ......... //循环个角度,找出最近的交点 vector<float>::iterator it_angle =m_angle.begin(); for( ; it_angle !=m_angle.end(); it_angle++ ) { float dx =cos(*it_angle); float dy =sin(*it_angle); //CCLOG("%f,%f,%f",*it_angle,dx,dy); LightLine l; l.segA =Point(light->getPositionX(),light->getPositionY() ); l.segB =Point(light->getPositionX() + dx,light->getPositionY() + dy ); float minT1 =0; it =m_segments.begin(); for( ; it !=m_segments.end(); it++ ) { float tmpT1 =GetIntersection( l,*it ); if( -1 == tmpT1 ) { continue; } if(0 == minT1 || minT1 > tmpT1 ) { minT1 = tmpT1; } } PointAndAngle p; p.point =Point(light->getPositionX() + minT1 * dx,light->getPositionY() + minT1 * dy ); p.angle = *it_angle; m_intersects.push_back(p); //CCLOG("%f,p.point.x,p.point.y,*it_angle); } sort(m_intersects.begin(),m_intersects.end(),SortAngle ); } floatHelloWorld::GetIntersection(LightLine ray,LightLine seg ) { //CCLOG("%f,ray.segA.x,ray.segA.y,ray.segB.x,ray.segB.y); int r_px = ray.segA.x; int r_py = ray.segA.y; float r_dx = ray.segB.x - ray.segA.x; float r_dy = ray.segB.y - ray.segA.y; int s_px = seg.segA.x; int s_py = seg.segA.y; float s_dx = seg.segB.x - seg.segA.x; float s_dy = seg.segB.y - seg.segA.y; float r_mag =sqrt( r_dx * r_dx + r_dy * r_dy ); float s_mag =sqrt( s_dx * s_dx + s_dy * s_dy ); if( ( r_dx / r_mag ) == ( s_dx / s_mag ) && ( r_dy / r_mag ) == ( s_dy / s_mag) ) { return -1; } float t2 = ( r_dx * (s_py-r_py) + r_dy * (r_px - s_px)) / (s_dx*r_dy - s_dy*r_dx); float t1 = ( s_px + s_dx * t2 - r_px )/ r_dx; if( t1 <0 || t2 <0 || t2 >1 ) { return -1; } CCLOG("%d,%d,r_px,r_py,r_dx,r_dy,s_px,s_py,s_dx,s_dy,t1); return t1; }
最后循环填充三角形即可。
这里说下的是,光线和多边形不在一个layer上,因为在你每次移动的过程中,需要清楚旧光线,来画新光线,所以进行了分层,并且对旧数据进行清除,每次移动,重新计算再绘制。
这里可能会出现不准确的情况,效果:
个人认为是移动时候,数值之间计算的误差导致。并非整数,所以我在 onTouchMoved 这样处理的:
/*********************************************************** * 移动触摸会有误差值,不是整数,所以会出现三角形连接错误 * 根据小数大小,取整运算 ***********************************************************/ subP.x = ( ( subP.x - (int)subP.x ) > 0.5 ) ? ceil(subP.x) : floor(subP.x); subP.y = ( ( subP.y - (int)subP.y ) > 0.5 ) ? ceil(subP.y) : floor(subP.y);
效果有所改善:
大神们勿喷,谢谢,谢谢。