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. var len = offset.Length();
  195. if (len > maxL) //距离太大了, 需要补间
  196. {
  197. //Debug.Log($"距离太大了, 启用补间: len: {len}, maxL: {maxL}");
  198. var count = Mathf.CeilToInt(len / maxL);
  199. var step = new Vector2(offset.X / count, offset.Y / count);
  200. var prevPos = prevPosition.Value - center;
  201. for (var i = 1; i <= count; i++)
  202. {
  203. foreach (var brushPixel in brush.Pixels)
  204. {
  205. var brushPos = RotatePixels(brushPixel.X, brushPixel.Y, center.X, center.Y, rotation);
  206. var x = (int)(prevPos.X + step.X * i + brushPos.X);
  207. var y = (int)(prevPos.Y + step.Y * i + brushPos.Y);
  208. if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight)
  209. {
  210. var temp = SetPixelData(x, y, brushPixel);
  211. if (!temp.TempFlag)
  212. {
  213. temp.TempFlag = true;
  214. _tempList.Add(temp);
  215. }
  216. }
  217. }
  218. }
  219. foreach (var brushPixel in brush.Pixels)
  220. {
  221. var brushPos = RotatePixels(brushPixel.X, brushPixel.Y, center.X, center.Y, rotation);
  222. var x = pos.X + brushPos.X;
  223. var y = pos.Y + brushPos.Y;
  224. if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight)
  225. {
  226. var temp = SetPixelData(x, y, brushPixel);
  227. if (!temp.TempFlag)
  228. {
  229. temp.TempFlag = true;
  230. _tempList.Add(temp);
  231. }
  232. }
  233. }
  234.  
  235. foreach (var imagePixel in _tempList)
  236. {
  237. _changeFlag = true;
  238. _image.SetPixel(imagePixel.X, imagePixel.Y, imagePixel.Color);
  239. imagePixel.TempFlag = false;
  240. }
  241.  
  242. _tempList.Clear();
  243. return;
  244. }
  245. }
  246. foreach (var brushPixel in brush.Pixels)
  247. {
  248. var brushPos = RotatePixels(brushPixel.X, brushPixel.Y, center.X, center.Y, rotation);
  249. var x = pos.X + brushPos.X;
  250. var y = pos.Y + brushPos.Y;
  251. if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight)
  252. {
  253. _changeFlag = true;
  254. var temp = SetPixelData(x, y, brushPixel);
  255. _image.SetPixel(x, y, temp.Color);
  256. }
  257. }
  258. }
  259.  
  260. /// <summary>
  261. /// 返回是否碰到任何有效像素点
  262. /// </summary>
  263. public bool Collision(int x, int y)
  264. {
  265. if (x >= 0 && x < _imagePixels.GetLength(0) && y >= 0 && y < _imagePixels.GetLength(1))
  266. {
  267. var result = _imagePixels[x, y];
  268. if (result != null && result.IsRun)
  269. {
  270. return true;
  271. }
  272. }
  273. return false;
  274. }
  275.  
  276. /// <summary>
  277. /// 返回碰撞到的有效像素点数据
  278. /// </summary>
  279. public LiquidPixel GetPixelData(int x, int y)
  280. {
  281. if (x >= 0 && x < _imagePixels.GetLength(0) && y >= 0 && y < _imagePixels.GetLength(1))
  282. {
  283. var result = _imagePixels[x, y];
  284. if (result != null && result.IsRun)
  285. {
  286. return result;
  287. }
  288. }
  289.  
  290. return null;
  291. }
  292. /// <summary>
  293. /// 更新像素点数据逻辑, 返回是否擦除
  294. /// </summary>
  295. private bool UpdateImagePixel(LiquidPixel imagePixel)
  296. {
  297. if (imagePixel.Color.A > 0)
  298. {
  299. if (imagePixel.Timer > 0) //继续等待消散
  300. {
  301. imagePixel.Timer -= _runTime - imagePixel.TempTime;
  302. imagePixel.TempTime = _runTime;
  303. }
  304. else
  305. {
  306. imagePixel.Color.A -= imagePixel.Material.WriteOffSpeed * (_runTime - imagePixel.TempTime);
  307. if (imagePixel.Color.A <= 0) //完全透明了
  308. {
  309. _changeFlag = true;
  310. _image.SetPixel(imagePixel.X, imagePixel.Y, new Color(0, 0, 0, 0));
  311. imagePixel.IsRun = false;
  312. imagePixel.IsUpdate = false;
  313. return true;
  314. }
  315. else
  316. {
  317. _changeFlag = true;
  318. _image.SetPixel(imagePixel.X, imagePixel.Y, imagePixel.Color);
  319. imagePixel.TempTime = _runTime;
  320. }
  321. }
  322. }
  323.  
  324. return false;
  325. }
  326. //记录像素点数据
  327. private LiquidPixel SetPixelData(int x, int y, BrushPixelData pixelData)
  328. {
  329. var temp = _imagePixels[x, y];
  330. if (temp == null)
  331. {
  332. temp = new LiquidPixel()
  333. {
  334. X = x,
  335. Y = y,
  336. Color = pixelData.Color,
  337. Material = pixelData.Material,
  338. Timer = pixelData.Material.Duration,
  339. };
  340. _imagePixels[x, y] = temp;
  341.  
  342. temp.IsRun = true;
  343. temp.IsUpdate = temp.Material.Duration >= 0;
  344. if (temp.IsUpdate)
  345. {
  346. _updateImagePixels.Add(temp);
  347. }
  348. temp.TempTime = _runTime;
  349. }
  350. else
  351. {
  352. if (temp.Material != pixelData.Material)
  353. {
  354. temp.Color = pixelData.Color;
  355. temp.Material = pixelData.Material;
  356. }
  357. else
  358. {
  359. var tempColor = pixelData.Color;
  360. temp.Color = new Color(tempColor.R, tempColor.G, tempColor.B, Mathf.Max(temp.Color.A, tempColor.A));
  361. }
  362. temp.Timer = pixelData.Material.Duration;
  363. var prevUpdate = temp.IsUpdate;
  364. temp.IsUpdate = temp.Material.Duration >= 0;
  365. if (!prevUpdate && temp.IsUpdate)
  366. {
  367. _updateImagePixels.Add(temp);
  368. }
  369. else if (prevUpdate && !temp.IsUpdate)
  370. {
  371. _updateImagePixels.Remove(temp);
  372. }
  373. temp.IsRun = true;
  374. temp.TempTime = _runTime;
  375. }
  376.  
  377. return temp;
  378. }
  379.  
  380. /// <summary>
  381. /// 根据 rotation 旋转像素点坐标, 并返回旋转后的坐标, rotation 为弧度制角度, 旋转中心点为 centerX, centerY
  382. /// </summary>
  383. private Vector2I RotatePixels(int x, int y, int centerX, int centerY, float rotation)
  384. {
  385. if (rotation == 0)
  386. {
  387. return new Vector2I(x, y);
  388. }
  389.  
  390. x -= centerX;
  391. y -= centerY;
  392. var sv = Mathf.Sin(rotation);
  393. var cv = Mathf.Cos(rotation);
  394. var newX = Mathf.RoundToInt(x * cv - y * sv);
  395. var newY = Mathf.RoundToInt(x * sv + y * cv);
  396. newX += centerX;
  397. newY += centerY;
  398. return new Vector2I(newX, newY);
  399. }
  400. }