MapControl.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. /**
  2. * @author xshu
  3. * @date 2019-12-07
  4. */
  5. const { ccclass, property } = cc._decorator;
  6. @ccclass
  7. class MapControl extends cc.Component {
  8. @property({
  9. type: cc.Node,
  10. tooltip: '目标节点'
  11. })
  12. public map: cc.Node = null;
  13. @property(cc.Label)
  14. public scaleTime: cc.Label = null;
  15. @property({
  16. tooltip: '图片初始缩放'
  17. })
  18. public defaultScaling: number = 1.1;
  19. @property({
  20. tooltip: '图片缩放最小scale'
  21. })
  22. public minScale: number = 1;
  23. @property({
  24. tooltip: '图片缩放最大scale'
  25. })
  26. public maxScale: number = 3;
  27. @property({
  28. tooltip: '单点触摸容忍误差'
  29. })
  30. public moveOffset: number = 2;
  31. @property({
  32. tooltip: '滚轮缩放比率'
  33. })
  34. public increaseRate: number = 10000;
  35. @property({
  36. displayName: '双指缩放速率',
  37. max: 10,
  38. min: 0.001,
  39. })
  40. public fingerIncreaseRate: number = 1;
  41. public locked: boolean = false; // 操作锁
  42. public singleTouchCb: Function = null; // 点击回调函数
  43. private isMoving: boolean = false; // 是否拖动地图flag
  44. private mapTouchList: any[] = []; // 触摸点列表容器
  45. @property
  46. public isStrict: boolean = false; // 默认为非严格模式
  47. protected onLoad(): void {
  48. }
  49. protected start() {
  50. this.addEvent();
  51. this.smoothOperate(this.map, cc.Vec2.ZERO, this.defaultScaling);
  52. }
  53. // 有些设备单点过于灵敏,单点操作会触发TOUCH_MOVE回调,在这里作误差值判断
  54. private canStartMove(touch: any): boolean {
  55. let startPos: any = touch.getStartLocation();
  56. let nowPos: any = touch.getLocation();
  57. // 有些设备单点过于灵敏,单点操作会触发TOUCH_MOVE回调,在这里作误差值判断
  58. return (Math.abs(nowPos.x - startPos.x) > this.moveOffset
  59. || Math.abs(nowPos.y - startPos.y) > this.moveOffset);
  60. }
  61. private addEvent(): void {
  62. this.node.on(cc.Node.EventType.TOUCH_MOVE, function (event: any) {
  63. if (this.locked) return;
  64. let touches: any[] = event.getTouches(); // 获取所有触摸点
  65. if (this.isStrict) { // 严格模式下过滤掉初始点击位置不在目标节点范围内的触摸点
  66. touches
  67. .filter(v => {
  68. let startPos: cc.Vec2 = cc.v2(v.getStartLocation()); // 触摸点最初的位置
  69. let worldPos: cc.Vec2 = this.node.convertToWorldSpaceAR(cc.Vec2.ZERO);
  70. let worldRect: cc.Rect = cc.rect(
  71. worldPos.x - this.node.width / 2,
  72. worldPos.y - this.node.height / 2,
  73. this.node.width,
  74. this.node.height
  75. );
  76. return worldRect.contains(startPos);
  77. })
  78. .forEach(v => { // 将有效的触摸点放在容器里自行管理
  79. let intersection: any[] = this.mapTouchList.filter(v1 => v1.id === v.getID());
  80. if (intersection.length === 0)
  81. this.mapTouchList[this.mapTouchList.length] = ({ id: v.getID(), touch: v });
  82. });
  83. touches = this.mapTouchList.map(v => v.touch);
  84. }
  85. if (touches.length >= 2) {
  86. // cc.log('multi touch');
  87. // multi touch
  88. this.isMoving = true;
  89. let touch1: any = touches[0];
  90. let touch2: any = touches[1];
  91. let delta1: cc.Vec2 = cc.v2(touch1.getDelta());
  92. let delta2: cc.Vec2 = cc.v2(touch2.getDelta());
  93. let touchPoint1: cc.Vec2 = this.map.convertToNodeSpaceAR(cc.v2(touch1.getLocation()));
  94. let touchPoint2: cc.Vec2 = this.map.convertToNodeSpaceAR(cc.v2(touch2.getLocation()));
  95. let distance: cc.Vec2 = touchPoint1.sub(touchPoint2);
  96. const rateV2: cc.Vec2 = cc.v2(this.fingerIncreaseRate, this.fingerIncreaseRate);
  97. let delta: cc.Vec2 = delta1.sub(delta2).scale(rateV2);
  98. let scale: number = 1;
  99. if (Math.abs(distance.x) > Math.abs(distance.y)) {
  100. scale = (distance.x + delta.x) / distance.x * this.map.scaleX;
  101. }
  102. else {
  103. scale = (distance.y + delta.y) / distance.y * this.map.scaleY;
  104. }
  105. let pos: cc.Vec2 = touchPoint2.add(cc.v2(distance.x / 2, distance.y / 2));
  106. this.smoothOperate(this.map, pos, scale);
  107. }
  108. else if (touches.length === 1) {
  109. // cc.log('single touch');
  110. // single touch
  111. if (this.isMoving || this.canStartMove(touches[0])) {
  112. this.isMoving = true;
  113. let dir: cc.Vec2 = cc.v2(touches[0].getDelta());
  114. this.dealMove(dir, this.map, this.node);
  115. }
  116. }
  117. }, this);
  118. this.node.on(cc.Node.EventType.TOUCH_END, function (event: any) {
  119. if (this.locked) return;
  120. let touches: any[] = this.isStrict ? this.mapTouchList : event.getTouches();
  121. if (touches.length <= 1) {
  122. if (!this.isMoving) {
  123. let worldPos: cc.Vec2 = cc.v2(event.getLocation());
  124. let nodePos: cc.Vec2 = this.map.convertToNodeSpaceAR(worldPos);
  125. this.dealSelect(nodePos);
  126. }
  127. this.isMoving = false; // 当容器中仅剩最后一个触摸点时将移动flag还原
  128. };
  129. if (this.isStrict)
  130. this.removeTouchFromContent(event, this.mapTouchList);
  131. }, this);
  132. this.node.on(cc.Node.EventType.TOUCH_CANCEL, function (event: any) {
  133. if (this.locked) return;
  134. let touches: any[] = this.isStrict ? this.mapTouchList : event.getTouches();
  135. // 当容器中仅剩最后一个触摸点时将移动flag还原
  136. if (touches.length <= 1) this.isMoving = false;
  137. if (this.isStrict)
  138. this.removeTouchFromContent(event, this.mapTouchList);
  139. }, this);
  140. this.node.on(cc.Node.EventType.MOUSE_WHEEL, function (event: any) {
  141. if (this.locked) return;
  142. let worldPos: cc.Vec2 = cc.v2(event.getLocation());
  143. let scrollDelta: number = event.getScrollY();
  144. let scale: number = (this.map.scale + (scrollDelta / this.increaseRate));
  145. let target: cc.Node = this.map;
  146. let pos: cc.Vec2 = target.convertToNodeSpaceAR(worldPos);
  147. this.smoothOperate(target, pos, scale);
  148. }, this);
  149. // 监听键盘按下事件
  150. cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
  151. // 监听键盘松开事件
  152. cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
  153. }
  154. onKeyDown(event) {
  155. switch (event.keyCode) {
  156. case cc.macro.KEY.ctrl:
  157. this.increaseRate = 1000
  158. break;
  159. // 添加更多按键处理逻辑
  160. }
  161. }
  162. onKeyUp(event) {
  163. switch (event.keyCode) {
  164. case cc.macro.KEY.ctrl:
  165. this.increaseRate = 10000
  166. break;
  167. // 添加更多按键处理逻辑
  168. }
  169. }
  170. onDestroy() {
  171. // 在组件销毁时,取消键盘事件监听
  172. cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
  173. cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
  174. }
  175. public removeTouchFromContent(event: any, content: any[]): void {
  176. let eventToucheIDs: number[] = event['getTouches']().map(v => v.getID());
  177. for (let len = content.length, i = len - 1; i >= 0; --i) {
  178. if (eventToucheIDs.indexOf(content[i].id) > -1)
  179. content.splice(i, 1); // 删除触摸
  180. }
  181. }
  182. private smoothOperate(target: cc.Node, pos: cc.Vec2, scale: number): void {
  183. // 放大缩小
  184. if (this.minScale <= scale && scale <= this.maxScale) {
  185. // 当前缩放值与原来缩放值之差
  186. let deltaScale: number = scale - target.scaleX;
  187. // 当前点击的坐标与缩放值差像乘
  188. let gapPos: cc.Vec2 = pos.scale(cc.v2(deltaScale, deltaScale));
  189. // 当前node坐标位置减去点击 点击坐标和缩放值的值
  190. //@ts-ignore
  191. let mapPos: cc.Vec2 = target.position.sub(gapPos);
  192. // 获取速率的小数后几位,防止速率过小时取整直接舍弃掉了变化
  193. const rateStr: string = this.fingerIncreaseRate.toString();
  194. const digit: number = rateStr.split('.')[1] ? rateStr.split('.')[1].length : 0;
  195. const rate: number = Math.pow(10, 2 + digit);
  196. scale = Math.floor(scale * rate) / rate;
  197. target.scale = scale;
  198. this.dealScalePos(mapPos, target);
  199. }
  200. else {
  201. scale = cc.misc.clampf(scale, this.minScale, this.maxScale);
  202. }
  203. // render ui
  204. if (cc.isValid(this.scaleTime))
  205. this.scaleTime.string = `${Math.floor(scale * 100)}%`;
  206. }
  207. private dealScalePos(pos: cc.Vec2, target: cc.Node): void {
  208. if (target.scale === 1) {
  209. pos = cc.Vec2.ZERO;
  210. }
  211. else {
  212. let worldPos: cc.Vec2 = this.node.convertToWorldSpaceAR(pos);
  213. let nodePos: cc.Vec2 = this.node.convertToNodeSpaceAR(worldPos);
  214. let edge: any = this.calculateEdge(target, this.node, nodePos);
  215. if (edge.left > 0) {
  216. pos.x -= edge.left;
  217. }
  218. if (edge.right > 0) {
  219. pos.x += edge.right;
  220. }
  221. if (edge.top > 0) {
  222. pos.y += edge.top;
  223. }
  224. if (edge.bottom > 0) {
  225. pos.y -= edge.bottom;
  226. }
  227. }
  228. //@ts-ignore
  229. target.position = pos;
  230. }
  231. private dealMove(dir: cc.Vec2, map: cc.Node, container: cc.Node): void {
  232. let worldPos: cc.Vec2 = map.convertToWorldSpaceAR(cc.Vec2.ZERO);
  233. let nodePos: cc.Vec2 = container.convertToNodeSpaceAR(worldPos);
  234. nodePos.x += dir.x;
  235. nodePos.y += dir.y;
  236. let edge: any = this.calculateEdge(map, container, nodePos);
  237. if (edge.left <= 0 && edge.right <= 0) {
  238. map.x += dir.x;
  239. }
  240. if (edge.top <= 0 && edge.bottom <= 0) {
  241. map.y += dir.y;
  242. }
  243. }
  244. private dealSelect(nodePos: cc.Vec2): void {
  245. cc.log(`click map on (${nodePos.x}, ${nodePos.y})`);
  246. // do sth
  247. if (this.singleTouchCb) this.singleTouchCb(nodePos);
  248. }
  249. // 计算map的四条边距离容器的距离,为负代表超出去
  250. public calculateEdge(target: cc.Node, container: cc.Node, nodePos: cc.Vec2): any {
  251. // distance to the edge when anchor is (0.5, 0.5)
  252. let horizontalDistance: number = (container.width - target.width * target.scaleX) / 2;
  253. let verticalDistance: number = (container.height - target.height * target.scaleY) / 2;
  254. let left: number = horizontalDistance + nodePos.x;
  255. let right: number = horizontalDistance - nodePos.x;
  256. let top: number = verticalDistance - nodePos.y;
  257. let bottom: number = verticalDistance + nodePos.y;
  258. return { left, right, top, bottom };
  259. }
  260. /**
  261. * @brief 设置是否严格模式,如果为严格模式,则会过滤不在目标身上的触摸点, 反之不作处理
  262. * 默认为非严格模式
  263. * @param isStrict
  264. */
  265. public setStrictPattern(isStrict: boolean): void {
  266. this.isStrict = isStrict;
  267. }
  268. public getStrictPattern(): boolean {
  269. return this.isStrict;
  270. }
  271. }
  272. export = MapControl;