Newer
Older
DungeonShooting / DungeonShooting_Godot / src / framework / map / liquid / LiquidCanvas.cs
@小李xl 小李xl on 8 Apr 2024 14 KB 优化液体shader
  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. var oldA = imagePixel.Color.A;
  308. imagePixel.Color.A -= imagePixel.Material.WriteOffSpeed * (_runTime - imagePixel.TempTime);
  309. if (imagePixel.Color.A <= 0) //完全透明了
  310. {
  311. _changeFlag = true;
  312. _image.SetPixel(imagePixel.X, imagePixel.Y, new Color(0, 0, 0, 0));
  313. imagePixel.IsRun = false;
  314. imagePixel.IsUpdate = false;
  315. return true;
  316. }
  317. else if (!Utils.IsSameGradient(oldA, imagePixel.Color.A, GameConfig.LiquidGradient)) //同一渐变梯度下才会有颜色变化
  318. {
  319. _changeFlag = true;
  320. _image.SetPixel(imagePixel.X, imagePixel.Y, imagePixel.Color);
  321. imagePixel.TempTime = _runTime;
  322. }
  323. }
  324. }
  325.  
  326. return false;
  327. }
  328. //记录像素点数据
  329. private LiquidPixel SetPixelData(int x, int y, BrushPixelData pixelData)
  330. {
  331. var temp = _imagePixels[x, y];
  332. if (temp == null)
  333. {
  334. temp = new LiquidPixel()
  335. {
  336. X = x,
  337. Y = y,
  338. Color = pixelData.Color,
  339. Material = pixelData.Material,
  340. Timer = pixelData.Material.Duration,
  341. };
  342. _imagePixels[x, y] = temp;
  343.  
  344. temp.IsRun = true;
  345. temp.IsUpdate = temp.Material.Duration >= 0;
  346. if (temp.IsUpdate)
  347. {
  348. _updateImagePixels.Add(temp);
  349. }
  350. temp.TempTime = _runTime;
  351. }
  352. else
  353. {
  354. if (temp.Material != pixelData.Material)
  355. {
  356. temp.Color = pixelData.Color;
  357. temp.Material = pixelData.Material;
  358. }
  359. else
  360. {
  361. var tempColor = pixelData.Color;
  362. temp.Color = new Color(tempColor.R, tempColor.G, tempColor.B, Mathf.Max(temp.Color.A, tempColor.A));
  363. }
  364. temp.Timer = pixelData.Material.Duration;
  365. var prevUpdate = temp.IsUpdate;
  366. temp.IsUpdate = temp.Material.Duration >= 0;
  367. if (!prevUpdate && temp.IsUpdate)
  368. {
  369. _updateImagePixels.Add(temp);
  370. }
  371. else if (prevUpdate && !temp.IsUpdate)
  372. {
  373. _updateImagePixels.Remove(temp);
  374. }
  375. temp.IsRun = true;
  376. temp.TempTime = _runTime;
  377. }
  378.  
  379. return temp;
  380. }
  381.  
  382. /// <summary>
  383. /// 根据 rotation 旋转像素点坐标, 并返回旋转后的坐标, rotation 为弧度制角度, 旋转中心点为 centerX, centerY
  384. /// </summary>
  385. private Vector2I RotatePixels(int x, int y, int centerX, int centerY, float rotation)
  386. {
  387. if (rotation == 0)
  388. {
  389. return new Vector2I(x, y);
  390. }
  391.  
  392. x -= centerX;
  393. y -= centerY;
  394. var sv = Mathf.Sin(rotation);
  395. var cv = Mathf.Cos(rotation);
  396. var newX = Mathf.RoundToInt(x * cv - y * sv);
  397. var newY = Mathf.RoundToInt(x * sv + y * cv);
  398. newX += centerX;
  399. newY += centerY;
  400. return new Vector2I(newX, newY);
  401. }
  402. }