Newer
Older
DungeonShooting / DungeonShooting_Godot / src / framework / map / image / ImageCanvas.cs
@小李xl 小李xl on 11 Apr 2024 8 KB 更新敌人效果

using System;
using Godot;

/// <summary>
/// 静态图像画布类, 用于处理游戏中大量静态物体的解决方案<br/>
/// 将物体纹理绘直接绘制到画布上, 这样大大减少GPU开销, 从而提高帧率<br/>
/// 图像旋转遵循完美像素
/// </summary>
public partial class ImageCanvas : Sprite2D, IDestroy
{
    /// <summary>
    /// 画布大小
    /// </summary>
    public int Width { get; }
    
    /// <summary>
    /// 画布高度
    /// </summary>
    public int Height { get; }

    public bool IsDestroyed { get; private set; }

    private Image _canvas;
    private ImageTexture _texture;

    public ImageCanvas(int width, int height)
    {
        Width = width;
        Height = height;

        _canvas = Image.Create(width, height, false, Image.Format.Rgba8);
        _texture = ImageTexture.CreateFromImage(_canvas);
    }

    public override void _Ready()
    {
        Centered = false;
        Texture = _texture;
        ZIndex = -1;
    }

    /// <summary>
    /// 将指定纹理添加到预渲染队列中, 完成后会调用 onDrawingComplete 回调函数
    /// </summary>
    /// <param name="texture">需要渲染的纹理</param>
    /// <param name="modulate">混色</param>
    /// <param name="material">渲染材质, 不需要则传null</param>
    /// <param name="x">离画布左上角x坐标</param>
    /// <param name="y">离画布左上角y坐标</param>
    /// <param name="angle">旋转角度, 角度制</param>
    /// <param name="centerX">旋转中心点x</param>
    /// <param name="centerY">旋转中心点y</param>
    /// <param name="flipY">是否翻转y轴</param>
    /// <param name="onDrawingComplete">绘制完成的回调函数</param>
    public void DrawImageInCanvas(Texture2D texture, Color modulate, Material material, float x, float y, float angle, int centerX, int centerY, bool flipY, Action onDrawingComplete = null)
    {
        DrawImageInCanvas(texture,modulate, material, x, y, angle, centerX, centerY, flipY, true, onDrawingComplete);
    }
    private void DrawImageInCanvas(Texture2D texture, Color modulate, Material material, float x, float y, float angle, int centerX, int centerY, bool flipY, bool enableQueueCutting, Action onDrawingComplete)
    {
        var item = new ImageRenderData();
        item.OnDrawingComplete = onDrawingComplete;
        item.EnableQueueCutting = enableQueueCutting;
        item.ImageCanvas = this;
        item.SrcImage = texture.GetImage();
        item.Modulate = modulate;
        item.Material = material;
        var width = item.SrcImage.GetWidth();
        var height = item.SrcImage.GetHeight();
        if (width > 128)
        {
            Debug.LogError("警告: 图像宽度大于 128, 旋转后像素点可能绘制到画布外导致像素丢失!");
        }
        if (height > 128)
        {
            Debug.LogError("警告: 图像高度大于 128, 旋转后像素点可能绘制到画布外导致像素丢失!");
        }
        item.X = Mathf.RoundToInt(x);
        item.Y = Mathf.RoundToInt(y);
        item.Rotation = Mathf.DegToRad(Mathf.RoundToInt(Utils.ConvertAngle(angle)));
        item.CenterX = centerX;
        item.CenterY = centerY;
        if (item.Rotation > Mathf.Pi)
        {
            item.CenterX = width - item.CenterX;
            item.CenterY = height - item.CenterY;
            item.Rotation -= Mathf.Pi;
            item.RotationGreaterThanPi = true;
        }
        item.FlipY = flipY;
        
        var cosAngle = Mathf.Cos(item.Rotation);
        var sinAngle = Mathf.Sin(item.Rotation);
        if (cosAngle == 0)
        {
            cosAngle = 1e-6f;
        }

        if (sinAngle == 0)
        {
            sinAngle = 1e-6f;
        }

        //旋转后的图片宽高
        item.RenderWidth = Mathf.CeilToInt(width * Mathf.Abs(cosAngle) + height * sinAngle) + 2;
        item.RenderHeight = Mathf.CeilToInt(width * sinAngle + height * Mathf.Abs(cosAngle)) + 2;

        if (item.RenderWidth >= RenderViewportSize.X)
        {
            Debug.LogError($"图像旋转后的宽度大于{RenderViewportSize.X}, 不支持绘制到 ImageCanvas 下!");
            item.SrcImage.Dispose();
            return;
        }
        
        //旋转后的图像中心点偏移
        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;

        _queueItems.Add(item);
    }
    
    /// <summary>
    /// 将指定 ActivityObject 添加到预渲染队列中, 完成后会调用 onDrawingComplete 回调函数
    /// </summary>
    /// <param name="activityObject">物体实例</param>
    /// <param name="x">离画布左上角x坐标</param>
    /// <param name="y">离画布左上角y坐标</param>
    /// <param name="onDrawingComplete">绘制完成的回调</param>
    public void DrawActivityObjectInCanvas(ActivityObject activityObject, float x, float y, Action onDrawingComplete = null)
    {
        //是否翻转y轴
        var flipY = activityObject.Scale.Y < 0;

        var animatedSprite = activityObject.AnimatedSprite;
        var animatedSpritePosition = animatedSprite.Position;
        var ax = x + animatedSpritePosition.X;
        var ay = y + animatedSpritePosition.X;

        //先绘制阴影
        var shadowSprite = activityObject.ShadowSprite;
        var shadowSpriteTexture = activityObject.ShadowSprite.Texture;
        if (shadowSpriteTexture != null)
        {
            var spriteOffset = shadowSprite.Offset;
            var centerX = (int)-spriteOffset.X;
            var centerY = (int)-spriteOffset.Y;
            var angle = Utils.ConvertAngle(shadowSprite.GlobalRotationDegrees);
            if (shadowSprite.Centered)
            {
                centerX += shadowSpriteTexture.GetWidth() / 2;
                centerY += shadowSpriteTexture.GetHeight() / 2;
            }
            
            DrawImageInCanvas(
                shadowSprite.Texture, shadowSprite.Modulate, shadowSprite.Material,
                ax + activityObject.ShadowOffset.X, ay + activityObject.ShadowOffset.Y,
                angle,
                centerX, centerY, flipY,
                true, null
            );
        }
        
        //再绘制纹理
        var texture = activityObject.GetCurrentTexture();
        if (texture != null)
        {
            var spriteOffset = animatedSprite.Offset;
            var centerX = (int)-spriteOffset.X;
            var centerY = (int)-spriteOffset.Y;
            if (animatedSprite.Centered)
            {
                centerX += texture.GetWidth() / 2;
                centerY += texture.GetHeight() / 2;
            }
            //为了保证阴影在此之前渲染, 所以必须关闭插队渲染
            DrawImageInCanvas(
                texture, animatedSprite.Modulate, animatedSprite.Material,
                ax, ay,
                animatedSprite.GlobalRotationDegrees,
                centerX, centerY, flipY,
                false, onDrawingComplete
            );
        }
    }
    
    public void Destroy()
    {
        if (IsDestroyed)
        {
            return;
        }

        IsDestroyed = true;
        QueueFree();
        _canvas.Dispose();
        _texture.Dispose();
    }

    /// <summary>
    /// 使用透明色替换掉整个画布
    /// </summary>
    public void Clear()
    {
        Clear(new Color(0, 0, 0, 0));
    }

    /// <summary>
    /// 使用指定颜色替换掉整个画布
    /// </summary>
    public void Clear(Color color)
    {
        _canvas.Fill(color);
        Redraw();
    }
    
    private void Redraw()
    {
        _texture.Update(_canvas);
    }
}