label-3d.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. import { _decorator, Component, Node, Font, BitmapFont, Vec2, ImageAsset, Texture2D, Asset, RenderTexture, MeshRenderer, Material, Vec3, Size, utils, geometry, EventHandler, Color, ccenum, isValid } from 'cc';
  2. const { ccclass, property, executeInEditMode } = _decorator;
  3. export enum FontFilter {
  4. None,
  5. Blur,
  6. Gray,
  7. }
  8. ccenum(FontFilter)
  9. export const BASELINE_RATIO = 0.26;
  10. const MAX_SIZE = 2048;
  11. @ccclass('Label3D')
  12. @executeInEditMode
  13. export class Label3D extends Component {
  14. /**
  15. * 显示文字
  16. * Arial,serif, 仿宋、黑体、华文彩云、华文细黑、华文新魏、华文行楷、华文中宋、楷体、隶书、幼圆
  17. */
  18. @property
  19. private _font: string = "serif";
  20. @property({ displayOrder: 0, displayName: "字体" })
  21. get font() {
  22. return this._font;
  23. }
  24. set font(val) {
  25. if (this._font === val) {
  26. return;
  27. }
  28. this._font = val;
  29. if (this._font.length <= 0) {
  30. this._font = "serif";
  31. }
  32. this.updateRenderData();
  33. }
  34. /**
  35. * 显示文字
  36. */
  37. @property
  38. private _string: string = "";
  39. @property({ displayOrder: 1, multiline: true, displayName: "文字" })
  40. get string() {
  41. return this._string;
  42. }
  43. set string(val) {
  44. if (val === null || val === undefined) {
  45. val = '';
  46. } else {
  47. val = val.toString();
  48. }
  49. if (this._string === val) {
  50. return;
  51. }
  52. this._string = val;
  53. this.updateRenderData();
  54. }
  55. private _fontSize: number = 50;
  56. @property
  57. private _color: Color = new Color();
  58. @property({ displayOrder: 2, displayName: "字体颜色" })
  59. get color() {
  60. return this._color;
  61. }
  62. set color(val) {
  63. this._color = val;
  64. this.updateRenderData();
  65. }
  66. @property
  67. private _lineHeight: number = 0;
  68. @property({ displayOrder: 3, displayName: "行高" })
  69. get lineHeight() {
  70. return this._lineHeight;
  71. }
  72. set lineHeight(val) {
  73. this._lineHeight = val;
  74. this.updateRenderData();
  75. }
  76. @property
  77. _strokeWidth: number = 2;
  78. @property({ displayOrder: 4, displayName: "描边宽度" })
  79. get strokeWidth() {
  80. return this._strokeWidth;
  81. }
  82. set strokeWidth(val) {
  83. this._strokeWidth = val;
  84. if (this._strokeWidth < 0) {
  85. this._strokeWidth = 0;
  86. }
  87. this.updateRenderData();
  88. }
  89. @property
  90. private _strokeColor: Color = new Color();
  91. @property({ displayOrder: 5, displayName: "描边颜色" })
  92. get strokeColor() {
  93. return this._strokeColor;
  94. }
  95. set strokeColor(val) {
  96. this._strokeColor = val;
  97. this.updateRenderData();
  98. }
  99. @property
  100. _shadowSize: number = 0;
  101. @property({ displayOrder: 6, displayName: "阴影大小" })
  102. get shadowSize() {
  103. return this._shadowSize;
  104. }
  105. set shadowSize(val) {
  106. this._shadowSize = val;
  107. if (this._shadowSize < 0) {
  108. this._shadowSize = 0;
  109. }
  110. this.updateRenderData();
  111. }
  112. @property
  113. private _shadowColor: Color = new Color();
  114. @property({ displayOrder: 7, displayName: "阴影颜色" })
  115. get shadowColor() {
  116. return this._shadowColor;
  117. }
  118. set shadowColor(val) {
  119. this._shadowColor = val;
  120. this.updateRenderData();
  121. }
  122. @property
  123. private _filter: number = FontFilter.None;
  124. @property({ type: FontFilter, displayOrder: 8, displayName: "滤镜" })
  125. get filter() {
  126. return this._filter;
  127. }
  128. set filter(val) {
  129. this._filter = val;
  130. this.updateRenderData();
  131. }
  132. @property
  133. private _blurLenght: number = 5;
  134. @property({ displayOrder: 9, displayName: "模糊强度", range: [0, 10], step: 0.01, slide: true, visible: function (this) { return this._filter === FontFilter.Blur ? true : false; } })
  135. get blurLenght() {
  136. return this._blurLenght;
  137. }
  138. set blurLenght(val) {
  139. this._blurLenght = val;
  140. this.updateRenderData();
  141. }
  142. @property
  143. private _grayLenght: number = 5;
  144. @property({ displayOrder: 10, displayName: "置灰强度", range: [0, 100], step: 1, slide: true, visible: function (this) { return this._filter === FontFilter.Gray ? true : false; } })
  145. get grayLenght() {
  146. return this._grayLenght;
  147. }
  148. set grayLenght(val) {
  149. this._grayLenght = val;
  150. this.updateRenderData();
  151. }
  152. private _splitStrings: string[] = [];
  153. private _context: CanvasRenderingContext2D = null!
  154. private _canvas: HTMLCanvasElement = null!;
  155. private _texture: Texture2D = null!;
  156. private _meshRender: MeshRenderer = null!;
  157. private _canvasSize: Size = new Size();
  158. /**
  159. * mesh uvs
  160. */
  161. private _uvs: number[] = [];
  162. /**
  163. * mesh 顶点坐标
  164. */
  165. private _positions: number[] = [];
  166. private _startPosition: Vec2 = new Vec2();
  167. onLoad() {
  168. this.initRenderingContext();
  169. this.initMeshRender();
  170. this.initTexture2D();
  171. this.updateRenderData();
  172. }
  173. onEnable() {
  174. }
  175. start() {
  176. }
  177. private initRenderingContext(): void {
  178. this._canvas = document.createElement('canvas');
  179. this._context = this._canvas.getContext('2d');
  180. }
  181. private initMeshRender(): void {
  182. this._meshRender = this.node.getComponent(MeshRenderer)!;
  183. if (!this._meshRender) {
  184. this._meshRender = this.node.addComponent(MeshRenderer);
  185. }
  186. }
  187. private initTexture2D(): void {
  188. this._texture = new Texture2D();
  189. let image: ImageAsset = new ImageAsset(this._canvas);
  190. this._texture.image = image;
  191. }
  192. /**
  193. * 刷新渲染
  194. */
  195. private updateRenderData(): void {
  196. this.resetRenderData();
  197. this.updateProperties();
  198. this.updateTexture();
  199. this.updateRenderMesh();
  200. this.updateMaterial();
  201. }
  202. private updateProperties(): void {
  203. //设置canvas 的宽和高
  204. this._splitStrings = this._string.split("\\n");
  205. for (let i = 0; i < this._splitStrings.length; i++) {
  206. //获取文本的宽度
  207. let len: number = this._context.measureText(this._splitStrings[i]).width;
  208. if (len > this._canvasSize.width) {
  209. this._canvasSize.width = len;
  210. }
  211. }
  212. this._canvasSize.height = this._splitStrings.length * this.getLineHeight() + BASELINE_RATIO * this.getLineHeight();
  213. this._canvasSize.width = Math.min(this._canvasSize.width, MAX_SIZE);
  214. this._canvasSize.height = Math.min(this._canvasSize.height, MAX_SIZE);
  215. if (this._canvas.width != this._canvasSize.width) {
  216. this._canvas.width = this._canvasSize.width;
  217. }
  218. if (this._canvas.height != this._canvasSize.height) {
  219. this._canvas.height = this._canvasSize.height;
  220. }
  221. //设置字体样式
  222. this._context.font = "bold " + this._fontSize + "px " + "Arial";
  223. // this._context.
  224. this._context.lineWidth = this._strokeWidth;
  225. this._context.fillStyle = "#" + this._color.toHEX();
  226. this._context.strokeStyle = "#" + this._strokeColor.toHEX();
  227. this._context.shadowBlur = this._shadowSize;
  228. this._context.shadowColor = "#" + this._shadowColor.toHEX();
  229. if (this._filter === FontFilter.Blur) {
  230. this._context.filter = "blur(" + this._blurLenght + "px)";
  231. } else if (this._filter === FontFilter.Gray) {
  232. this._context.filter = "grayscale(" + this._grayLenght + "%)";
  233. } else {
  234. this._context.filter = "none";
  235. }
  236. }
  237. private updateTexture(): void {
  238. if (!this._context || !this._canvas) return;
  239. this._context.clearRect(0, 0, this._canvas.width, this._canvas.height);
  240. let textPosX: number = 0;
  241. let textPosY: number = 0;
  242. for (let i = 0; i < this._splitStrings.length; i++) {
  243. textPosY = this._startPosition.y + (i + 1) * this.getLineHeight();
  244. let len: number = this._context.measureText(this._splitStrings[i]).width;
  245. textPosX = (this._canvas.width - len) / 2;
  246. this._context.fillText(this._splitStrings[i], textPosX, textPosY);
  247. if (this._strokeWidth > 0) {
  248. this._context.strokeText(this._splitStrings[i], textPosX, textPosY);
  249. }
  250. }
  251. let uploadAgain: boolean = this._canvas.width !== 0 && this._canvas.height !== 0;
  252. if (uploadAgain) {
  253. this._texture.reset({
  254. width: this._canvas.width,
  255. height: this._canvas.height,
  256. mipmapLevel: 1,
  257. });
  258. this._texture.uploadData(this._canvas);
  259. this._texture.setWrapMode(RenderTexture.WrapMode.CLAMP_TO_EDGE, RenderTexture.WrapMode.CLAMP_TO_EDGE);
  260. }
  261. }
  262. private updateMaterial(): void {
  263. if (!this._texture) return;
  264. if (!this._meshRender) return;
  265. let material: Material = this._meshRender.getMaterialInstance(0)!;
  266. material.setProperty("mainTexture", this._texture);
  267. }
  268. /**
  269. * 根据canvas的实际宽高
  270. * 动态的调整mesh的坐标
  271. */
  272. private updateRenderMesh(): void {
  273. let rate: number = this._canvas.width / this._canvas.height;
  274. this._positions = [];
  275. this._uvs = [];
  276. this._positions.push(-0.5 * rate, -0.5, 0); this._uvs.push(0, 1);
  277. this._positions.push(0.5 * rate, -0.5, 0); this._uvs.push(1, 1);
  278. this._positions.push(-0.5 * rate, 0.5, 0); this._uvs.push(0, 0);
  279. this._positions.push(-0.5 * rate, 0.5, 0); this._uvs.push(0, 0);
  280. this._positions.push(0.5 * rate, -0.5, 0); this._uvs.push(1, 1);
  281. this._positions.push(0.5 * rate, 0.5, 0); this._uvs.push(1, 0);
  282. this._meshRender.mesh = utils.MeshUtils.createMesh({
  283. positions: this._positions,
  284. uvs: this._uvs,
  285. minPos: { x: -0.5 * rate, y: -0.5, z: 0 },
  286. maxPos: { x: 0.5 * rate, y: 0.5, z: 0 }
  287. });
  288. this._meshRender.model?.updateWorldBound();
  289. }
  290. /**
  291. * 获取行高
  292. */
  293. private getLineHeight(): number {
  294. return this._fontSize + this._lineHeight; //行高 -暂时写成40
  295. }
  296. private resetRenderData(): void {
  297. this._canvasSize.width = 0;
  298. this._canvasSize.height = 0;
  299. //设置字体样式
  300. this._context.font = "bold " + this._fontSize + "px " + "Arial";
  301. this._context.lineWidth = this._strokeWidth;
  302. this._context.fillStyle = "#" + this._color.toHEX();
  303. this._context.strokeStyle = "#" + this._strokeColor.toHEX();
  304. this._context.shadowBlur = this._shadowSize;
  305. this._context.shadowColor = "#" + this._shadowColor.toHEX();
  306. if (this._filter === FontFilter.Blur) {
  307. this._context.filter = "blur(" + this._blurLenght + "px)";
  308. } else if (this._filter === FontFilter.Gray) {
  309. this._context.filter = "grayscale(" + this._grayLenght + "%)";
  310. } else {
  311. this._context.filter = "none";
  312. }
  313. }
  314. update(deltaTime: number) {
  315. }
  316. onDestroy() {
  317. this._canvas = null;
  318. this._context = null;
  319. this._meshRender = null!;
  320. }
  321. }