Skip to content

Canvas 绘制系统 ​

Canvas 控件提供即时模式 2D 绘制 API,用于自定义图形、动画、小地图等。仅客户端可用(#if CLIENT)。

定义位置:GameUI.Control.Primitive.Canvas

Canvas 是对底层 NanoVG 绘制能力的 C# 封装:通过当前画笔、变换状态、路径和裁剪区域进行即时绘制。熟悉 NanoVG / SVG / HTML5 Canvas 的开发者可以复用这些图形概念,但业务代码应调用 WasiCore Canvas API,不应直接使用 nvg* 底层函数。

基本用法 ​

csharp
var canvas = new Canvas();
canvas.FullScreen();
canvas.OnRender += (s, e) =>
{
    canvas.FillPaint = Color.Red;
    canvas.FillRectangle(10, 10, 100, 50);
};
canvas.AddToVisualTree();

绘制在 OnRender 事件中进行。订阅 OnRender 启用渲染循环,取消订阅停止渲染。

生命周期与资源释放 ​

Canvas 是普通 GameUI 控件,生命周期规则与 Panel、Button、Label 一致:

操作含义适用场景
RemoveFromParent() / RemoveFromVisualTree()只从 UI 树摘除,不释放控件和底层资源临时隐藏、换父节点、对象池复用
ClearChildren()只移除容器的直接子控件,不调用子控件 Destroy()清空容器后复用子控件或由业务另行销毁
Destroy()释放控件,清事件,递归销毁子控件并销毁 native control控件永久不用

因此,临时 Canvas 可以移出 UI 树等待复用;但浮字、战斗 HUD、弹窗、过期特效画布等不再使用时,应调用 Destroy()。不要把 RemoveFromParent() 或 ClearChildren() 当成销毁。

csharp
// ✅ 关闭后不再复用
canvas.OnRender -= DrawHud;
canvas.Destroy();

// ✅ CanvasAnimated 同理
animatedCanvas.OnAnimatedRender -= DrawAnimation;
animatedCanvas.Destroy();

Canvas / CanvasAnimated 可能持有底层渲染资源和回调。长期运行中反复创建后只 RemoveFromParent() 或由父容器 ClearChildren() 摘掉,尤其在移动端,可能累积资源压力并最终表现为 FBO 或纹理创建失败。

NanoVG 概念对照 ​

NanoVG / 通用概念WasiCore Canvas API注意
Fill / Stroke paintFillPaint / StrokePaintColor 可隐式转换为 SolidPaint
Stroke widthStrokeWidth形状和路径描边宽度
Path commandsPathF.MoveTo / LineTo / CurveTo用 FillPath 或 DrawPath 输出
Save / Restore stateSaveState() / RestoreState()控制样式、变换、裁剪作用域
RotateRotate(degrees) / RotateRadians(radians)Rotate() 使用度数
Scissor / clipClipRect(...)裁剪后续绘制
TextCreateFont / FontFaceId / DrawTextCanvas 字体独立于普通 GameUI 字体 family
Image drawingImage + DrawImage(...)资源路径从项目 ui/ 下一级开始写

构造 ​

csharp
Canvas();                                              // 使用默认模板
Canvas(IGameLink<GameDataControlCanvas> link);         // 使用指定模板

样式属性 ​

csharp
Paint FillPaint { get; set; }              // 填充画笔(Color 可隐式转换为 Paint)
Paint StrokePaint { get; set; }            // 描边画笔
float StrokeWidth { get; set; }            // 描边宽度
LineCap LineCap { get; set; }              // 线端样式
LineJoin LineJoin { get; set; }            // 连接样式
float MiterLimit { get; set; }             // 斜接限制
float Alpha { get; set; }                  // 全局透明度
BlendMode BlendMode { get; set; }          // 混合模式
WindingMode DefaultPathWinding { get; set; } // 路径默认填充规则

void SetColors(Color fillColor, Color strokeColor);  // 同时设置填充和描边

绘制方法 ​

线段 ​

csharp
void DrawLine(float x1, float y1, float x2, float y2);

矩形 ​

csharp
void FillRectangle(float x, float y, float width, float height);
void StrokeRectangle(float x, float y, float width, float height);
void FillRoundedRectangle(float x, float y, float width, float height, float cornerRadius);
void StrokeRoundedRectangle(float x, float y, float width, float height, float cornerRadius);
void FillSquare(float x, float y, float size);
void StrokeSquare(float x, float y, float size);

圆形/椭圆 ​

csharp
void FillCircle(float centerX, float centerY, float radius);
void StrokeCircle(float centerX, float centerY, float radius);
void StrokeCircle(PointF center, float radius);
void FillEllipse(float centerX, float centerY, float radiusX, float radiusY);
void StrokeEllipse(float centerX, float centerY, float radiusX, float radiusY);
void FillEllipse(PointF center, float radiusX, float radiusY);
void StrokeEllipse(PointF center, float radiusX, float radiusY);

三角形 ​

csharp
void FillTriangle(float x1, float y1, float x2, float y2, float x3, float y3);
void StrokeTriangle(float x1, float y1, float x2, float y2, float x3, float y3);

路径 ​

csharp
void DrawPath(PathF path);
void FillPath(PathF path, WindingMode? windingMode = null);

图片 ​

csharp
void DrawImage(Image image, float x, float y);
void DrawImage(Image image, float x, float y, float width, float height);
void DrawImage(Image image, float sourceX, float sourceY, float sourceWidth, float sourceHeight,
               float x, float y, float width, float height);
void DrawImage(Image image, RectangleF sourceRect, RectangleF destRect);

图片文件应放在项目 ui/ 目录下,代码路径从 ui/ 下一级开始写。例如 ui/image/abc.png 的读取路径是 image/abc.png:

csharp
var image = new Image("image/abc.png");
canvas.DrawImage(image, 100, 100, 256, 128);

不要写成 new Image("ui/image/abc.png"),也不要把 Canvas 图片放到 user_files。Image 是引擎资源路径,不是 .NET 文件路径。完整资源路径规则见 项目资源路径指南。

文本 ​

csharp
float DrawText(float x, float y, string text);
void DrawTextBox(float x, float y, float breakRowWidth, string text);
(float advance, RectangleF bounds) MeasureText(float x, float y, string text);
RectangleF MeasureTextBox(float x, float y, float breakRowWidth, string text);
TextMetrics? GetTextMetrics();

MeasureText 和 MeasureTextBox 必须在 OnRender 内部调用才能获得正确结果。

文本样式 ​

csharp
float FontSize { get; set; }
float FontBlur { get; set; }
float TextLetterSpacing { get; set; }
float TextLineHeight { get; set; }
TextAlign TextAlign { get; set; }
void FontFace(string fontName);
void FontFaceId(int fontId);
static int CreateFont(string name, string filename);
static int FindFont(string name);

流式扩展:

csharp
canvas.FontSize(24f).LetterSpacing(1f).TextAlign(TextAlign.Center);

动画图片 ​

csharp
void DrawAnimatedImage(IAnimatedImageSource animatedImageSource, float x, float y);

DrawAnimatedImage 面向 AnimatedImageSource 这类脚本层动画数据源,适合在 Canvas 游戏循环中绘制角色、敌人、飞行物或带图层/命中框的复杂动画。若只是按钮光效、加载动画、头像框等简单 UI 序列帧,优先使用 GameUI 原生 Sprites 控件。选型细节见 GameUI 序列帧选型指南。

变换 ​

csharp
void SaveState();                                  // 保存当前变换和样式
void RestoreState();                               // 恢复上次保存的状态
void ResetState();                                 // 重置所有状态

void Translate(float deltaX, float deltaY);
void Rotate(float degrees);
void RotateDegrees(float degrees);
void RotateRadians(float radians);
void Scale(float scaleX, float scaleY);
void Transform(float a, float b, float c, float d, float e, float f);
void ConcatenateTransform(Matrix3x2 transform);
void ResetTransform();

SaveState/RestoreState 成对使用,避免变换泄漏:

csharp
canvas.SaveState();
canvas.Translate(100, 100);
canvas.Rotate(45);
canvas.FillRectangle(-25, -25, 50, 50);  // 在 (100,100) 处绘制旋转 45 度的矩形
canvas.RestoreState();

裁剪 ​

csharp
void ClipRect(float x, float y, float width, float height);
void SubtractFromClip(float x, float y, float width, float height);

PathF ​

路径构建器,所有路径方法返回 this 支持链式调用。

构造 ​

csharp
PathF();
PathF(float x, float y);
PathF(PointF point);
PathF(PathF path);      // 复制

构建 ​

csharp
PathF MoveTo(float x, float y);
PathF LineTo(float x, float y);
PathF QuadTo(float cx, float cy, float x, float y);           // 二次贝塞尔
PathF CurveTo(float c1X, float c1Y, float c2X, float c2Y, float x, float y);  // 三次贝塞尔
PathF ArcTo(PointF controlPoint1, PointF controlPoint2, float radius);
PathF AddCircleArc(PointF center, float radius, float startAngle, float endAngle, bool clockwise);
PathF AddEllipseArc(PointF center, float radiusX, float radiusY, float rotation,
                    float startAngle, float endAngle, bool clockwise);
void Close();   // 闭合路径
void Open();    // 开始新子路径

便捷形状 ​

csharp
PathF AddRectangle(float x, float y, float w, float h);
PathF AddRoundedRectangle(float x, float y, float w, float h, float cornerRadius);
PathF AddCircle(PointF center, float radius);
PathF AddEllipse(PointF center, float radiusX, float radiusY);

变换和查询 ​

csharp
PathF Rotate(float angleAsDegrees, PointF pivot);
void Transform(Matrix3x2 transform);
void Move(float x, float y);
RectangleF Bounds { get; }
PathF Reverse();
List<PathF> Separate();

示例:星形 ​

csharp
var star = new PathF();
for (int i = 0; i < 5; i++)
{
    float outerAngle = i * 72 - 90;
    float innerAngle = outerAngle + 36;
    float outerX = center.X + outerRadius * MathF.Cos(outerAngle * MathF.PI / 180);
    float outerY = center.Y + outerRadius * MathF.Sin(outerAngle * MathF.PI / 180);
    float innerX = center.X + innerRadius * MathF.Cos(innerAngle * MathF.PI / 180);
    float innerY = center.Y + innerRadius * MathF.Sin(innerAngle * MathF.PI / 180);

    if (i == 0) star.MoveTo(outerX, outerY);
    else star.LineTo(outerX, outerY);
    star.LineTo(innerX, innerY);
}
star.Close();
canvas.FillPath(star);

Paint 类型 ​

类型说明
SolidPaint(Color color)纯色
LinearGradientPaint(PointF start, PointF end, Color startColor, Color endColor)线性渐变
RadialGradientPaint(PointF center, float startRadius, float endRadius, Color startColor, Color endColor)径向渐变
BoxGradientPaint(RectangleF rect, float cornerRadius, float feather, Color startColor, Color endColor)矩形渐变

Color 可隐式转换为 SolidPaint,因此可直接赋值:

csharp
canvas.FillPaint = Color.Red;
canvas.FillPaint = new LinearGradientPaint(
    new PointF(0, 0), new PointF(100, 0),
    Color.Red, Color.Blue);

坐标和分辨率 ​

csharp
SizeF Resolution { get; set; }                          // 画布逻辑分辨率
bool AutoUpdateResolutionOnResize { get; set; }         // 自动跟随控件尺寸
CanvasCoordinateMode CoordinateMode { get; set; }       // 坐标模式
int TotalElapsedTimeInMilliseconds { get; }             // 累计时间(用于动画)

CanvasAnimated ​

CanvasAnimated 继承 Canvas,为动画和游戏开发提供帧间隔时间。定义位置:GameUI.Control.Primitive.CanvasAnimated

csharp
var canvas = new CanvasAnimated();
canvas.FullScreen();
canvas.StartTiming();  // 必须调用,否则 OnAnimatedRender 不触发

canvas.OnAnimatedRender += (sender, e) =>
{
    float dt = e.DeltaTimeInSeconds;            // 帧间隔(秒)
    float total = e.TotalElapsedTimeInSeconds;  // 总计时(秒)
    canvas.ResetState();
    // 使用 dt 实现帧率无关的动画
};
canvas.AddToVisualTree();

CanvasAnimatedEventArgs ​

属性类型说明
DeltaTimeInMillisecondsint距上次渲染的时间差(毫秒)
DeltaTimeInSecondsfloat距上次渲染的时间差(秒)
TotalElapsedTimeInMillisecondsint从 StartTiming() 开始的总时间(毫秒)
TotalElapsedTimeInSecondsfloat从 StartTiming() 开始的总时间(秒)

附加方法 ​

方法说明
StartTiming()设置计时起点为当前时刻(开始触发 OnAnimatedRender)
StartTimingDelayed(int ms)延迟 ms 毫秒后开始计时

> CanvasAnimated 拥有 Canvas 的全部绘图 API 和事件(含 OnRender)。动态游戏推荐使用 OnAnimatedRender 以获取时间信息。

相关文档 ​