2D遊戲中的碰撞檢測:圓形與矩形碰撞檢測(Javascript&C++版)
這幾天放寒假了,時間也多了起來,當然又有時間搞搞程式了。哈哈~
昨天在開發我的塔防遊戲時突然發現人物實際攻擊範圍比規定的範圍小,按理說應該是一樣大的,但偏偏不是,我被這個問題搞得糊里糊塗的,一直沒想出問題所在。最後詢問了一個程式高手——我哥哥。他雖然是搞C++的,但聽了我程式碼解釋中有檢測圓形碰撞時,他立刻就發現了問題,他告訴我,敵人可以看作是方塊,而攻擊範圍是圓的,如果把敵人弄成圓形進行碰撞檢測那必然不準,應該檢測矩形和圓形碰撞才行。我聽了之後恍然大悟,但是lufylegend中沒有這個功能,怎麼辦呢?我第一想法是對lufy說說,讓他老人家實現吧。當我點開Google Talk準備發起對話時,突然又想到一來要是lufy老人家去實現,那要等到猴年馬月去了,況且lufy前輩瑣事纏身,要是我總是給他提意見不幫忙解決,他老人家是不是想打我啊……最後我還是決定自己來實現吧。但是我沒搞過這方面的研究,沒有經驗,所以一開始有點懵,於是就去Google了一下,發現還真有人講過,於是就點開看了,不知道是我理解能力不好還是文章寫得差(估計都是我理解能力不好……)我看了半晌沒看懂,嗚呼,無法可想,我當時就失望了。但是文章下面有段C程式碼,於是我把它移植到js上來,運行了一下,感覺效果還不錯,於是就馬上跑到github上把程式碼上傳給lufy了,並做了一個demo,並在Google Talk上提醒了lufy叫他老人家測試,結果lufy拿到程式碼一測試就發現了bug。我X,不愧是大神啊……沒有辦法,只有另謀出路了。
今早起來,哎呀,天氣不錯啊,成都好久沒有這麼爽的天氣了。只見空氣清新,陽光和煦,真是外出騎車的好機會啊!於是我便和家人一塊兒跑到外面溜達了一圈。邊走邊想矩形和圓形碰撞的事。半天想不明白,嗚呼,我只好在路上問了問哥哥。哥哥果然是高手,他想了一會兒便說出了重點,給了我啟發。於是回到家,我便馬上開啟電腦,進行了實驗,結果還成功了。當然,按照以往我的習慣,這次小小“發明”也當然也要分享給大家啦~
(以上事情均發生在1月11日和1月12日,所以lufy看到文章開頭不要以為走錯家門了……)
廢話寫了一大堆,接下來還是來看看矩形和圓形碰撞實現過程吧。
一,原理介紹
這回有點複雜,不過看懂了還是很好理解的。當然,我不敢保證這種演算法在任何情況下都會起效果,如果有同學測試時,發現出現錯誤,請及時聯絡我。
我們首先來建立一個以圓心為原點的座標系:
然後要檢測碰撞就只有兩種情況了。
情況一,矩形全部都在一個象限內,如圖:
當然,圖中只是舉個例子,不一定是隻在第二象限,任何一個象限都行,只要是矩形全在該象限。
這種情況比較好解決,首先,我們計算出矩形每個角的座標,然後用勾股定律依次算出這個角到圓心的距離是否小於或者等於半徑。設這個角與圓心橫座標之差為d1,縱座標之差為d2,半徑為r,公式表達如下:
如果有一個角滿足要求說明產生碰撞,返回true。
但是有朋友懵了,怎麼判斷矩形是不是在一個象限內呢?很簡單,只要判斷這個矩形左上角和右下角是否在同一個象限內就可以了。於是我們得寫個函式來實現判斷某兩個角是否在同一象限。
函式程式碼如下:
function isSameQuadrant(cood,objA,objB){
var coodX = cood.x;
var coodY = cood.y;
var xoA = objA.x
,yoA = objA.y
,xoB = objB.x
,yoB = objB.y;
if(xoA-coodX>0 && xoB-coodX>0){
if((yoA-coodY>0 && yoB-coodY>0) || (yoA-coodY<0 && yoB-coodY<0)){
return true;
}
return false;
}else if(xoA-coodX<0 && xoB-coodX<0){
if((yoA-coodY>0 && yoB-coodY>0) || (yoA-coodY<0 && yoB-coodY<0)){
return true;
}
return false;
}else{
return false;
}
}
這個函式原本是準備寫到lufylegend中LMath靜態類中的,引數原本是LPoint物件,但是這裡可以用json,因為LPoint裡的x,y屬性可以寫到json裡,函式也就同樣取得出值了。函式引數介紹:[cood建立的座標系原點座標, objA第一個點座標, objB第二個點座標] 這幾個引數均為json物件,格式為:{x:點的x座標, y:點的y座標}
函式中的程式碼還是很好理解的,就是判斷一下兩個點的x座標都分別減去原點x座標,看得出的數正負符號是否相同,然後又用同樣的辦法算出y軸上的符號是否相同,如果都相同就在同一象限。有了這個函式,剩下得就好辦了,直接代入開頭給出的公式進行計算即可。
情況二,矩形跨度兩個象限或者兩個象限以上
這種情況更好辦,我們就可以直接把圓看作一個邊長為2r正方形,然後用矩形碰撞演算法檢測正方形和矩形的碰撞,如下圖所示:
矩形碰撞的演算法是什麼呢?很easy,如圖:
如果要橫向判斷碰撞的話,判斷(x1-x2)的絕對值是否小於或者等於w1/2+w2/2,如果是則橫向則有碰撞。縱向判斷是一樣的,判斷(y1-y2)的絕對值是否小於或等於h1/2+h2/2即可。
有了這些演算法,我們就可以實現情況2了。
二,Javascript版演算法&測試程式碼
先上程式碼吧:
function hitTestRectArc(rectObj,arcObj,rectVec,arcR){
var rw = rectObj.getWidth()
,rh = rectObj.getHeight()
,ar = arcObj.getWidth()*0.5
,rx = rectObj.x
,ry = rectObj.y
,ax = arcObj.x
,ay = arcObj.y;
if(typeof rectVec != UNDEFINED){
rx += (rw - rectVec[0])*0.5;
ry += (rh - rectVec[1])*0.5;
rw = rectVec[0];
rh = rectVec[1];
}
if(typeof arcR != UNDEFINED){
ax += (ar - arcR);
ay += (ar - arcR);
ar = arcR;
}
var rcx = rx+rw*0.5,rcy = ry+rh*0.5;
var rltx = rx
,rlty = ry
,rlbx = rx
,rlby = ry+rh
,rrtx = rx+rw
,rrty = ry
,rrbx = rx+rw
,rrby = ry+rh;
if(
isSameQuadrant(
{x:ax,y:ay},
{x:rltx,y:rlty},
{x:rrbx,y:rrby}
)
){
var dX1 = Math.abs(ax-rltx),dY1 = Math.abs(ay-rlty);
var dX2 = Math.abs(ax-rlbx),dY2 = Math.abs(ay-rlby);
var dX3 = Math.abs(ax-rrtx),dY3 = Math.abs(ay-rrty);
var dX4 = Math.abs(ax-rrbx),dY4 = Math.abs(ay-rrby);
if(
(((dX1*dX1) + (dY1*dY1)) <= (ar*ar))
||(((dX2*dX2) + (dY2*dY2)) <= (ar*ar))
||(((dX3*dX3) + (dY3*dY3)) <= (ar*ar))
||(((dX4*dX4) + (dY4*dY4)) <= (ar*ar))
){
return true;
}
return false;
}else{
var result = false;
var squareX = ax
,squareY = ay
,squareW = ar*2
,squareH = squareW;
if(
(Math.abs(squareX-rcx) <= (squareW+rw)*0.5)
&&(Math.abs(squareY-rcy) <= (squareH+rh)*0.5)
){
result = true;
}
return result;
}
}
由於是為lufylegend設計的函式,所以引數為 [ rectObj矩形物件(LSprite或者LShape物件), arcObj圓形物件(LSprite或者LShape物件), rectVec矩形規定大小(可不填), arcR圓形半徑(可不填)] 當然,或許些朋友不懂這幾行程式碼:var rw = rectObj.getWidth()
,rh = rectObj.getHeight()
,ar = arcObj.getWidth()*0.5
,rx = rectObj.x
,ry = rectObj.y
,ax = arcObj.x
,ay = arcObj.y;
好吧,我告訴你,這裡用到的是lufylegend中LSprite和LShape,這兩個類有x、y屬性,還有獲取寬度和高度的getWidth()和getHeight(),這裡看不懂沒關係,你知道是取高度和寬度還有x,y座標的就行了。當然你要深究,那就看看lufylegend.js的API文件吧:http://lufylegend.com/lufylegend/api ,以下測試程式碼也用到了lufylegend.js,據說這個引擎是個不錯的引擎,想了解的同學,去官方網站看看吧:http://lufylegend.com/lufylegend/ 或者看看我的文章,大多數是講解有關lufylegend開發的。示例程式碼:
init(50,"mylegend",500,250,main);
function main(){
LGlobal.setDebug(true);
var back = new LSprite();
back.graphics.drawRect(5,"green",[0,0,LStage.width,LStage.height],true,"lightblue");
addChild(back);
var cObj = new LSprite();
cObj.x = 200;
cObj.y = 120;
cObj.graphics.drawArc(0,"",[0,0,50,0,2*Math.PI],true,"red");
back.addChild(cObj);
var rObj = new LSprite();
rObj.x = 250;
rObj.y = 70;
rObj.alpha = 0.8;
rObj.graphics.drawRect(0,"",[0,0,100,100],true,"green");
back.addChild(rObj);
trace(hitTestRectArc(rObj,cObj));
back.addEventListener(LMouseEvent.MOUSE_DOWN,function(e){
rObj.x = e.offsetX-rObj.getWidth()*0.5;
rObj.y = e.offsetY-rObj.getHeight()*0.5;
trace(hitTestRectArc(rObj,cObj));
});
}
測試連結:http://www.cnblogs.com/yorhom/articles/hitTestRectArc.html三,C++版
C++版我用的是Qt,所以大家執行要在Qt creator裡編譯執行。
HitTestAlg.h裡的程式碼:
#ifndef HITTESTALG_H
#define HITTESTALG_H
#include <math.h>
#include <QPoint>
#include <QRect>
class CMath
{
public:
static int pow(int base, int powerOf)
{
return (int)::pow((double)base, (double)powerOf);
}
static int sqrt(int n)
{
return (int)::sqrt((double)n);
}
static int abs(int n)
{
n = n < 0 ? -n : n;
return n;
}
static int distance(const QPoint& pt1, const QPoint& pt2)
{
return CMath::sqrt(CMath::pow(CMath::abs(pt1.x() - pt2.x()), 2) + CMath::pow(CMath::abs(pt1.y() - pt2.y()), 2));
}
};
class CArc
{
protected:
int m_nRadius;
QPoint m_ptCenter;
public:
CArc() : m_nRadius(0), m_ptCenter(0, 0){}
CArc(const CArc& arc) : m_nRadius(arc.radius()), m_ptCenter(arc.center()){}
CArc(int radius, QPoint center) : m_nRadius(radius), m_ptCenter(center){}
CArc(int radius, int centerX, int centerY) : m_nRadius(radius), m_ptCenter(centerX, centerY){}
~CArc(){}
void setRadius(int radius){m_nRadius = radius;}
int radius() const {return m_nRadius;}
void setCenter(const QPoint& center){m_ptCenter = center;}
void setCenter(int centerX, int centerY){m_ptCenter = QPoint(centerX, centerY);}
QPoint center() const {return m_ptCenter;}
QRect rect() const {return QRect(center().x() - radius(), center().y() - radius(), 2 * radius(), 2 * radius());}
};
class CHitTestAlg
{
protected:
QRect m_rtRect;
CArc m_arArc;
protected:
bool locatedSameQuadrant() const
{
bool bRes = false;
int nRectLeft = m_rtRect.left(), nRectTop = m_rtRect.top(), nRectRight = m_rtRect.right(), nRectBottom = m_rtRect.bottom();
int nArcCenterX = m_arArc.center().x(), nArcCenterY = m_arArc.center().y();
if((nRectLeft - nArcCenterX >= 0 && nRectRight - nArcCenterX >= 0 && nRectTop - nArcCenterY <= 0 && nRectBottom - nArcCenterY <= 0)
|| (nRectLeft - nArcCenterX <= 0 && nRectRight - nArcCenterX <= 0 && nRectTop - nArcCenterY <= 0 && nRectBottom - nArcCenterY <= 0)
|| (nRectLeft - nArcCenterX <= 0 && nRectRight - nArcCenterX <= 0 && nRectTop - nArcCenterY >= 0 && nRectBottom - nArcCenterY >= 0)
|| (nRectLeft - nArcCenterX >= 0 && nRectRight - nArcCenterX >= 0 && nRectTop - nArcCenterY >= 0 && nRectBottom - nArcCenterY >= 0)
){
bRes = true;
}
return bRes;
}
bool hitTestRect() const
{
QRect rtArc = m_arArc.rect();
bool bRes = false;
if(CMath::abs(m_rtRect.center().x() - rtArc.center().x()) <= CMath::abs((m_rtRect.width() + rtArc.width()) / 2)
&& CMath::abs(m_rtRect.center().y() - rtArc.center().y()) <= CMath::abs((m_rtRect.height() + rtArc.height()) / 2)
){
bRes = true;
}
return bRes;
}
bool hitTestAngleArc() const
{
bool bRes = false;
QPoint ptRectTopLeft = m_rtRect.topLeft(), ptRectTopRight = m_rtRect.topRight()
, ptRectBottomLeft = m_rtRect.bottomLeft(), ptRectBottomRight = m_rtRect.bottomRight()
, ptArcCenter = m_arArc.center();
int nArcRadius = m_arArc.radius();
if(CMath::distance(ptRectTopLeft, ptArcCenter) <= nArcRadius
|| CMath::distance(ptRectTopRight, ptArcCenter) <= nArcRadius
|| CMath::distance(ptRectBottomLeft, ptArcCenter) <= nArcRadius
|| CMath::distance(ptRectBottomRight, ptArcCenter) <= nArcRadius
){
bRes = true;
}
return bRes;
}
public:
CHitTestAlg(const QRect& rect, const CArc& arc) : m_rtRect(rect), m_arArc(arc){}
~CHitTestAlg(){}
bool hitTest() const
{
bool bRes = false;
if(locatedSameQuadrant()){
bRes = hitTestAngleArc();
}else{
bRes = hitTestRect();
}
return bRes;
}
};
#endif // HITTESTALG_H
mainwindow.h裡的程式碼:#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
#include "HitTestAlg.h"
class MainWindow : public QWidget
{
Q_OBJECT
protected:
QRect m_rtRect;
CArc m_arArc;
bool m_bHit;
protected:
virtual void mouseReleaseEvent(QMouseEvent *mouseEvent);
virtual void paintEvent(QPaintEvent *paintEvent);
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
};
#endif // MAINWINDOW_H
mainwindow.cpp裡的程式碼:#include <QDebug>
#include <QMouseEvent>
#include <QBrush>
#include <QPainter>
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent)
, m_rtRect(0, 0, 100, 50)
, m_arArc(50, 200, 200)
, m_bHit(false)
{
QWidget::showMaximized();
}
MainWindow::~MainWindow()
{
}
void MainWindow::mouseReleaseEvent(QMouseEvent *mouseEvent)
{
if(mouseEvent){
QPoint ptPos = mouseEvent->pos();
QRect rtRect;
rtRect.setX(ptPos.x() - m_rtRect.width() / 2);
rtRect.setY(ptPos.y() - m_rtRect.height() / 2);
rtRect.setWidth(m_rtRect.width());
rtRect.setHeight(m_rtRect.height());
m_rtRect = rtRect;
m_bHit = CHitTestAlg(m_rtRect, m_arArc).hitTest();
QWidget::update();
}
}
void MainWindow::paintEvent(QPaintEvent *paintEvent)
{
Q_UNUSED(paintEvent)
QPainter xPainter(this);
{
xPainter.save();
QBrush xBrush; xBrush.setColor(Qt::red); xBrush.setStyle(Qt::SolidPattern);
QPen xPen; xPen.setColor(Qt::black); xPen.setStyle(Qt::SolidLine);
xPainter.setBrush(xBrush);
xPainter.setPen(xPen);
xPainter.drawEllipse(m_arArc.center(), m_arArc.radius(), m_arArc.radius());
xPainter.restore();
}
{
xPainter.save();
QBrush xBrush; xBrush.setColor(Qt::darkGreen); xBrush.setStyle(Qt::SolidPattern);
QPen xPen; xPen.setColor(Qt::black); xPen.setStyle(Qt::SolidLine);
xPainter.setBrush(xBrush);
xPainter.setPen(xPen);
xPainter.drawRect(m_rtRect);
xPainter.restore();
}
{
xPainter.save();
QString sContent = QString("Hit Test: %1").arg(m_bHit ? "true" : "false");
QFont ftFont("Tahoma", 12, QFont::DemiBold, true);
xPainter.setFont(ftFont);
xPainter.drawText(20, m_arArc.rect().bottom() + 30, sContent);
xPainter.restore();
}
}
main.cpp裡的程式碼:#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
原理和js版是一樣的,就不多解釋了,在下面我會放出所有程式碼。C++版執行demo如下:
Qt做的介面感覺還是不錯的,哈哈~~
本文就到此為止,以上就是本篇所有內容,歡迎大家交流。
----------------------------------------------------------------
歡迎大家轉載我的文章。
歡迎繼續關注我的部落格