Newer
Older
DungeonShooting / DungeonShooting_Godot / src / framework / map / liquid / LiquidCanvas.cs
@小李xl 小李xl on 28 Nov 2023 13 KB 将将液体材质抽成配置表

using System;
using System.Collections.Generic;
using Godot;

/// <summary>
/// 液体画布
/// </summary>
public partial class LiquidCanvas : Sprite2D, IDestroy
{
    /// <summary>
    /// 程序每帧最多等待执行时间, 超过这个时间的像素点将交到下一帧执行, 单位: 毫秒
    /// </summary>
    public static float MaxWaitTime { get; set; } = 4f;

    /// <summary>
    /// 画布缩放
    /// </summary>
    public static int CanvasScale { get; } = 4;
    
    public bool IsDestroyed { get; private set; }
    
    private Image _image;
    private ImageTexture _texture;
    
    //画布上的像素点
    private LiquidPixel[,] _imagePixels;
    //需要执行更新的像素点
    private List<LiquidPixel> _updateImagePixels = new List<LiquidPixel>();
    //画布已经运行的时间
    private float _runTime = 0;
    private int _executeIndex = -1;
    //用于记录补间操作下有变动的像素点
    private List<LiquidPixel> _tempList = new List<LiquidPixel>();
    //记录是否有像素点发生变动
    private bool _changeFlag = false;
    //所属房间
    private RoomInfo _roomInfo;

    public LiquidCanvas(RoomInfo roomInfo, int width, int height)
    {
        _roomInfo = roomInfo;
        Centered = false;
        Material = ResourceManager.Load<Material>(ResourcePath.resource_material_Sawtooth_tres);
        
        _image = Image.Create(width, height, false, Image.Format.Rgba8);
        _texture = ImageTexture.CreateFromImage(_image);
        Texture = _texture;
        _imagePixels = new LiquidPixel[width, height];
    }
    
    public void Destroy()
    {
        if (IsDestroyed)
        {
            return;
        }
        
        IsDestroyed = true;
        QueueFree();
        _texture.Dispose();
        _image.Dispose();
    }

    public override void _Process(double delta)
    {
        //这里待优化, 应该每次绘制都将像素点放入 _tempList 中, 然后帧结束再统一提交

        //更新消除逻辑
        if (_updateImagePixels.Count > 0)
        {
            var startIndex = _executeIndex;
            if (_executeIndex < 0 || _executeIndex >= _updateImagePixels.Count)
            {
                _executeIndex = _updateImagePixels.Count - 1;
            }

            var startTime = DateTime.UtcNow;
            var isOver = false;
            var index = 0;
            for (; _executeIndex >= 0; _executeIndex--)
            {
                index++;
                var imagePixel = _updateImagePixels[_executeIndex];
                if (UpdateImagePixel(imagePixel)) //移除
                {
                    _updateImagePixels.RemoveAt(_executeIndex);
                    if (_executeIndex < startIndex)
                    {
                        startIndex--;
                    }
                }

                if (index > 200)
                {
                    index = 0;
                    if ((DateTime.UtcNow - startTime).TotalMilliseconds > MaxWaitTime) //超过最大执行时间
                    {
                        isOver = true;
                        break;
                    }
                }
            }

            if (!isOver && startIndex >= 0 && _executeIndex < 0)
            {
                _executeIndex = _updateImagePixels.Count - 1;
                for (; _executeIndex >= startIndex; _executeIndex--)
                {
                    index++;
                    var imagePixel = _updateImagePixels[_executeIndex];
                    if (UpdateImagePixel(imagePixel)) //移除
                    {
                        _updateImagePixels.RemoveAt(_executeIndex);
                    }
                    
                    if (index > 200)
                    {
                        index = 0;
                        if ((DateTime.UtcNow - startTime).TotalMilliseconds > MaxWaitTime) //超过最大执行时间
                        {
                            break;
                        }
                    }
                }
            }
        }

        if (_changeFlag)
        {
            _texture.Update(_image);
            _changeFlag = false;
        }

        _runTime += (float)delta;
    }

    /// <summary>
    /// 将画布外的坐标转为画布内的坐标
    /// </summary>
    public Vector2I ToLiquidCanvasPosition(Vector2 position)
    {
        return (_roomInfo.ToCanvasPosition(position) / CanvasScale).AsVector2I();
    }

    /// <summary>
    /// 根据画笔数据在画布上绘制液体, 转换坐标请调用 ToLiquidCanvasPosition() 函数
    /// </summary>
    /// <param name="brush">画笔数据</param>
    /// <param name="position">绘制坐标, 相对于画布坐标</param>
    public void DrawBrush(BrushImageData brush, Vector2I position)
    {
        DrawBrush(brush, null, position, 0);
    }
    
    /// <summary>
    /// 根据画笔数据在画布上绘制液体, 转换坐标请调用 ToLiquidCanvasPosition() 函数
    /// </summary>
    /// <param name="brush">画笔数据</param>
    /// <param name="position">绘制坐标, 相对于画布坐标</param>
    /// <param name="rotation">旋转角度, 弧度制</param>
    public void DrawBrush(BrushImageData brush, Vector2I position, float rotation)
    {
        DrawBrush(brush, null, position, rotation);
    }
    
    /// <summary>
    /// 根据画笔数据在画布上绘制液体, 转换坐标请调用 ToLiquidCanvasPosition() 函数
    /// </summary>
    /// <param name="brush">画笔数据</param>
    /// <param name="prevPosition">上一帧坐标, 相对于画布坐标, 改参数用于两点距离较大时执行补间操作, 如果传 null, 则不会进行补间</param>
    /// <param name="position">绘制坐标, 相对于画布坐标</param>
    /// <param name="rotation">旋转角度, 弧度制</param>
    public void DrawBrush(BrushImageData brush, Vector2I? prevPosition, Vector2I position, float rotation)
    {
        var center = new Vector2I(brush.Width, brush.Height) / 2;
        var pos = position - center;
        var canvasWidth = _texture.GetWidth();
        var canvasHeight = _texture.GetHeight();
        //存在上一次记录的点
        if (prevPosition != null)
        {
            var offset = new Vector2(position.X - prevPosition.Value.X, position.Y - prevPosition.Value.Y);
            var maxL = brush.Material.Ffm * Mathf.Lerp(
                brush.PixelHeight,
                brush.PixelWidth,
                Mathf.Abs(Mathf.Sin(offset.Angle() - rotation + Mathf.Pi * 0.5f))
            );
            var len = offset.Length();
            if (len > maxL) //距离太大了, 需要补间
            {
                Debug.Log($"距离太大了, 启用补间: len: {len}, maxL: {maxL}");
                var count = Mathf.CeilToInt(len / maxL);
                var step = new Vector2(offset.X / count, offset.Y / count);
                var prevPos = prevPosition.Value - center;
                
                for (var i = 1; i <= count; i++)
                {
                    foreach (var brushPixel in brush.Pixels)
                    {
                        var brushPos = RotatePixels(brushPixel.X, brushPixel.Y, center.X, center.Y, rotation);
                        var x = (int)(prevPos.X + step.X * i + brushPos.X);
                        var y = (int)(prevPos.Y + step.Y * i + brushPos.Y);
                        if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight)
                        {
                            var temp = SetPixelData(x, y, brushPixel);
                            if (!temp.TempFlag)
                            {
                                temp.TempFlag = true;
                                _tempList.Add(temp);
                            }
                        }
                    }
                }
                
                foreach (var brushPixel in brush.Pixels)
                {
                    var brushPos = RotatePixels(brushPixel.X, brushPixel.Y, center.X, center.Y, rotation);
                    var x = pos.X + brushPos.X;
                    var y = pos.Y + brushPos.Y;
                    if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight)
                    {
                        var temp = SetPixelData(x, y, brushPixel);
                        if (!temp.TempFlag)
                        {
                            temp.TempFlag = true;
                            _tempList.Add(temp);
                        }
                    }
                }

                foreach (var imagePixel in _tempList)
                {
                    _changeFlag = true;
                    _image.SetPixel(imagePixel.X, imagePixel.Y, imagePixel.Color);
                    imagePixel.TempFlag = false;
                }

                _tempList.Clear();
                return;
            }
        }
        
        foreach (var brushPixel in brush.Pixels)
        {
            var brushPos = RotatePixels(brushPixel.X, brushPixel.Y, center.X, center.Y, rotation);
            var x = pos.X + brushPos.X;
            var y = pos.Y + brushPos.Y;
            if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight)
            {
                _changeFlag = true;
                var temp = SetPixelData(x, y, brushPixel);
                _image.SetPixel(x, y, temp.Color);
            }
        }
    }

    /// <summary>
    /// 返回是否碰到任何有效像素点
    /// </summary>
    public bool Collision(int x, int y)
    {
        if (x >= 0 && x < _imagePixels.GetLength(0) && y >= 0 && y < _imagePixels.GetLength(1))
        {
            var result = _imagePixels[x, y];
            if (result != null && result.IsRun)
            {
                return true;
            }
        }
        return false;
    }

    /// <summary>
    /// 返回碰撞到的有效像素点数据
    /// </summary>
    public LiquidPixel GetPixelData(int x, int y)
    {
        if (x >= 0 && x < _imagePixels.GetLength(0) && y >= 0 && y < _imagePixels.GetLength(1))
        {
            var result = _imagePixels[x, y];
            if (result != null && result.IsRun)
            {
                return result;
            }
        }

        return null;
    }
    
    /// <summary>
    /// 更新像素点数据逻辑, 返回是否擦除
    /// </summary>
    private bool UpdateImagePixel(LiquidPixel imagePixel)
    {
        if (imagePixel.Color.A > 0)
        {
            if (imagePixel.Timer > 0) //继续等待消散
            {
                imagePixel.Timer -= _runTime - imagePixel.TempTime;
                imagePixel.TempTime = _runTime;
            }
            else
            {
                imagePixel.Color.A -= imagePixel.Material.WriteOffSpeed * (_runTime - imagePixel.TempTime);
                
                if (imagePixel.Color.A <= 0) //完全透明了
                {
                    _changeFlag = true;
                    _image.SetPixel(imagePixel.X, imagePixel.Y, new Color(0, 0, 0, 0));
                    imagePixel.IsRun = false;
                    imagePixel.IsUpdate = false;
                    return true;
                }
                else
                {
                    _changeFlag = true;
                    _image.SetPixel(imagePixel.X, imagePixel.Y, imagePixel.Color);
                    imagePixel.TempTime = _runTime;
                }
            }
        }

        return false;
    }
    
    //记录像素点数据
    private LiquidPixel SetPixelData(int x, int y, BrushPixelData pixelData)
    {
        var temp = _imagePixels[x, y];
        if (temp == null)
        {
            temp = new LiquidPixel()
            {
                X = x,
                Y = y,
                Color = pixelData.Color,
                Material = pixelData.Material,
                Timer = pixelData.Material.Duration,
            };
            _imagePixels[x, y] = temp;

            temp.IsRun = true;
            temp.IsUpdate = temp.Material.Duration >= 0;
            if (temp.IsUpdate)
            {
                _updateImagePixels.Add(temp);
            }
            temp.TempTime = _runTime;
        }
        else
        {
            if (temp.Material != pixelData.Material)
            {
                temp.Color = pixelData.Color;
                temp.Material = pixelData.Material;
            }
            else
            {
                var tempColor = pixelData.Color;
                temp.Color = new Color(tempColor.R, tempColor.G, tempColor.B, Mathf.Max(temp.Color.A, tempColor.A));
            }
            
            temp.Timer = pixelData.Material.Duration;
            
            var prevUpdate = temp.IsUpdate;
            temp.IsUpdate = temp.Material.Duration >= 0;
            if (!prevUpdate && temp.IsUpdate)
            {
                _updateImagePixels.Add(temp);
            }
            else if (prevUpdate && !temp.IsUpdate)
            {
                _updateImagePixels.Remove(temp);
            }
            
            temp.IsRun = true;
            temp.TempTime = _runTime;
        }

        return temp;
    }

    /// <summary>
    /// 根据 rotation 旋转像素点坐标, 并返回旋转后的坐标, rotation 为弧度制角度, 旋转中心点为 centerX, centerY
    /// </summary>
    private Vector2I RotatePixels(int x, int y, int centerX, int centerY, float rotation)
    {
        if (rotation == 0)
        {
            return new Vector2I(x, y);
        }

        x -= centerX;
        y -= centerY;
        var sv = Mathf.Sin(rotation);
        var cv = Mathf.Cos(rotation);
        var newX = Mathf.RoundToInt(x * cv - y * sv);
        var newY = Mathf.RoundToInt(x * sv + y * cv);
        newX += centerX;
        newY += centerY;
        return new Vector2I(newX, newY);
    }
}