《 ⭐ Cocos 论坛首发 Bookmarklet 简略编写 + Snip 片段 + 自定义查看动态合图 + 简要自定义 WebGL 图形(有写插件) + RVO避障简析 (附写源码) | 社区征文》

:elephant: Cocos 论坛首发 Bookmarklet 简略编写 + Snip 片段 + 自定义查看动态合图 + 简要自定义 WebGL 图形(有写插件)+ RVO避障简析 (附写源码) | 社区征文》

申明 [本文使用的技术很菜_仅供不太了解这块的朋友稍作阅读] :rofl:

本文章使用的技术并不复杂, 想做深入技术研究的大佬可以略过了

文内并无比较高深的技术内容, 最后感谢各位大佬的阅读

本文主旨:言简意赅 · 词可达意 :star: 言之有理 · 言而有物

本文内容目录概览
使用方法和效果:::
1.8.1 :wolf:使用 Bookmarklet + Snip 片段的 GIF 操作演示
1.8.2 演示=>自定义查看动态合图 (拓展 Cocos 2.x 的一个查看的功能) 及效果
正文:::
1. :elephant: Cocos 论坛首发 Bookmarklet 简略编写 + Snip 片段介绍
1.1 在任何网页上运行 JavaScript 的代码片段
1.2 「引用」本条目参考自 Bookmarklet编写指南 - 阮一峰日志
1.3 什么是 Bookmarklet ?
1.4 Bookmarklet 的优点
1.5 Bookmarklet 的编写规则
1.6 Bookmarklet 的编写技巧
1.7 Snip 片段简介 (这里指的是浏览器控制台里面的 Snip 片段代码)
1.9 使用 Bookmarklet + Snip 片段的 GIF 操作演示代码
2. :dog2: 在 Cocos 内用代码写最简单的 WebGL图形(Ts) + 超小杯 [付费插件] :cat2:
3. RVO 避障的简单理解 + [免费源码] :star:

先看使用方法和效果:=>

1.8.1 :wolf:使用 Bookmarklet + Snip 片段的 GIF 操作演示

1.8.2 演示=>自定义查看动态合图 (拓展 Cocos 2.x 的一个查看的功能) 及效果

1.8.3 在浏览器上使用 Bookmarklet 片段代码(先复制好要用的 Js 代码)

收藏书签, 书签的网址栏, 填入 Bookmarklet 片段代码, 点击书签即可使用

1.8.4 在浏览器上使用 Snip 片段代码(先复制好要用的 Js 代码)

ctrl+shift+p 输入 snip 选择新建 snip, 填入 Snip 片段代码, ctrl+enter 快捷键运行代码(可换行)

正文介绍:=>

1. :elephant: Cocos 论坛首发 Bookmarklet 简略编写 + Snip 片段介绍

1.1 「引子」 看了下论坛内容, 并没有发现有讲述这个内容的大佬, 所以小弟来抛砖引玉 :lion:
1.1.1「述1」本文内的小图标采用用的是 Unicode 特殊编码格式表
1.1.2「述2」本文内录制的 GIF 图片,里面使用录屏软件, 参考之前写的这个介绍
1.2.1 「引用」本条目参考 Bookmarklet 编写指南 - 阮一峰日志
1.2.2 「引用」本条目参考 中文网最全 Bookmarklet 小书签 - 奔跑中的奶酪
1.2.3 「注」因为时间久远, 浏览器逐渐废除了很多相关代码, 所以使用老代码时需要考虑目前的执行环境(本文内的代码目前还能使用)

1.3 什么是 Bookmarklet ?

  • 内容介绍
    Bookmarklet ,由 Bookmark(书签)和-let(小的)构成,中文可以译成"书签工具"。它在形式上与"书签"一样,都保存在浏览器收藏夹里, 是一段Javascript代码,以"javascript:"开头。点击之后,会对当前页面执行某种操作。
    Bookmarklet,由英文单词 BookmarkApplet 组合而来,也就是书签小程序的意思。简单地说,小书签就是把一段带有特定功能的 JavaScript 代码保存到收藏夹,当你需要的时候,点击它可以实现这段 JavaScript 代码的功能。
  • 功能介绍
    小书签无需安装,也就不会对浏览器速度造成任何的影响,它和普通的书签一样放到书签栏里,只需要点击一下就能实现各式各样的功能,比如生成二维码,生成短链接,阅读模式等等功能,十分方便

1.4 Bookmarklet 的优点

1.4.1 安装快速

Bookmarklet的安装,就是在收藏夹中保存一段代码,一步就能完成。所有浏览器都原生支持。

1.4.2 使用方便

用的时候,点一下这个链接就行了。

1.4.3 开发容易

一段Javascript代码就是Bookmarklet的所有内容,不需要用到其他技术,比开发一个浏览器插件简单多了。

1.4.4 跨浏览器 (PS:在IOS系统里面的浏览器上也可以当做书签点击运行)

所有浏览器都支持Bookmarklet。如果写的正确,同样一个Bookmarklet在各种浏览器上都能正常使用。

1.5 Bookmarklet 的编写规则

1.5.1 必须以"javascript:"开头

浏览器把"javascript:"当做协议看待。有了它,
浏览器才知道要用javascript解释后面的代码。它的作用等同于将代码放在之间运行。

最简单的代码::
javascript:alert(‘Bookmarklets Cocos !’);

1.5.2 所有代码必须在同一行 (Snip 片段比这好,而且长度没限制)

因为浏览器把Bookmarklet当做网址保存,而网址是不能分行的,所以Bookmarklet也不能分行。
另一方面,网址是有长度限制的。IE的最长网址不能超过2083个字符(IE6不能超过508个字符),
这也就是Bookmarklet的最长长度。压缩工具 可以帮忙减少长度,
但是连接外部代码,可以避开这个限制。

1.5.3 使用单引号

根据Javascript的语法,单引号(‘xxx’)和双引号(“xxx”)都能使用。
但是由于html语言主要使用双引号,所以Bookmarklet优先使用单引号。
万一遇到必须使用双引号的情况,就采用它的URL编码形式"%22"。

1.5.4 不要污染全局变量

Bookmarklet最好不要生成新的全局变量,可以采用直接运行匿名函数的方式:

javascript: (function(){…})();

上面式子的第一个括号,定义了一个匿名函数;最后一个括号表示立即执行这个匿名函数。
所有的变量都是匿名函数的内部变量,不会生成任何新的全局变量。
如果必须设置全局变量,就取罕见的变量名(比如 CCoRCEoAsTOR2022 ),防止与已经存在的全局变量同名。

1.5.5 对文本和URL进行编码

为了防止出现非法字符,代码以外的文本都应该使用encodeURIComponent()函数进行编码,比如把空格变成%20。

1.6 Bookmarklet的编写技巧

1.6.1 获取网页信息 (可用)

1.6.1.2 获取当前网页信息-复制下方代码, 粘贴到浏览器控制台, 回车看效果

javascript:(()=>{var t;t = (function () {if (window.getSelection) {return window.getSelection().toString();} else if (document.getSelection) {return document.getSelection();} else if (document.selection) {return document.selection.createRange().text;};return '';})();alert('当前页面的标题:\n'+document.title+'\n当前页面的URL:\n'+location.href+'\n当前选中的文本\n\n'+t);})()

获取当前页面的标题:document.title
获取当前页面的URL: location.href
获取当前选中的文本:

var t;
t = (function () {
    if (window.getSelection) {
        return window.getSelection().toString();
    } else if (document.getSelection) {
        return document.getSelection();
    } else if (document.selection) {
        return document.selection.createRange().text;
    };
    return '';
})();
alert('当前页面的标题:\n'+document.title+'\n当前页面的URL:\n'+location.href+'\n当前选中的文本\n\n'+t);

1.6.2 防止刷新页面 (已失效)

如果代码对页面有改动(比如使用了document.write),浏览器就会用一个新页面替换原有页面。
所以最好用void()命令,把语句放在里面。
举例来说,下面这个 Bookmarklet 会导致原页面被一个新页面替代:

javascript:document.links[0].href=‘https://forum.cocos.org/’;

// 加上void以后,页面就不会跳转了:

javascript:void(document.links[0].href=‘https://forum.cocos.org/’);

1.7 Snip 片段简介 (这里指的是浏览器控制台里面的 Snip 片段代码)

  • Code snippet (代码片段)在VS中指的是 基于IDE支持的利用快捷方式快速输入一小段 ,或者称之为一整块代码的功能,在日常编程,特别是在工作中写内容相似的业务代码时,利用Snippet功能,可以极大加快编程效率

1.9 使用 Bookmarklet + Snip 片段的 GIF 操作演示代码 (条目 1.8.1 的使用演示效果在最上面)

「注」代码前演示的是如何使用 Bookmarklet 和 Snip 片段以及其效果, (个人建议用 Snip 片段)

1.9.1 改写记录:::=>

  • 改动来源:: 动态合图·调试文档 动态合图 · Cocos Creator · 2.x 文档
  • 如果希望看到动态合图的效果,那么可以开启调试来看到最终生成的大图,这些大图会添加到一个 ScrollView 展示出来。
// 开启调试
cc.dynamicAtlasManager.showDebug(true);
// 关闭调试
cc.dynamicAtlasManager.showDebug(false);

1.9.2 先出自定义查看动态合图_核心代码( Bookmarklet 片段代码):::=>

javascript:(()=>{var getUserConfirm=confirm("是否查看合图?");window.showCodeInfoTime=" 【"+new Date().toLocaleString()+"=>星期"+['天','壹','贰','叁','肆','伍','陆'][new Date().getDay()]+"=>周"+['末','一','二','三','四','五','六'][new Date().getDay()]+"】 ";window.dragBlockOffset=cc.Vec2.ZERO;cc.dynamicAtlasManager.showDebug(getUserConfirm);if(getUserConfirm){var setNew_scroll=new cc.Node();var setNew_cont=new cc.Node();setNew_scroll.name="DYNAMIC_ATLAS_DEBUG_NODE_selfSet";setNew_cont.name="CONTENT";setNew_scroll.anchorX=0.5;setNew_scroll.anchorY=0.5;setNew_scroll.x=0;setNew_scroll.y=0;setNew_scroll.width=cc.winSize.width;setNew_scroll.height=cc.winSize.height;setNew_cont.anchorX=0.5;setNew_cont.anchorY=0.5;setNew_cont.x=0;setNew_cont.y=0;setNew_cont.width=cc.winSize.width;setNew_cont.height=cc.winSize.height;setNew_cont.parent=setNew_scroll;setNew_scroll.parent=cc.director.getScene().getComponentInChildren("cc.Canvas").node;setNew_scroll.zIndex=9;var getDEBUG_NODE=cc.find("DYNAMIC_ATLAS_DEBUG_NODE");var getAtlas=cc.find("DYNAMIC_ATLAS_DEBUG_NODE/CONTENT/ATLAS");var getAtlas2=setNew_cont;if(getAtlas){cc.log(window.showCodeInfoTime+"%c  已将动态合图的图集输出至 Canvas 节点下 => \n Canvas/DYNAMIC_ATLAS_DEBUG_NODE_selfSet/CONTENT/ATLAS \n 可以自由拖动屏幕上的新图集节点 \n 已自动贴边屏幕","font-size:18px;color:yellow;background-color:black;");var ins_atlas=cc.instantiate(getAtlas);ins_atlas.anchorX=0.5;ins_atlas.anchorY=0.5;ins_atlas.addComponent("cc.Widget");var ins_atlasWid=ins_atlas.getComponent("cc.Widget");ins_atlasWid.isAlignTop=true;ins_atlasWid.isAlignLeft=true;ins_atlasWid.top=55;ins_atlasWid.left=55;ins_atlas.on(cc.Node.EventType.TOUCH_START,onTouchStart,this);ins_atlas.on(cc.Node.EventType.TOUCH_MOVE,onTouchMove,this);ins_atlas.on(cc.Node.EventType.TOUCH_END,onTouchEnd,this);ins_atlas.on(cc.Node.EventType.TOUCH_CANCEL,onTouchCancel,this);if(getAtlas2){ins_atlas.parent=getAtlas2;getDEBUG_NODE.active=false}else{ins_atlas.parent=cc.director.getScene().getComponentInChildren("cc.Canvas").node;getDEBUG_NODE.active=false}}else{console.clear();cc.log(window.showCodeInfoTime+"%c  未找到=>动态合图的大贴图图集 (2048 * 2048) ATLAS ","font-size:25px;color:red;background-color:black;");setNew_scroll.parent=null;setNew_scroll.destroy()}}else{cc.log(window.showCodeInfoTime+"%c  已移除当前新增的 ATLAS 的 DYNAMIC_ATLAS_DEBUG_NODE_selfSet 节点","font-size:25px;color:red;background-color:black;");var getAtlas=cc.find("DYNAMIC_ATLAS_DEBUG_NODE");var getAtlas2=cc.director.getScene().getComponentInChildren("cc.Canvas").node.getChildByName("DYNAMIC_ATLAS_DEBUG_NODE_selfSet");if(getAtlas){getAtlas.parent=null;getAtlas.destroy()};if(getAtlas2){getAtlas2.parent=null;getAtlas2.destroy()}};function onTouchStart(event,customEventData){cc.log("拖动合图图片_坐标_onTouchStart=>",[event.target.getPosition().x,event.target.getPosition().y]);const positionInNode=event.target.getParent().convertToNodeSpaceAR(event.getLocation());window.dragBlockOffset=positionInNode.sub(event.target.getPosition())};function onTouchMove(event,customEventData){if(!window.dragBlockOffset){return};const positionInWorld=event.getLocation(),positionInNode=event.target.getParent().convertToNodeSpaceAR(positionInWorld);cc.log("拖动合图图片_坐标_onTouchMove=>",[positionInNode.sub(window.dragBlockOffset).x,positionInNode.sub(window.dragBlockOffset).y]);event.target.setPosition(positionInNode.sub(window.dragBlockOffset))};function onTouchCancel(event,customEventData){cc.log("拖动合图图片_坐标_onTouchCancel=>",[event.getLocation().x,event.getLocation().y]);onTouchEnd(event,"")};function onTouchEnd(event,customEventData){if(!window.dragBlockOffset){return};cc.log("拖动合图图片_坐标_onTouchEnd=>",[event.getLocation().x,event.getLocation().y]);window.dragBlockOffset=null}})();

1.9.3 再出自定义查看动态合图_核心代码( Snip 片段代码):::=>

// 这个头写法就可以省略不写了
// javascript: (()=>{
    var getUserConfirm = confirm("是否查看合图?");
    window.showCodeInfoTime = " 【" + new Date().toLocaleString() + "=>星期" + ['天', '壹', '贰', '叁', '肆', '伍', '陆'][new Date().getDay()] + "=>周" + ['末', '一', '二', '三', '四', '五', '六'][new Date().getDay()] + "】 ";
    window.dragBlockOffset = cc.Vec2.ZERO;
    cc.dynamicAtlasManager.showDebug(getUserConfirm);
    if (getUserConfirm) {
        var setNew_scroll = new cc.Node();
        var setNew_cont = new cc.Node();

        setNew_scroll.name = "DYNAMIC_ATLAS_DEBUG_NODE_selfSet";
        setNew_cont.name = "CONTENT";

        setNew_scroll.anchorX = 0.5;
        setNew_scroll.anchorY = 0.5;
        setNew_scroll.x = 0;
        setNew_scroll.y = 0;
        setNew_scroll.width = cc.winSize.width;
        setNew_scroll.height = cc.winSize.height;

        setNew_cont.anchorX = 0.5;
        setNew_cont.anchorY = 0.5;
        setNew_cont.x = 0;
        setNew_cont.y = 0;
        setNew_cont.width = cc.winSize.width;
        setNew_cont.height = cc.winSize.height;

        setNew_cont.parent = setNew_scroll;
        setNew_scroll.parent = cc.director.getScene().getComponentInChildren("cc.Canvas").node;
        setNew_scroll.zIndex = 9;
        var getDEBUG_NODE = cc.find("DYNAMIC_ATLAS_DEBUG_NODE");
        var getAtlas = cc.find("DYNAMIC_ATLAS_DEBUG_NODE/CONTENT/ATLAS");
        var getAtlas2 = setNew_cont;
        if (getAtlas) {
            cc.log(window.showCodeInfoTime + "%c  已将动态合图的图集输出至 Canvas 节点下 => \n Canvas/DYNAMIC_ATLAS_DEBUG_NODE_selfSet/CONTENT/ATLAS \n 可以自由拖动屏幕上的新图集节点 \n 已自动贴边屏幕", "font-size:18px;color:yellow;background-color:black;");
            var ins_atlas = cc.instantiate(getAtlas);

            ins_atlas.anchorX = 0.5;
            ins_atlas.anchorY = 0.5;
            ins_atlas.addComponent("cc.Widget");
            var ins_atlasWid = ins_atlas.getComponent("cc.Widget");
            ins_atlasWid.isAlignTop = true;
            ins_atlasWid.isAlignLeft = true;
            ins_atlasWid.top = 55;
            ins_atlasWid.left = 55;

            ins_atlas.on(cc.Node.EventType.TOUCH_START, onTouchStart, this);
            ins_atlas.on(cc.Node.EventType.TOUCH_MOVE, onTouchMove, this);
            ins_atlas.on(cc.Node.EventType.TOUCH_END, onTouchEnd, this);
            ins_atlas.on(cc.Node.EventType.TOUCH_CANCEL, onTouchCancel, this);
            if (getAtlas2) {
                ins_atlas.parent = getAtlas2;
                getDEBUG_NODE.active = false;
            } else {
                ins_atlas.parent = cc.director.getScene().getComponentInChildren("cc.Canvas").node;
                getDEBUG_NODE.active = false;
            }
            ;
        } else {
            console.clear();
            cc.log(window.showCodeInfoTime + "%c  未找到=>动态合图的大贴图图集 (2048 * 2048) ATLAS ", "font-size:25px;color:red;background-color:black;");
            setNew_scroll.parent = null;
            setNew_scroll.destroy();
        }
        ;
    } else {
        cc.log(window.showCodeInfoTime + "%c  已移除当前新增的 ATLAS 的 DYNAMIC_ATLAS_DEBUG_NODE_selfSet 节点", "font-size:25px;color:red;background-color:black;");
        var getAtlas = cc.find("DYNAMIC_ATLAS_DEBUG_NODE");
        var getAtlas2 = cc.director.getScene().getComponentInChildren("cc.Canvas").node.getChildByName("DYNAMIC_ATLAS_DEBUG_NODE_selfSet");
        if (getAtlas) {
            getAtlas.parent = null;
            getAtlas.destroy();
        }
        ;if (getAtlas2) {
            getAtlas2.parent = null;
            getAtlas2.destroy();
        }
        ;
    }
    ;function onTouchStart(event, customEventData) {
        cc.log("拖动合图图片_坐标_onTouchStart=>", [event.target.getPosition().x, event.target.getPosition().y]);
        const positionInNode = event.target.getParent().convertToNodeSpaceAR(event.getLocation());
        window.dragBlockOffset = positionInNode.sub(event.target.getPosition());
    }
    ;function onTouchMove(event, customEventData) {
        if (!window.dragBlockOffset) {
            return;
        }
        ;const positionInWorld = event.getLocation()
          , positionInNode = event.target.getParent().convertToNodeSpaceAR(positionInWorld);
        cc.log("拖动合图图片_坐标_onTouchMove=>", [positionInNode.sub(window.dragBlockOffset).x, positionInNode.sub(window.dragBlockOffset).y]);
        event.target.setPosition(positionInNode.sub(window.dragBlockOffset));
    }
    ;function onTouchCancel(event, customEventData) {
        cc.log("拖动合图图片_坐标_onTouchCancel=>", [event.getLocation().x, event.getLocation().y]);
        onTouchEnd(event, "");
    }
    ;function onTouchEnd(event, customEventData) {
        if (!window.dragBlockOffset) {
            return;
        }
        ;cc.log("拖动合图图片_坐标_onTouchEnd=>", [event.getLocation().x, event.getLocation().y]);
        window.dragBlockOffset = null;
    }
    ;

// })();

1.9.4 自定义输出 FPS 帧率日志( Snip 片段代码):::=>

// javascript: (()=>{
    var getShowFps = confirm("是否实时输出 FPS 帧率日志?");
    if (!getShowFps) {
        console.log("%c 取消输出 FPS 帧率日志", "background-color:#040;color:#f1e75a !important;font-size:calc(64px/2);");
        // return;
    }else{
    ;var timerNum = 0
      , tempShowNum = 100;
    var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
        window.setTimeout(callback, 1000 / 60);
    }
    ;
    var e, pe, pid, fps, last, offset, step, appendFps;
    fps = 0;
    last = Date.now();
    step = function() {
        offset = Date.now() - last;
        fps += 1;
        if (offset >= 1000) {
            last += offset;
            appendFps(fps);
            fps = 0;
        }
        requestAnimationFrame(step);
    }
    ;
    appendFps = function(fps) {
        if (timerNum > tempShowNum) {
            timerNum = 0;
            console.clear();
        }
        ;timerNum++;
        console.log('%c 当前页面的帧率::' + timerNum + '=>\n ' + fps + ' FPS', "background-color:#000;color:#f1e05a !important;font-size:calc(55px/2);");
    }
    ;
    step();
};
// })();

1.9.5 自定义场景内节点的 Label Cache Mode 类型_核心代码( Bookmarklet 片段代码):::=>

这个 getComponentInChildren 代码也可以使用

// 先写 2.x 的::
javascript:var modeType=prompt("自定义 2.x 的 Label Cache Mode 类型,0-2",1);cc.director.getScene().children[0].getComponentsInChildren(cc.Label).forEach((nod)=>{nod.cacheMode=modeType;});

// 这个是 3.x 的::
javascript:var modeType=prompt("自定义 3.x 的 Label Cache Mode 类型,0-2",1);cc.director.getScene().getChildByName("Canvas").getComponentsInChildren(cc.LabelComponent).forEach((nod)=>{nod.cacheMode=modeType;});

1.9.6 自定义场景内节点的 Label Cache Mode 类型_核心代码( Snip 片段代码):::=>

关键使用的是 getComponentsInChildren 代码

  • 其实, getComponentsInChildren 可以用在很多的场景里面, 大家可以自行研究
// 先写 2.x 的::
// javascript: 
var modeType = prompt("自定义 2.x 的 Label Cache Mode 类型,0-2", 1);
cc.director.getScene().children[0].getComponentsInChildren(cc.Label).forEach((nod)=>{
    nod.cacheMode = modeType;
});

// 这个是 3.x 的::
// javascript: 
var modeType = prompt("自定义 3.x 的 Label Cache Mode 类型,0-2", 1);
cc.director.getScene().getChildByName("Canvas").getComponentsInChildren(cc.LabelComponent).forEach((nod)=>{
    nod.cacheMode = modeType;
});

2. :dog2: 在 Cocos 内用代码写最简单的 WebGL图形(Ts) + 超小杯 [付费插件] :cat2:

2.1 直接出核心代码( html 写法代码):::=>

// Cocos 的写法请参考插件内的代码
// 简单调用 GL 绘制画布变成红色
const gl0 = document.querySelector('canvas').getContext('webgl');
gl0.clearColor(1, 0, 0, 1); 
gl0.clear(gl0.COLOR_BUFFER_BIT);

// 简单绘制多个不同颜色的矩形
const gl = document.querySelector('#c').getContext('webgl'); 
gl.enable(gl.SCISSOR_TEST); 
function drawRect(x, y, width, height, color) {
  gl.scissor(x, y, width, height);
  gl.clearColor(...color);
  gl.clear(gl.COLOR_BUFFER_BIT);
}; 
for (let i = 0; i < 100; ++)) {
  const x = rand(0, 300);
  const y = rand(0, 150);
  const width = rand(0, 300 - x);
  const height = rand(0, 150 - y);
  drawRect(x, y, width, height, [rand(1), rand(1), rand(1), 1]);
}; 
function rand(min, max) {
  if (max === undefined) {
    max = min;
    min = 0;
  }
  return Math.random() * (max - min) + min;
};
参考:: WebGL 最小的程序 (webglfundamentals.org)
// 简单来说,就是获取一下 GL ,然后调用 WebGL 的代码绘制小矩形
2.2 看看这个简单的 十万(1000*100)++ 往上的不断重绘 WebGL 图, 可以知道, Cocos 会越来越好:star:

3. RVO 避障的简单理解 + [免费源码] :star:

  • 本条目行文参考论坛代码如下

3.1.1 半参考(仅作研究) 金币落袋效果
3.1.2 半参考(仅作研究) 50 行代码,优化列表draw call
3.1.3 半参考(仅作研究) 50 行写个简单的状态机

原理解析: 简略来说,就是一个圆周运动计算后,中点 坐标偏移
简单来说: 避障, 就是一个方向+速度的一个坐标计算的过程

3.2 RVO 避障的简单理解效果展示

3.2.1 开启此效果


3.2.2 场景内拖动节点后的效果

本文总结:::=>

想说的太多, 说的多了就成废话了, 因为本人懂得太少, 所以, 都在代码里了 :elephant:

小疑惑:::=>

各位大佬, 请问 3.x 的插件开发还有比较好的能参考的地方吗? :confused:
比如 2.x 插件可以自由读取场景内的节点数据并操作, 还有 run-time 展示插件内的 ts 代码.
请问 3.x 的这两个功能有什么地方说的比较详细的吗 ? :face_with_monocle:
在下才疏学浅, 多有不足之处, 还请各位大佬多多见谅, 欢迎提出您宝贵的见解, 感谢 :smiley:

10赞

:star: 在下才疏学浅, 多有不足之处, 还请各位大佬多多见谅, 欢迎提出您宝贵的见解, 感谢 :smiley:

好~好长啊~ :laughing:

1赞

啊!看不懂啊~

:joy:请问是有什么地方写的太复杂了吗?我再改改?

啊!不不不,是我水平比较次……

:rofl: 大佬太谦虚了, 我其实很菜的

一脸懵逼进来一辆懵逼出去,这是整理文?还是啥?这题目长得吓人。。。。还以为啥高大的技术分享

多个主题就分开写吧?

来龙去脉交待清楚好些

:sweat_smile: :sweat_smile:不是高大上的文章, 就是些简单的内容,
目前文章还在补完中, 后续会继续完善下本文的内容,
尽量争取让对 BookMarklet不太明白的朋友, 在看了本文后可以自己写或者使用这一块的内容.

:thinking:
拆分成三四个主题吗? 好像这个文章内容不是很多的话, 拆分会不会可读内容就比较太少了 ?
大佬您看, 加了个目录是不是要清晰点?

你这有点像官方文档,没有阅读兴趣

好的, 后续再改改, 开始的时候确实是有点想写个类似文档(附带代码)之类的风格

哈哈,之前写了一个饿了吗拼单插件,用来计算每个人要出多少钱,就是用 bookmarklet 干的

1赞

哈哈,我在你的git看过这个插件的代码,属实厉害

1赞

:smile::+1:
确实, 感觉这个 Bookmarklet 和 Snip 代码片段使用比较方便, 而且在浏览器上调试也很方便

感谢思路,试试3.x的(我3.8.3 ok)
觉得有用的点个赞吧

javascript:(()=>{const ID="__CC_DYNAMIC_ATLAS_INSPECTOR__";const old=document.getElementById(ID);if(old){old.remove();document.getElementById(ID+"_STYLE")?.remove();return}const m=window.__CocosDynamicAtlasManager;if(!m||!m._atlases){alert("没有找到 Cocos DynamicAtlasManager\n\n请确认调试脚本已经执行,并且 window.__CocosDynamicAtlasManager 存在");return}const css=document.createElement("style");css.id=ID+"_STYLE";css.textContent=`#${ID}{position:fixed;z-index:2147483647;left:10px;top:10px;width:calc(100vw - 20px);height:calc(100vh - 20px);background:#18191d;color:#ddd;font:13px Arial,sans-serif;border:1px solid #555;border-radius:8px;box-shadow:0 10px 50px rgba(0,0,0,.8);overflow:hidden;user-select:none}#${ID} *{box-sizing:border-box}#${ID} .bar{height:46px;background:#292b31;display:flex;align-items:center;padding:0 12px;gap:8px;border-bottom:1px solid #444}#${ID} .title{font-size:15px;font-weight:bold;color:#fff;margin-right:8px}#${ID} .stat{color:#aaa;white-space:nowrap}#${ID} button{background:#3b3e46;color:#ddd;border:1px solid #666;border-radius:4px;padding:6px 11px;cursor:pointer}#${ID} button:hover{background:#50545d}#${ID} .close{margin-left:auto;font-size:20px;padding:1px 11px;line-height:24px}#${ID} .body{height:calc(100% - 46px);display:flex;overflow:hidden}#${ID} .side{width:270px;min-width:270px;background:#202126;border-right:1px solid #444;overflow:auto;padding:10px}#${ID} .sectionTitle{font-size:12px;color:#888;margin:4px 2px 8px}#${ID} .atlasItem{padding:11px;border:1px solid #444;border-radius:6px;margin-bottom:8px;cursor:pointer;background:#292b31}#${ID} .atlasItem:hover{background:#34373e}#${ID} .atlasItem.active{border-color:#aaa;background:#3a3d44}#${ID} .atlasItem b{display:block;color:#fff;font-size:14px;margin-bottom:6px}#${ID} .atlasItem span{display:block;color:#999;margin-top:3px}#${ID} .progress{height:5px;background:#111;border-radius:3px;margin-top:8px;overflow:hidden}#${ID} .progress i{display:block;height:100%;background:#777}#${ID} .main{position:relative;flex:1;overflow:hidden;background:#101114}#${ID} .canvasWrap{position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden}#${ID} canvas{position:absolute;left:50%;top:50%;transform-origin:center center;cursor:grab;image-rendering:auto}#${ID} canvas.dragging{cursor:grabbing}#${ID} .tip{position:absolute;display:none;z-index:20;background:rgba(20,20,22,.96);border:1px solid #777;border-radius:5px;padding:8px 10px;pointer-events:none;white-space:nowrap;box-shadow:0 5px 20px #000;color:#ddd;line-height:18px}#${ID} .tip b{color:#fff}#${ID} .detail{position:absolute;right:12px;bottom:12px;width:300px;background:rgba(25,26,30,.96);border:1px solid #555;border-radius:6px;padding:11px;display:none;line-height:20px;z-index:10;box-shadow:0 5px 25px #000}#${ID} .detail b{color:#fff}#${ID} .detail .label{color:#888}#${ID} .hint{position:absolute;left:12px;bottom:12px;color:#777;background:rgba(0,0,0,.45);padding:5px 8px;border-radius:4px;pointer-events:none}`;document.head.appendChild(css);const root=document.createElement("div");root.id=ID;root.innerHTML=`<div class="bar"><span class="title">Cocos Dynamic Atlas Inspector</span><span class="stat" id="${ID}_stat"></span><button id="${ID}_refresh">刷新</button><button id="${ID}_reset">重置</button><button class="close" id="${ID}_close">×</button></div><div class="body"><div class="side" id="${ID}_side"></div><div class="main"><div class="canvasWrap"><canvas id="${ID}_canvas"></canvas></div><div class="tip" id="${ID}_tip"></div><div class="detail" id="${ID}_detail"></div><div class="hint">左键拖动:移动 滚轮:缩放 点击 Frame:查看详情</div></div></div>`;document.body.appendChild(root);const side=root.querySelector("#"+ID+"_side"),canvas=root.querySelector("#"+ID+"_canvas"),ctx=canvas.getContext("2d"),stat=root.querySelector("#"+ID+"_stat"),tip=root.querySelector("#"+ID+"_tip"),detail=root.querySelector("#"+ID+"_detail"),wrap=root.querySelector(".canvasWrap");let atlasIndex=0,scale=.5,offsetX=0,offsetY=0,drag=false,lastX=0,lastY=0,currentFrames=[],imageCache=new Map();function getGLTexture(a){try{const t=a&&a._texture,g=t&&t._gfxTexture,gpu=g&&g._gpuTexture;return{texture:t,gfx:g,gpu:gpu,glTexture:gpu&&gpu.glTexture,gl:t&&t._gfxDevice&&t._gfxDevice.gl}}catch(e){return null}}function readAtlas(a){const q=getGLTexture(a);if(!q||!q.gl||!q.glTexture){console.error("Dynamic Atlas GPU texture unavailable",a,q);return null}if(imageCache.has(a))return imageCache.get(a);const gl=q.gl,w=a._width||2048,h=a._height||2048,fb=gl.createFramebuffer(),oldFB=gl.getParameter(gl.FRAMEBUFFER_BINDING),oldTex=gl.getParameter(gl.TEXTURE_BINDING_2D),oldVP=gl.getParameter(gl.VIEWPORT),oldActive=gl.getParameter(gl.ACTIVE_TEXTURE),pixels=new Uint8Array(w*h*4);try{gl.bindFramebuffer(gl.FRAMEBUFFER,fb);gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,q.glTexture,0);const status=gl.checkFramebufferStatus(gl.FRAMEBUFFER);if(status!==gl.FRAMEBUFFER_COMPLETE)throw new Error("Framebuffer status = "+status);gl.readPixels(0,0,w,h,gl.RGBA,gl.UNSIGNED_BYTE,pixels);const err=gl.getError();if(err!==gl.NO_ERROR)throw new Error("readPixels glError = "+err);const cv=document.createElement("canvas");cv.width=w;cv.height=h;const c=cv.getContext("2d"),im=c.createImageData(w,h);im.data.set(pixels);c.putImageData(im,0,0);imageCache.set(a,cv);return cv}catch(e){console.error("[Dynamic Atlas Inspector] Atlas read failed",e);return null}finally{gl.bindFramebuffer(gl.FRAMEBUFFER,oldFB);gl.activeTexture(oldActive);gl.bindTexture(gl.TEXTURE_2D,oldTex);gl.viewport(oldVP[0],oldVP[1],oldVP[2],oldVP[3]);gl.deleteFramebuffer(fb)}}function findInfo(a,sf){const map=a&&a._innerTextureInfos;if(!map)return null;const keys=Object.keys(map);for(let i=0;i<keys.length;i++){const v=map[keys[i]];if(v&&(v.spriteFrame===sf||v._spriteFrame===sf||v.frame===sf))return v}return null}function getRect(a,sf){const info=findInfo(a,sf),r=sf&&sf.rect;return{x:info?.x??info?.rect?.x??r?.x??0,y:info?.y??info?.rect?.y??r?.y??0,w:info?.width??info?.rect?.width??r?.width??0,h:info?.height??info?.rect?.height??r?.height??0,info}}function getName(sf,i){return sf?.name||sf?._name||("Frame_"+i)}function getFrames(a){const result=[],frames=a?._innerSpriteFrames||[];for(let i=0;i<frames.length;i++){const sf=frames[i],r=getRect(a,sf);if(r.w>0&&r.h>0)result.push({sf,i,r,name:getName(sf,i)})}return result}function calcUsed(a){const w=a._width||2048,h=a._height||2048;let area=0;getFrames(a).forEach(f=>area+=f.r.w*f.r.h);return Math.min(100,area/(w*h)*100)}function setTransform(){canvas.style.transform="translate(-50%,-50%) translate("+offsetX+"px,"+offsetY+"px) scale("+scale+")"}function render(){const a=m._atlases[atlasIndex];if(!a)return;const w=a._width||2048,h=a._height||2048;canvas.width=w;canvas.height=h;canvas.style.width=w+"px";canvas.style.height=h+"px";ctx.setTransform(1,0,0,1,0,0);ctx.clearRect(0,0,w,h);const img=imageCache.get(a);if(img)ctx.drawImage(img,0,0);else{ctx.fillStyle="#202126";ctx.fillRect(0,0,w,h);ctx.fillStyle="#888";ctx.font="18px Arial";ctx.textAlign="center";ctx.fillText("GPU Atlas 读取失败",w/2,h/2)}currentFrames=getFrames(a);ctx.save();ctx.lineWidth=Math.max(1,2/scale);currentFrames.forEach(f=>{const r=f.r;ctx.strokeStyle="rgba(0,255,80,.85)";ctx.strokeRect(r.x,r.y,r.w,r.h);if(scale>=.32&&r.w>=45&&r.h>=18){ctx.fillStyle="rgba(0,0,0,.65)";ctx.fillRect(r.x,r.y,Math.min(r.w,180),18);ctx.fillStyle="#fff";ctx.font="11px Arial";ctx.textAlign="left";ctx.fillText(f.name.length>25?f.name.slice(0,25)+"…":f.name,r.x+3,r.y+13)}});ctx.restore();setTransform();stat.textContent="Atlas "+atlasIndex+" | "+w+" × "+h+" | Frames: "+currentFrames.length+" | 估算占用: "+calcUsed(a).toFixed(1)+"%"}function load(){const a=m._atlases[atlasIndex];if(!a)return;readAtlas(a);render()}function buildSide(){side.innerHTML='<div class="sectionTitle">DYNAMIC ATLAS · '+m._atlases.length+' 张</div>';m._atlases.forEach((a,i)=>{const d=document.createElement("div"),count=a._count??a._innerSpriteFrames?.length??0,used=calcUsed(a);d.className="atlasItem"+(i===atlasIndex?" active":"");d.innerHTML="<b>Atlas "+i+"</b><span>Size "+(a._width||0)+" × "+(a._height||0)+"</span><span>Frames "+count+"</span><span>Position "+(a._x??0)+", "+(a._y??0)+"</span><span>估算占用 "+used.toFixed(1)+"%</span><div class=\"progress\"><i style=\"width:"+used+"%\"></i></div>";d.onclick=()=>{atlasIndex=i;scale=.5;offsetX=0;offsetY=0;tip.style.display="none";detail.style.display="none";buildSide();load()};side.appendChild(d)})}function hit(e){const cr=canvas.getBoundingClientRect(),x=(e.clientX-cr.left)/scale,y=(e.clientY-cr.top)/scale;for(let i=currentFrames.length-1;i>=0;i--){const f=currentFrames[i],r=f.r;if(x>=r.x&&x<=r.x+r.w&&y>=r.y&&y<=r.y+r.h)return f}return null}canvas.addEventListener("mousemove",e=>{if(drag)return;const f=hit(e);if(!f){tip.style.display="none";return}const wr=wrap.getBoundingClientRect();tip.style.display="block";tip.style.left=(e.clientX-wr.left+14)+"px";tip.style.top=(e.clientY-wr.top+14)+"px";tip.innerHTML="<b>"+f.name+"</b><br>Rect "+f.r.x+", "+f.r.y+"<br>Size "+f.r.w+" × "+f.r.h});canvas.addEventListener("mouseleave",()=>{tip.style.display="none"});canvas.addEventListener("click",e=>{if(drag)return;const f=hit(e);if(!f)return;console.log("%c========== Dynamic Atlas Frame ==========","color:#0f0;font-weight:bold");console.log("Frame index =",f.i);console.log("Frame name =",f.name);console.log("Atlas Rect =",f.r);console.log("SpriteFrame =",f.sf);console.log("TextureInfo =",f.r.info);detail.style.display="block";detail.innerHTML="<b>"+f.name+"</b><br><span class=\"label\">Index</span> "+f.i+"<br><span class=\"label\">Rect</span> "+f.r.x+", "+f.r.y+"<br><span class=\"label\">Size</span> "+f.r.w+" × "+f.r.h+"<br><span class=\"label\">UUID</span> "+(f.sf?.uuid||f.sf?._uuid||"未知")+"<br><br><span class=\"label\">完整 SpriteFrame 已输出到 Console</span>"});canvas.addEventListener("mousedown",e=>{if(e.button!==0)return;drag=true;lastX=e.clientX;lastY=e.clientY;tip.style.display="none";canvas.classList.add("dragging");e.preventDefault()});window.addEventListener("mouseup",()=>{if(!drag)return;drag=false;canvas.classList.remove("dragging")});window.addEventListener("mousemove",e=>{if(!drag)return;const dx=e.clientX-lastX,dy=e.clientY-lastY;offsetX+=dx;offsetY+=dy;lastX=e.clientX;lastY=e.clientY;setTransform()});wrap.addEventListener("wheel",e=>{e.preventDefault();const oldScale=scale,newScale=Math.max(.1,Math.min(5,oldScale*(e.deltaY<0?1.1:.9))),wr=wrap.getBoundingClientRect(),cx=wr.left+wr.width/2,cy=wr.top+wr.height/2,mouseX=e.clientX-cx,mouseY=e.clientY-cy;offsetX=mouseX-(mouseX-offsetX)*(newScale/oldScale);offsetY=mouseY-(mouseY-offsetY)*(newScale/oldScale);scale=newScale;setTransform()},{passive:false});root.querySelector("#"+ID+"_refresh").onclick=()=>{imageCache.clear();buildSide();load()};root.querySelector("#"+ID+"_reset").onclick=()=>{scale=.5;offsetX=0;offsetY=0;tip.style.display="none";setTransform()};root.querySelector("#"+ID+"_close").onclick=()=>{root.remove();css.remove()};buildSide();load()})()

还需要再cocoscreator代码里加一下

macro.CLEANUP_IMAGE_CACHE = false;
DynamicAtlasManager.instance.enabled = true;

(window as any).__CocosDynamicAtlasManager =
DynamicAtlasManager.instance;

1赞