123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- /**
- * @author xshu
- * @date 2019-12-07
- */
- const { ccclass, property } = cc._decorator;
- @ccclass
- class MapControl extends cc.Component {
- @property({
- type: cc.Node,
- tooltip: '目标节点'
- })
- public map: cc.Node = null;
- @property(cc.Label)
- public scaleTime: cc.Label = null;
- @property({
- tooltip: '图片初始缩放'
- })
- public defaultScaling: number = 1.1;
- @property({
- tooltip: '图片缩放最小scale'
- })
- public minScale: number = 1;
- @property({
- tooltip: '图片缩放最大scale'
- })
- public maxScale: number = 3;
- @property({
- tooltip: '单点触摸容忍误差'
- })
- public moveOffset: number = 2;
- @property({
- tooltip: '滚轮缩放比率'
- })
- public increaseRate: number = 10000;
- @property({
- displayName: '双指缩放速率',
- max: 10,
- min: 0.001,
- })
- public fingerIncreaseRate: number = 1;
- public locked: boolean = false; // 操作锁
- public singleTouchCb: Function = null; // 点击回调函数
- private isMoving: boolean = false; // 是否拖动地图flag
- private mapTouchList: any[] = []; // 触摸点列表容器
- @property
- public isStrict: boolean = false; // 默认为非严格模式
- protected onLoad(): void {
- }
- protected start() {
- this.addEvent();
- this.smoothOperate(this.map, cc.Vec2.ZERO, this.defaultScaling);
- }
- // 有些设备单点过于灵敏,单点操作会触发TOUCH_MOVE回调,在这里作误差值判断
- private canStartMove(touch: any): boolean {
- let startPos: any = touch.getStartLocation();
- let nowPos: any = touch.getLocation();
- // 有些设备单点过于灵敏,单点操作会触发TOUCH_MOVE回调,在这里作误差值判断
- return (Math.abs(nowPos.x - startPos.x) > this.moveOffset
- || Math.abs(nowPos.y - startPos.y) > this.moveOffset);
- }
- private addEvent(): void {
- this.node.on(cc.Node.EventType.TOUCH_MOVE, function (event: any) {
- if (this.locked) return;
- let touches: any[] = event.getTouches(); // 获取所有触摸点
- if (this.isStrict) { // 严格模式下过滤掉初始点击位置不在目标节点范围内的触摸点
- touches
- .filter(v => {
- let startPos: cc.Vec2 = cc.v2(v.getStartLocation()); // 触摸点最初的位置
- let worldPos: cc.Vec2 = this.node.convertToWorldSpaceAR(cc.Vec2.ZERO);
- let worldRect: cc.Rect = cc.rect(
- worldPos.x - this.node.width / 2,
- worldPos.y - this.node.height / 2,
- this.node.width,
- this.node.height
- );
- return worldRect.contains(startPos);
- })
- .forEach(v => { // 将有效的触摸点放在容器里自行管理
- let intersection: any[] = this.mapTouchList.filter(v1 => v1.id === v.getID());
- if (intersection.length === 0)
- this.mapTouchList[this.mapTouchList.length] = ({ id: v.getID(), touch: v });
- });
- touches = this.mapTouchList.map(v => v.touch);
- }
- if (touches.length >= 2) {
- // cc.log('multi touch');
- // multi touch
- this.isMoving = true;
- let touch1: any = touches[0];
- let touch2: any = touches[1];
- let delta1: cc.Vec2 = cc.v2(touch1.getDelta());
- let delta2: cc.Vec2 = cc.v2(touch2.getDelta());
- let touchPoint1: cc.Vec2 = this.map.convertToNodeSpaceAR(cc.v2(touch1.getLocation()));
- let touchPoint2: cc.Vec2 = this.map.convertToNodeSpaceAR(cc.v2(touch2.getLocation()));
- let distance: cc.Vec2 = touchPoint1.sub(touchPoint2);
- const rateV2: cc.Vec2 = cc.v2(this.fingerIncreaseRate, this.fingerIncreaseRate);
- let delta: cc.Vec2 = delta1.sub(delta2).scale(rateV2);
- let scale: number = 1;
- if (Math.abs(distance.x) > Math.abs(distance.y)) {
- scale = (distance.x + delta.x) / distance.x * this.map.scaleX;
- }
- else {
- scale = (distance.y + delta.y) / distance.y * this.map.scaleY;
- }
- let pos: cc.Vec2 = touchPoint2.add(cc.v2(distance.x / 2, distance.y / 2));
- this.smoothOperate(this.map, pos, scale);
- }
- else if (touches.length === 1) {
- // cc.log('single touch');
- // single touch
- if (this.isMoving || this.canStartMove(touches[0])) {
- this.isMoving = true;
- let dir: cc.Vec2 = cc.v2(touches[0].getDelta());
- this.dealMove(dir, this.map, this.node);
- }
- }
- }, this);
- this.node.on(cc.Node.EventType.TOUCH_END, function (event: any) {
- if (this.locked) return;
- let touches: any[] = this.isStrict ? this.mapTouchList : event.getTouches();
- if (touches.length <= 1) {
- if (!this.isMoving) {
- let worldPos: cc.Vec2 = cc.v2(event.getLocation());
- let nodePos: cc.Vec2 = this.map.convertToNodeSpaceAR(worldPos);
- this.dealSelect(nodePos);
- }
- this.isMoving = false; // 当容器中仅剩最后一个触摸点时将移动flag还原
- };
- if (this.isStrict)
- this.removeTouchFromContent(event, this.mapTouchList);
- }, this);
- this.node.on(cc.Node.EventType.TOUCH_CANCEL, function (event: any) {
- if (this.locked) return;
- let touches: any[] = this.isStrict ? this.mapTouchList : event.getTouches();
- // 当容器中仅剩最后一个触摸点时将移动flag还原
- if (touches.length <= 1) this.isMoving = false;
- if (this.isStrict)
- this.removeTouchFromContent(event, this.mapTouchList);
- }, this);
- this.node.on(cc.Node.EventType.MOUSE_WHEEL, function (event: any) {
- if (this.locked) return;
- let worldPos: cc.Vec2 = cc.v2(event.getLocation());
- let scrollDelta: number = event.getScrollY();
- let scale: number = (this.map.scale + (scrollDelta / this.increaseRate));
- let target: cc.Node = this.map;
- let pos: cc.Vec2 = target.convertToNodeSpaceAR(worldPos);
- this.smoothOperate(target, pos, scale);
- }, this);
- // 监听键盘按下事件
- cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
- // 监听键盘松开事件
- cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
- }
- onKeyDown(event) {
- switch (event.keyCode) {
- case cc.macro.KEY.ctrl:
- this.increaseRate = 1000
- break;
- // 添加更多按键处理逻辑
- }
- }
- onKeyUp(event) {
- switch (event.keyCode) {
- case cc.macro.KEY.ctrl:
- this.increaseRate = 10000
- break;
- // 添加更多按键处理逻辑
- }
- }
- onDestroy() {
- // 在组件销毁时,取消键盘事件监听
- cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
- cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
- }
- public removeTouchFromContent(event: any, content: any[]): void {
- let eventToucheIDs: number[] = event['getTouches']().map(v => v.getID());
- for (let len = content.length, i = len - 1; i >= 0; --i) {
- if (eventToucheIDs.indexOf(content[i].id) > -1)
- content.splice(i, 1); // 删除触摸
- }
- }
- private smoothOperate(target: cc.Node, pos: cc.Vec2, scale: number): void {
- // 放大缩小
- if (this.minScale <= scale && scale <= this.maxScale) {
- // 当前缩放值与原来缩放值之差
- let deltaScale: number = scale - target.scaleX;
- // 当前点击的坐标与缩放值差像乘
- let gapPos: cc.Vec2 = pos.scale(cc.v2(deltaScale, deltaScale));
- // 当前node坐标位置减去点击 点击坐标和缩放值的值
- //@ts-ignore
- let mapPos: cc.Vec2 = target.position.sub(gapPos);
- // 获取速率的小数后几位,防止速率过小时取整直接舍弃掉了变化
- const rateStr: string = this.fingerIncreaseRate.toString();
- const digit: number = rateStr.split('.')[1] ? rateStr.split('.')[1].length : 0;
- const rate: number = Math.pow(10, 2 + digit);
- scale = Math.floor(scale * rate) / rate;
- target.scale = scale;
- this.dealScalePos(mapPos, target);
- }
- else {
- scale = cc.misc.clampf(scale, this.minScale, this.maxScale);
- }
- // render ui
- if (cc.isValid(this.scaleTime))
- this.scaleTime.string = `${Math.floor(scale * 100)}%`;
- }
- private dealScalePos(pos: cc.Vec2, target: cc.Node): void {
- if (target.scale === 1) {
- pos = cc.Vec2.ZERO;
- }
- else {
- let worldPos: cc.Vec2 = this.node.convertToWorldSpaceAR(pos);
- let nodePos: cc.Vec2 = this.node.convertToNodeSpaceAR(worldPos);
- let edge: any = this.calculateEdge(target, this.node, nodePos);
- if (edge.left > 0) {
- pos.x -= edge.left;
- }
- if (edge.right > 0) {
- pos.x += edge.right;
- }
- if (edge.top > 0) {
- pos.y += edge.top;
- }
- if (edge.bottom > 0) {
- pos.y -= edge.bottom;
- }
- }
- //@ts-ignore
- target.position = pos;
- }
- private dealMove(dir: cc.Vec2, map: cc.Node, container: cc.Node): void {
- let worldPos: cc.Vec2 = map.convertToWorldSpaceAR(cc.Vec2.ZERO);
- let nodePos: cc.Vec2 = container.convertToNodeSpaceAR(worldPos);
- nodePos.x += dir.x;
- nodePos.y += dir.y;
- let edge: any = this.calculateEdge(map, container, nodePos);
- if (edge.left <= 0 && edge.right <= 0) {
- map.x += dir.x;
- }
- if (edge.top <= 0 && edge.bottom <= 0) {
- map.y += dir.y;
- }
- }
- private dealSelect(nodePos: cc.Vec2): void {
- cc.log(`click map on (${nodePos.x}, ${nodePos.y})`);
- // do sth
- if (this.singleTouchCb) this.singleTouchCb(nodePos);
- }
- // 计算map的四条边距离容器的距离,为负代表超出去
- public calculateEdge(target: cc.Node, container: cc.Node, nodePos: cc.Vec2): any {
- // distance to the edge when anchor is (0.5, 0.5)
- let horizontalDistance: number = (container.width - target.width * target.scaleX) / 2;
- let verticalDistance: number = (container.height - target.height * target.scaleY) / 2;
- let left: number = horizontalDistance + nodePos.x;
- let right: number = horizontalDistance - nodePos.x;
- let top: number = verticalDistance - nodePos.y;
- let bottom: number = verticalDistance + nodePos.y;
- return { left, right, top, bottom };
- }
- /**
- * @brief 设置是否严格模式,如果为严格模式,则会过滤不在目标身上的触摸点, 反之不作处理
- * 默认为非严格模式
- * @param isStrict
- */
- public setStrictPattern(isStrict: boolean): void {
- this.isStrict = isStrict;
- }
- public getStrictPattern(): boolean {
- return this.isStrict;
- }
- }
- export = MapControl;
|