Newer
Older
DungeonShooting / DungeonShooting_Godot / src / framework / map / liquid / LiquidCanvas.cs
  1.  
  2. using System;
  3. using System.Collections.Generic;
  4. using Godot;
  5.  
  6. /// <summary>
  7. /// 液体画布
  8. /// </summary>
  9. public partial class LiquidCanvas : Sprite2D, IDestroy
  10. {
  11. /// <summary>
  12. /// 程序每帧最多等待执行时间, 超过这个时间的像素点将交到下一帧执行, 单位: 毫秒
  13. /// </summary>
  14. public static float MaxWaitTime { get; set; } = 4f;
  15.  
  16. /// <summary>
  17. /// 画布缩放
  18. /// </summary>
  19. public static int CanvasScale { get; } = 4;
  20.  
  21. /// <summary>
  22. /// 画布每秒更新频率
  23. /// </summary>
  24. public static int UpdateFps { get; set; } = 30;
  25. public bool IsDestroyed { get; private set; }
  26. private Image _image;
  27. private ImageTexture _texture;
  28. //画布上的像素点
  29. private LiquidPixel[,] _imagePixels;
  30. //需要执行更新的像素点
  31. private List<LiquidPixel> _updateImagePixels = new List<LiquidPixel>();
  32. //画布已经运行的时间
  33. private float _runTime = 0;
  34. private int _executeIndex = -1;
  35. //用于记录补间操作下有变动的像素点
  36. private List<LiquidPixel> _tempList = new List<LiquidPixel>();
  37. //记录是否有像素点发生变动
  38. private bool _changeFlag = false;
  39. //所属房间
  40. private RoomInfo _roomInfo;
  41. private double _processTimer;
  42.  
  43. public LiquidCanvas(RoomInfo roomInfo, int width, int height)
  44. {
  45. _roomInfo = roomInfo;
  46. Centered = false;
  47. Material = ResourceManager.Load<Material>(ResourcePath.resource_material_Sawtooth_tres);
  48. _image = Image.Create(width, height, false, Image.Format.Rgba8);
  49. _texture = ImageTexture.CreateFromImage(_image);
  50. Texture = _texture;
  51. _imagePixels = new LiquidPixel[width, height];
  52. }
  53. public void Destroy()
  54. {
  55. if (IsDestroyed)
  56. {
  57. return;
  58. }
  59. IsDestroyed = true;
  60. QueueFree();
  61. _texture.Dispose();
  62. _image.Dispose();
  63. }
  64.  
  65. public override void _Process(double delta)
  66. {
  67. //这里待优化, 应该每次绘制都将像素点放入 _tempList 中, 然后帧结束再统一提交
  68.  
  69. _processTimer += delta;
  70. if (_processTimer < 1d / UpdateFps)
  71. {
  72. return;
  73. }
  74.  
  75. var newDelta = _processTimer;
  76. _processTimer %= 1d / UpdateFps;
  77. //更新消除逻辑
  78. if (_updateImagePixels.Count > 0)
  79. {
  80. var startIndex = _executeIndex;
  81. if (_executeIndex < 0 || _executeIndex >= _updateImagePixels.Count)
  82. {
  83. _executeIndex = _updateImagePixels.Count - 1;
  84. }
  85.  
  86. var startTime = DateTime.UtcNow;
  87. var isOver = false;
  88. var index = 0;
  89. for (; _executeIndex >= 0; _executeIndex--)
  90. {
  91. index++;
  92. var imagePixel = _updateImagePixels[_executeIndex];
  93. if (UpdateImagePixel(imagePixel)) //移除
  94. {
  95. _updateImagePixels.RemoveAt(_executeIndex);
  96. if (_executeIndex < startIndex)
  97. {
  98. startIndex--;
  99. }
  100. }
  101.  
  102. if (index > 200)
  103. {
  104. index = 0;
  105. if ((DateTime.UtcNow - startTime).TotalMilliseconds > MaxWaitTime) //超过最大执行时间
  106. {
  107. isOver = true;
  108. break;
  109. }
  110. }
  111. }
  112.  
  113. if (!isOver && startIndex >= 0 && _executeIndex < 0)
  114. {
  115. _executeIndex = _updateImagePixels.Count - 1;
  116. for (; _executeIndex >= startIndex; _executeIndex--)
  117. {
  118. index++;
  119. var imagePixel = _updateImagePixels[_executeIndex];
  120. if (UpdateImagePixel(imagePixel)) //移除
  121. {
  122. _updateImagePixels.RemoveAt(_executeIndex);
  123. }
  124. if (index > 200)
  125. {
  126. index = 0;
  127. if ((DateTime.UtcNow - startTime).TotalMilliseconds > MaxWaitTime) //超过最大执行时间
  128. {
  129. break;
  130. }
  131. }
  132. }
  133. }
  134. }
  135.  
  136. if (_changeFlag)
  137. {
  138. _texture.Update(_image);
  139. _changeFlag = false;
  140. }
  141.  
  142. _runTime += (float)newDelta;
  143. }
  144.  
  145. /// <summary>
  146. /// 将画布外的坐标转为画布内的坐标
  147. /// </summary>
  148. public Vector2I ToLiquidCanvasPosition(Vector2 position)
  149. {
  150. return (_roomInfo.ToCanvasPosition(position) / CanvasScale).AsVector2I();
  151. }
  152.  
  153. /// <summary>
  154. /// 根据画笔数据在画布上绘制液体, 转换坐标请调用 ToLiquidCanvasPosition() 函数
  155. /// </summary>
  156. /// <param name="brush">画笔数据</param>
  157. /// <param name="position">绘制坐标, 相对于画布坐标</param>
  158. public void DrawBrush(BrushImageData brush, Vector2I position)
  159. {
  160. DrawBrush(brush, null, position, 0);
  161. }
  162. /// <summary>
  163. /// 根据画笔数据在画布上绘制液体, 转换坐标请调用 ToLiquidCanvasPosition() 函数
  164. /// </summary>
  165. /// <param name="brush">画笔数据</param>
  166. /// <param name="position">绘制坐标, 相对于画布坐标</param>
  167. /// <param name="rotation">旋转角度, 弧度制</param>
  168. public void DrawBrush(BrushImageData brush, Vector2I position, float rotation)
  169. {
  170. DrawBrush(brush, null, position, rotation);
  171. }
  172. /// <summary>
  173. /// 根据画笔数据在画布上绘制液体, 转换坐标请调用 ToLiquidCanvasPosition() 函数
  174. /// </summary>
  175. /// <param name="brush">画笔数据</param>
  176. /// <param name="prevPosition">上一帧坐标, 相对于画布坐标, 改参数用于两点距离较大时执行补间操作, 如果传 null, 则不会进行补间</param>
  177. /// <param name="position">绘制坐标, 相对于画布坐标</param>
  178. /// <param name="rotation">旋转角度, 弧度制</param>
  179. public void DrawBrush(BrushImageData brush, Vector2I? prevPosition, Vector2I position, float rotation)
  180. {
  181. var center = new Vector2I(brush.Width, brush.Height) / 2;
  182. var pos = position - center;
  183. var canvasWidth = _texture.GetWidth();
  184. var canvasHeight = _texture.GetHeight();
  185. //存在上一次记录的点
  186. if (prevPosition != null)
  187. {
  188. var offset = new Vector2(position.X - prevPosition.Value.X, position.Y - prevPosition.Value.Y);
  189. var maxL = brush.Material.Ffm * Mathf.Lerp(
  190. brush.PixelHeight,
  191. brush.PixelWidth,
  192. Mathf.Abs(Mathf.Sin(offset.Angle() - rotation + Mathf.Pi * 0.5f))
  193. );
  194. maxL = Mathf.Max(1, maxL);
  195. var len = offset.Length();
  196. if (len > maxL) //距离太大了, 需要补间
  197. {
  198. //Debug.Log($"距离太大了, 启用补间: len: {len}, maxL: {maxL}");
  199. var count = Mathf.CeilToInt(len / maxL);
  200. var step = new Vector2(offset.X / count, offset.Y / count);
  201. var prevPos = prevPosition.Value - center;
  202. for (var i = 1; i <= count; i++)
  203. {
  204. foreach (var brushPixel in brush.Pixels)
  205. {
  206. var brushPos = RotatePixels(brushPixel.X, brushPixel.Y, center.X, center.Y, rotation);
  207. var x = (int)(prevPos.X + step.X * i + brushPos.X);
  208. var y = (int)(prevPos.Y + step.Y * i + brushPos.Y);
  209. if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight)
  210. {
  211. var temp = SetPixelData(x, y, brushPixel);
  212. if (!temp.TempFlag)
  213. {
  214. temp.TempFlag = true;
  215. _tempList.Add(temp);
  216. }
  217. }
  218. }
  219. }
  220. foreach (var brushPixel in brush.Pixels)
  221. {
  222. var brushPos = RotatePixels(brushPixel.X, brushPixel.Y, center.X, center.Y, rotation);
  223. var x = pos.X + brushPos.X;
  224. var y = pos.Y + brushPos.Y;
  225. if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight)
  226. {
  227. var temp = SetPixelData(x, y, brushPixel);
  228. if (!temp.TempFlag)
  229. {
  230. temp.TempFlag = true;
  231. _tempList.Add(temp);
  232. }
  233. }
  234. }
  235.  
  236. foreach (var imagePixel in _tempList)
  237. {
  238. _changeFlag = true;
  239. _image.SetPixel(imagePixel.X, imagePixel.Y, imagePixel.Color);
  240. imagePixel.TempFlag = false;
  241. }
  242.  
  243. _tempList.Clear();
  244. return;
  245. }
  246. }
  247. foreach (var brushPixel in brush.Pixels)
  248. {
  249. var brushPos = RotatePixels(brushPixel.X, brushPixel.Y, center.X, center.Y, rotation);
  250. var x = pos.X + brushPos.X;
  251. var y = pos.Y + brushPos.Y;
  252. if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight)
  253. {
  254. _changeFlag = true;
  255. var temp = SetPixelData(x, y, brushPixel);
  256. _image.SetPixel(x, y, temp.Color);
  257. }
  258. }
  259. }
  260.  
  261. /// <summary>
  262. /// 返回是否碰到任何有效像素点
  263. /// </summary>
  264. public bool Collision(int x, int y)
  265. {
  266. if (x >= 0 && x < _imagePixels.GetLength(0) && y >= 0 && y < _imagePixels.GetLength(1))
  267. {
  268. var result = _imagePixels[x, y];
  269. if (result != null && result.IsRun)
  270. {
  271. return true;
  272. }
  273. }
  274. return false;
  275. }
  276.  
  277. /// <summary>
  278. /// 返回碰撞到的有效像素点数据
  279. /// </summary>
  280. public LiquidPixel GetPixelData(int x, int y)
  281. {
  282. if (x >= 0 && x < _imagePixels.GetLength(0) && y >= 0 && y < _imagePixels.GetLength(1))
  283. {
  284. var result = _imagePixels[x, y];
  285. if (result != null && result.IsRun)
  286. {
  287. return result;
  288. }
  289. }
  290.  
  291. return null;
  292. }
  293. /// <summary>
  294. /// 更新像素点数据逻辑, 返回是否擦除
  295. /// </summary>
  296. private bool UpdateImagePixel(LiquidPixel imagePixel)
  297. {
  298. if (imagePixel.Color.A > 0)
  299. {
  300. if (imagePixel.Timer > 0) //继续等待消散
  301. {
  302. imagePixel.Timer -= _runTime - imagePixel.TempTime;
  303. imagePixel.TempTime = _runTime;
  304. }
  305. else
  306. {
  307. imagePixel.Color.A -= imagePixel.Material.WriteOffSpeed * (_runTime - imagePixel.TempTime);
  308. if (imagePixel.Color.A <= 0) //完全透明了
  309. {
  310. _changeFlag = true;
  311. _image.SetPixel(imagePixel.X, imagePixel.Y, new Color(0, 0, 0, 0));
  312. imagePixel.IsRun = false;
  313. imagePixel.IsUpdate = false;
  314. return true;
  315. }
  316. else
  317. {
  318. _changeFlag = true;
  319. _image.SetPixel(imagePixel.X, imagePixel.Y, imagePixel.Color);
  320. imagePixel.TempTime = _runTime;
  321. }
  322. }
  323. }
  324.  
  325. return false;
  326. }
  327. //记录像素点数据
  328. private LiquidPixel SetPixelData(int x, int y, BrushPixelData pixelData)
  329. {
  330. var temp = _imagePixels[x, y];
  331. if (temp == null)
  332. {
  333. temp = new LiquidPixel()
  334. {
  335. X = x,
  336. Y = y,
  337. Color = pixelData.Color,
  338. Material = pixelData.Material,
  339. Timer = pixelData.Material.Duration,
  340. };
  341. _imagePixels[x, y] = temp;
  342.  
  343. temp.IsRun = true;
  344. temp.IsUpdate = temp.Material.Duration >= 0;
  345. if (temp.IsUpdate)
  346. {
  347. _updateImagePixels.Add(temp);
  348. }
  349. temp.TempTime = _runTime;
  350. }
  351. else
  352. {
  353. if (temp.Material != pixelData.Material)
  354. {
  355. temp.Color = pixelData.Color;
  356. temp.Material = pixelData.Material;
  357. }
  358. else
  359. {
  360. var tempColor = pixelData.Color;
  361. temp.Color = new Color(tempColor.R, tempColor.G, tempColor.B, Mathf.Max(temp.Color.A, tempColor.A));
  362. }
  363. temp.Timer = pixelData.Material.Duration;
  364. var prevUpdate = temp.IsUpdate;
  365. temp.IsUpdate = temp.Material.Duration >= 0;
  366. if (!prevUpdate && temp.IsUpdate)
  367. {
  368. _updateImagePixels.Add(temp);
  369. }
  370. else if (prevUpdate && !temp.IsUpdate)
  371. {
  372. _updateImagePixels.Remove(temp);
  373. }
  374. temp.IsRun = true;
  375. temp.TempTime = _runTime;
  376. }
  377.  
  378. return temp;
  379. }
  380.  
  381. /// <summary>
  382. /// 根据 rotation 旋转像素点坐标, 并返回旋转后的坐标, rotation 为弧度制角度, 旋转中心点为 centerX, centerY
  383. /// </summary>
  384. private Vector2I RotatePixels(int x, int y, int centerX, int centerY, float rotation)
  385. {
  386. if (rotation == 0)
  387. {
  388. return new Vector2I(x, y);
  389. }
  390.  
  391. x -= centerX;
  392. y -= centerY;
  393. var sv = Mathf.Sin(rotation);
  394. var cv = Mathf.Cos(rotation);
  395. var newX = Mathf.RoundToInt(x * cv - y * sv);
  396. var newY = Mathf.RoundToInt(x * sv + y * cv);
  397. newX += centerX;
  398. newY += centerY;
  399. return new Vector2I(newX, newY);
  400. }
  401. }