Kaboom-drag

范例为 drag 案例

同样机制的另一个游戏是来自作者tga的 粉色鱼鱼 Find the Pink Fish 情感表现力很强,我很喜欢

笔记

创建一个let变量[1] 来存放鼠标点击拖拽的对象

// there should only be one that's currently being dragged
    let curDraggin = null;

我们可以先看下面的代码部分,这部分的代码初始化了整个场景

//进行一次64次的循环,每次循环添加 obj ,并且为这个obj添加sprite,设置位置,放大,中心对齐
for (let i = 0; i < 64; i++) {
        add([
            sprite("mark"),
            pos(rand(width()), rand(height())),
            scale(5),
            origin("center"),
            drag(), //为每一个obj调用drag方法
            i !== 0 ? color(1, 1, 1) : color(1, 0, 1),
            //利用一个三元表达式来将第一个创建的图片颜色变为粉色,因为最先创建,所以在最下层,起到隐藏的作用
        ]);
    }

然后我们再来回到上方的,这个为每一个创建的obj所创建的方法 drag(),这个方法调用的时候将

  • 先看Update 每帧会判断,上面那个被创建的变量curDraggin是不是自己,如果是的话就就持续修改自己的位置 = mousePos() .sub(offset);

  • 在创建这个obj的时候,把以下方法添加进点击事件

    • 如果此刻有其他obj被点击(curDraggin不为null),那么直接返回

    • 否则玩家就是选中的此obj,计算出与鼠标之间的offset,然后调用readd来让这个图形显示在最上方

    function drag() {
        let offset = vec2(0);// 创建一个误差变量,避免点击直接设置位置会带来位置变化
        return {
            // "add" is a special lifecycle method gets called when the obj is added to scene
            add() {
               // "this" in all methods refer to the obj
                this.clicks(() => {
                    if (curDraggin) {
                        return;
                    }
                    curDraggin = this;
                    offset = mousePos().sub(this.pos);
                    readd(this);
                });
            },
            // "update" is a special lifecycle method gets called every frame the obj is in scene
            update() {
                if (curDraggin === this) {
                    this.pos = mousePos().sub(offset);
                }
            },
        };
    }

然后添加方法到松开鼠标的事件:

mouseRelease(() => {
        curDraggin = null;//如果鼠标松开,则把当前选中的变量重新设置回空
    });

涉及到的方法

鼠标松开事件调用方法

mouseRelease(cb) // runs once when left mouse is just released

obj. clicks(()=>{}) register an onClick callback obj.clicks 是注册方法到obj,当其被点击时候调用。obj.clicks 是 area() component 带的函数,不过如果你给了一个sprite() 或者 rect() 或者 text() 这些带渲染功能的component 他们会自动计算区域并加上一个 area()

obj.clicks(() => {
    // ...
});

Readd(): remove and add froggy to the scene without triggering events tied to "add" or "destroy", so it'll be drawn on the top of the layer it belongs to 方法readd是重新添加使其出现在屏幕上方,有摘出来的感觉

readd(froggy);

组件化

上方的的drag方法的使用会比较复杂一些

// a custom component handling drag 创建一个drag的方法作为component来处理拖拽
function drag() {
...
}

drag() 函数是在定义一个component,在kaboom里一个component就是一个函数,返回一个对象里面一些特殊生命周期函数,如add,update,挂在这里的add() 跟那个添加全局add()不一样,这里的是一个事件,有点类似keyDown(),当这个component的宿主obj被成功add()到场景后,这个component的add()函数会被执行(有点绕)

一个component可以看作一个单位的数据+行为,比如说sprite() component就只负责绘制图像,pos() component就只负责管理坐标,一个game object由一串component组成,所以add() 函数接受一个数组作为参数,这里的drag() 就是定义了一个新的component,只负责关于拖拽相关的行为和数据(来自@tga的帮助!)

其实这部分tga传递的是一个组件化的思想,其实在Unity的Component中就有很多的使用,而在js里面,这个component是需要把方法返回出来而不是直接贴上去写在monobehaviour里面的start()和update()就可以的

参考:

组件优先继承 Composition over inheritance

Unity Software Design – Inheritance and Composition

补充基础知识

*javaScript ES6 添加了两个关键字:

let 变量是增加的块级作用域

const 声明一个只读的常量,一旦声明,常量的值就不能改变

*条件(三元)运算符是 JavaScript 仅有的使用三个操作数的运算符。一个条件后面会跟一个问号(?),如果条件为 truthy ,则问号后面的表达式A将会执行;表达式A后面跟着一个冒号(:),如果条件为 falsy ,则冒号后面的表达式B将会执行。本运算符经常作为 if 语句的简捷形式来使用。参考

最后更新于