window.DIRECTION_DECODER = [ // The 3rd value matches low-precision constants in backend. [0, 0], [0, +2], [0, -2], [+2, 0], [-2, 0], [+1, +1], [-1, -1], [+1, -1], [-1, +1], ]; cc.Class({ extends: cc.Component, properties: { // For joystick begins. translationListenerNode: { default: null, type: cc.Node }, zoomingListenerNode: { default: null, type: cc.Node }, stickhead: { default: null, type: cc.Node }, base: { default: null, type: cc.Node }, joyStickEps: { default: 0.10, type: cc.Float }, magicLeanLowerBound: { default: 0.1, type: cc.Float }, magicLeanUpperBound: { default: 0.9, type: cc.Float }, // For joystick ends. linearScaleFacBase: { default: 1.00, type: cc.Float }, minScale: { default: 1.00, type: cc.Float }, maxScale: { default: 2.50, type: cc.Float }, maxMovingBufferLength: { default: 1, type: cc.Integer }, zoomingScaleFacBase: { default: 0.10, type: cc.Float }, zoomingSpeedBase: { default: 4.0, type: cc.Float }, linearSpeedBase: { default: 320.0, type: cc.Float }, canvasNode: { default: null, type: cc.Node }, mapNode: { default: null, type: cc.Node }, linearMovingEps: { default: 0.10, type: cc.Float }, scaleByEps: { default: 0.0375, type: cc.Float }, btnA: { default: null, type: cc.Node }, btnB: { default: null, type: cc.Node }, }, start() {}, onLoad() { this.cachedStickHeadPosition = cc.v2(0.0, 0.0); this.cachedBtnUpLevel = 0; this.cachedBtnDownLevel = 0; this.cachedBtnLeftLevel = 0; this.cachedBtnRightLevel = 0; this.realtimeBtnALevel = 0; this.cachedBtnALevel = 0; this.btnAEdgeTriggerLock = false; this.realtimeBtnBLevel = 0; this.cachedBtnBLevel = 0; this.btnBEdgeTriggerLock = false; this.canvasNode = this.mapNode.parent; this.mainCameraNode = this.canvasNode.getChildByName("Main Camera"); // Cannot drag and assign the `mainCameraNode` from CocosCreator EDITOR directly, otherwise it'll cause an infinite loading time, till v2.1.0. this.mainCamera = this.mainCameraNode.getComponent(cc.Camera); this.activeDirection = { dx: 0.0, dy: 0.0 }; this.maxHeadDistance = (0.5 * this.base.width); this._initTouchEvent(); this._cachedMapNodePosTarget = []; this._cachedZoomRawTarget = null; this.mapScriptIns = this.mapNode.getComponent("Map"); this.initialized = true; }, _initTouchEvent() { const self = this; const translationListenerNode = (self.translationListenerNode ? self.translationListenerNode : self.mapNode); const zoomingListenerNode = (self.zoomingListenerNode ? self.zoomingListenerNode : self.mapNode); translationListenerNode.on(cc.Node.EventType.TOUCH_START, function(event) { self._touchStartEvent(event); }); translationListenerNode.on(cc.Node.EventType.TOUCH_MOVE, function(event) { self._translationEvent(event); }); translationListenerNode.on(cc.Node.EventType.TOUCH_END, function(event) { self._touchEndEvent(event); }); translationListenerNode.on(cc.Node.EventType.TOUCH_CANCEL, function(event) { self._touchEndEvent(event); }); translationListenerNode.inTouchPoints = new Map(); /* zoomingListenerNode.on(cc.Node.EventType.TOUCH_START, function(event) { self._touchStartEvent(event); }); zoomingListenerNode.on(cc.Node.EventType.TOUCH_MOVE, function(event) { self._zoomingEvent(event); }); zoomingListenerNode.on(cc.Node.EventType.TOUCH_END, function(event) { self._touchEndEvent(event); }); zoomingListenerNode.on(cc.Node.EventType.TOUCH_CANCEL, function(event) { self._touchEndEvent(event); }); zoomingListenerNode.inTouchPoints = new Map(); */ if (self.btnA) { self.btnA.on(cc.Node.EventType.TOUCH_START, function(evt) { self._triggerEdgeBtnA(true); }); self.btnA.on(cc.Node.EventType.TOUCH_END, function(evt) { self._triggerEdgeBtnA(false); }); self.btnA.on(cc.Node.EventType.TOUCH_CANCEL, function(evt) { self._triggerEdgeBtnA(false); }); } if (self.btnB) { self.btnB.on(cc.Node.EventType.TOUCH_START, function(evt) { self._triggerEdgeBtnB(true); }); self.btnB.on(cc.Node.EventType.TOUCH_END, function(evt) { self._triggerEdgeBtnB(false); }); self.btnB.on(cc.Node.EventType.TOUCH_CANCEL, function(evt) { self._triggerEdgeBtnB(false); }); } // Setup keyboard controls for the ease of attach debugging cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, function(evt) { switch (evt.keyCode) { case cc.macro.KEY.w: self.cachedBtnUpLevel = 0; self.cachedBtnDownLevel = 0; self.cachedBtnLeftLevel = 0; self.cachedBtnRightLevel = 0; self.cachedBtnUpLevel = 1; break; case cc.macro.KEY.s: self.cachedBtnUpLevel = 0; self.cachedBtnDownLevel = 0; self.cachedBtnLeftLevel = 0; self.cachedBtnRightLevel = 0; self.cachedBtnDownLevel = 1; break; case cc.macro.KEY.a: self.cachedBtnUpLevel = 0; self.cachedBtnDownLevel = 0; self.cachedBtnLeftLevel = 0; self.cachedBtnRightLevel = 0; self.cachedBtnLeftLevel = 1; break; case cc.macro.KEY.d: self.cachedBtnUpLevel = 0; self.cachedBtnDownLevel = 0; self.cachedBtnLeftLevel = 0; self.cachedBtnRightLevel = 0; self.cachedBtnRightLevel = 1; break; case cc.macro.KEY.h: self._triggerEdgeBtnA(true); break; case cc.macro.KEY.j: self._triggerEdgeBtnB(true); break; default: break; } }, this); cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, function(evt) { switch (evt.keyCode) { case cc.macro.KEY.w: self.cachedBtnUpLevel = 0; break; case cc.macro.KEY.s: self.cachedBtnDownLevel = 0; break; case cc.macro.KEY.a: self.cachedBtnLeftLevel = 0; break; case cc.macro.KEY.d: self.cachedBtnRightLevel = 0; break; case cc.macro.KEY.h: self._triggerEdgeBtnA(false); break; case cc.macro.KEY.j: self._triggerEdgeBtnB(false); break; default: break; } }, this); }, _isMapOverMoved(mapTargetPos) { const virtualPlayerPos = cc.v2(-mapTargetPos.x, -mapTargetPos.y); return tileCollisionManager.isOutOfMapNode(this.mapNode, virtualPlayerPos); }, _touchStartEvent(event) { const theListenerNode = event.target; for (let touch of event._touches) { theListenerNode.inTouchPoints.set(touch._id, touch); } }, _translationEvent(event) { if (ALL_MAP_STATES.VISUAL != this.mapScriptIns.state) { return; } const theListenerNode = event.target; const linearScaleFacBase = this.linearScaleFacBase; // Not used yet. if (1 != theListenerNode.inTouchPoints.size) { return; } if (!theListenerNode.inTouchPoints.has(event.currentTouch._id)) { return; } const diffVec = event.currentTouch._point.sub(event.currentTouch._startPoint); const distance = diffVec.mag(); const overMoved = (distance > this.maxHeadDistance); if (overMoved) { const ratio = (this.maxHeadDistance / distance); this.cachedStickHeadPosition = diffVec.mul(ratio); } else { const ratio = (distance / this.maxHeadDistance); this.cachedStickHeadPosition = diffVec.mul(ratio); } }, _zoomingEvent(event) { if (ALL_MAP_STATES.VISUAL != this.mapScriptIns.state) { return; } const theListenerNode = event.target; if (2 != theListenerNode.inTouchPoints.size) { return; } if (2 == event._touches.length) { const firstTouch = event._touches[0]; const secondTouch = event._touches[1]; const startMagnitude = firstTouch._startPoint.sub(secondTouch._startPoint).mag(); const currentMagnitude = firstTouch._point.sub(secondTouch._point).mag(); let scaleBy = (currentMagnitude / startMagnitude); scaleBy = 1 + (scaleBy - 1) * this.zoomingScaleFacBase; if (1 < scaleBy && Math.abs(scaleBy - 1) < this.scaleByEps) { // Jitterring. cc.log(`ScaleBy == ${scaleBy} is just jittering.`); return; } if (1 > scaleBy && Math.abs(scaleBy - 1) < 0.5 * this.scaleByEps) { // Jitterring. cc.log(`ScaleBy == ${scaleBy} is just jittering.`); return; } if (!this.mainCamera) return; const targetScale = this.mainCamera.zoomRatio * scaleBy; if (this.minScale > targetScale || targetScale > this.maxScale) { return; } this.mainCamera.zoomRatio = targetScale; for (let child of this.mainCameraNode.children) { child.setScale(1 / targetScale); } } }, _touchEndEvent(event) { const theListenerNode = event.target; do { if (!theListenerNode.inTouchPoints.has(event.currentTouch._id)) { break; } const diffVec = event.currentTouch._point.sub(event.currentTouch._startPoint); const diffVecMag = diffVec.mag(); if (this.linearMovingEps <= diffVecMag) { break; } // Only triggers map-state-switch when `diffVecMag` is sufficiently small. if (ALL_MAP_STATES.VISUAL != this.mapScriptIns.state) { break; } // TODO: Handle single-finger-click event. } while (false); for (let touch of event._touches) { if (touch) { theListenerNode.inTouchPoints.delete(touch._id); } } }, _touchCancelEvent(event) {}, update(dt) { if (this.inMultiTouch) return; if (true != this.initialized) return; const self = this; // Keyboard takes top priority let keyboardDiffVec = cc.v2(0, 0); if (1 == this.cachedBtnUpLevel) { if (1 == this.cachedBtnLeftLevel) { keyboardDiffVec = cc.v2(-1.0, +1.0); } else if (1 == this.cachedBtnRightLevel) { keyboardDiffVec = cc.v2(+1.0, +1.0); } else { keyboardDiffVec = cc.v2(0.0, +1.0); } } else if (1 == this.cachedBtnDownLevel) { if (1 == this.cachedBtnLeftLevel) { keyboardDiffVec = cc.v2(-1.0, -1.0); } else if (1 == this.cachedBtnRightLevel) { keyboardDiffVec = cc.v2(+1.0, -1.0); } else { keyboardDiffVec = cc.v2(0.0, -1.0); } } else if (1 == this.cachedBtnLeftLevel) { keyboardDiffVec = cc.v2(-1.0, 0.0); } else if (1 == this.cachedBtnRightLevel) { keyboardDiffVec = cc.v2(+1.0, 0.0); } if (0 != keyboardDiffVec.x || 0 != keyboardDiffVec.y) { this.cachedStickHeadPosition = keyboardDiffVec.mul(this.maxHeadDistance / keyboardDiffVec.mag()); } this.stickhead.setPosition(this.cachedStickHeadPosition); const translationListenerNode = (self.translationListenerNode ? self.translationListenerNode : self.mapNode); if (0 == translationListenerNode.inTouchPoints.size && (0 == keyboardDiffVec.x && 0 == keyboardDiffVec.y) ) { this.cachedStickHeadPosition = cc.v2(0, 0); // Important reset! } }, discretizeDirection(continuousDx, continuousDy, eps) { let ret = { dx: 0, dy: 0, encodedIdx: 0 }; if (Math.abs(continuousDx) < eps && Math.abs(continuousDy) < eps) { return ret; } const criticalRatio = continuousDy / continuousDx; if (Math.abs(criticalRatio) < this.magicLeanLowerBound) { ret.dy = 0; if (0 < continuousDx) { ret.dx = +2; // right ret.encodedIdx = 3; } else { ret.dx = -2; // left ret.encodedIdx = 4; } } else if (Math.abs(criticalRatio) > this.magicLeanUpperBound) { ret.dx = 0; if (0 < continuousDy) { ret.dy = +2; // up ret.encodedIdx = 1; } else { ret.dy = -2; // down ret.encodedIdx = 2; } } else { if (0 < continuousDx) { if (0 < continuousDy) { ret.dx = +1; ret.dy = +1; ret.encodedIdx = 5; } else { ret.dx = +1; ret.dy = -1; ret.encodedIdx = 7; } } else { // 0 >= continuousDx if (0 < continuousDy) { ret.dx = -1; ret.dy = +1; ret.encodedIdx = 8; } else { ret.dx = -1; ret.dy = -1; ret.encodedIdx = 6; } } } return ret; }, getEncodedInput() { const discretizedDir = this.discretizeDirection(this.stickhead.x, this.stickhead.y, this.joyStickEps).encodedIdx; // There're only 9 dirs, thus using only the lower 4-bits const btnALevel = (this.cachedBtnALevel << 4); const btnBLevel = (this.cachedBtnBLevel << 5); this.cachedBtnALevel = this.realtimeBtnALevel; this.cachedBtnBLevel = this.realtimeBtnBLevel; this.btnAEdgeTriggerLock = false; this.btnBEdgeTriggerLock = false; return (btnBLevel + btnALevel + discretizedDir); }, decodeInput(encodedInput) { const encodedDirection = (encodedInput & 15); const mappedDirection = window.DIRECTION_DECODER[encodedDirection]; if (null == mappedDirection) { console.error("Unexpected encodedDirection = ", encodedDirection); } const btnALevel = ((encodedInput >> 4) & 1); const btnBLevel = ((encodedInput >> 5) & 1); return window.pb.protos.InputFrameDecoded.create({ dx: mappedDirection[0], dy: mappedDirection[1], btnALevel: btnALevel, btnBLevel: btnBLevel, }); }, _triggerEdgeBtnA(rising) { this.realtimeBtnALevel = (rising ? 1 : 0); if (!this.btnAEdgeTriggerLock && (1 - this.realtimeBtnALevel) == this.cachedBtnALevel) { this.cachedBtnALevel = this.realtimeBtnALevel; this.btnAEdgeTriggerLock = true; } if (rising) { this.btnA.runAction(cc.scaleTo(0.1, 0.3)); } else { this.btnA.runAction(cc.scaleTo(0.1, 0.5)); } }, _triggerEdgeBtnB(rising, evt) { this.realtimeBtnBLevel = (rising ? 1 : 0); if (!this.btnBEdgeTriggerLock && (1 - this.realtimeBtnBLevel) == this.cachedBtnBLevel) { this.cachedBtnBLevel = this.realtimeBtnBLevel; this.btnBEdgeTriggerLock = true; } if (rising) { this.btnB.runAction(cc.scaleTo(0.1, 0.3)); } else { this.btnB.runAction(cc.scaleTo(0.1, 0.5)); } }, });