diff --git a/DungeonShooting_Godot/project.godot b/DungeonShooting_Godot/project.godot index 601f8cb..fac0b6e 100644 --- a/DungeonShooting_Godot/project.godot +++ b/DungeonShooting_Godot/project.godot @@ -11,7 +11,7 @@ [application] config/name="DungeonShooting" -run/main_scene="res://scene/Main.tscn" +run/main_scene="res://scene/test/TestOptimizeSprite.tscn" config/features=PackedStringArray("4.1", "C#") config/icon="res://icon.png" diff --git a/DungeonShooting_Godot/scene/test/TestOptimizeSprite.tscn b/DungeonShooting_Godot/scene/test/TestOptimizeSprite.tscn index 0ac97b4..2122239 100644 --- a/DungeonShooting_Godot/scene/test/TestOptimizeSprite.tscn +++ b/DungeonShooting_Godot/scene/test/TestOptimizeSprite.tscn @@ -11,6 +11,16 @@ [ext_resource type="Texture2D" uid="uid://yn8t7ovmt4gj" path="res://resource/sprite/weapon/gun1.png" id="9_mddgj"] [ext_resource type="Texture2D" uid="uid://5geiuvv6hyov" path="res://resource/sprite/weapon/gun2.png" id="10_rxwsh"] -[node name="TestOptimizeSprite" type="Node2D"] +[node name="TestOptimizeSprite" type="Node2D" node_paths=PackedStringArray("SubViewport", "ViewCamera")] script = ExtResource("1_rwuav") ImageList = Array[Texture2D]([ExtResource("2_qvs0g"), ExtResource("3_jejmg"), ExtResource("4_twsss"), ExtResource("5_bdcjp"), ExtResource("6_sqcu7"), ExtResource("7_w4iwy"), ExtResource("8_csd6d"), ExtResource("9_mddgj"), ExtResource("10_rxwsh")]) +SubViewport = NodePath("SubViewport") +ViewCamera = NodePath("SubViewport/Camera2D") + +[node name="SubViewport" type="SubViewport" parent="."] +transparent_bg = true +canvas_item_default_texture_filter = 0 +render_target_update_mode = 4 + +[node name="Camera2D" type="Camera2D" parent="SubViewport"] +anchor_mode = 0 diff --git a/DungeonShooting_Godot/src/framework/map/image/ImageCanvas.cs b/DungeonShooting_Godot/src/framework/map/image/ImageCanvas.cs index 0e8f50d..80eeea2 100644 --- a/DungeonShooting_Godot/src/framework/map/image/ImageCanvas.cs +++ b/DungeonShooting_Godot/src/framework/map/image/ImageCanvas.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; using Godot; public partial class ImageCanvas : Sprite2D, IDestroy @@ -28,7 +26,7 @@ _canvas = Image.Create(width, height, false, Image.Format.Rgba8); _texture = ImageTexture.CreateFromImage(_canvas); - _canvas.Fill(Colors.Gray); + //_canvas.Fill(Colors.Gray); var w = _canvas.GetWidth(); var h = _canvas.GetHeight(); for (int i = 0; i < w; i++) @@ -60,12 +58,12 @@ /// 是否翻转y轴 public void DrawImageInCanvas(Texture2D texture, int x, int y, float angle, int centerX, int centerY, bool flipY) { - var item = new EnqueueItem(); - item.Canvas = this; - item.Image = texture.GetImage(); + var item = new ImageRenderData(); + item.ImageCanvas = this; + item.SrcImage = texture.GetImage(); item.X = x; item.Y = y; - item.Angle = angle; + item.Rotation = Mathf.DegToRad(Mathf.RoundToInt(Utils.ConvertAngle(angle))); item.CenterX = centerX; item.CenterY = centerY; item.FlipY = flipY; @@ -81,6 +79,9 @@ } IsDestroyed = true; + QueueFree(); + _canvas.Dispose(); + _texture.Dispose(); } private void Redraw() @@ -88,204 +89,8 @@ _texture.Update(_canvas); } - private void HandlerDrawImageInCanvas(EnqueueItem item) + private void HandlerDrawImageInCanvas(ImageRenderData item) { - var image = item.Image; - var newAngle = Mathf.RoundToInt(Utils.ConvertAngle(item.Angle)); - - if (newAngle == 0) //原图, 直接画上去 - { - if (item.FlipY) - { - image.FlipY(); - } - - if (image.GetFormat() != Image.Format.Rgba8) - { - image.Convert(Image.Format.Rgba8); - } - _canvas.BlendRect(image, new Rect2I(0, 0, image.GetWidth(), image.GetHeight()), - new Vector2I(item.X - item.CenterX, item.Y - item.CenterY)); - } - else //其他角度 - { - if (newAngle <= 180) - { - if (item.FlipY) - { - image.FlipY(); - } - - DrawRotateImage(image, item.X, item.Y, newAngle, item.CenterX, item.CenterY); - } - else - { - image.FlipX(); - if (!item.FlipY) - { - image.FlipY(); - } - - DrawRotateImage( - image, item.X, item.Y, - newAngle - 180, - image.GetWidth() - item.CenterX - 1, image.GetHeight() - item.CenterY - 1 - ); - } - } - - image.Dispose(); - item.Image = null; - } - - private void DrawRotateImage(Image origin, int x, int y, int angle, int centerX, int centerY) - { - var rotation = Mathf.DegToRad(angle); - var width = origin.GetWidth(); - var height = origin.GetHeight(); - - var cosAngle = Mathf.Cos(rotation); - var sinAngle = Mathf.Sin(rotation); - if (cosAngle == 0) - { - cosAngle = -1e-6f; - } - - if (sinAngle == 0) - { - sinAngle = 1e-6f; - } - - var newWidth = Mathf.RoundToInt(width * Mathf.Abs(cosAngle) + height * sinAngle); - var newHeight = Mathf.RoundToInt(width * sinAngle + height * Mathf.Abs(cosAngle)); - - var offsetX = - Mathf.RoundToInt((centerX / sinAngle + - (0.5f * newWidth * sinAngle - 0.5f * newHeight * cosAngle + 0.5f * height) / - cosAngle - - (-0.5f * newWidth * cosAngle - 0.5f * newHeight * sinAngle + 0.5f * width) / - sinAngle - centerY / cosAngle) / - (cosAngle / sinAngle + sinAngle / cosAngle)); - - - var offsetY = - Mathf.RoundToInt((centerX / cosAngle - - (-0.5f * newWidth * cosAngle - 0.5f * newHeight * sinAngle + 0.5f * width) / - cosAngle - - (0.5f * newWidth * sinAngle - 0.5f * newHeight * cosAngle + 0.5f * height) / - sinAngle + centerY / sinAngle) / - (sinAngle / cosAngle + cosAngle / sinAngle)); - - var num1 = -0.5f * newWidth * cosAngle - 0.5f * newHeight * sinAngle + 0.5f * width; - var num2 = 0.5f * newWidth * sinAngle - 0.5f * newHeight * cosAngle + 0.5f * height; - - var cw = _canvas.GetWidth(); - var ch = _canvas.GetHeight(); - var numX = Mathf.Min(num1, (newWidth - 1) * cosAngle + (newHeight - 1) * sinAngle + num1); - var numY = Mathf.Min(num2, -(newWidth - 1) * sinAngle + (newHeight - 1) * cosAngle + num2); - - for (var x2 = 0; x2 < newWidth; x2++) - { - for (var y2 = 0; y2 < newHeight; y2++) - { - //映射到原图的位置 - // var x1 = x2 * cosAngle + y2 * sinAngle + num1; - // var y1 = -x2 * sinAngle + y2 * cosAngle + num2; - - - var x1 = Mathf.FloorToInt(x2 * cosAngle + y2 * sinAngle + num1 - numX); - var y1 = Mathf.FloorToInt(-x2 * sinAngle + y2 * cosAngle + num2 - numY); - - if (x1 < 0 || x1 >= width || y1 < 0 || y1 >= height) - { - //在图片外 - continue; - } - - - var cx = x2 + x - offsetX; - if (cx < 0 || cx >= cw) - { - continue; - } - - var cy = y2 + y - offsetY; - if (cy < 0 || cy >= ch) - { - continue; - } - - //_canvas.SetPixel(cx, cy, origin.GetPixel(x1, y1)); - _canvas.SetPixel(cx, cy, _canvas.GetPixel(cx, cy).Blend(origin.GetPixel( - //Mathf.RoundToInt(x1), Mathf.RoundToInt(y1) - x1, y1 - ))); - } - } - } - - //-------------------------------------------------------------------------------------------------------------- - - private class EnqueueItem - { - public ImageCanvas Canvas; - public Image Image; - public int X; - public int Y; - public float Angle; - public int CenterX; - public int CenterY; - public bool FlipY; - } - - /// - /// 同一帧下将队列里的image绘制到指定画布下最大消耗时间, 如果绘制的时间超过了这个值, 则队列中后面的image将会放到下一帧绘制 - /// - public static float MaxHandlerTime { get; set; } = 2f; - - private static readonly Queue _enqueueItems = new Queue(); - private static readonly HashSet _redrawCanvas = new HashSet(); - - /// - /// 根据重绘队列更新画布 - /// - public static void UpdateImageCanvas(float delta) - { - if (_enqueueItems.Count > 0) - { - _redrawCanvas.Clear(); - var startTime = DateTime.Now; - - var c = _enqueueItems.Count; - //执行绘制操作 - //绘制的总时间不能超过MaxHandlerTime, 如果超过了, 那就下一帧再画其它的吧 - do - { - var item = _enqueueItems.Dequeue(); - if (!item.Canvas.IsDestroyed) - { - item.Canvas.HandlerDrawImageInCanvas(item); - _redrawCanvas.Add(item.Canvas); - } - } while (_enqueueItems.Count > 0 && (DateTime.Now - startTime).TotalMilliseconds < MaxHandlerTime); - - if (_enqueueItems.Count > 0) - { - GD.Print($"ImageCanvas: 还剩下{_enqueueItems.Count}个, 已经处理了{c - _enqueueItems.Count}个, 重绘画布{_redrawCanvas.Count}张"); - } - else - { - GD.Print($"ImageCanvas: 全部画完, 已经处理了{c - _enqueueItems.Count}个, 重绘画布{_redrawCanvas.Count}张"); - } - - //重绘画布 - foreach (var drawCanvas in _redrawCanvas) - { - drawCanvas.Redraw(); - } - - _redrawCanvas.Clear(); - } } } \ No newline at end of file diff --git a/DungeonShooting_Godot/src/framework/map/image/ImageCanvas_Static.cs b/DungeonShooting_Godot/src/framework/map/image/ImageCanvas_Static.cs new file mode 100644 index 0000000..1e63e71 --- /dev/null +++ b/DungeonShooting_Godot/src/framework/map/image/ImageCanvas_Static.cs @@ -0,0 +1,173 @@ + + +using System; +using System.Collections.Generic; +using Godot; + +public partial class ImageCanvas +{ + + /// + /// 同一帧下将队列里的image绘制到指定画布下最大消耗时间, 如果绘制的时间超过了这个值, 则队列中后面的image将会放到下一帧绘制 + /// + public static float MaxHandlerTime { get; set; } = 4f; + + /// + /// 渲染窗口 + /// + public static SubViewport RenderViewport { get; private set; } + + /// + /// 渲染偏移位置 + /// + public static Vector2 RenderOffset { get; set; } + + //预渲染队列 + private static readonly Queue _enqueueItems = new Queue(); + //渲染中的队列 + private static readonly Queue _drawingEnqueueItems = new Queue(); + + private static readonly Stack _renderSpriteStack = new Stack(); + + public static void Init(SubViewport renderViewport, Vector2 renderOffset) + { + RenderViewport = renderViewport; + RenderOffset = renderOffset; + RenderingServer.FramePostDraw += OnFramePostDraw; + } + + private static ImageRenderSprite GetRenderSprite(Vector2 position) + { + ImageRenderSprite renderSprite; + if (_renderSpriteStack.Count > 0) + { + renderSprite = _renderSpriteStack.Pop(); + } + else + { + renderSprite = new ImageRenderSprite(); + } + + renderSprite.Sprite.Position = position; + RenderViewport.AddChild(renderSprite.Sprite); + return renderSprite; + } + + private static void ReclaimRenderSprite(ImageRenderSprite renderSprite) + { + RenderViewport.RemoveChild(renderSprite.Sprite); + _renderSpriteStack.Push(renderSprite); + } + + private static void OnFramePostDraw() + { + //上一帧绘制的image + if (_drawingEnqueueItems.Count > 0) + { + var redrawCanvas = new HashSet(); + var viewportTexture = RenderViewport.GetTexture(); + using (var image = viewportTexture.GetImage()) + { + var num = 0; + do + { + var item = _drawingEnqueueItems.Dequeue(); + if (!item.ImageCanvas.IsDestroyed) + { + redrawCanvas.Add(item.ImageCanvas); + //截取Viewport像素点 + item.ImageCanvas._canvas.BlendRect(image, + new Rect2I(0, 0, item.RenderWidth, item.RenderHeight), + new Vector2I(item.X - item.RenderOffsetX, item.Y - item.RenderOffsetY) + ); + + item.SrcImage.Dispose(); + item.SrcImage = null; + num++; + } + + //回收 RenderSprite + if (item.RenderSprite != null) + { + ReclaimRenderSprite(item.RenderSprite); + } + } while (_drawingEnqueueItems.Count > 0); + + GD.Print($"当前帧绘制完成数量: {num}, 绘制队列数量: {_drawingEnqueueItems.Count}"); + } + + //重绘画布 + foreach (var drawCanvas in redrawCanvas) + { + drawCanvas.Redraw(); + } + } + + //处理下一批image + if (_enqueueItems.Count > 0) + { + var startTime = DateTime.Now; + + var num = 0; + //执行绘制操作 + //绘制的总时间不能超过MaxHandlerTime, 如果超过了, 那就下一帧再画其它的吧 + do + { + var item = _enqueueItems.Dequeue(); + if (!item.ImageCanvas.IsDestroyed) + { + + var cosAngle = Mathf.Cos(item.Rotation); + var sinAngle = Mathf.Sin(item.Rotation); + if (cosAngle == 0) + { + cosAngle = 1e-6f; + } + + if (sinAngle == 0) + { + sinAngle = 1e-6f; + } + + var width = item.SrcImage.GetWidth(); + var height = item.SrcImage.GetHeight(); + //旋转后的图片宽高 + item.RenderWidth = Mathf.CeilToInt(width * Mathf.Abs(cosAngle) + height * sinAngle) + 2; + item.RenderHeight = Mathf.CeilToInt(width * sinAngle + height * Mathf.Abs(cosAngle)) + 2; + + item.RenderOffsetX = + (int)((item.CenterX / sinAngle + + (0.5f * item.RenderWidth * sinAngle - 0.5f * item.RenderHeight * cosAngle + + 0.5f * height) / + cosAngle - + (-0.5f * item.RenderWidth * cosAngle - 0.5f * item.RenderHeight * sinAngle + + 0.5f * width) / + sinAngle - item.CenterY / cosAngle) / + (cosAngle / sinAngle + sinAngle / cosAngle)) + 1; + + + item.RenderOffsetY = + (int)((item.CenterX / cosAngle - + (-0.5f * item.RenderWidth * cosAngle - 0.5f * item.RenderHeight * sinAngle + 0.5f * width) / + cosAngle - + (0.5f * item.RenderWidth * sinAngle - 0.5f * item.RenderHeight * cosAngle + 0.5f * height) / + sinAngle + item.CenterY / sinAngle) / + (sinAngle / cosAngle + cosAngle / sinAngle)) + 1; + + var renderSprite = GetRenderSprite(new Vector2(0 + item.RenderOffsetX, 0 + item.RenderOffsetY)); + item.RenderSprite = renderSprite; + //设置绘制信息 + renderSprite.Sprite.Offset = new Vector2(item.CenterX, item.CenterY); + renderSprite.Sprite.Rotation = item.Rotation; + + renderSprite.SetImage(item.SrcImage); + _drawingEnqueueItems.Enqueue(item); + num++; + } + + } while (_enqueueItems.Count > 0 && (DateTime.Now - startTime).TotalMilliseconds < MaxHandlerTime); + + GD.Print($"当前帧进入绘制绘队列数量: {num}, 待绘制队列数量: {_enqueueItems.Count}, 绘制队列数量: {_drawingEnqueueItems.Count}"); + } + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/framework/map/image/ImageRenderData.cs b/DungeonShooting_Godot/src/framework/map/image/ImageRenderData.cs new file mode 100644 index 0000000..da66b00 --- /dev/null +++ b/DungeonShooting_Godot/src/framework/map/image/ImageRenderData.cs @@ -0,0 +1,29 @@ + + +using Godot; + +public class ImageRenderData +{ + /// + /// 指定的画布 + /// + public ImageCanvas ImageCanvas; + /// + /// 需要绘制的原图 + /// + public Image SrcImage; + public int X; + public int Y; + public float Rotation; + public int CenterX; + public int CenterY; + public bool FlipY; + + //---------------------------------------- + public ImageRenderSprite RenderSprite; + public int RenderWidth; + public int RenderHeight; + + public int RenderOffsetX; + public int RenderOffsetY; +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/framework/map/image/ImageRenderSprite.cs b/DungeonShooting_Godot/src/framework/map/image/ImageRenderSprite.cs new file mode 100644 index 0000000..a5eb803 --- /dev/null +++ b/DungeonShooting_Godot/src/framework/map/image/ImageRenderSprite.cs @@ -0,0 +1,38 @@ + +using Godot; + +public class ImageRenderSprite +{ + public Sprite2D Sprite { get; } + + public ImageTexture Texture { get; } + + private static Image _emptyImage; + private static Image EmptyImage + { + get + { + if (_emptyImage == null) + { + _emptyImage = Image.Create(1, 1, false, Image.Format.Rgba8); + } + + return _emptyImage; + } + } + + public ImageRenderSprite() + { + var sprite = new Sprite2D(); + Sprite = sprite; + Texture = ImageTexture.CreateFromImage(EmptyImage); + sprite.Name = "RenderSprite"; + sprite.Texture = Texture; + sprite.Centered = false; + } + + public void SetImage(Image image) + { + Texture.SetImage(image); + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/test/TestOptimizeSprite.cs b/DungeonShooting_Godot/src/test/TestOptimizeSprite.cs index 664adf1..f9dda88 100644 --- a/DungeonShooting_Godot/src/test/TestOptimizeSprite.cs +++ b/DungeonShooting_Godot/src/test/TestOptimizeSprite.cs @@ -4,45 +4,48 @@ { [Export()] public Texture2D[] ImageList; + [Export()] public SubViewport SubViewport; + + [Export()] public Camera2D ViewCamera; + public override void _Ready() { - ImageCanvas.MaxHandlerTime = 16; + ImageCanvas.Init(SubViewport, ViewCamera.Position); + ImageCanvas.MaxHandlerTime = 4; + var scale = 10; var imageCanvas = new ImageCanvas(1920 / scale, 1080 / scale); imageCanvas.Scale = new Vector2(scale, scale); var delta = 360f / (15 * 8); var angle = 0f; - for (int i = 0; i < 15; i++) - { - for (int j = 0; j < 8; j++) - { - //var texture = Utils.RandomChoose(ImageList); - var texture = ImageList[6]; - var centerX = 0; - var centerY = 0; - //var angle = Utils.RandomRangeInt(0, 360); - GD.Print($"x: {i}, y: {j}, angle: " + angle); - imageCanvas.DrawImageInCanvas(texture, - //Utils.RandomRangeInt(0, imageCanvas.Width), Utils.RandomRangeInt(0, imageCanvas.Height), - 10 + i * 10, 10 + j * 10, - angle, centerX, centerY, false - ); - angle += delta; - } - } + + + + // for (int i = 0; i < 15; i++) + // { + // for (int j = 0; j < 8; j++) + // { + // //var texture = Utils.RandomChoose(ImageList); + // var texture = ImageList[6]; + // var centerX = 0; + // var centerY = 0; + // //var angle = Utils.RandomRangeInt(0, 360); + // GD.Print($"x: {i}, y: {j}, angle: " + angle); + // imageCanvas.DrawImageInCanvas(texture, + // //Utils.RandomRangeInt(0, imageCanvas.Width), Utils.RandomRangeInt(0, imageCanvas.Height), + // 10 + i * 10, 10 + j * 10, + // angle, centerX, centerY, false + // ); + // angle += delta; + // } + // } - //var texture = ImageList[6]; - //imageCanvas.DrawImageInCanvas(texture, imageCanvas.Width / 2, imageCanvas.Height / 2, 0, 0, 0, false); + var texture = ImageList[0]; + imageCanvas.DrawImageInCanvas(texture, imageCanvas.Width / 2, imageCanvas.Height / 2, 45, 0, 0, false); //imageCanvas.DrawImageInCanvas(texture, imageCanvas.Width / 2, imageCanvas.Height / 2, 45, texture.GetWidth() - 1, texture.GetHeight() - 1, false); AddChild(imageCanvas); } - - - public override void _Process(double delta) - { - ImageCanvas.UpdateImageCanvas((float)delta); - } }