DelayNoMore/frontend/assets/scripts/TouchEventsManager.js
2023-01-19 09:20:52 +08:00

517 lines
15 KiB
JavaScript

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));
}
},
});