cocos creator基礎-(二十八)creator網格導航尋路


1、插件或者TileMap工具生成地圖json文件

2、astar尋路算法(終點 詳情請看JS學習內的文章)

3、將json文件與尋路算法結合,獲得路徑坐標,並轉化為游戲內的實際坐標

 

// astar算法 模仿blake老師的寫法,用來熟悉算法

var map_maze = []; // 場景節點順序保存數組
var open_table = []; // 開啟列表
var close_table = []; // 關閉列表
var path_stack = []; // 保存路徑

var is_found = 0; // 是否找到路徑 1 true 0 false
var open_node_count = 0; // 開啟列表元素個數
var close_node_count = 0; // 關閉列表元素個數
var top = -1; // path_stack從后往前變量指針

var map_height = 0; //地圖高度
var map_width = 0; // 地圖寬度
var BARRIER = 1; // 阻擋標記

function swap(idx1, idx2) {
    var tmp = open_table[idx1];
    open_table[idx1] = open_table[idx2];
    open_table[idx2] = tmp;
}

function adjust_heap(nIndex){
    var curr = nIndex;
    var child = curr * 2 - 1; // 得到左孩子idx( 下標從0開始,所有做孩子是curr*2+1 )
    var parent = Math.floor((curr - 1) / 2); // 得到雙親idx  
    if(nIndex < 0 || nIndex >= open_node_count){
        return;
    }

    // 往下調整( 要比較左右孩子和cuur parent )  
    while(child < open_node_count){
        if(child + 1 < open_node_count && 
            open_table[child].s_g + open_table[child].s_h > open_table[child + 1].s_g + open_table[child + 1].s_h){
            ++child; // 判斷左右孩子大小  
        }

        if (open_table[curr].s_g + open_table[curr].s_h <= open_table[child].s_g + open_table[child].s_h) {
            break;
        }else{
            swap(child, curr); // 交換節點  
            curr = child;// 再判斷當前孩子節點  
            child = curr * 2 + 1; // 再判斷左孩子  
        }
    }

    if (curr != nIndex) {
        return;
    }

    // 往上調整( 只需要比較cuur child和parent )  
    while (curr != 0) {
        if (open_table[curr].s_g + open_table[curr].s_h >= open_table[parent].s_g + open_table[parent].s_h) {
            break;
        } else {
            swap(curr, parent);
            curr = parent;
            parent = Math.floor((curr - 1) / 2);
        }
    }
}

function insert_to_opentable(x, y, curr_node, end_node, w){ // w損耗
    var i;
    if (map_maze[x * map_width + y].s_style != BARRIER){ // 不是障礙物 
        if (!map_maze[x * map_width + y].s_is_in_closetable){ // 不在閉表中  
            if (map_maze[x * map_width + y].s_is_in_opentable){ // 在open表中 
                // 需要判斷是否是一條更優化的路徑  
                // 檢查如果用新的路徑 (就是經過C 的路徑) 到達它的話, G值是否會更低一些, 
                // 如果新的G值更低, 那就把它的 "父方格" 改為目前選中的方格 C, 
                // 然后重新計算它的 F 值和 G 值 (H 值不需要重新計算, 因為對於每個方塊, H 值是不變的). 
                // 如果新的 G 值比較高, 就說明經過 C 再到達 D 不是一個明智的選擇, 因為它需要更遠的路, 這時我們什么也不做.
                if (map_maze[x * map_width + y].s_g > curr_node.s_g + w){ //如果更優化
                    map_maze[x * map_width + y].s_g = curr_node.s_g + w;
                    map_maze[x * map_width + y].s_parent = curr_node;

                    for (i = 0; i < open_node_count; ++i) {
                        if (open_table[i].s_x == map_maze[x * map_width + y].s_x && open_table[i].s_y == map_maze[x * map_width + y].s_y) {
                            break;
                        }
                    }

                    adjust_heap(i); // 下面調整點  
                }
            }else{// 不在open中  
                map_maze[x * map_width + y].s_g = curr_node.s_g + w;
                map_maze[x * map_width + y].s_h = Math.abs(end_node.s_x - x) + Math.abs(end_node.s_y - y);
                map_maze[x * map_width + y].s_parent = curr_node;
                map_maze[x * map_width + y].s_is_in_opentable = 1;
                open_table[open_node_count++] = (map_maze[x * map_width + y]);
            }
        }
    }  
}

// 鄰居處理
function get_neighbors(curr_node, end_node) {
    var x = curr_node.s_x;
    var y = curr_node.s_y;

    // 下面對於8個鄰居進行處理!  
    //  直線損耗10 斜線損耗14
    if ((x + 1) >= 0 && (x + 1) < map_height && y >= 0 && y < map_width) {
        insert_to_opentable(x + 1, y, curr_node, end_node, 10);
    }

    if ((x - 1) >= 0 && (x - 1) < map_height && y >= 0 && y < map_width) {
        insert_to_opentable(x - 1, y, curr_node, end_node, 10);
    }

    if (x >= 0 && x < map_height && (y + 1) >= 0 && (y + 1) < map_width) {
        insert_to_opentable(x, y + 1, curr_node, end_node, 10);
    }

    if (x >= 0 && x < map_height && (y - 1) >= 0 && (y - 1) < map_width) {
        insert_to_opentable(x, y - 1, curr_node, end_node, 10);
    }

    if ((x + 1) >= 0 && (x + 1) < map_height && (y + 1) >= 0 && (y + 1) < map_width) {
        insert_to_opentable(x + 1, y + 1, curr_node, end_node, 10 + 4);
    }

    if ((x + 1) >= 0 && (x + 1) < map_height && (y - 1) >= 0 && (y - 1) < map_width) {
        insert_to_opentable(x + 1, y - 1, curr_node, end_node, 10 + 4);
    }

    if ((x - 1) >= 0 && (x - 1) < map_height && (y + 1) >= 0 && (y + 1) < map_width) {
        insert_to_opentable(x - 1, y + 1, curr_node, end_node, 10 + 4);
    }

    if ((x - 1) >= 0 && (x - 1) < map_height && (y - 1) >= 0 && (y - 1) < map_width) {
        insert_to_opentable(x - 1, y - 1, curr_node, end_node, 10 + 4);
    }
}

// 0. 初始化01地圖

// 1. 從起點A開始, 把它作為待處理的方格存入一個"開啟列表", 開啟列表就是一個等待檢查方格的列表.

// 2. 尋找起點A周圍可以到達的方格, 將它們放入"開啟列表", 並設置它們的"父方格"為A.

// 3. 從"開啟列表"中刪除起點 A, 並將起點 A 加入"關閉列表", "關閉列表"中存放的都是不需要再次檢查的方格

// 4. 從 "開啟列表" 中選擇 F 值最低的方格 C (綠色起始方塊 A 右邊的方塊),檢查它所有相鄰並且可以到達 (障礙物和 "關閉列表" 的方格都不考慮) 的方格. 如果這些方格還不在 "開啟列表" 里的話, 將它們加入 "開啟列表", 計算這些方格的 G, H 和 F 值各是多少, 並設置它們的 "父方格" 為 C.

// 5. 如果某個相鄰方格 D 已經在 "開啟列表" 里了, 檢查如果用新的路徑 (就是經過C 的路徑) 到達它的話, G值是否會更低一些, 如果新的G值更低, 那就把它的 "父方格" 改為目前選中的方格 C, 然后重新計算它的 F 值和 G 值 (H 值不需要重新計算, 因為對於每個方塊, H 值是不變的). 如果新的 G 值比較高, 就說明經過 C 再到達 D 不是一個明智的選擇, 因為它需要更遠的路, 這時我們什么也不做.

// 就這樣, 我們從 "開啟列表" 找出 F 值最小的, 將它從 "開啟列表" 中移掉, 添加到 "關閉列表". 再繼續找出它周圍可以到達的方塊, 如此循環下去...

// 那么什么時候停止呢? —— 當我們發現 "開始列表" 里出現了目標終點方塊的時候, 說明路徑已經被找到.

// 最后以終點為起點通過 "父方塊" 可以依次索引到最初的 "起始方塊", 這樣就得到了路徑


// 0.(此處是否可優化下map_maze的初始化,不必每次都清空push一次???)
function astar_init(map){
    open_table = [];
    close_table = [];
    path_stack = [];
    map_maze = [];

    map_height = map.height;
    map_width = map.width;

    is_found = 0;
    open_node_count = 0;
    close_node_count = 0;
    top = -1;

    for (var i = 0; i < map.length; i++){
        for(var j = 0; j < map.width; j++){
            var node = {};
            // F = G + H 其中,F 是從起點經過該點到終點的總路程,G 為起點到該點的“已走路程”,H 為該點到終點的“預計路程”。
            node.s_g = 0; // g值
            node.s_h = 0;
            node.s_is_in_closetable = 0;
            node.s_is_in_opentable = 0;
            node.s_style = map.data[i * map.width + j]; // 數據類型 0 1
            node.s_x = i;
            node.s_y = j;
            node.s_parent = null;
            map_maze.push(node);

            path_stack.push(null);
            open_table.push(null);
            close_table.push(null);
        }
    }
}

// 1. A*核心代碼
function astar_search(map, src_x, src_y, dst_x, dst_y){
    var path = [];
    if(src_x == dst_x && src_y == dst_y){
        console.log("起點==終點!");
        return path;
    }

    // 初始化map
    astar_init(map);

    //1. 從起點A開始, 把它作為待處理的方格存入一個"開啟列表", 開啟列表就是一個等待檢查方格的列表.
    var start_node = map_maze[src_y * map.width + src_x];
    var end_node = map_maze[dst_y * map.width + dst_x];
    var curr_node = null;

    open_table[open_node_count++] = start_node;

    start_node.s_is_in_opentable = 1; // 加入open表
    start_node.s_g = 0;
    // 曼哈頓距離
    start_node.s_h = Math.abs(end_node.s_x - start_node.s_x) + Math.abs(end_node.s_y - start_node.s_y);
    start_node.s_parent = null;

    is_found = 0;

    while(1){
        curr_node = open_table[0]; // open表的第一個點一定是f值最小的點(通過堆排序得到的)  
        open_table[0] = open_table[--open_node_count]; // 最后一個點放到第一個點,然后進行堆調整  
        adjust_heap(0); // 調整堆

        close_table[close_node_count++] = curr_node; // 當前點加入close表  
        curr_node.s_is_in_closetable = 1; // 已經在close表中了 

        if (curr_node.s_x == end_node.s_x && curr_node.s_y == end_node.s_y) // 終點在close中,結束  
        {
            is_found = 1;
            break;
        }

        get_neighbors(curr_node, end_node); // 對鄰居的處理  


        if (open_node_count == 0) // 沒有路徑到達  
        {
            is_found = 0;
            break;
        }
    }    

    if(is_found){
        curr_node = end_node;

        while(curr_node){
            path_stack[++top] = curr_node;
            curr_node = curr_node.s_parent;
        }

        while (top >= 0) // 下面是輸出路徑看看~  
        {
            console.log(path_stack[top].s_y, path_stack[top].s_x);
            path.push(cc.v2(path_stack[top].s_y, path_stack[top].s_x));
            top--;
        }
    }else{
        console.log("么有找到路徑");    
    }
    return path;
}

module.exports = {
    search: astar_search,
};

// nav_map.js 將路徑轉換為游戲內的坐標
var astar = require("astar");

cc.Class({
    extends: cc.Component,

    properties: {
        // foo: {
        //    default: null,      // The default value will be used only when the component attaching
        //                           to a node for the first time
        //    url: cc.Texture2D,  // optional, default is typeof default
        //    serializable: true, // optional, default is true
        //    visible: true,      // optional, default is true
        //    displayName: 'Foo', // optional
        //    readonly: false,    // optional, default is false
        // },
        // ...
        is_debug: true,
    },

    onLoad: function(){
        var newNode = new cc.Node();

        this.new_draw_node = newNode.addComponent(cc.Graphics);
        this.node.addChild(newNode);

        this.new_draw_node.lineWidth = 2;

        this.color1 = new cc.Color(0, 255, 0, 255);
        this.color2 = new cc.Color(0, 0, 255, 255);
    },

    map_degbu_draw: function() {
        var x_line = this.map.item_size * 0.5;
        var ypos = this.map.item_size * 0.5;


        this.new_draw_node.clear();

        for (var i = 0; i < this.map.height; i++) {
            var xpos = x_line;
            for (var j = 0; j < this.map.width; j++) {
                if (this.map.data[i * this.map.width + j] === 0) {

                    this.new_draw_node.strokeColor = this.color1;
                    this.new_draw_node.moveTo(xpos, ypos);
                    this.new_draw_node.lineTo(xpos + 1, ypos + 1);
                    this.new_draw_node.stroke();
                } else {


                    this.new_draw_node.strokeColor = this.color2;
                    this.new_draw_node.moveTo(xpos, ypos);
                    this.new_draw_node.lineTo(xpos + 1, ypos + 1);
                    this.new_draw_node.stroke();
                }

                xpos += this.map.item_size;
            }

            ypos += this.map.item_size;
        }

    },

    start: function(){
        this.map = require("game_map_" + this.node.name);
        if (this.is_debug) {
            this.map_degbu_draw();
        }
    },

    astar_search: function(src_w, dst_W){
        var src = this.node.convertToNodeSpaceAR(src_w);
        var dst = this.node.convertToNodeSpaceAR(dst_w);

        var src_mx = Math.floor((src.x) / this.map.item_size);
        var src_my = Math.floor((src.y) / this.map.item_size);

        var dst_mx = Math.floor((dst.x) / this.map.item_size);
        var dst_my = Math.floor((dst.y) / this.map.item_size);        

        var path = astar.search(this.map, src_mx, src_my, dst_mx, dst_my);

        var world_offset = this.node.convertToWorldSpaceAR(cc.v2(this.map.item_size * 0.5, this.map.item_size * 0.5));
           
           var path_pos = [];
           for(var i = 0; i < path.length; i++){
               var x = path[i].x * this.map.item_size;
               var y = path[i].y * this.map.item_size;

               var pos = cc.v2(world_offset.x + x, world_offset.y + y);
               path_pos.push(pos);
           }
           return path_pos;
    }
});

 

// nav_agent.js 尋路的實際應用
var nav_map = require("nav_map");

var State = {
    Idle: 0,
    Walk: 1,
};

cc.Class({
    extends: cc.Component,

    properties: {
        // foo: {
        //    default: null,      // The default value will be used only when the component attaching
        //                           to a node for the first time
        //    url: cc.Texture2D,  // optional, default is typeof default
        //    serializable: true, // optional, default is true
        //    visible: true,      // optional, default is true
        //    displayName: 'Foo', // optional
        //    readonly: false,    // optional, default is false
        // },
        // ...
        speed: 100,

        game_map: {
            type: nav_map,
            default: null,
        }
    },

    // use this for initialization
    onLoad: function() {
        this.state = State.Idle;
        this.walk_total = 0.0;
        this.walk_time = 0;
    },

    nav_to_map: function(dst_wpos) {
        var src_wpos = this.node.convertToWorldSpaceAR(cc.v2(0, 0));

        this.road_set = this.game_map.astar_search(src_wpos, dst_wpos);
        console.log(this.road_set);

        if (!this.road_set || this.road_set.length <= 1) {
            this.state = State.Idle;
            return;
        }

        this.walk_next = 1;
        this._walk_to_next();
    },

    stop_nav: function() {
        this.state = State.Idle;
    },

    _walk_to_next: function() {
        if (!this.road_set || this.walk_next >= this.road_set.length) {
            this.state = State.Idle;
            return;
        }
        var src = this.node.getPosition();
        var dst = this.node.parent.convertToNodeSpaceAR(this.road_set[this.walk_next]);

        var dir = dst.sub(src); // 朝向
        var len = dir.mag();

        this.vx = (dir.x / len) * this.speed;
        this.vy = (dir.y / len) * this.speed;

        this.walk_total = len / this.speed;
        this.walk_time = 0;
        this.state = State.Walk;
    },
    _walk_update: function(dt) {
        if (this.state != State.Walk) {
            return;
        }

        this.walk_time += dt;
        if (this.walk_time > this.walk_total) {
            dt -= (this.walk_time - this.walk_total);
        }

        var sx = this.vx * dt;
        var sy = this.vy * dt;

        this.node.x += sx;
        this.node.y += sy;

        if (this.walk_time > this.walk_total) {
            this.walk_next++;
            this._walk_to_next();
        }
    },

    update: function(dt) {
        if (this.state == State.Walk) {
            this._walk_update(dt);
        }
    },
});
// enemy_AI.js 簡單的怪物AI
var nav_agent = require("nav_agent");

cc.Class({
    extends: cc.Component,

    properties: {
        // foo: {
        //    default: null,      // The default value will be used only when the component attaching
        //                           to a node for the first time
        //    url: cc.Texture2D,  // optional, default is typeof default
        //    serializable: true, // optional, default is true
        //    visible: true,      // optional, default is true
        //    displayName: 'Foo', // optional
        //    readonly: false,    // optional, default is false
        // },
        // ...
        think_f_time: 0.25,

        search_R: 150, // 發現玩家追擊上去
        attack_R: 30, // 攻擊玩家。 

        player_agent: {
            type: nav_agent,
            default: null,
        }
    },

    // use this for initialization
    onLoad: function() {
        this.think_time = 0.0;
        this.agent = this.getComponent("nav_agent");
    },

    _do_think_AI: function() {
        var target_pos = this.player_agent.node.getPosition();
        var now_pos = this.node.getPosition();

        var dir = target_pos.sub(now_pos);
        var len = dir.mag();
        if (len > this.search_R) { // 停止下來
            this.agent.stop_nav();
            return;
        }

        if (len < this.attack_R) {
            this.agent.stop_nav();
            return;
        }

        target_pos = this.player_agent.node.convertToWorldSpaceAR(cc.v2(0, 0));
        this.agent.nav_to_map(target_pos);
    },

    // called every frame, uncomment this function to activate update callback
    update: function(dt) {
        this.think_time += dt;
        if (this.think_time >= this.think_f_time) { // 決策來做思考
            this.think_time = 0.0;
            this._do_think_AI();
        }
    },
});

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM