重复的点,去掉试一下.
画圆还是太耗顶点了,用图片吧
那mask擦除呢
都操作像素点了,擦除不会啊 
我想到的办法也只有去重,但是也涉及到保存点的数组太大,遍历会不会很费时间的问题,还有面积重叠检测问题
你问codex啊
是类似画板/圆形笔触?一坨是涂鸦部分?
如果圆形不是可编辑/可撤销,用纹理当画布?后面的圆覆盖前面的。
用mask/模板测试/裁剪测试剔除,性能改善不了多少。
或者检测完全被覆盖的圆形不要绘制,或者把轨迹转为平滑的多边形而不是一个个圆
几年前做的了
既然是 自由涂鸦的画板 ,并且需要 支持更换颜色和粗细 ,那么“方案一:RenderTexture(渲染到纹理)”就是你唯一的、也是最完美的解决方案。
因为你每次更改颜色或粗细后画的线,都会被“印”在画板上,之前画的内容已经被固化成图片像素了,所以怎么改都不会影响之前的涂鸦。而且无论你画了多少笔,最终屏幕上只有一张图片,帧率永远满帧。
下面是在 Cocos Creator 3.x 中实现这个功能的完整实战指南。
核心实现原理
在这个方案中,你需要三个核心组件来打配合:
- Graphics(画笔): 只负责画 当前正在画 的这一笔。
- Camera(专用摄像机): 眼睛死死盯着 Graphics,把 Graphics 画出来的东西实时拍下来。
- Sprite(画纸): 显示摄像机拍下来的照片(RenderTexture)。
最核心的魔法在于摄像机的 ClearFlags (清除标志)。 正常摄像机每一帧都会把上一帧的画面擦除(清空成背景色),但如果我们把这个摄像机设置为“不清除(Dont Clear)”,它就会把每一帧的画面叠加起来。这时候我们只要调用 graphics.clear() 清空画笔,画纸上的图案依然会保留!
步步为营:操作步骤
第一步:设置专属 Layer
为了不影响游戏里的其他 UI,我们需要给画笔一个专属的图层。
- 在编辑器右上角点击 Project -> Project Settings -> Layers 。
- 添加一个新的 Layer,比如命名为
DRAWING_BRUSH。
第二步:搭建节点层级
在场景树(Hierarchy)中,搭建如下结构:
-
Canvas (默认的主画布)
- Main Camera (主摄像机,Visibility 勾选 UI_2D 等, 千万不要勾选 DRAWING_BRUSH )
-
DrawingBoard (Sprite) (用来当画纸的节点)
- BrushCamera (Camera) (专属摄像机)
- BrushGraphics (Graphics) (用来当画笔的节点)
第三步:配置专属摄像机 (BrushCamera)
选中 BrushCamera 节点,在属性检查器(Inspector)中进行如下极为关键的设置:
-
Visibility: 只勾选
DRAWING_BRUSH(只看画笔)。 -
ClearFlags: 不要勾选 Color (如果是英文界面,设置为
DONT_CLEAR或者只保留DEPTH_STENCIL)。这一步是让图案叠加的灵魂! -
Projection: 设置为
ORTHO(正交透视,和 2D UI 一致)。
第四步:配置画笔 (BrushGraphics)
选中 BrushGraphics 节点:
- 将其右上角的 Layer 设置为你刚才创建的
DRAWING_BRUSH。 - 挂载
Graphics组件。
完整核心代码 (TypeScript)
新建一个脚本 DrawingBoard.ts ,挂载到 DrawingBoard 节点上,并把对应的节点拖入组件槽位。
import { _decorator, Component, Node, Graphics, Camera, RenderTexture, Sprite, SpriteFrame, UITransform, EventTouch, Color } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('DrawingBoard')
export class DrawingBoard extends Component {
@property(Camera)
public brushCamera: Camera = null!;
@property(Graphics)
public brushGraphics: Graphics = null!;
@property(Sprite)
public paperSprite: Sprite = null!;
// 内部变量
private renderTex: RenderTexture = null!;
private pointCount: number = 0;
// 画笔设置
private currentLineWidth: number = 5;
private currentColor: Color = Color.BLACK;
start() {
this.initRenderTexture();
this.initTouchEvents();
this.applyBrushSettings();
}
private initRenderTexture() {
// 获取画板的宽高
const uiTransform = this.getComponent(UITransform);
const width = uiTransform.width;
const height = uiTransform.height;
// 1. 创建 RenderTexture
this.renderTex = new RenderTexture();
this.renderTex.reset({ width: width, height: height });
// 2. 让摄像机渲染到这个纹理上
this.brushCamera.targetTexture = this.renderTex;
// 3. 将纹理赋值给画纸(Sprite)
const spriteFrame = new SpriteFrame();
spriteFrame.texture = this.renderTex;
this.paperSprite.spriteFrame = spriteFrame;
}
private initTouchEvents() {
// 监听触摸事件
this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
this.node.on(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
this.node.on(Node.EventType.TOUCH_END, this.onTouchEnd, this);
this.node.on(Node.EventType.TOUCH_CANCEL, this.onTouchEnd, this);
}
private onTouchStart(event: EventTouch) {
// 获取局部坐标
const uiTransform = this.getComponent(UITransform);
const pos = uiTransform.convertToNodeSpaceAR(event.getUILocation());
this.brushGraphics.moveTo(pos.x, pos.y);
this.pointCount = 0;
}
private onTouchMove(event: EventTouch) {
const uiTransform = this.getComponent(UITransform);
const pos = uiTransform.convertToNodeSpaceAR(event.getUILocation());
this.brushGraphics.lineTo(pos.x, pos.y);
this.brushGraphics.stroke();
this.pointCount++;
// 优化:如果这一笔画得太长(比如超过 50 个点),就“印”一次并清空画笔
// 防止用户手指一直不松开导致单次绘制的三角形过多
if (this.pointCount > 50) {
this.bakeToTexture();
}
}
private onTouchEnd(event: EventTouch) {
// 手指抬起时,将这一笔彻底“印”到画纸上
this.bakeToTexture();
}
private bakeToTexture() {
// 因为 BrushCamera 的 ClearFlags 没勾选 Color,
// 所以这一步并不会清空 RenderTexture,而是把 Graphics 的新线段叠加印上去。
// 随后立刻清空 Graphics,释放本轮产生的三角形。
this.brushGraphics.clear();
// 清空后,重置起点,防止下一笔连着上一笔
this.pointCount = 0;
}
// ================= 公开 API:供外部 UI 按钮调用 =================
/** 修改画笔颜色 */
public setBrushColor(color: Color) {
this.currentColor = color;
this.applyBrushSettings();
}
/** 修改画笔粗细 */
public setBrushWidth(width: number) {
this.currentLineWidth = width;
this.applyBrushSettings();
}
/** 橡皮擦功能(其实就是把颜色换成背景色,通常是白色或透明) */
public useEraser(eraserWidth: number = 20) {
// 假设背景是白色的
this.currentColor = Color.WHITE;
this.currentLineWidth = eraserWidth;
this.applyBrushSettings();
}
/** 清空整个画板 */
public clearBoard() {
// 销毁旧的,重新建一个干净的
this.renderTex.destroy();
this.initRenderTexture();
}
private applyBrushSettings() {
this.brushGraphics.lineWidth = this.currentLineWidth;
this.brushGraphics.strokeColor = this.currentColor;
this.brushGraphics.fillColor = this.currentColor;
}
}
进阶优化细节说明
-
防止单笔画太长导致卡顿 (
pointCount > 50的逻辑): 如果玩家一直在屏幕上画圈圈不松手,Graphics里的顶点依然会堆积。上面的代码中加入了pointCount > 50自动触发bakeToTexture的逻辑。这就相当于玩家虽然还在画,但底层的画笔每画 50 个点就悄悄把墨水“印”到纸上并换一根新画笔接着画,做到了无缝衔接且永远不卡。 -
橡皮擦的本质: 使用这种方案,橡皮擦非常容易实现。你不需要写复杂的遮罩或裁剪算法,只需要调用
setBrushColor(Color.WHITE)(或者你画板的背景色),把画笔调粗一点,直接覆盖上去即可。 -
抗锯齿:
Graphics组件自带一定的抗锯齿,但如果渲染到 RenderTexture 后感觉边缘不够平滑,可以在Graphics组件的设置里调整Line Join(线段交汇处)为ROUND,Line Cap(线段两端)也设置为ROUND,这样画出来的线条会非常圆润。
[.WebGL-0x10c000c2c00] GL_INVALID_OPERATION: glDrawElements: Feedback loop formed between Framebuffer and active Texture.
这个代码,我跑了.报这个warn,画不出来.我继续看看
摄像机可见性的问题吧,camera拍到的内容输出到renderTexture,然后这个renderTexture又被camera拍到了.应该给这个renderTexure专门设置额外的一个摄像机.
maks擦除也可以结合shader,你是要怎么样的擦除

