Canvas 内部元素如何实现 mouseover/mousemove 事件?


我在使用 Collie 引擎 来开发一个简单的游戏,它提供了 mousedown、mouseup、click 这三个鼠标事件,但是我想实现的功能是:当我的鼠标移到元素上的时候,显示该元素的名字。但这个引擎并没有提供 mouseover 或 mousemove 事件。

Canvas 是不是只能通过获取指针在画面上的坐标,然后判断是否在元素的范围,来模拟这个事件?

如果是的话,有什么好的算法,用来判断指针是否在 x、y、width、height (或者是圆型 x、y、radius) 范围中?

如果不是的话,应该怎么做呢?

游戏开发 canvas html5 JavaScript

女王请用力些 10 years, 11 months ago

自己看源代碼不就好了

和我想得一樣,就是循環判斷,比大小確定範圍,直到找到爲止。

想要實現 mouseover 啥的,自己照着 _fireEvent 調用 _getTargetOnHitEvent 即可


 /**
 * 레이어에서 이벤트가 일어났을 때 표시 객체에 이벤트를 발생 시킨다
 * 
 * @param {Object} e 이벤트 원본
 * @param {String} sType 이벤트 타입, mouse 이벤트로 변형되서 들어온다
 * @param {Number} nX 이벤트가 일어난 상대좌표
 * @param {Number} nY 이벤트가 일어난 상대좌표
 * @return {Boolean} 표시 객체에 이벤트가 발생했는지 여부 
 * @private
 */
_fireEvent : function (e, sType, nX, nY) {
    var oDisplayObject = null;
    var bIsNotStoppedBubbling = true;

    // 캔버스에서 이전 레이어에 객체에 이벤트가 일어났으면 다음 레이어의 객체에 전달되지 않는다
    if (sType !== "mousemove" && !collie.Renderer.isStopEvent(sType)) {
        var aDisplayObjects = this._oLayer.getChildren();
        oDisplayObject = this._getTargetOnHitEvent(aDisplayObjects, nX, nY);

        // mousedown일 경우 객체를 저장한다
        if (oDisplayObject) {
            bIsNotStoppedBubbling = this._bubbleEvent(oDisplayObject, sType, e, nX, nY);

            if (sType === "mousedown") {
                this._setMousedownObject(oDisplayObject);
            }
            if (sType === "mouseup") {
                this._unsetMousedownObject(oDisplayObject);
            }
        }
    }

    // mouseup 처리가 안된 경우 임의 발생
    if (sType === "mouseup" && this._getMousedownObject() !== null) {
        oDisplayObject = this._getMousedownObject();
        this._bubbleEvent(oDisplayObject, sType, e, nX, nY);
        this._unsetMousedownObject(oDisplayObject);
    }

    /**
     * click 이벤트, 모바일 환경일 때는 touchstart, touchend를 비교해서 좌표가 일정 이내로 움직였을 경우 click 이벤트를 발생한다d
     * @name collie.Layer#click
     * @event
     * @param {Object} htEvent
     * @param {collie.DisplayObject} htEvent.displayObject 대상 객체
     * @param {HTMLEvent} htEvent.event 이벤트 객체
     * @param {Number} htEvent.x 상대 x좌표
     * @param {Number} htEvent.y 상대 y좌표
     */
    /**
     * mousedown 이벤트, 모바일 환경일 때는 touchstart 이벤트도 해당 된다.
     * @name collie.Layer#mousedown
     * @event
     * @param {Object} htEvent
     * @param {collie.DisplayObject} htEvent.displayObject 대상 객체
     * @param {HTMLEvent} htEvent.event 이벤트 객체
     * @param {Number} htEvent.x 상대 x좌표
     * @param {Number} htEvent.y 상대 y좌표
     */
    /**
     * mouseup 이벤트, 모바일 환경일 때는 touchend 이벤트도 해당 된다.
     * @name collie.Layer#mouseup
     * @event
     * @param {Object} htEvent
     * @param {collie.DisplayObject} htEvent.displayObject 대상 객체
     * @param {HTMLEvent} htEvent.event 이벤트 객체
     * @param {Number} htEvent.x 상대 x좌표
     * @param {Number} htEvent.y 상대 y좌표
     */
    /**
     * mousemove 이벤트, 모바일 환경일 때는 touchmove 이벤트도 해당 된다.
     * @name collie.Layer#mouseup
     * @event
     * @param {Object} htEvent
     * @param {collie.DisplayObject} htEvent.displayObject 대상 객체
     * @param {HTMLEvent} htEvent.event 이벤트 객체
     * @param {Number} htEvent.x 상대 x좌표
     * @param {Number} htEvent.y 상대 y좌표
     */
    if (bIsNotStoppedBubbling) { // stop되면 Layer이벤트도 일어나지 않는다
        this._oLayer.fireEvent(sType, {
            event : e,
            displayObject : oDisplayObject,
            x : nX,
            y : nY
        });
    }

    return !!oDisplayObject;
},

/**
 * 이벤트 대상을 고른다
 * - 가장 위에 있는 대상이 선정되어야 한다
 * @private
 * @param {Array|collie.DisplayObject} vDisplayObject
 * @param {Number} nX 이벤트 상대 x 좌표
 * @param {Number} nY 이벤트 상대 y 좌표
 * @return {collie.DisplayObject|Boolean}
 */
_getTargetOnHitEvent : function (vDisplayObject, nX, nY) {
    var oTargetObject = null;

    if (vDisplayObject instanceof Array) {
        for (var i = vDisplayObject.length - 1; i >= 0; i--) {
            // 자식부터
            if (vDisplayObject[i].hasChild()) {
                oTargetObject = this._getTargetOnHitEvent(vDisplayObject[i].getChildren(), nX, nY);

                // 찾았으면 멈춤
                if (oTargetObject) {
                    return oTargetObject;
                }
            }

            // 본인도
            oTargetObject = this._getTargetOnHitEvent(vDisplayObject[i], nX, nY);

            // 찾았으면 멈춤
            if (oTargetObject) {
                return oTargetObject;
            }
        }
    } else {
        return this._isPointInDisplayObjectBoundary(vDisplayObject, nX, nY) ? vDisplayObject : false;
    }
},


/**
 * DisplayObject 범위 안에 PointX, PointY가 들어가는지 확인
 * 
 * @private
 * @param {collie.DisplayObject} oDisplayObject
 * @param {Number} nPointX 확인할 포인트 X 좌표
 * @param {Number} nPointY 확인할 포인트 Y 좌표
 * @return {Boolean} 들어간다면 true
 */
_isPointInDisplayObjectBoundary : function (oDisplayObject, nPointX, nPointY) {
    // 안보이는 상태거나 이벤트를 받지 않는다면 지나감
    if (
        !oDisplayObject._htOption.useEvent ||
        !oDisplayObject._htOption.visible ||
        !oDisplayObject._htOption.width ||
        !oDisplayObject._htOption.height ||
        (oDisplayObject._htOption.useEvent === "auto" && !oDisplayObject.hasAttachedHandler())
        ) {
        return false;
    }

    var htHitArea = oDisplayObject.getHitAreaBoundary();

    // 영역 안에 들어왔을 경우
    if (
        htHitArea.left <= nPointX && nPointX <= htHitArea.right &&
        htHitArea.top <= nPointY && nPointY <= htHitArea.bottom
    ) {
        // hitArea 설정이 없으면 사각 영역으로 체크
        if (!oDisplayObject._htOption.hitArea) {
            return true;
        } else {
            var htPos = oDisplayObject.getRelatedPosition();

            // 대상 Point를 상대 좌표로 변경
            nPointX -= htPos.x;
            nPointY -= htPos.y;

            // transform 적용
            var aHitArea = oDisplayObject._htOption.hitArea;
            aHitArea = collie.Transform.points(oDisplayObject, aHitArea);
            return this._isPointInPolygon(aHitArea, nPointX, nPointY);
        }
    }

    return false;
},

lolicn answered 10 years, 11 months ago

Your Answer