首页 > 代码库 > C++多小球非对心弹性碰撞(HGE引擎)

C++多小球非对心弹性碰撞(HGE引擎)

程序是一个月前完成的,之前一直没正儿八经的来整理下这个程序,感觉比较简单,不过即使简单的东西也要跟大家分享下。
源码下载:http://download.csdn.net/detail/y85171642/7209727


开篇
    开始上代码之前我先说下我为啥写的这个程序,大三的时候学习C#接触过GDI+而发现原来做图形界面的程序也可以这么简单。之后便开始用GDI+做起动画、游戏等,其中便有一个模拟多小球碰撞的,在CSDN上有分享过(http://pan.baidu.com/s/1qWjTkmS),不过现在回头想想那个程序还是存在很多问题的,比如:没考虑非对心碰撞问题,或者说是球体斜碰问题,帧率低问题,碰撞检测精度不够导致小球粘连问题(使用的是球体像素整型)。说到这里想必使用过GDI+的同学都会有一个想法就是GDI+用来做游戏效率确实不行。于是呢转战C++,正好自己的毕设要写一个C++ 2D游戏,所以在写游戏项目之余,便花费一天时间写了这个模拟球体“弹性斜碰”程序。至于为什么要花这么长时间,是因为大多数时间我都花在了这个“斜碰”问题上,这也是我要写这篇博客的主要原因,网上关于小球弹性斜碰的程序确实很少。不信可以百度下。
上代码
模拟小球碰撞嘛,所以小球的实体类是必须的(Ball.h)
#pragma once
#include <math.h>
#include "hge.h"
class CBall
{
public:
    CBall();
    CBall(float _x,float _y,float _speedX ,float _speedY,float _radius ,DWORD _color,float _density = 1.0f);
    ~CBall(){};
public:
    bool IsCollision(CBall *ball,float dt);  //碰撞检测
    void CollisionWith(CBall *ball);         //弹性正碰
    void CollisionWith2(CBall *ball);        //弹性斜碰
    void SwapColor(CBall *ball);             //好玩点,加个交换颜色
    void MoveNext(float dt,float _width,float _height);

//由于程序不大,方便起见所有就都public了
public:
    float x;        //x轴坐标
    float y;        //y轴坐标
    float speed_x;  //x轴方向速度
    float speed_y;  //x轴方向速度
    float radius;   //球体半径
    float density;  //密度
    float weight;   //质量
    DWORD color;    //混合颜色
};

从Ball的类成员定义中可以看出来,球体的大概属性基本包括全面了,不过多了个Color这个属性是用来设置球体颜色的,因为图片素材是纯白实心圆,在使用color颜色进行顶点着色时候可直接设置获得该颜色的圆。
下面依次说下各个成员方法的作用,部分有代码。

构造方法CBall(float _x,float _y,float ... ,float _density = 1.0f);对成员变量初始化,需要注意,密度默认是1.0f,而质量通过体积和密度的计算求得。【球体质量 m=ρ*v , v = 4/3*π*r^3】。

碰撞检测,返回当前对象时候与参数ball发生碰撞,dt是一帧的时间。

bool CBall::IsCollision(CBall *ball,float dt)
{
    //计算的是下一刻的位置,以免发生粘连
    float disX = (this->x+this->speed_x*dt)-(ball->x+ball->speed_x*dt);
    float disY = (this->y+this->speed_y*dt)-(ball->y+ball->speed_y*dt);
    float dis = sqrt(disX*disX+disY*disY);
    //判断下一刻是否 发生碰撞
    if(dis < this->radius+ball->radius)
        return true;
    return false;
}

弹性正碰 void CBall::CollisionWith(CBall *ball)根据动能定理和动量守恒定律得出公式
    //弹性正碰撞公式
    //v1‘ = [ (m1-m2)*v1 + 2*m2*v2 ] / (m1+m2)
    //v2‘ = [ (m2-m1)*v2 + 2*m1*v1 ] / (m1+m2)
碰撞后,两小球的速度,方向,会发生改变。

弹性斜碰,文章的主角,由于计算比较复杂所以需要用到向量(Vector)。
void CBall::CollisionWith2(CBall *ball)
{
    //参考资料:
    //http://www.cnblogs.com/kenkofox/archive/2011/09/06/2168944.html
    //http://tina0152.blog.163.com/blog/static/119447958200910229109326/

    //球心点
    float x1 = this->x ;
    float y1 = this->y ;
    float x2 = ball->x ;
    float y2 = ball->y ;

    //碰撞处切平面向量t,及其法向量s
    hgeVector s(x2-x1, y2-y1);
    s.Normalize();//标准化矢量
    hgeVector t(x2-x1, y2-y1);
    t.Rotate(3.1415926f/2);
    t.Normalize();

    //速度向量
    hgeVector v1(this->speed_x,this->speed_y);
    hgeVector v2(ball->speed_x,ball->speed_y);

    //先算v1(v1x, v1y)在s和t轴的投影值,分别设为v1s和v1t
    //再算v2(v2x, v2y)在s和t轴的投影值,分别设为v2s和v2t:
    float v1s = v1.Dot(&s);
    float v1t = v1.Dot(&t);
    float v2s = v2.Dot(&s);
    float v2t = v2.Dot(&t);

    //转换后于s向量上的弹性正碰撞。质量不等
    //弹性正碰撞公式
    //v1‘ = [ (m1-m2)*v1 + 2*m2*v2 ] / (m1+m2)
    //v2‘ = [ (m2-m1)*v2 + 2*m1*v1 ] / (m1+m2)
    float m1 = this->weight;
    float m2 = ball->weight;

    float temp_v1s = ((m1-m2)*v1s + 2*m2*v2s )/ (m1+m2);
    v2s = ((m2-m1)*v2s + 2*m1*v1s )/ (m1+m2);
    v1s = temp_v1s;

    //首先求出v1t和v2t在t轴的向量v1t‘和v2t‘(将数值变为向量)
    //再求出v1s‘和v2s‘在s轴的向量v1s‘和v2s‘(将数值变为向量)
    hgeVector v1tVector = t*v1t;
    hgeVector v1sVector = s*v1s;
    hgeVector v2tVector = t*v2t;
    hgeVector v2sVector = s*v2s;

    //新速度矢量
    hgeVector v1_new = v1tVector+v1sVector;
    hgeVector v2_new = v2tVector+v2sVector;

    //划分成x,y方向分量速度
    this->speed_x = v1_new.x;
    this->speed_y = v1_new.y;
    ball->speed_x = v2_new.x;
    ball->speed_y = v2_new.y;
}

交换颜色,发生碰撞后的两个小球进行颜色交换,纯属娱乐。
void CBall::SwapColor(CBall *ball)

小球移动,参数 _width,_height分别是窗口的宽高,因为这个方法内包含边界碰撞的检测和反弹。
void CBall::MoveNext(float dt,float _width,float _height)
{
    float moveX = speed_x*dt;
    float moveY = speed_y*dt;
    //x方向边界
    if (x+moveX<radius||x+moveX>_width-radius)
        speed_x = -speed_x;
    //Y方向边界
    if(y+moveY<radius||y+moveY>_height-radius)
        speed_y = -speed_y;

    x+=speed_x*dt;
    y+=speed_y*dt;
}

主函数,使用HGE引擎,看注释吧这里不详细说了。
(HGE是一个小型的2D游戏引擎,基于DirectX8,相关学习资料下载:http://pan.baidu.com/s/1dDtdd2h)
#include "hge.h"
#include "hgesprite.h"
#include "hgefont.h"
#include "hgeparticle.h"
#include "Ball.h"
#include "hgecommon.h"

//宏定义
#define WIND_WIDTH  800
#define WIND_HEIGHT 600

HGE *hge=0;

hgeFont                *fnt;//字体
HEFFECT                snd;//音频
HTEXTURE            tex;//加载气泡纹理,即图片
hgeSprite            *spr_ball;//气泡精灵

//函数声明
bool LoadRes();
bool InitData();

bool FrameFunc();//回调函数
bool RenderFunc();

//抽象对象Balls
CBall *balls[16];

//全局变量
int g_nBalls = 5;//小球的个数
bool g_bCollision = false;//判断一帧中是否有碰撞

DWORD g_alpha = 0xA0FFFFFF;//设置Alpha透明度,与操作
DWORD g_colors[16] = {g_alpha&0xFFFFFFFF/*纯白色*/,
    g_alpha&0xFFFF0000/*红*/,g_alpha&0xFF00FF00/*绿*/,g_alpha&0xFF0000FF/*蓝*/,
    g_alpha&0xFFFFFF00/*红绿*/,g_alpha&0xFFFF00FF/*红蓝*/,g_alpha&0xFF00FFFF/*绿蓝*/,
    g_alpha&0xFF790000/*淡红*/,g_alpha&0xFF007900/*淡绿*/,g_alpha&0xFF000079/*淡蓝*/,
    g_alpha&0xFFFF7900/*红淡绿*/,g_alpha&0xFFFF0079/*红淡蓝*/,
    g_alpha&0xFF79FF00/*绿淡红*/,g_alpha&0xFF00FF79/*绿淡蓝*/,
    g_alpha&0xFF7900FF/*蓝淡红*/,g_alpha&0xFF0079FF/*蓝淡绿*/};


int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    hge = hgeCreate(HGE_VERSION);

    hge->System_SetState(HGE_FRAMEFUNC, FrameFunc);
    hge->System_SetState(HGE_RENDERFUNC, RenderFunc);
    hge->System_SetState(HGE_TITLE, "Balls Collision");
    hge->System_SetState(HGE_FPS, 100);
    hge->System_SetState(HGE_WINDOWED, true);
    hge->System_SetState(HGE_HIDEMOUSE,false);
    hge->System_SetState(HGE_SCREENWIDTH, WIND_WIDTH);
    hge->System_SetState(HGE_SCREENHEIGHT, WIND_HEIGHT);
    hge->System_SetState(HGE_SCREENBPP, 32);

    if(hge->System_Initiate()) 
    {
        //加载资源
        if (!LoadRes()||!InitData())
        {
            //加载资源失败
            hge->System_Shutdown();
            hge->Release();
        }

        // Let‘s rock now!,进入消息循环
        hge->System_Start();

        // 直到任意回调函数返回true时,停止消息循环
        delete fnt;
        delete spr_ball;
        hge->Texture_Free(tex);
        hge->Effect_Free(snd);
    }
    // Clean up and shutdown
    hge->System_Shutdown();
    hge->Release();
    return 0;
}

bool FrameFunc()
{
    float dt=hge->Timer_GetDelta();

    // Process keys 
    if (hge->Input_GetKeyState(HGEK_ESCAPE)) return true;

    //碰撞检测
    for (int i= 0;i<g_nBalls;i++)
    {
        for(int j = i+1;j<g_nBalls;j++)
        {
            if(balls[i]->IsCollision(balls[j],dt))
            {
                balls[i]->CollisionWith2(balls[j]);
                balls[i]->SwapColor(balls[j]);
                g_bCollision = true;
            }
        }
    }

    //撞击声音
    if (g_bCollision)
    {
        hge->Effect_Play(snd);
        g_bCollision = false;
    }

    //balls move
    for (int i=0;i<g_nBalls;i++)
    {
        balls[i]->MoveNext(dt,WIND_WIDTH,WIND_HEIGHT);
    }

    return false;
}

bool RenderFunc()
{
    // 开始绘图
    hge->Gfx_BeginScene();

    //以0X00000000颜色清屏
    hge->Gfx_Clear(0);

    //绘制
    for (int i=0;i<g_nBalls;i++)
    {
        spr_ball->SetColor(balls[i]->color);
        float temp_scale = 2*balls[i]->radius/spr_ball->GetWidth();
        spr_ball->RenderEx(balls[i]->x,balls[i]->y,0,temp_scale,0);

        //绘制方向线,与球速度有关,顶点位置为其以当前速度下一秒钟的位置
        hge->Gfx_RenderLine(balls[i]->x,balls[i]->y,balls[i]->x+balls[i]->speed_x,balls[i]->y+balls[i]->speed_y,0xFFFF0000);
    }

    //fps
    fnt->printf(5, 5, HGETEXT_LEFT, "FPS:%d (constant)\ndt:%.3f",hge->Timer_GetFPS(),hge->Timer_GetDelta());

    //end
    hge->Gfx_EndScene();
    return false;
}

bool InitData()
{
    for(int i=1;i<=g_nBalls;i++)
    {
        float temp_ratio = 16.0f/g_nBalls;
        balls[i-1]= new CBall(150.f*(i%4+1), 100.f*(i/4+1), 
            80+i*5*temp_ratio, 70, 20+i*2*temp_ratio, g_colors[i%16]);
    }

    return true;
}

bool LoadRes()
{
    fnt = new hgeFont("resource/font1.fnt");
    tex = hge->Texture_Load("resource/ball.png");
    snd=hge->Effect_Load("resource/Collision.wav");

    spr_ball = new hgeSprite(tex,0,0,256,256);
    spr_ball->SetHotSpot(spr_ball->GetWidth()/2,spr_ball->GetHeight()/2);
    spr_ball->SetBlendMode(BLEND_COLORMUL|BLEND_ALPHABLEND|BLEND_NOZWRITE);
    spr_ball->SetColor(0xA00000FF);

    if(!fnt || !tex || !spr_ball || !snd)
        return false;
    return true;
}

结束语
大四快毕业了,现在才发现大学期间学的确实太少,玩的太多(DotAer哈哈),好多感慨,都化成两个字“时间”。回想起当初刚上大一的时候,拿着书本照着上面一字不差的敲着C程序代码,直到程序运行出来还不能理解程序是怎么个原理,怎么实现的,不过还好“时间”告诉了我。从大一学的枯燥只能用来ACM的C/C++到大二终于见到图形界面却又笨拙而不适合图形界面开发的Java再到大三时让图形界面开发简单至极却又低效无法满足游戏开发的C#到最后又回到了最初那个高效而复杂的让人抓狂的C++,就这样走完了大学四年。同学现在有不少出去工作了的,不过给我的感觉就是大家好像都是一知半解,拿着刚好养活自己的工资,在不断地学习着,不过期待同学们都能找到自己满意的工作,也希望自己找工作能顺利。写这篇文章是还在考虑要不要参加蓝桥杯的决赛,毕竟我都大四了,而且最近的事情会好多。不好意思思路有点断篇。言归正传,毕设在做一个“仿《保卫萝卜》塔防游戏”,框架基本上搭建差不多了,还有部分需要完善的地方,在此先发个不完整版,http://pan.baidu.com/s/1dDEbK4L,框架仅供参考。毕竟我是菜鸟级的,很多东西都一知半解,所以程序有什么不妥的地方还望不吝提出。先谢谢了!

程序源码下载:http://download.csdn.net/detail/y85171642/7209727