一、技能碰撞检测方式分类讲解
1)AABB
-
定义
采用一个长方体将物体包裹起来,进行两个物体的相交性检测时仅检测物体对应包围盒(包裹物体的长方体)的相交性。长方体每一个面都是与某个坐标轴平面平行的,因此,AABB 包围盒又称了 轴对齐包围盒 。
-
备注
确定 AABB 包围盒就简单多了,仅需要记录 6 个值即可,这 6 个值分别代表包围盒在每个坐标轴上的最小值与最大值,即 xmin、xmax、ymin、ymax、zmin、zmax。也就是说,实际物体上所有的点都必须满足以下条件:
-
代码计算
1)C++版本检测AABB碰撞
法一:(1)包围盒
struct stBoundingBox
{
stBoundingBox () { min[0] = min[1] = min[2] =
max[0] = max[1] = max[2] = 0; }
float min[3];
float max[3];
};
//检测点是否在包围盒内
// Point inside box.
bool OnCollision(stBoundingBox bb, float x, float y, float z)
{
if(bb.max[0] <= x) return false;
if(bb.min[0] >= x) return false;
if(bb.max[1] <= y) return false;
if(bb.min[1] >= y) return false;
if(bb.max[2] <= z) return false;
if(bb.min[2] >= z) return false;
// Return true then we have collision.
return true;
}
//检测包围盒相撞
// Box on box.
bool OnCollision(stBoundingBox &bb1, stBoundingBox &bb2)
{
if((bb1.min[0] > bb2.max[0]) || (bb2.min[0] > bb1.max[0]))
return false;
if((bb1.min[1] > bb2.max[1]) || (bb2.min[1] > bb1.max[1]))
return false;
if((bb1.min[2] > bb2.max[2]) || (bb2.min[2] > bb1.max[2]))
return false;
return true;
}
法二:立方体检测
(一)优点:对结构而言,对边界框使用4个浮点数需要的空间较少,而这是较合理的空间坐标。
(二)缺点:盒子变成了立方体。这样在尝试封装物体时会浪费更多的空间,因为需要为深度、宽度和高度使用最
大的尺寸值,而不是使用每个方向上所必须的扩展值
(这里给出了一个使用4个浮点数以及一个最大、最小数值对来定义和创建边框的方法)
struct stBoundingBox
{
CVector3 min, max;
};
struct stBoundingBox
{
CVector3 center;
float size;
};
法三:边界求检测
(一)定义:边界球完全围绕在一些几何图形周围,它除了不用方
框之外,和边界框结构非常相似。边界球结构可以使用4个浮点值
确定。前3个浮点值代表了边界球的中心位置,第四个浮点数代表
了边界球的半径
(二)优点:边界框相比较而言,边界球在处理碰撞检测和可见度时速度更快
(三)缺点:环绕造成的空间浪费要比边界框多
struct stBoundingSphere
{
CVector3 center;
double radius;
};
stBoundingSphere CalculateBoundingSphere(CVector3 *vList, int totalVerts)
{
float d, maxDistance = 0.0f;
stBoundingSphere sphere;
for(int i = 0; i < totalVerts; i++)
{
if(vList[i].x < min[0]) min[0] = vList[i].x;
if(vList[i].x > max[0]) max[0] = vList[i].x;
if(vList[i].y < min[1]) min[1] = vList[i].y;
if(vList[i].y > max[1]) max[1] = vList[i].y;
if(vList[i].z < min[2]) min[2] = vList[i].z;
if(vList[i].z > max[2]) max[2] = vList[i].z;
}
CVector3 center = (max + min) * 0.5f;
for(i = 0; i < totalVerts; i++)
{
d = ((vList[i].x - center.x) * (vList[i].x - center.x)) +
((vList[i].y - center.y) * (vList[i].y - center.y)) +
((vList[i].z - center.z) * (vList[i].z - center.z));
if(d > maxDistance)
maxDistance = d;
}
sphere.radius = sqrt(maxDistance);
sphere.center = center;
return sphere;
}
- 球体检测综合评价
如前所述,边界球要比边界框更容易处理
(碰撞、能见度等),因为对球所做的操作都是比较便宜的CPU相关的
。这样在想要快速确定一个物体是否和其他物体发生碰撞或是那个物体是否可见的情况下,就最有可能选择使用边界球。某些混合法包括先使用边界球快速拒绝几何图形,然后使用边界框进行更准确的测试。如果这两个测试都通过了,那么接下来的操作要么是接受物体测试成功,要么是将物体分解成更小的块,然后分别对围绕这些块的边界框进行测试。当然,使用的方法很大程度上取决于程序的要求。取代使用大的球,然后是大的边界框,可以先使用大的边界球,然后再直接使用小一些的边界框。无论如何,最初使用边界球进行测试经过证明十分有用,因为进行碰撞和可见度测试使用的大量时间对包含大量物体的场景而言都是不够的
2)java版本
(1)3D结构体定义
public class Vector3f {
float x;//三维变量中的x值
float y;//三维变量中的y值
float z;//三维变量中的z值
public Vector3f(float x,float y,float z)
{
this.x=x;
this.y=y;
this.z=z;
}
public void add(Vector3f temp)
{
this.x+=temp.x;
this.y+=temp.y;
this.z+=temp.z;
}
}
(2)包围盒定义
public class AABBBox {
float minX;//x轴最小位置
float maxX;//x轴最大位置
float minY;//y轴最小位置
float maxY;//y轴最大位置
float minZ;//z轴最小位置
float maxZ;//z轴最大位置
public AABBBox(float[] vertices)
{
init();
findMinAndMax(vertices);
}
public AABBBox(float minX,float maxX,float minY,float maxY,float minZ,float maxZ)
{
this.minX=minX;
this.maxX=maxX;
this.minY=minY;
this.maxY=maxY;
this.minZ=minZ;
this.maxZ=maxZ;
}
//初始化包围盒的最小以及最大顶点坐标
public void init()
{
minX=Float.POSITIVE_INFINITY; // 正无穷
maxX=Float.NEGATIVE_INFINITY; // 负无穷
minY=Float.POSITIVE_INFINITY;
maxY=Float.NEGATIVE_INFINITY;
minZ=Float.POSITIVE_INFINITY;
maxZ=Float.NEGATIVE_INFINITY;
}
//获取包围盒的实际最小以及最大顶点坐标
public void findMinAndMax(float[] vertices)
{
for(int i=0;i<vertices.length/3;i++)
{
//判断X轴的最小和最大位置
if(vertices[i*3]<minX)
{
minX=vertices[i*3];
}
if(vertices[i*3]>maxX)
{
maxX=vertices[i*3];
}
//判断Y轴的最小和最大位置
if(vertices[i*3+1]<minY)
{
minY=vertices[i*3+1];
}
if(vertices[i*3+1]>maxY)
{
maxY=vertices[i*3+1];
}
//判断Z轴的最小和最大位置
if(vertices[i*3+2]<minZ)
{
minZ=vertices[i*3+2];
}
if(vertices[i*3+2]>maxZ)
{
maxZ=vertices[i*3+2];
}
}
}
//获得物体平移后的AABB包围盒
public AABBBox getCurrAABBBox(Vector3f currPosition)
{
AABBBox result=new AABBBox
(
this.minX+currPosition.x,
this.maxX+currPosition.x,
this.minY+currPosition.y,
this.maxY+currPosition.y,
this.minZ+currPosition.z,
this.maxZ+currPosition.z
);
return result;
}
}
(3)碰撞检测函数
public boolean check(RigidBody ra, RigidBody rb)//true为撞上
{
float[] over = calOverTotal
(
// 两个物体的 AABB 包围盒
ra.collObject.getCurrAABBBox(ra.currLocation),
rb.collObject.getCurrAABBBox(rb.currLocation)
);
// 三个方向的交叠值与设定的阈值进行比较
return over[0] > V_UNIT && over[1] > V_UNIT && over[2] > V_UNIT;
}
// 传入两个物体的 AABB 包围盒
public float[] calOverTotal(AABBBox a, AABBBox b) {
float xOver = calOverOne(a.maxX, a.minX, b.maxX, b.minX);
float yOver = calOverOne(a.maxY, a.minY, b.maxY, b.minY);
float zOver = calOverOne(a.maxZ, a.minZ, b.maxZ, b.minZ);
return new float[]{xOver, yOver, zOver};
}
// 计算每个轴方向的交叠值
public float calOverOne(float amax, float amin, float bmax, float bmin) {
float minMax = 0;
float maxMin = 0;
if (amax < bmax)//a物体在b物体左侧
{
minMax = amax;
maxMin = bmin;
} else //a物体在b物体右侧
{
minMax = bmax;
maxMin = amin;
}
if (minMax > maxMin) {
return minMax - maxMin;
} else {
return 0;
}
}
2)OBB
- 定义
两个凸包多边形,当且仅当存在一条线,这两个多边形在这条线上的投影不相交,则这两个多边形也不相交. - 举例
这里不能取2因为投影会相交,可以取1为投影轴
- 链接传送门
3)像素检测碰撞
-
使用背景
1)使用像素碰撞检测法算是最精确的算法
2)效率上的低下
。除非是在极为特殊的情况下,要求使用非常精确的碰撞,否则,一般情况下在游戏中是不建议使用这种算法,特别是在运行效率不太高的HTML5游戏中。 -
使用顺序
一般来说在使用像素碰撞检测之前会使用AABB矩形包围盒先检测两个精灵是否有碰撞,如果AABB包围盒检测没有碰撞,那一定是没有碰撞到,反之,则不一定,需要进一步进行像素检测。如下图所示,很明显,虽然两个精灵的包围盒发生了碰撞,但两个精灵本身没有发生碰撞,所以在这种精灵的形状极为不规则的情况下,除非使用多边形包围盒,并且需要多边形和精灵的形状极为接近,才能够获取好的效果,而且,别忘了,多边形只适合凸多边形的情况。
-
定义
这样,我们就只能采用像素检测算法达到精确检测的目的。接下来,先来看看像素碰撞的原理,首先,我们知道所有的精灵都是由像素点组成,而在canvas的像素数据中每个点都是由RGBA四个数据组成。如果某个点的A(alpha值,透明度)为0则表示该点是透明的,比如在图6-19中两个精灵的空白部分的点的A值为0。如果两个精灵发生碰撞,则表示两个精灵有像素点发生了重叠,即两个精灵的像素点坐标相同,如下图所示。
根据这个原理,基本上我们可以采取以下步骤来进行检测。
(1)选择需要检测的两个精灵。
(2)先检测两个精灵是否发生包围盒碰撞,如果没有则退出,否则获取两个矩形的相交区域,并继续。
(3)把一个精灵绘制到和游戏屏幕等大的空白的后台缓冲区中,获取缓冲区中在相交区域的像素数据。
(4)清除后台缓冲区。
(5)对另一个精灵进行步骤3的操作。
(6)得到两个精灵在同一个相交矩形的像素数据后,循环比较每一个像素点,如果两个精灵在同一位置的透明度不都是0,则表示两个精灵有相交,退出循环,返回真。
- 备注点
需要注意的一点是,在第3步获取相交区域的像素数据中,需要在后台另外的一个和游戏屏幕等大的空白区域中绘制,而不能直接获取游戏画面中的相交区域,因为两个精灵在相交区域中的像素已经发生了重叠【包围盒】。 - 注意点
由以上的算法可以看出,在进行像素检测的时候,需要另外一个缓冲区,把需要检测的精灵绘制一次,需要清空缓冲区,最后还要使用一个for循环检测像素。如果相交区域为100×100个像素点,则最坏的情况需要循环10 000 次,由此看来,这真不是一个省时的工作。 - 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta charset="UTF-8">
<title>盒包围碰撞算法-像素检测算法</title>
<style>
#stage {
border: 1px solid lightgray;
}
</style>
</head>
<body>
<h1>包围盒是否碰撞:<span class="hitTestBox">否</span></h1>
<h1>像素检测是否碰撞:<span class="hitTestPixel">否</span></h1>
<canvas id="stage"></canvas>
<img src="./images/penguin.png" alt="penguinImg" id="penguinImg" style="display:none" />
<img src="./images/giraffe.png" alt="giraffeImg" id="giraffeImg" style="display:none" />
</body>
<script>
window.onload = function () {
var stage = document.querySelector('#stage'),
ctx = stage.getContext('2d');
stage.width = 600;
stage.height = 600;
//创建后台canvas
var bC = document.createElement("canvas");
bC.width = stage.width;
bC.height = stage.height;
var backBuf = bC.getContext("2d");
var penguin = document.querySelector('#penguinImg'),giraffe = document.querySelector('#giraffeImg');
var penguinImg = {
x:stage.width/2,
y:stage.height/2,
r:128,
data:null
},giraffeImg = {
x:100,
y:100,
r:128,
data:null
};
function drawImageBox(img,x,y,width,height){
ctx.beginPath();
ctx.rect(x,y,width,height);
ctx.stroke();
ctx.drawImage(img,x,y,width,height);
}
//缓存画布绘制方法
function drawImageBC(img,x,y,width,height){
backBuf.clearRect(0,0,stage.width,stage.height);
backBuf.save();
backBuf.drawImage(img,x,y,width,height);
backBuf.restore();
}
//获取两个矩形相交区域
function getInRect(x1,y1,x2,y2,x3,y3,x4,y4) {
return [Math.max(x1,x3),Math.max(y1,y3),Math.min(x2,x4),Math.min(y2,y4)];
}
document.onkeydown = function (event) {
var e = event || window.event || arguments.callee.caller.arguments[0];
//根据地图数组碰撞将测
switch (e.keyCode) {
case 37:
console.log("Left");
if (penguinImg.x > 0) {
penguinImg.x -= 2;
}
break;
case 38:
console.log("Top");
if (penguinImg.y > 0) {
penguinImg.y -= 2;
}
break;
case 39:
console.log("Right");
if (penguinImg.x < stage.width) {
penguinImg.x += 2;
}
break;
case 40:
console.log("Bottom");
if (penguinImg.y < stage.height) {
penguinImg.y += 2;
}
break;
default:
return false;
}
};
stage.addEventListener('click', function (event) {
var x = event.clientX - stage.getBoundingClientRect().left;
var y = event.clientY - stage.getBoundingClientRect().top;
penguinImg.x = x;
penguinImg.y = y;
});
function update() {
ctx.clearRect(0, 0, 600, 600);
drawImageBox(giraffe,giraffeImg.x,giraffeImg.y,giraffeImg.r,giraffeImg.r);
drawImageBox(penguin,penguinImg.x,penguinImg.y,penguinImg.r,penguinImg.r);
document.querySelector('.hitTestBox').innerHTML = "否";
document.querySelector('.hitTestPixel').innerHTML = "否";
var rect = getInRect(giraffeImg.x,giraffeImg.y,giraffeImg.x+giraffeImg.r,giraffeImg.y+giraffeImg.r,penguinImg.x,penguinImg.y,penguinImg.x+penguinImg.r,penguinImg.y+penguinImg.r)
//如果没有相交则退出
if(rect[0]>=rect[2]||rect[1]>=rect[3]) {
} else{
document.querySelector('.hitTestBox').innerHTML = "是";
giraffeImg.data = null;
penguinImg.data = null;
//获取精灵在相交矩形像素数据
drawImageBC(giraffe,giraffeImg.x,giraffeImg.y,giraffeImg.r,giraffeImg.r);
giraffeImg.data = backBuf.getImageData(rect[0],rect[1],rect[2],rect[3]).data;
drawImageBC(penguin,penguinImg.x,penguinImg.y,penguinImg.r,penguinImg.r);
penguinImg.data = backBuf.getImageData(rect[0],rect[1],rect[2],rect[3]).data;
for(var i=3;i<giraffeImg.data.length;i+=4)
{
if(giraffeImg.data[i]>0&&penguinImg.data[i]>0){
document.querySelector('.hitTestPixel').innerHTML = "是";
}
}
}
requestAnimationFrame(update);
}
update();
};
</script>
</html>
4)矩阵变换检测碰撞(打住,略)
5)Tile地形检测碰撞(其实就是九方格遍历查找攻击对象)
(1)头文件中
#include <iostream>
#include "cocos2d.h"
using namespace cocos2d;
class AA:public CCLayer
{
public:
virtual bool init();
static CCScene *scene();
CREATE_FUNC(AA);
CCSprite *_player;
int count;
bool mode;
CCArray *bulletArray;
CCArray *enemyArray;
CCTMXTiledMap *_tileMap;
CCTMXTiledMap *_titleMap;
CCTMXLayer *wall,*foreground;
CCPoint mapPoint,heroPoint,beginPoint;
CCLabelTTF *label;
//碰撞检测
void detection();
bool iscallision(CCSprite *mysprite,CCSprite *testsprite);
CCRect getRect(CCSprite *sprite);
//触摸事件
virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);
virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);
virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);
virtual void ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent);
void goon(CCNode *pSender);
void toggleGame(CCObject *pSender);
void targetFinish(CCNode *pSender);
};
(2)实现文件中
//
// AA.cpp
// date10
//
// Created by Student on 14-9-22.
//
//
#include "AA.h"
CCScene *AA::scene()
{
CCScene *s=CCScene::create();
AA *layer=AA::create();
s->addChild(layer);
return s;
}
bool AA::init()
{
if(!CCLayer::init())
{
return false;
}
mode=true;
//初始化数组
bulletArray = CCArray::create();
bulletArray->retain();
enemyArray = CCArray::create();
enemyArray->retain();
//加载瓦片地图
_tileMap=CCTMXTiledMap::create("ThefirstTiled1.tmx");
_tileMap->setPosition(mapPoint);
addChild(_tileMap);
//读取瓦片地图中的层和对象
wall=_tileMap->layerNamed("wall");
foreground=_tileMap->layerNamed("water");
CCTMXObjectGroup *objects=_tileMap->objectGroupNamed("hero");
CCDictionary *spawnPoint=objects->objectNamed("pa");
heroPoint.x=spawnPoint->valueForKey("x")->floatValue();
heroPoint.y=spawnPoint->valueForKey("y")->floatValue();
CCLog("x=%f,y=%f",heroPoint.x,heroPoint.y);
//加载英雄到界面
_player=CCSprite::create("www.png");
_player->setAnchorPoint(CCPoint(0,0));
_player->setPosition(heroPoint);
_tileMap->addChild(_player,0);
//加载敌人到界面
for(int i=0;i<int(objects->getObjects()->count());i++) //得到敌人的数量
{
CCDictionary *enemy=(CCDictionary *)objects->getObjects()->objectAtIndex(i);
if(enemy->valueForKey("n")->intValue()==1)
{
CCSprite *s=CCSprite::create("enemy1.png");
float x=enemy->valueForKey("x")->floatValue();
float y=enemy->valueForKey("y")->floatValue();
s->setPosition(ccp(x,y));
s->setAnchorPoint(CCPoint(0,0));
_tileMap->addChild(s,4);
enemyArray->addObject(s);
CCActionInterval *move=CCMoveBy::create(0.5,CCPoint(_player->getPosition().x-s->getPosition().x>0?10:-10,_player->getPosition().y-s->getPosition().y>0?10:-10));
//goon 表示不断的移动
CCFiniteTimeAction *func=CCCallFuncN::create(this,callfuncN_selector(AA::goon));
s->runAction(CCSequence::create(move,func,NULL));
}
}
//加载开关选项
CCMenuItem *oon,*ooff;
oon=CCMenuItemImage::create("projectile-button-on.png","projectile-button-on.png");
ooff=CCMenuItemImage::create("projectile-button-off.png","projectile-button-off.png");
CCMenuItemToggle *toggle=CCMenuItemToggle::createWithTarget(this,
menu_selector(AA::toggleGame),
ooff,oon,NULL);
CCMenu *menu=CCMenu::create(toggle,NULL);
menu->setPosition(ccp(oon->getContentSize().width/2,oon->getContentSize().height/2));
addChild(menu);
//加入触摸的单例
CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 1, true);
//碰撞检测的调用
this->schedule(schedule_selector(AA::detection), 0.2);
label=CCLabelTTF::create("0","Arial",20);
label->setPosition(ccp(CCDirector::sharedDirector()->getWinSize().width-label->getContentSize().width/2,label->getContentSize().height/2));
addChild(label,10);
return true;
}
//表示敌人不断移动的距离
void AA::goon(CCNode *pSender)
{
CCLOG("goon");
CCSprite *s=(CCSprite *)pSender;
CCActionInterval *move=CCMoveBy::create(0.5,CCPoint(_player->getPosition().x-s->getPosition().x>0?10:-10,_player->getPosition().y-s->getPosition().y>0?10:-10));
CCFiniteTimeAction *func=CCCallFuncN::create(this,callfuncN_selector(AA::goon));
s->runAction(CCSequence::create(move,func,NULL));
}
void AA::toggleGame(CCObject *pSender)
{
mode=mode?false:true;
}
bool AA::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)
{
beginPoint=pTouch->getLocation();
return true;
}
void AA::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent)
{
/*
if(mode)
{
//得到先前的坐标(0,0)
CCPoint PP=pTouch->getPreviousLocation();
//得到当前的坐标
CCPoint np=pTouch->getLocation();
//两坐标相减
CCPoint dp=ccpSub(np, PP);
//向左为负 右侧为正
if(_titleMap->getPosition().x+dp.x>-480 && _titleMap->getPosition().x+dp.x<0)
{
mapPoint=ccp(_titleMap->getPosition().x+dp.x,0);
_titleMap->setPosition(mapPoint);
}
}
*/
if(mode)
{
CCPoint pp=pTouch->getPreviousLocation();
CCPoint np=pTouch->getLocation();
CCPoint dp=ccpSub(np,pp);
if(_tileMap->getPosition().x+dp.x>-480&&_tileMap->getPosition().x+dp.x<0)
{
mapPoint=ccp(_tileMap->getPosition().x+dp.x,0);
_tileMap->setPosition(mapPoint);
}
}
}
void AA::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent)
{
/*
//地图里的坐标(0,0)是从左上角开始的
步骤:
1、判断是否点击
2、判断点击的位置与英雄的位置的X,Y的差值 比较DX与DY的大小
3、判断英雄的X做标要大于点击的X坐标 才能向左
4、判断移动时是否撞击墙和移出地图外
*/
//获得点击的位置
/*
CCPoint pp=pTouch->getLocation();
if(mode)
{
//是点击不是滑动 在原地点击下又点击离开
if(beginPoint.x==pp.x && beginPoint.y==pp.y)//beginPoint是开始触摸的位置
{
//—player的位置
CCPoint pt=_player->getPosition();
//判断是否 x>y
if(fabs(pt.x-(pp.x-mapPoint.x))>=fabs( pt.y-(pp.y-mapPoint.y)) )
{
点击点向左移动 英雄的X做标要大于点击的X坐标 才能向左
if(pt.x>=pp.x-mapPoint.x)
{
if(9-_player->getPosition().y/32<10 && 9-_player->getPosition().y/32>=0&&
_player->getPosition().x/32-1<50&&_player->getPosition().x/32-1>=0&&
!(wall->tileGIDAt(ccp(_player->getPosition().x/32-1,9-_player->getPosition().y/32))))//判断是否是地图外面,或者是不是墙
{
_player->setPosition(ccp(_player->getPosition().x-32,_player->getPosition().y));
if(foreground->tileGIDAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32)))
{
foreground->removeTileAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32));
count++;
CCString *temp=CCString::createWithFormat("%d",count);
label->setString(temp->getCString());
}
}
}
else
{
if(9-_player->getPosition().y/32<10&&9-_player->getPosition().y/32>=0&&_player->getPosition().x/32+1<50&&_player->getPosition().x/32+1>=0&&!(wall->tileGIDAt(ccp(_player->getPosition().x/32+1,9-_player->getPosition().y/32))))
{
_player->setPosition(ccp(_player->getPosition().x+32,_player->getPosition().y));
if(foreground->tileGIDAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32)))
{
foreground->removeTileAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32));
count++;
CCString *temp=CCString::createWithFormat("%d",count);
label->setString(temp->getCString());
}
}
}
}
else
{
if(pt.y>=pp.y-mapPoint.y)
{
if(9-_player->getPosition().y/32+1<10&&9-_player->getPosition().y/32+1>=0&&_player->getPosition().x/32<50&&_player->getPosition().x/32>=0&&!(wall->tileGIDAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32+1))))
{
_player->setPosition(ccp(_player->getPosition().x,_player->getPosition().y-32));
if(foreground->tileGIDAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32)))
{
foreground->removeTileAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32));
count++;
CCString *temp=CCString::createWithFormat("%d",count);
label->setString(temp->getCString());
}
}
}
else
{
if(9-_player->getPosition().y/32-1<10&&9-_player->getPosition().y/32-1>=0&&_player->getPosition().x/32<50&&_player->getPosition().x/32>=0&&!(wall->tileGIDAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32-1))))
{
_player->setPosition(ccp(_player->getPosition().x,_player->getPosition().y+32));
if(foreground->tileGIDAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32)))
{
foreground->removeTileAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32));
count++;
CCString *temp=CCString::createWithFormat("%d",count);
label->setString(temp->getCString());
}
}
}
}
}
}
else
{
CCSprite *s=CCSprite::create("Projectile.png");
s->setPosition(_player->getPosition());
_tileMap->addChild(s,4);
bulletArray->addObject(s);
float dx=pp.x-_player->getPosition().x;
float dy=pp.y-_player->getPosition().y;
if(dx>0)
{
float lx=32*30-_player->getPosition().x;
float ly=dy/dx*lx;
CCActionInterval *move=CCMoveBy::create(3,ccp(lx+s->getContentSize().width,ly));
CCFiniteTimeAction *ff=CCCallFuncN::create(this,callfuncN_selector(AA::targetFinish));
s->runAction(CCSequence::create(move,ff,NULL));
}
else
{
float lx=0-_player->getPosition().x;
float ly=dy/dx*lx;
CCActionInterval *move=CCMoveBy::create(3,ccp(lx-s->getContentSize().width,ly));
CCFiniteTimeAction *ff=CCCallFuncN::create(this,callfuncN_selector(AA::targetFinish));
s->runAction(CCSequence::create(move,ff,NULL));
}
}
*/
CCPoint pp=pTouch->getLocation();
if(mode)//移动
{
if(beginPoint.x==pp.x&&beginPoint.y==pp.y)
{
cocos2d::CCPoint pt=_player->getPosition();
//dx>dy
if(fabs(pt.x-(pp.x-mapPoint.x))>=fabs(pt.y-(pp.y-mapPoint.y)))
{
//左移
if(pt.x>=pp.x-mapPoint.x)
{
if(9-_player->getPosition().y/32<10&&9-_player->getPosition().y/32>=0&&
_player->getPosition().x/32-1<50&&_player->getPosition().x/32-1>=0&&
!(wall->tileGIDAt(ccp(_player->getPosition().x/32-1,9-_player->getPosition().y/32))))//判断是否是地图外面,或者是不是墙 //tileGIDAt表示有没有的意思
{
//32表示32个像素 表示向左挪动了1个单位
_player->setPosition(ccp(_player->getPosition().x-32,_player->getPosition().y));
//判断西瓜的和_player相撞
if(foreground->tileGIDAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32)))
{
//西瓜的位置消失
foreground->removeTileAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32));
count++;
CCString *temp=CCString::createWithFormat("%d",count);
label->setString(temp->getCString());
}
}
}
//右移
else
{
if(9-_player->getPosition().y/32<10&&9-_player->getPosition().y/32>=0&&_player->getPosition().x/32+1<50&&_player->getPosition().x/32+1>=0&&!(wall->tileGIDAt(ccp(_player->getPosition().x/32+1,9-_player->getPosition().y/32))))
{
_player->setPosition(ccp(_player->getPosition().x+32,_player->getPosition().y));
if(foreground->tileGIDAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32)))
{
foreground->removeTileAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32));
count++;
CCString *temp=CCString::createWithFormat("%d",count);
label->setString(temp->getCString());
}
}
}
}
//dx<dy
else
{
//下移
if(pt.y>=pp.y-mapPoint.y)
{
if(9-_player->getPosition().y/32+1<10&&9-_player->getPosition().y/32+1>=0&&_player->getPosition().x/32<50&&_player->getPosition().x/32>=0&&!(wall->tileGIDAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32+1))))
{
_player->setPosition(ccp(_player->getPosition().x,_player->getPosition().y-32));
if(foreground->tileGIDAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32)))
{
foreground->removeTileAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32));
count++;
CCString *temp=CCString::createWithFormat("%d",count);
label->setString(temp->getCString());
}
}
}
//上移
else
{
if(9-_player->getPosition().y/32-1<10&&9-_player->getPosition().y/32-1>=0&&_player->getPosition().x/32<50&&_player->getPosition().x/32>=0&&!(wall->tileGIDAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32-1))))
{
_player->setPosition(ccp(_player->getPosition().x,_player->getPosition().y+32));
if(foreground->tileGIDAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32)))
{
foreground->removeTileAt(ccp(_player->getPosition().x/32,9-_player->getPosition().y/32));
count++;
CCString *temp=CCString::createWithFormat("%d",count);
label->setString(temp->getCString());
}
}
}
}
}
}
//英雄发射子弹
else
{
CCSprite *s=CCSprite::create("Projectile.png");
s->setPosition(_player->getPosition());
_tileMap->addChild(s,4);
bulletArray->addObject(s);
float dx=pp.x-_player->getPosition().x;
float dy=pp.y-_player->getPosition().y;
if(dx>0)
{
float lx=32*30-_player->getPosition().x;
float ly=dy/dx*lx;
CCActionInterval *move=CCMoveBy::create(3,ccp(lx+s->getContentSize().width,ly));
CCFiniteTimeAction *ff=CCCallFuncN::create(this,callfuncN_selector(AA::targetFinish));
s->runAction(CCSequence::create(move,ff,NULL));
}
else
{
float lx=0-_player->getPosition().x;
float ly=dy/dx*lx;
CCActionInterval *move=CCMoveBy::create(3,ccp(lx-s->getContentSize().width,ly));
CCFiniteTimeAction *ff=CCCallFuncN::create(this,callfuncN_selector(AA::targetFinish));
s->runAction(CCSequence::create(move,ff,NULL));
}
}
}
void AA::targetFinish(CCNode *pSender)
{
pSender->removeFromParent();
}
void AA::ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent)
{
}
//碰撞检测
void AA::detection()
{
/*
1、英雄与敌人碰撞
2、子弹与敌人碰撞
*/
//遍历敌人
for(int i=0;i<enemyArray->count();i++)
{
CCSprite *s=(CCSprite *)enemyArray->objectAtIndex(i);
//判断英雄是否和敌人碰撞
if(iscallision(_player, s))
{
CCLog("--------aaaaaaaa-------------");
}
}
//创建临时的数组
CCArray *bulletA=CCArray::create();
CCArray *enemyA=CCArray::create();
//遍历子弹
for(int i=0;i<bulletArray->count();i++)
{
CCSprite *bullet=(CCSprite *)bulletArray->objectAtIndex(i);
//遍历敌人
for(int j=0;j<enemyArray->count();j++)
{
CCSprite *enemy=(CCSprite *)enemyArray->objectAtIndex(i);
//判断子弹是否和敌人相撞
if(iscallision(bullet, enemy))
{
//这里移除后应该加入数组 否则会崩溃
enemy->removeFromParent();
bullet->removeFromParent();
bulletA->addObject(bullet);
enemyA->addObject(enemy);
}
}
}
//从数组中移除子弹
for(int i=0;i<bulletA->count();i++)
{
CCSprite *bullet = (CCSprite *) bulletA->objectAtIndex(i);
bulletArray->removeObject(bullet);
}
//从数组中移除敌人
for(int i=0;i<enemyA->count();i++)
{
CCSprite *enemy = (CCSprite *) enemyA->objectAtIndex(i);
enemyArray->removeObject(enemy);
}
}
//碰撞检测
bool AA::iscallision(CCSprite *mysprite,CCSprite *testsprite)
{
CCRect myrect=mysprite->boundingBox();
CCRect testrect=testsprite->boundingBox();
return myrect.intersectsRect(testrect);
}
-----------------------------------
©著作权归作者所有:来自51CTO博客作者春蕾夏荷的原创作品,请联系作者获取转载授权,否则将追究法律责任
Cocos2d-X 瓦片地图使用的实例项目(包含碰撞检测,移动)
https://blog.51cto.com/u_10486491/3217098
二、武林风云录
1)老一:陈斩槐(只有四个机制,dps压力不大,留爆发打影子就行)
(1)点名红色扇形区域
(2)点名红色长条,注意最后还有一段大劈
(3)BOSS在百分之80 、50、20血量时会击退玩家,会刷新三个输出位置
- 应对策略
需要三个输出职业进入篮圈击杀影子 - 注意点
①每个篮圈只能进一个人,
②如果没在30s
内击杀影子,影子会爆炸,BOSS将释放地图炮
导致团灭
③进去打影子的队友,奶妈是奶不了的,需要躲好红圈,躲不了平A
(4)BOSS在百分之35血时会吸玩家到其周围,释放旋风斩,得交解控技能。但是不要着急回去,还有一个圆圈的下坠伤害
- 额外注意
boss释放完技能后原地会生成一个龙卷风,靠近会暴毙,需要T尽可能的往副本初始位置拉boss
2)老二:醉仙
- 机制讲解
boss是软狂暴boss,每一次喝酒都会叠加后劲不足buff,叠加到12层后会一直放技能而不会平A,解茶小厮也不会出现
- 备注
①奶妈一定要切天问,打好飘絮给boss百分之25增伤
②四个输出位置平均喝酒后需要3w的秒伤,T是第二轮供酒可以喝,不然吸引不了仇恨
③奶妈不要喝酒
④喝酒叠加的buff不要满了,否则点名必死
⑤小二都刷新在周边,需要把boss往中心拉否则容易误伤小二
(1)红圈和点名略过
(2)输出记得喝酒(龙吟内功,铁衣外功等)
(3)每一个奉茶小厮都有名字,得按照诗词来按照顺序击杀小二
3)老三:楚相玉
- 备注
①老三技能不按照顺序,有时得同时处理多个boss
(1)boss有狂暴机制,冰火玄功达到极致,倒数360s,将直接团灭(人均得有1.2Wdps)
(2)地上出现红圈,0.5s后将冒出冰柱,击飞造成一段伤害,落地造成二段伤害(估计代码是设计成击飞后掉落下来还在圆圈内)
(3)点名T红色长条,多段伤害
- 备注
若红条击中冰柱,则冰柱直接消失
(4)烈焰焚身,需要交减伤或无敌规避伤害
(5)冰风火凤融合产生圆形爆炸区域
- 应对
铁衣或神像击飞或吹走
(6)赤炎寒光破
全图玩家都被吸附到boss周围,得躲到冰柱后面,注意得站在冰柱后面,否则服务器判断坐标和冰柱重合或稍微飘逸,则会死亡,这很坑爹
(7)暴风雪
得站在地图的火焰点处
4)老四:刀狂
(1)略过半圆斩、三段长条大劈、落地红圈、黑水机制不在赘述
(2)达到百分之90血时,地上出现追忆碎片,分内外场地(血河上马溜boss,需要奶妈往返内网场用追忆碎片奶安抚boss并奶白剑)
(3)刀狂有怒气机制,到百分之100时增加移速和攻击力
内场会出现安抚之力,需要奶妈拾取
对boss使用安抚之力,boss怒气值清零,地上将会掉落追忆草
,失去后增加玩家治疗量
(4)内场讲解
内场将会有白剑npc和各种小怪
白剑:场中npc,需要玩家治疗她,奶满后击败boss后通关
小怪:集火秒掉就行
嗔:打死后出现追忆碎片,使用后出现治疗圆圈,可以治疗玩家和白剑
痴:减疗怪
狂:秒人怪
贪:会回血,且会吃掉场上没被使用的追忆碎片
三、舞阳城
1)老一:黑天尊使
(1)攻击BOSS会叠加debuff原理及解决方法
- 代码机制
①boss被击事件
:在攻击怪物对象,若该被击obj类型是boss类型,则读表若boss有对应被击事件,则触发对应事件
,这个事件是叠加某个debuff
②传功黑雾事件
:内外功相互传功后,定时器几秒后产生黑雾(黑雾是特效),实际上在传功这个消息到服务器后,服务器启发一个几秒的定时器,这个定时器几秒后对一个圆形范围内玩家进行伤害,可能有多段定时器,一个定时器触发另外一个定时器,一步步增大范围 - debuff对应的作用
攻击频率越高叠加越快,超过20层开始持续掉血,层数越高掉血越快
- 解决方法
①需要20层以上的内外功职业互相按F清零层数,传功完地上会出现黑雾造成大量伤害要马上原理
还有一种方法是所有都20层后,去一个敌方站在一起,团长倒数321一起按F一起出
(2)BOSS怒气机制
- 怒气基本机制
boss遭受任何被击事件
都会涨怒气,而怒气达到百分之百又会触发
解锁新技能这个事件
1、一阶段
-
描述
开局boss怒气是满的,只会平A -
一阶段技能
开怪20秒后,BOSS怒气清零,学会技能:红圈砸地
、红盾
-
技能说明
①红圈
:红圈要躲,飘字广播给玩家后,定时器几秒在地上出现红圈秒杀圆形范围内玩家
②红盾
:红盾出现要在boss后面输出,在前面会被反弹以及破招,但是外功职业的输出可以破盾
,不破盾内功伤害会变得非常低(boss的盾其实也算是一种类似血量的属性,加盾其实也是加了一层减少伤害的BUFF)
2、二阶段
- 解锁技能
飞爪
- 技能说明
①飞爪
:随机跳抓一个人,抓到人技能会在抓到人的地点多次砸地造成AOE伤害 - 解决方法
所有人轻功跳/燕回风/位移技能躲避 - 躲避成功
若BOSS没抓到人,会进入虚弱状态(其实也是加了定时器,技能触发结束会有个定时器去判断是否抓到人)
3、三阶段
- 解锁技能
吸人
- 解决方法
需要远程往吸的范围内打技能,奶妈在吸里卖弄解控+扶风即可破掉 - 破掉吸人触发对应事件
若是第一次,则给boss触发虚弱状态 - 若技能到期没被破掉
若没被破掉就会原地抓人,造成AOE
4、四阶段
- 解锁技能
反噬煞气
- 技能说明
战斗一定事件后BOSS会条上天全屏秒杀,可以跳跃躲避
2)老二:火焰围攻(机制说明)
①轻功跳跃会被飞针扎下来,并被定身+封脉,所以老二不要跳
②神像冰系技能(神像)和荒城剑气
(奇遇)灭火,副本里面的荒城剑气CD会变成1分钟而不是24小时
③打怪死后得用火系技能烧尸体(神像平A
和火技能、血河烈焰枪
、铁艺炼火拳
等)
④火器攻击
得躲,截图里面的绿点和蓝点是安全点位,不会被火器轰击到
⑤不要在地图边缘和台阶下面打,会被炸弹炸到
⑥飞针
英雄服务器是连续扎四下并定身,灭了火进白雾也无法规避,但可以用无敌技能卡规避,被飞针命中的玩家技能会有Debuff:受到火属性伤害提升
⑦副本倒计时158s会有一只屠百怪
从天上劈下造成范围AOE伤害,得一直用血河的击飞
或其他职业的击飞技能
⑧最后三只怪会释放黑雾,黑雾内玩家打怪会失手以及被压制,所有人远离让怪出来再打即可