码迷,mamicode.com
首页 > 其他好文 > 详细

游戏底层逻辑,运动&&寻路(四)

时间:2016-04-19 19:48:07      阅读:322      评论:0      收藏:0      [点我收藏+]

标签:

接着上次的来,我们在群体算法之前把基本的个体运动解决掉。

9、WallAvoidance避开墙壁

此处的墙被抽象为一条线段,不论你的游戏使用的是一条线段作为墙面的碰撞检测,或者用一个几何形状作为墙面,几何形状我们可以看作多条线段的集合,都可以用此方法。

墙类的实现

首先是线段类,作为基类,拥有几种几何计算的方法,便于计算平面线段的交点,不多说。

struct Seg
{
    Seg(Point p1, Point p2):
        _from(p1), _to(p2)
    {

    }
#define eps 1e-6
    //static math functions
    static int sgn(double x)
    {
        return x<-eps ? -1 : (x>eps);
    }

    static double Cross(const Point& p1, const Point& p2, const Point& p3, const Point& p4)
    {
        return (p2.x - p1.x)*(p4.y - p3.y) - (p2.y - p1.y)*(p4.x - p3.x);
    }

    static double Area(const Point& p1, const Point& p2, const Point& p3)
    {
        return Cross(p1, p2, p1, p3);
    }

    static double fArea(const Point& p1, const Point& p2, const Point& p3)
    {
        return fabs(Area(p1, p2, p3));
    }

    static bool Meet(const Point& p1, const Point& p2, const Point& p3, const Point& p4)
    {
        return max(min(p1.x, p2.x), min(p3.x, p4.x)) <= min(max(p1.x, p2.x), max(p3.x, p4.x))
            && max(min(p1.y, p2.y), min(p3.y, p4.y)) <= min(max(p1.y, p2.y), max(p3.y, p4.y))
            && sgn(Cross(p3, p2, p3, p4) * Cross(p3, p4, p3, p1)) >= 0
            && sgn(Cross(p1, p4, p1, p2) * Cross(p1, p2, p1, p3)) >= 0;
    }

    static Point Inter(const Point& p1, const Point& p2, const Point& p3, const Point& p4)
    {
        double s1 = fArea(p1, p2, p3), s2 = fArea(p1, p2, p4);
        return Point((p4.x*s1 + p3.x*s2) / (s1 + s2), (p4.y*s1 + p3.y*s2) / (s1 + s2));
    }

    static double PointToSegDist(double x, double y, double x1, double y1, double x2, double y2)
    {
        double cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1);
        if (cross <= 0) return sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));

        double d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
        if (cross >= d2) return sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));

        double r = cross / d2;
        double px = x1 + (x2 - x1) * r;
        double py = y1 + (y2 - y1) * r;
        return sqrt((x - px) * (x - px) + (py - y1) * (py - y1));
    }

    Point _from;
    Point _to;
};

墙类保护继承于线段类,我们并不把Seg类的几何计算方法暴露出来。

class Wall :public BaseEntity,protected Seg//inherite math functions as protected functions
{
public:
    Wall(Vec2, Vec2);
    virtual ~Wall();
public:
    virtual void update();
    virtual bool handleMsg(const Telegram&);
public:
    Vec2 from()const { return _from; }
    Vec2 to()const { return _to; }
    Vec2 normal()const
    {
        Vec2 normalLine(_to.x - _from.x, _from.x - _to.x);
        return (normalLine.getNormalized());
    }
    static bool lineIntersection(const Vec2 entPo, const Vec2 fleer, const Wall wall, double& entity2wall, Vec2& intersection);
};

bool Wall::lineIntersection(const Vec2 entPo, const Vec2 feeler, const Wall wall, double& entity2wall, Vec2& intersection)
{
    if (!Seg::Meet(entPo, feeler, wall.from(), wall.to()))
    {
        return false;
    }
    else
    {
        entity2wall = Seg::PointToSegDist(entPo.x, entPo.y, wall.from().x, wall.from().y, wall.to().x, wall.to().y);
        intersection = Seg::Inter(entPo, feeler, wall.from(), wall.to());
        return true;
    }
}

碰撞的避免

类似于上一篇,这次我们用“触须”来作为辅助图形

在猫科动物运动过程中,触须会帮助它们判断前方物体是否会碰撞,例如洞口能否钻过,此处仿真了这一点。

技术分享

一般我们使用三根触须,触须从实体坐标出发,方向分别为沿朝向,然后两个45°角,沿朝向的长度为两侧的两倍。
触须会提前插入墙壁,产生的斥力作用于交点,斥力大小与插入深度成正比。

触须长度满足这样的规则:
类型一:永不碰撞规则
物体以最大速度运动,垂直于墙壁时,从触须接触墙壁开始,产生的斥力刚好让其在墙壁前停止运动。
m·v=∫f·dt 冲量的积分刚好等于其动量,此处f表示斥力的计算公式,在此处,f=插入墙壁深度。
类型二:允许碰撞
如果允许碰撞,表示你设定的最大速度超过了“安全速度”,我们需要使用基本刚体碰撞来避免其重叠(这步在游戏逻辑层的最后实现)。

触须

我们先来创建触须,添加成员变量:

std::vector<Vec2> _feelers;

double _wallDetectionFeelerLength;
void SteeringBehaviors::createFeelers()
{
    //feeler pointing straight in front
    _feelers[0] = _ownerVehicle->position() + _wallDetectionFeelerLength * _ownerVehicle->heading();

    //feeler to left
    Vec2 temp = _ownerVehicle->heading();
    //Vec2DRotateAroundOrigin(temp, halfPI * 3.5f);
    temp.rotate(Vec2::ZERO, halfPI*3.5f);
    _feelers[1] = _ownerVehicle->position() + _wallDetectionFeelerLength / 2.0f * temp;

    //feeler to right
    temp = _ownerVehicle->heading();
    temp.rotate(Vec2::ZERO, halfPI * 0.5f);
    _feelers[2] = _ownerVehicle->position() + _wallDetectionFeelerLength / 2.0f * temp;
}

其实就是设定好大小和角度而已。

紧接着我们开始计算_wallDetectionFeelerLength

在总时间t内,动量等于冲量的积分:

m.Vmax=t0f(t)dt(1)

在任意时刻t0,速度等于初始速度减加速度的积分:
Vt0=Vmax?t00f(t)/mdt(2)

作用力的表达式(此处定义一个值来配平单位:Da=1m/s3 ):
f(t)=dis=t00Vt0dt(3)

长度的表达式:
L=t0Vt0dt(4)

解出触须长度的表达式即可。
然后我们根据想要实现的效果
(1)允许碰撞:_wallDetectionFeelerLength<L
(2)不允许碰撞:_wallDetectionFeelerLength=L

斥力

说了很多题外话,其实我们游戏开发中远没有那么精确,大部分是经验值,但往往经验值效果会很好。
我们继续来看控制力的计算,我们接下来遍历三条触须与墙面的交点,找出离物体最近的墙面与触须的交点,得出斥力:

Vec2 SteeringBehaviors::wallAvoidance(const std::vector<Wall>& walls)
{
    createFeelers();

    //temporary vars
    double dis2wall = 0.0;

    double dis2Closestwall = max_double;

    int closestWall = -1;

    Vec2 steeringForce = Vec2::ZERO;

    Vec2 point = Vec2::ZERO;

    Vec2 closestPoint = Vec2::ZERO;

    for (int i = 0; i < _feelers.size(); i++)
    {
        for (int j = 0; j < walls.size(); j++)
        {
            if (Wall::lineIntersection(_ownerVehicle->position(), _feelers.at(i), walls.at(j), dis2wall, point))
            {
                if (dis2wall < dis2Closestwall)
                {
                    dis2Closestwall = dis2wall;

                    closestWall = j;

                    closestPoint = point;
                }
            }
        }//next wall
        if (closestWall != -1)
        {
            Vec2 over = _feelers.at(i);

            steeringForce = walls.at(closestWall).normal()*over.getLength();
        }
    }//next feelers

    return steeringForce;
}

10、Hide躲猫猫

物体为了躲避危险,会藏到危险物看不见的地方,就像躲猫猫一样,此处的障碍体我们暂且设定为圆形,以后可以添加墙面等作为优化。

技术分享

如图,我们找出躲藏点hiding:方向=障碍物圆心 - danger的position,位置=障碍物半径 + 定义的固定长度:

#define disFromBoundary 30.0
Vec2 SteeringBehaviors::getHidePosition(const BaseEntity* obstacle,const Vec2 target)
{
    double dis = obstacle->getBoundingRadius() + disFromBoundary;

    Vec2 target2Obstacle = (obstacle->position() - target).getNormalized();

    return (dis*target2Obstacle + obstacle->position());
}

如果障碍物是由墙面构成的几何体,我们重载该方法,将障碍物的圆心替换为几何体中心,其余基本相同,但是arrive方法要考虑到避免碰撞,需要写一个新的组合方法,实际遇到该问题时再来重写。

我们遍历距离物体一定范围内的所有障碍物,找出所有hiding,找出最近的点,靠近它:

Vec2 SteeringBehaviors::hide(const Vehicle* target, const std::vector<BaseEntity*> obstacles)
{
    double disSq2Closest = max_double;
    Vec2 closestHidingSpot = Vec2::ZERO;

    for_each(obstacles.cbegin(), obstacles.cend(), [this,target,&disSq2Closest,&closestHidingSpot](BaseEntity* eachObstacle)
    {
        Vec2 hidingSpot = getHidePosition(eachObstacle, target->position());

        double disSq = (hidingSpot - _ownerVehicle->position()).getLengthSq();

        if (disSq < disSq2Closest)
        {
            disSq2Closest = disSq;

            closestHidingSpot = hidingSpot;
        }
    }
    );
    //end for_each

    if (disSq2Closest == max_double)
    {
        return evade(target);
    }

    return arrive(closestHidingSpot, fast);
}

我们可以加上视觉元素让物体不那么“聪明”(这样会很笨拙),物体只有在看到danger时才会做出hide,只需要在hide方法最开头加一段:

    bool isHide = false;
    for_each(obstacles.cbegin(), obstacles.cend(), [this,target,&isHide](BaseEntity* obs){
        if (Seg::PointToSegDist(obs->position(), Seg(_ownerVehicle->position(), target->position())) < obs->getBoundingRadius())
            isHide = true;
    });
    if (isHide)
        return Vec2::ZERO;
    if (Vec2(_ownerVehicle->position(), target->position()).getLengthSq < _sightDis*_sightDis)
        return Vec2::ZERO;

11、PathFollowing路径跟随

玩过dota,war3,星际等游戏的童鞋应该知道shift操作就是按照你的指定路线操作,此处相同,我们规定path为多条首尾相接的直线的集合,物体会沿着path做循环或不循环的运动。

Path类

typedef std::function<void(Vec2,Vec2)> PointOperater;

class Path
{
private:
    std::list<Vec2> _pointsOfPath;
    std::list<Vec2>::iterator _currentPoint;
    bool _isLooped;
public:
#define two_PI 6.283185
    Path(bool isLooped):
        _isLooped(isLooped)
    {

    }

    Path(int numberOfPoints, Rect limit, bool isLooped):
        _isLooped(isLooped)
    {

    }

    virtual ~Path()
    {

    }

    Path(const Path& other)
    {
        assert(this != &other&&"same");
        this->set(other); 
    }

    Path& operator=(const Path& other)
    {
        assert(this != &other&&"same");
        this->set(other);
        return *this;
    }

public:

    Vec2 currentPoint()const { return *_currentPoint; }

    bool isFinished()const { return _currentPoint == _pointsOfPath.end(); }

    void loopOn(){ this->_isLooped = true; }

    void loopOff(){ this->_isLooped = false; }

    bool isLooped()const { return _isLooped; }

    //methods for setting the path with either another Path or a list of vectors
    void set(std::list<Vec2> new_path){ _pointsOfPath = new_path; _currentPoint = _pointsOfPath.begin(); }
    void set(const Path& path){ _pointsOfPath = path.getPath(); _currentPoint = _pointsOfPath.begin(); }

    void clear(){ _pointsOfPath.clear(); }

    std::list<Vec2> getPath()const{ return _pointsOfPath; }
public:

    void addWayPoint(Vec2 new_point);

    void moveToNextPoint();

    void createRandomPath(int, Rect);

    void pathOperate(PointOperater);//对两个相邻点做回调操作,可以是纹理,动画等
};

void Path::moveToNextPoint()
{
    assert(this->_pointsOfPath.size() != 0 && "NULL");

    if (++_currentPoint == _pointsOfPath.end())
    {
        if (_isLooped)
        {
            _currentPoint = _pointsOfPath.begin();
        }
    }
}

void Path::addWayPoint(Vec2 point)
{
    this->_pointsOfPath.push_back(point);
}

void Path::createRandomPath(int num,Rect limit)
{
    double midX = (limit.getMaxX() + limit.getMinX()) / 2;
    double midY = (limit.getMaxY() + limit.getMinY()) / 2;

    double smallerRadius = std::min(midX, midY);
    double largerRadius = std::max(midX, midY);

    double dDegree = two_PI / num;

    for (int i = 0; i < num; i++)
    {
        Vec2 point = Vec2::ZERO;
        while (limit.containsPoint(point) && point != Vec2::ZERO)
        {
            double randomDegree = Random::Rand((i)*dDegree, (i + 1)*dDegree);

            double randomDis = Random::Rand(0, largerRadius);

            point = (randomDis, 0);
            point.rotateByAngle(Vec2(midX, midY), randomDegree);
        }
        _pointsOfPath.push_back(point);
    }
}

void Path::pathOperate(PointOperater func)
{
    std::list<Vec2>::const_iterator iter = _pointsOfPath.cbegin();

    Vec2 point = *iter++;

    while (iter != _pointsOfPath.end())
    {
        func(point,*iter);
        point = *iter++;
    }

    if (_isLooped)
    {
        func(*(--iter), *_pointsOfPath.cbegin());
    }
}

path类比较简单,只有一个createRandomPath比较麻烦,此处做了近似处理,只有测试的时候可能用一用,所以不用管它。
我为path的遍历添加了回调函数,我们可以其中执行cocos的render渲染等操作。

路径跟随就很简单了,找到最近的路径点,然后沿着点运动

Vec2 SteeringBehaviors::pathFollowing()
{
    //ignore evaluting if it‘ on path
    if (_currentPath->currentPoint().distance(_ownerVehicle->position()) < _pathPointSeekDis*_pathPointSeekDis);
    {
        _currentPath->moveToNextPoint();
    }

    if (!_currentPath->isFinished())
    {
        return seek(_currentPath->currentPoint());
    }
    else
    {
        return arrive(_currentPath->currentPoint(), normal);
    }
}  

准备写一个有关游戏底层算法,物理算法,以及AI(重点是机器学习在游戏中的应用)的长篇博客,欢迎大家指正交流╰( ̄▽ ̄)╯

游戏底层逻辑,运动&&寻路(四)

标签:

原文地址:http://blog.csdn.net/qq_22984991/article/details/51189519

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!