網站首頁 編程語言 正文
前言
上一篇介紹了手勢在畫布上的應用,那么手勢與繪制畫布究竟能摩擦出怎樣的火花呢,本篇文章將為你詳解手游中操縱桿移動角色的的原理與實現過程。
基本思路
確定操縱桿區域,確定點擊時手勢響應區域,當手指滑動操縱桿時,計算出當前的手指位置與當前操縱桿圓心偏移弧度,從而確定當前角色的移動方向。接下來就一步一步實現吧。
繪制
繪制操縱桿的靜態圖形,玩過手游應該知道操縱桿基本構成由底部圓和手指移動圓球組成,手指移動的圓球圍繞底圓進行360°旋轉從而控制角色朝不同方向移動。
靜態效果
操縱桿的核心是由兩個圓形組成,代碼也非常簡單。
繪制代碼:
// 底圓 canvas.drawCircle( Offset(0,0), bgR, _paint ..style = PaintingStyle.fill ..color = Colors.blue.withOpacity(0.2)); _paint.color = color; _paint.style = PaintingStyle.stroke; /// 手勢小圓 canvas.drawCircle( Offset(0,0), bgR / 3, _paint ..style = PaintingStyle.fill ..color = Colors.blue.withOpacity(0.9));
添加手勢交互 GestureDetector
大概思路:
當點擊可觸控區域,將操縱桿移動到當前手指按下的位置,移動手指,根據手指位置坐標和按下時圓心位置坐標計算偏移角度得出手指相對于底圓的坐標點,松開手指,操縱桿進行復位回到初始位置。
手勢組件
return GestureDetector( child: CustomPaint( size: size, painter: JoyStickPainter( offset: _offset, offsetCenter: _offsetCenter, listenable: Listenable.merge([_offset, _offsetCenter])), ), // 按下 onPanDown: down, // 移動 onPanUpdate: update, // 抬起 onPanEnd: reset, );
備注:上篇文章介紹了,手指觸控屏幕的坐標點永遠都是以左上角為原點的,為了方便理解和計算,我們同樣也需要將手指的坐標的原點進行偏移到畫布中央和畫布保持一致,所以這里我們通過手勢獲取的坐標點之后需要進行偏移。
不管手指點擊、移動、還是抬起都要通知畫布進行更新,這里使用ValueNotifier<Offset>
通知坐標點更新。
ValueNotifier<Offset> _offset = ValueNotifier(Offset.zero);
點擊交互 down: 當用戶點擊可觸控區域,將大圓和小圓移動至手指點擊的位置。
因為底圓在點擊之后抬起之前都是處于靜止狀態,當移動手指只有小圓移動,所以這里用兩個坐標來保存底圓的圓心,和小圓的圓心,當點擊時,底圓和小圓的中心是一致的,所以這里當點擊時同時更新兩個圓心位置。
down(DragDownDetails details) { Offset offset = details.localPosition; _offsetCenter.value = offset.translate(-size.width / 2, -size.height / 2); _offset.value = offset.translate(-size.width / 2, -size.height / 2); }
這里需要注意的是,當我們的手指點擊在可觸控區域邊界距離小于底圓半徑時,需要控制圓心位置的x軸和y軸距離可觸控區域邊界距離大于等于底圓半徑。
如果不控制邊界點擊時,操縱桿會偏離出觸控區域
所以這里最好在點擊時可以加一個邊界處理,上下左右加一個邊界控制。
if (offset.dx > size.width - bgR) { offset = Offset(size.width - bgR, offset.dy); } if (offset.dx < bgR) { offset = Offset(bgR, offset.dy); } if (offset.dy > size.height - bgR) { offset = Offset(offset.dx, size.height - bgR); } if (offset.dy < bgR) { offset = Offset(offset.dx, bgR); }
之后再點擊邊界時就不會出界了。
移動交互 update: 當用戶移動手指時,小圓根據手指在底圓內部進行移動。
手指移動是操縱桿的核心交互邏輯。
思路: 當手指點擊之后移動離開圓心,計算當前坐標點以當前底圓圓心為原點的偏移弧度,通過反正切函數atan2(y,x)
可以得出當前坐標針對x軸向右為正,y軸向下為正的偏移弧度α
,默認范圍 [-pi]-[pi]
, 為了方便理解計算,這里我們將得到的角度+pi
轉換為 0-2pi,角度范圍:0-360°
。見下圖:
Offset
類里的direction(y,x)
方法就是通過atan2
方法計算當前坐標的偏移弧度。
/// The angle of this offset as radians clockwise from the positive x-axis, in /// the range -[pi] to [pi], assuming positive values of the x-axis go to the /// right and positive values of the y-axis go down. double get direction => math.atan2(dy, dx);
角色移動的關鍵就是通過得出的偏移弧度來進行不同方向的移動。
核心代碼:
/// 手指移動坐標 var offsetTranslate = offset.value; /// 操縱桿圓心坐標 var offsetTranslateCenter = offsetCenter.value; /// 計算當前位置坐標點 左半區域 X為負數 double x = offsetTranslateCenter.dx - offsetTranslate.dx; /// y軸 下半區域 Y為負數 double y = offsetTranslateCenter.dy - offsetTranslate.dy; /// 反正切函數 通過此函數可以計算出此坐標旋轉的弧度 為正 代表X軸逆時針旋轉的角度 為負 順時針旋轉角度 /// 范圍 [-pi] - [pi] double ata = atan2(y, x); /// 默認坐標系范圍為-pi - pi 順時針旋轉坐標系180度 變為 0 - 2*pi; var thta = ata + pi; print("angle ${(180 / pi * thta).toInt()}");
這里手指移動分為2種情況,手指在底圓內部和手指在底圓外部。見下圖:
當手指在底圓內部,我們可以直接使用當前手指傳遞的坐標計算。
當手指移動到底圓外部,我們需要控制小圓的圓形坐標不能跑到底圓的外部,控制小圓 不能超過底圓的的范圍,所以,這里需要進行計算當前手指的坐標距離底圓圓心的距離有沒有超過底圓半徑,如果超出,需要計算小圓的臨界坐標值。
有了偏移弧度α
,我們就可以通過三角函數計算出上面x1,y1的坐標點
,也就是當前手指控制小圓圓心的臨界坐標。
核心代碼:
/// 當前手指坐標距離底圓圓心長度 var r = sqrt(pow(x, 2) + pow(y, 2)); if (r > bgR) { var dx = bgR * cos(thta) + offsetTranslateCenter.dx; // x軸坐標點 var dy = bgR * sin(thta) + offsetTranslateCenter.dy; // y軸坐標點 offsetTranslate = Offset(dx, dy); }
松開交互 reset: 當用戶點擊可觸控區域,將大圓和小圓移動至手指點擊的位置。
將兩個圓的圓心回歸坐標系原點。
reset(DragEndDetails details) { _offset.value = Offset.zero; _offsetCenter.value = Offset.zero; }
注意的是,當點擊和松開時,當前角色都是不動的,只有當移動時才傳遞角度值賦給角色進行移動,所以當這里需要判斷當前手指觸控點和底圓圓心是否重合,如果重合表示當前角色處于靜止狀態。因為默認不作處理,弧度獲取的是pi
,所以這里需要特殊處理一下。 這里我們需要將獲取的弧度值傳遞出去,如果當前處于靜止狀態,將弧度設為負數,因為我們的弧度范圍是0-2pi
,移動狀態中不可能為負。
if (x == 0 && y == 0) { onAngle?.call(-1); } else { onAngle?.call(thta); }
為了方便展示效果,我加了坐標軸輔助,這樣看起來更直觀一些。
最終效果:
通過當前獲取的弧度值即可傳遞給角色進行移動。
總結
本篇文章主要介紹了操縱桿如何向角色傳遞有效信息從而控制角色移動,其實操縱桿的實現邏輯并不復雜,主要難點集中在手指移動計算偏移弧度哪里,還有就是小圓球的邊界處理,掌握了這兩點,也就掌握了核心邏輯。
原文鏈接:https://juejin.cn/post/7118584436047740936
相關推薦
- 2023-03-28 Python代碼庫之Tuple如何append添加元素問題_python
- 2022-07-12 strcpy、strncpy與memcpy的區別你了解嗎?
- 2023-04-03 GoLang中拼接字符串性能優化方法詳解_Golang
- 2022-08-04 十分鐘教會你用Python處理CSV文件_python
- 2022-03-17 C語言判斷數是否為素數與素數輸出_C 語言
- 2022-12-04 pyecharts如何旋轉折線圖的X軸標簽_python
- 2024-01-11 spring 事務控制 設置手動回滾 TransactionAspectSupport.curren
- 2022-04-17 IDEA下載源文件,報找不到
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支