C++实现 JPS算法(Jump Point Search)

JPS算法是A*算法的一种高效实现,主要通过跳点策略减少搜索空间,提高寻路效率。在有障碍的地图中,JPS只考虑具有‘价值’的节点,即强迫邻居节点,从而减少无效计算。算法涉及方向判断、跳点识别以及递归过程,适用于游戏中的角色移动和路径计算。
摘要由CSDN通过智能技术生成

JumpPoint Search算法(JPS算法

JPS算法全称为Jump PointSearch,也就是跳点算法,可以视为A*算法的一种改进算法,它保留了A*算法的主体框架,区别在于:A*算法是将当前节点的所有未访问邻居节点加入openlist,而JPS则是使用一些方法将有“价值”的节点加入openlist,具体方法就是本文的内容。

了解A*算法再来看JPS算法将会事半功倍

原理: 如果p经过x点到达n, 比不经过x点到达n的距离代价都小, 那么n是x点的强迫邻居, x为跳点,

x的父亲为p

  1. 开销

公式: f = gCost + hCost

gCost: 开始点到当前点的实际距离代价

hCos: 当前点到目标点的估计距离代价

f: 总开销

二.方向(8个方向)

分别为右, 右上, 上, 左上, 左, 左下, 下, 右下

图一:

p为openList找出来行走的点

因为x点上方有阻碍物, 并且右上方有强迫邻居x1, 所以x为跳点, x的(方向)向右, x的(父亲)为p, 返回判断是否加入openList, 重新在openList找行走的点

图二:

因为p点右上方和右下方都有强迫邻居,

1. p向右走, x3点有邻居, 所以x3是跳点

所以x1, x2, x3都为跳点, (方向)分别是右上, 右下, 右, (父亲)都为p点

图三:

图四:

如图四, 单个点分析

1.因为p为斜方向的点, 所以有三个方向要走分别是上(l点开始), 右上(d点开始), 右(r点开始),

2.如果d点有强迫邻居返回d点, d点位跳点, d点的(方向)为右上, (父亲)为p, 返回, 如果没有进入第三步

3.那么就需要d点的上方向和右方向判断是否有跳点, 如果有, d点为跳点, d点的方向为右上, (父亲)为p, 返回, 如果没有进入第四步

4.d点向右上方向, 前进一步到达d2, 重复2步骤

如图三

因为p点上, 右上和右都有邻居, 所以p1(方向左上), p3(方向右上), p6(方向右下)都为跳点, (父亲)都为p点, 返回判断是否加入openList 重新在openList找行走的点

图五:

1.因为p点上方向, 左方向堵了, 所以只有p1点左上方向可以做跳点判断

因为p点有两个邻居p2, p3, 所以p1为跳点, 方向左上, 父亲为p, 返回判断是否加入openList, 重新在openList找行走的点

图六:

因为p点有邻居p1, p2, 所以p1(方向左下), p2(方向右上), 都为跳点, 父亲为p点

1.因为p为斜方向的点, 所以有三个方向要走分别是上,左上, 左

2.因为p5有邻居, 所以p5为跳点(方向左)

2.因为p3左方向p4有邻居, 所以p3为跳点(方向左上)

所以p1(方向左下), p2(方向右上), p5(方向左), p3(方向左上)都为跳点, (父亲)都为p点

代码:

//enum 方向              右   右上 上  左上 左  左下 下  右下
const int32 DirX[8] = { 1,  1,  0, -1, -1, -1,  0,  1 };
const int32 DirY[8] = { 0,  1,  1,  1,  0, -1, -1, -1 };

1.跳点算法获取最短的开销路径:

void JpsFind::CalcPath()
    {
        while( !openL_.empty() )
        {
            NodePtr current = openL_.front();
            if( !current )
                return;

            if( endNode_->x == current->x && endNode_->y == current->y )
            {
                endNode_ = current;
                return;
            }

            map_mgr_.TryViewEveryPointPath(current);//(忽略,该函数用于展示轨迹)

            openL_.pop_front();
            closeL_.insert((current->x<<16)|current->y);

            std::vector<NodePtr> nL = GetSuccessJumpNodeL(current);
            if( !nL.empty() )
            {
                for( const auto& m : nL )
                {
                    if( FindByCloseL(m) )//是否在closeList
                        continue;
                    
                    NodePtr is_open = FindByOpenL(m);

                    int32 newGcost = current->gCost + GetDistance(current->x, current->y, m->x, m->y);
                    if( newGcost < m->gCost || !is_open )
                    {
                        m->gCost = newGcost;
                        m->hCost = GetDistance(m->x, m->y, endNode_->x, endNode_->y);
                        m->parent = current;//设置父亲归属
                        if( !is_open )
                        {
                            AddToOpenL(m);
                        }
                    }
                }
            }
        }
    }

  1. 获取当前点的有关跳点列表

std::vector<NodePtr> JpsFind::GetSuccessJumpNodeL( const NodePtr& node )
    {
        std::vector<NodePtr> nL = GetForceNodeL(node);//强迫邻居
        
        for( const auto& m : GetNeighbours(node) )//当前点可以行走的方向
        {
            NodePtr jps_node = Jump(m, m->dir);//递归行走点, 返回跳点
            if( !jps_node )
                continue;

            nL.push_back(jps_node);
        }

        return nL;
    }

  1. 递归返回对应跳点

NodePtr JpsFind::Jump( const NodePtr& node, int32 dir )
    {
        map_mgr_.TryViewEveryPointPath(node);//(忽略,该函数用于展示轨迹)

        if( !node || !Walk(node->x, node->y) )//是否可行走
            return NodePtr();

        if( node->x == endNode_->x && node->y == endNode_->y )
        {
            jpsN_ = true;
            return node;
        }

        jpsN_ = false;//用于判断斜线方向是否有跳点

        if( dir%2 == 1 )
        {
            return JumpInclined(node, dir);//斜线方向
        }

        return JumpStr(node, dir);
    }
  1. 斜线方向递归

NodePtr JpsFind::JumpInclined( const NodePtr& node, int32 dir )
    {
        //斜方向
        int32 l_dir = (dir + 3) % 8;
        int32 r_dir = (dir + 5) % 8;
        //斜线交叉方向是否墙
        bool l_force_ = Walk(node->x + DirX[l_dir], node->y + DirY[l_dir]);
        bool r_force_ = Walk(node->x + DirX[r_dir], node->y + DirY[r_dir]);

        if( !l_force_ || !r_force_ )
        {//斜线方向有强迫邻居
            l_dir = (dir + 2) % 8;
            r_dir = (dir + 6) % 8;
            if( !l_force_ && Walk(node->x + DirX[l_dir], node->y + DirY[l_dir]) )
            {
                jpsN_ = true;
                return node;
            }

            if( !r_force_ && Walk(node->x + DirX[r_dir], node->y + DirY[r_dir]) )
            {
                jpsN_ = true;
                return node;
            }
        }

        //斜线两个方向是否有跳点
        l_dir = (dir + 1) % 8;
        r_dir = (dir + 7) % 8;

        bool l_force = false;
        bool r_force = false;
        NodePtr l_node = GetNode(node->x + DirX[l_dir], node->y + DirY[l_dir]);

        if( l_node && l_node->dir == -1 )
            Jump(l_node, l_dir);
        l_force = jpsN_;
        
        NodePtr r_node = GetNode(node->x + DirX[r_dir], node->y + DirY[r_dir]);
        if( r_node && r_node->dir == -1 )
            Jump(r_node, r_dir);
        r_force = jpsN_;

        if( l_force || r_force )
        {
            return node;
        }

        NodePtr next_node = GetNode(node->x + DirX[dir], node->y + DirY[dir]);
        if( next_node )
            next_node->dir = dir;
        
        return Jump(next_node, dir);
    }
  1. 直线方向递归

NodePtr JpsFind::JumpStr( const NodePtr& node, int32 dir )
    {
        //直线方向 上下左右
        int32 l_dir = (dir + 2) % 8;
        int32 r_dir = (dir + 6) % 8;
        bool l_force_ = Walk(node->x + DirX[l_dir], node->y + DirY[l_dir]);
        bool r_force_ = Walk(node->x + DirX[r_dir], node->y + DirY[r_dir]);
        
        if( !l_force_ || !r_force_ )
        {
            int32 l_force_dir = (dir + 1) % 8;
            int32 r_force_dir = (dir + 7) % 8;

            bool left_next  = Walk(node->x + DirX[l_force_dir], node->y + DirY[l_force_dir]);
            bool right_next = Walk(node->x + DirX[r_force_dir], node->y + DirY[r_force_dir]);

            if( !l_force_ && left_next )
            {
                jpsN_ = true;
                return node;
            }

            
            if( !r_force_ && right_next )
            {
                jpsN_ = true;
                return node;
            }
        }

        NodePtr next_node = GetNode(node->x + DirX[dir], node->y + DirY[dir]);
        if( next_node )
            next_node->dir = dir;
        
        return Jump(next_node, dir);
    }

完整效果图:

完整跳点图

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值