import { _decorator, CCObject, Color, Component, Graphics, IVec2Like, Layers, Node, toRadian, Vec3 } from 'cc'; import { Editor } from './Define'; const { ccclass, executeInEditMode } = _decorator; class GizmosRenderer { private _graphic: Graphics; private _drawCalls: ((graphic: Graphics) => void)[] = []; public addDrawCall(drawCall: (graphic: Graphics) => void) { this._drawCalls.push(drawCall); } constructor(graphic: Graphics) { this._graphic = graphic; } public clear() { this._graphic.clear(); } public setLayer(layer: Layers.Enum) { this._graphic.node.layer = layer; } public draw() { this._drawCalls.forEach((drawCall) => { drawCall(this._graphic); }); this._drawCalls = []; } } @ccclass('Gizmos2D.GizmosDebugDraw') @executeInEditMode class GizmosDebugDraw extends Component { private _renderers: Map = new Map(); private _color: Color = Gizmos2D.DEFAULT_COLOR; private _useLocalPosition: boolean = false; private _layer: Layers.Enum = Gizmos2D.DEFAULT_LAYER; private _parentNode: Node; protected update(dt: number): void { //only call in editor this.callNodeDrawGizmos(); this._renderers.forEach((renderer) => { renderer.clear(); renderer.draw(); }); this._color = Gizmos2D.DEFAULT_COLOR; this._useLocalPosition = false; this._layer = Gizmos2D.DEFAULT_LAYER; } private callNodeDrawGizmos() { if (Editor && this._parentNode) { const selectedList: string[] = Editor.Selection.getSelected('node'); if (selectedList.includes(this._parentNode.uuid)) { const comps = this._parentNode.components; for (let i = 0; i < comps.length; i++) { const comp: Component = comps[i]; comp.onDrawGizmos?.(); } } } } private createRenderer(color: Color) { const hex = color.toHEX(); const g = new Node(`color ${hex}`).addComponent(Graphics); g.lineWidth = 5; g.strokeColor = color; g.fillColor = color; g.node.layer = this.node.layer; g.node.parent = this.node; const renderer = new GizmosRenderer(g); return renderer; } private getRenderer(color: Color): GizmosRenderer { const hex = color.toHEX(); let renderer = this._renderers.get(hex); if (!renderer) { renderer = this.createRenderer(color); this._renderers.set(hex, renderer); } return renderer; } private worldToLocal(world: IVec2Like): IVec2Like { const local = new Vec3(); this.node.inverseTransformPoint(local, new Vec3(world.x, world.y)); return local; } public registerDrawGizmos(node: Node) { this._parentNode = node; } public setColor(color: Color) { this._color = color; } public setUseLocalPosition(value: boolean) { this._useLocalPosition = value; } public setLayer(layer: Layers.Enum) { this._layer = layer; } public clearAll() { this._renderers.forEach((renderer) => renderer.clear()); } public drawLine(point1: IVec2Like, point2: IVec2Like) { const color = this._color.clone(); const renderer = this.getRenderer(color); renderer.setLayer(this._layer); const p1 = this._useLocalPosition ? point1 : this.worldToLocal(point1); const p2 = this._useLocalPosition ? point1 : this.worldToLocal(point2); renderer.addDrawCall((g) => { g.moveTo(p1.x, p1.y); g.lineTo(p2.x, p2.y); g.stroke(); }); } public drawLineList(points: IVec2Like[], close: boolean = false) { const color = this._color.clone(); const renderer = this.getRenderer(color); renderer.setLayer(this._layer); renderer.addDrawCall((g) => { if (points.length > 0) { const p0 = this.worldToLocal(points[0]); g.moveTo(p0.x, p0.y); for (let i = 1; i < points.length; i++) { const p = this.worldToLocal(points[i]); g.lineTo(p.x, p.y); } if (close) g.close(); g.stroke(); } }); } public drawCircle(center: IVec2Like, radius: number) { const color = this._color.clone(); const renderer = this.getRenderer(color); renderer.setLayer(this._layer); const c = this._useLocalPosition ? center : this.worldToLocal(center); renderer.addDrawCall((g) => { g.circle(c.x, c.y, radius); g.stroke(); }); } public drawSolidCircle(center: IVec2Like, radius: number) { const color = this._color.clone(); const renderer = this.getRenderer(color); renderer.setLayer(this._layer); const c = this._useLocalPosition ? center : this.worldToLocal(center); renderer.addDrawCall((g) => { g.circle(c.x, c.y, radius); g.fill(); }); } public drawRect(position: IVec2Like, width: number, height: number) { const color = this._color.clone(); const renderer = this.getRenderer(color); renderer.setLayer(this._layer); const p = this._useLocalPosition ? position : this.worldToLocal(position); renderer.addDrawCall((g) => { g.rect(p.x, p.y, width, height); g.stroke(); }); } public drawSolidRect(center: IVec2Like, width: number, height: number) { const color = this._color.clone(); const renderer = this.getRenderer(color); renderer.setLayer(this._layer); const c = this._useLocalPosition ? center : this.worldToLocal(center); renderer.addDrawCall((g) => { g.rect(c.x, c.y, width, height); g.fill(); }); } public drawEllipse(center: IVec2Like, radiusX: number, radiusY: number) { const color = this._color.clone(); const renderer = this.getRenderer(color); renderer.setLayer(this._layer); const c = this._useLocalPosition ? center : this.worldToLocal(center); renderer.addDrawCall((g) => { g.ellipse(c.x, c.y, radiusX, radiusY); g.stroke(); }); } public drawSolidEllipse(center: IVec2Like, radiusX: number, radiusY: number) { const color = this._color.clone(); const renderer = this.getRenderer(color); renderer.setLayer(this._layer); const c = this._useLocalPosition ? center : this.worldToLocal(center); renderer.addDrawCall((g) => { g.ellipse(c.x, c.y, radiusX, radiusY); g.fill(); }); } public drawArc( center: IVec2Like, radius: number, startAngle: number, endAngle: number, counterclockwise: boolean = false, ) { const color = this._color.clone(); const renderer = this.getRenderer(color); renderer.setLayer(this._layer); const c = this._useLocalPosition ? center : this.worldToLocal(center); renderer.addDrawCall((g) => { g.arc(c.x, c.y, radius, toRadian(startAngle), toRadian(endAngle), counterclockwise); g.stroke(); }); } public drawSolidArc( center: IVec2Like, radius: number, startAngle: number, endAngle: number, counterclockwise: boolean = false, ) { const color = this._color.clone(); const renderer = this.getRenderer(color); renderer.setLayer(this._layer); const c = this._useLocalPosition ? center : this.worldToLocal(center); renderer.addDrawCall((g) => { g.arc(c.x, c.y, radius, toRadian(startAngle), toRadian(endAngle), counterclockwise); g.lineTo(c.x, c.y); g.fill(); }); } public drawBezierCurves(point1: IVec2Like, point2: IVec2Like, point3: IVec2Like, point4: IVec2Like) { const color = this._color.clone(); const renderer = this.getRenderer(color); renderer.setLayer(this._layer); const p1 = this._useLocalPosition ? point1 : this.worldToLocal(point1); const p2 = this._useLocalPosition ? point2 : this.worldToLocal(point2); const p3 = this._useLocalPosition ? point3 : this.worldToLocal(point3); const p4 = this._useLocalPosition ? point4 : this.worldToLocal(point4); renderer.addDrawCall((g) => { g.moveTo(p1.x, p1.y); g.bezierCurveTo(p2.x, p2.y, p3.x, p3.y, p4.x, p4.y); g.stroke(); }); } public drawQuadraticCurve(point1: IVec2Like, point2: IVec2Like, point3: IVec2Like) { const color = this._color.clone(); const renderer = this.getRenderer(color); renderer.setLayer(this._layer); const p1 = this._useLocalPosition ? point1 : this.worldToLocal(point1); const p2 = this._useLocalPosition ? point2 : this.worldToLocal(point2); const p3 = this._useLocalPosition ? point3 : this.worldToLocal(point3); renderer.addDrawCall((g) => { g.moveTo(p1.x, p1.y); g.quadraticCurveTo(p2.x, p2.y, p3.x, p3.y); g.stroke(); }); } } export default class Gizmos2D { public static readonly DEFAULT_COLOR = Color.BLUE; public static readonly DEFAULT_LAYER = Layers.Enum.GIZMOS; private static getDebugNode(node: Node) { let debugNode = node.getComponentInChildren(GizmosDebugDraw); if (!debugNode) { debugNode = new Node('DEBUG_DRAW_NODE').addComponent(GizmosDebugDraw); debugNode.node.layer = this.DEFAULT_LAYER; debugNode.node.hideFlags |= CCObject.Flags.DontSave | CCObject.Flags.HideInHierarchy; debugNode.node.parent = node; } return debugNode; } public static registerDrawGizmos(node: Node) { this.getDebugNode(node).registerDrawGizmos(node); } public static beginColor(node: Node, color: Color) { this.getDebugNode(node).setColor(color); } public static beginLocalPosition(node: Node) { this.getDebugNode(node).setUseLocalPosition(true); } public static endLocalPosition(node: Node) { this.getDebugNode(node).setUseLocalPosition(false); } public static beginLayer(node: Node, layer: Layers.Enum) { this.getDebugNode(node).setLayer(layer); } public static clearAll(node: Node) { this.getDebugNode(node).clearAll(); } public static drawLine(node: Node, point1: IVec2Like, point2: IVec2Like) { const debugNode = this.getDebugNode(node); debugNode.drawLine(point1, point2); } public static drawLineList(node: Node, points: IVec2Like[], close: boolean = false) { const debugNode = this.getDebugNode(node); debugNode.drawLineList(points, close); } public static drawCircle(node: Node, center, radius: number) { const debugNode = this.getDebugNode(node); debugNode.drawCircle(center, radius); } public static drawSolidCircle(node: Node, center, radius: number) { const debugNode = this.getDebugNode(node); debugNode.drawSolidCircle(center, radius); } public static drawRect(node: Node, position: IVec2Like, width: number, height: number) { const debugNode = this.getDebugNode(node); debugNode.drawRect(position, width, height); } public static drawSolidRect(node: Node, center: IVec2Like, width: number, height: number) { const debugNode = this.getDebugNode(node); debugNode.drawSolidRect(center, width, height); } public static drawEllipse(node: Node, center: IVec2Like, radiusX: number, radiusY: number) { const debugNode = this.getDebugNode(node); debugNode.drawEllipse(center, radiusX, radiusY); } public static drawSolidEllipse(node: Node, center: IVec2Like, radiusX: number, radiusY: number) { const debugNode = this.getDebugNode(node); debugNode.drawSolidEllipse(center, radiusX, radiusY); } public static drawArc( node: Node, center: IVec2Like, radius: number, startAngle: number, endAngle: number, counterclockwise: boolean = false, ) { const debugNode = this.getDebugNode(node); debugNode.drawArc(center, radius, startAngle, endAngle, counterclockwise); } public static drawSolidArc( node: Node, center: IVec2Like, radius: number, startAngle: number, endAngle: number, counterclockwise: boolean = false, ) { const debugNode = this.getDebugNode(node); debugNode.drawSolidArc(center, radius, startAngle, endAngle, counterclockwise); } public static drawBezierCurves( node: Node, point1: IVec2Like, point2: IVec2Like, point3: IVec2Like, point4: IVec2Like, ) { const debugNode = this.getDebugNode(node); debugNode.drawBezierCurves(point1, point2, point3, point4); } public static drawQuadraticCurve(node: Node, point1: IVec2Like, point2: IVec2Like, point3: IVec2Like) { const debugNode = this.getDebugNode(node); debugNode.drawQuadraticCurve(point1, point2, point3); } }