{"id":18275515,"url":"https://github.com/leeyip/cocos-text-mesh-pro","last_synced_at":"2025-08-31T20:10:28.947Z","repository":{"id":56754944,"uuid":"524127341","full_name":"LeeYip/cocos-text-mesh-pro","owner":"LeeYip","description":"一个用于Cocos Creator的文本渲染解决方案","archived":false,"fork":false,"pushed_at":"2025-01-19T13:32:40.000Z","size":8336,"stargazers_count":189,"open_issues_count":3,"forks_count":66,"subscribers_count":4,"default_branch":"v3.6.0","last_synced_at":"2025-05-24T09:05:23.707Z","etag":null,"topics":["cocos","cocos-creator","label","sdf","text-mesh-pro"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LeeYip.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-12T14:55:47.000Z","updated_at":"2025-05-14T01:12:48.000Z","dependencies_parsed_at":"2025-01-03T10:10:28.431Z","dependency_job_id":"b45f0841-f04f-4f4c-b527-34e212880cd8","html_url":"https://github.com/LeeYip/cocos-text-mesh-pro","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/LeeYip/cocos-text-mesh-pro","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeeYip%2Fcocos-text-mesh-pro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeeYip%2Fcocos-text-mesh-pro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeeYip%2Fcocos-text-mesh-pro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeeYip%2Fcocos-text-mesh-pro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LeeYip","download_url":"https://codeload.github.com/LeeYip/cocos-text-mesh-pro/tar.gz/refs/heads/v3.6.0","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LeeYip%2Fcocos-text-mesh-pro/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273032934,"owners_count":25034067,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-31T02:00:09.071Z","response_time":79,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cocos","cocos-creator","label","sdf","text-mesh-pro"],"created_at":"2024-11-05T12:13:10.198Z","updated_at":"2025-08-31T20:10:28.901Z","avatar_url":"https://github.com/LeeYip.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cocos TextMeshPro\r\n一个用于Cocos Creator的文本渲染解决方案\r\n\r\n## 目录\r\n- [前言](#preface)\r\n- [特性](#feature)\r\n- [版本支持](#version)\r\n- [如何使用](#how2use)\r\n    - [插件](#plugin)\r\n    - [组件](#component)\r\n    - [API](#api)\r\n    - [Example](#example)\r\n- [富文本](#richtext)\r\n- [注意事项](#note)\r\n\r\n## \u003ca id=\"preface\"\u003e\u003c/a\u003e前言\r\n用过Unity的应该知道，UGUI中的TextMeshPro功能强大，是一套极佳的文本渲染解决方案。此项目旨在为Cocos Creator提供类似的方案，以相对较低的代价，实现各种文本效果。由于需要重写渲染组件以及顶点数据填充，而Cocos Creator不同版本渲染实现差异较大无法兼容，故针对不同版本建了不同Git分支，使用前请切换对应的分支。\r\n\r\n## \u003ca id=\"feature\"\u003e\u003c/a\u003e特性\r\n- 基于SDF进行文本渲染，无损放大\r\n- 支持最多8张纹理的BMFont\r\n    - 导出参数合理，以及项目多语言种类不多的情况下，可以将项目中所有文本全部导出在一个字体文件中\r\n    - 一般来说，WebGL至少支持8个纹理单元，OpenGL至少支持16个纹理单元\r\n- 支持颜色渐变\r\n- 支持斜体\r\n- 支持下划线、删除线\r\n- 支持描边、镂空、阴影、辉光等特效，且这些特效会作用在下划线与删除线上\r\n- 提供顶点数据接口，可以自由实现顶点动画\r\n- 提供新的排版模式ELLIPSIS——当文本超出节点大小时，自动以\"...\"结尾\r\n- 支持富文本\r\n\r\n![image](./docs/images/showcase1.gif)\u003c/br\u003e\r\n\r\n## \u003ca id=\"version\"\u003e\u003c/a\u003e版本支持\r\n目前经过测试的版本与系统如下，未列出的版本与系统仅表示暂未测试。\r\n\r\n| Cocos Creator | [v2.4.9](https://github.com/LeeYip/cocos-text-mesh-pro/tree/v2.4.9) | [v3.6.0](https://github.com/LeeYip/cocos-text-mesh-pro/tree/v3.6.0) |\r\n| :-: | :-: | :-: |\r\n| Android | ✓ | ✓ |\r\n| Web | ✓ | ✓ |\r\n\r\n- **v2.4.9分支**\r\n    - 此分支应该都可用于2.4.x系列版本，只不过2.4.5及以下版本中引擎源码材质hash值计算有bug，会导致某些情况下无法合批，请自行测试\r\n- **v3.6.0分支**\r\n    - 目前不支持低于3.6的版本，在3.6中引擎渲染实现有较大改动，故无法兼容\r\n    - 此分支对native的支持需要用cpp目录下的文件替换引擎源码中对应的c++文件\r\n    - Cocos Creator3.x的合批判断过于严格，只要不是同一个材质的引用，就不会合批。在JS层我做了一些hack的写法，修改了合批判断，使得相同uniform参数的材质实例得以合批。而native上的合批判断实现在c++层，暂未进行修改，所以目前如果希望将相同uniform参数的进行合批，请自行屏蔽掉组件内使用材质实例动态修改材质宏和uniform参数的代码，并尽可能的使用共享材质\r\n\r\n## \u003ca id=\"how2use\"\u003e\u003c/a\u003e如何使用\r\n\r\n#### \u003ca id=\"plugin\"\u003e\u003c/a\u003e插件\r\n\r\n![image](./docs/images/plugin1.png)\u003c/br\u003e\r\n插件中有两个选项，Font Tool为SDF字体生成工具。Import Assets为将TextMeshPro组件与材质等导入到assets目录下，若无法自动导入，请到插件目录下手动复制。（此仓库的项目assets中已包含这些文件无需再次导入）\r\n\r\n![image](./docs/images/plugin2.png)\u003c/br\u003e\r\nFont Tool界面如上图所示\r\n- Hiero路径：字体导出依赖工具，点击下载按钮会进入下载地址，需要确保已安装Java环境才能运行此工具\r\n- 源字体：需要导出的ttf字体文件\r\n- 导出目录：SDF字体导出目录\r\n- 导出名称：导出的SDF字体文件名\r\n- 导出文本：可选择导出输入框内的文本或者导出txt文件内的文本\r\n- 字体参数：Font Size为字体导出大小，Padding为字体间距，这两个参数大一些会对渲染效果好一点，但过大可能会导致导出的纹理数量过多，注意不可超出纹理上限\r\n- 纹理参数：导出的纹理大小\r\n- SDF Scale：此参数越大对最终渲染效果越好，但过大会导致字体导出过于缓慢。原理是导出字体前先对所有字体进行放大，然后再生成SDF纹理，再将字体纹理缩小为导出的Font Size进行导出。\r\n- Save：保存插件配置\r\n- Export：导出字体，生成运行时所需的json和png。期间会用命令行自动打开Hiero工具，导出过程根据设置的参数可能会非常缓慢，请耐心等待Hiero自行关闭。\r\n\r\n#### \u003ca id=\"component\"\u003e\u003c/a\u003e组件\r\n\r\n![image](./docs/images/compnent1.png)\u003c/br\u003e\r\n组件参数如上图所示\r\n- Font：使用Font Tool导出的字体json文件\r\n- Overflow：除了Cocos Creator Label组件的排版方式之外，还提供了新的排版模式ELLIPSIS。会自动计算文本大小，若超出节点大小，则以\"...\"结尾（**字体导出文本中必须包含字符\".\"**）\r\n\r\n    ![image](./docs/images/compnent2.gif)\u003c/br\u003e\r\n- EnableItalic：斜体\r\n- EnableUnderline：下划线，可调节高度（**字体导出文本中必须包含字符\"_\"**）\r\n- EnableStrikethrough：删除线，可调节高度（**字体导出文本中必须包含字符\"_\"**）\r\n- ColorGradient：颜色渐变开关，提供四个顶点的颜色设置，会和顶点颜色混合为最终的顶点颜色\r\n\r\n    ![image](./docs/images/compnent3.png)\u003c/br\u003e\r\n- TmpUniform：控制shader部分的参数，不同参数会影响TextMeshPro的合批\r\n    - FaceColor：文本主体的颜色\r\n    - FaceDilate：文本主体的粗细，范围0-1，0.5为标准值\r\n    - FaceSoftness：文本主体的柔和度，越小字体显示越硬，越大则会让字体显示越虚\r\n\r\n        ![image](./docs/images/compnent5.png)\u003c/br\u003e\r\n    - EnableOutline：描边开关，配合FaceColor透明度可以实现文本镂空效果\r\n\r\n        ![image](./docs/images/compnent4.png)\u003c/br\u003e\r\n    - OutlineColor：描边颜色\r\n    - OutlineThickness：描边厚度\r\n    - EnableUnderlay：阴影开关\r\n\r\n        ![image](./docs/images/compnent6.png)\u003c/br\u003e\r\n    - UnderlayColor：阴影颜色\r\n    - UnderlayOffset：阴影偏移，如x方向偏移一个像素则需填入的值为1/纹理宽度，y方向同理\r\n    - UnderlayDilate：阴影厚度\r\n    - UnderlaySoftness：阴影柔和度\r\n    - EnableGlow：辉光开关，可以理解为在其他文本效果之上叠加一层额外的描边效果，所以底下的颜色越暗效果越明显\r\n\r\n        ![image](./docs/images/compnent7.png)\u003c/br\u003e\r\n    - GlowColor：辉光颜色\r\n    - GlowOffset：辉光偏移，范围0-1，0.5为标准值\r\n    - GlowInner：辉光向内的厚度\r\n    - GlowOuter：辉光向外的厚度\r\n    - GlowPower：辉光强度，范围0-1，1为最强\r\n- Textures：字体依赖的纹理\r\n\r\n#### \u003ca id=\"api\"\u003e\u003c/a\u003eAPI\r\n- **`forceUpdateRenderData(): void`**  立即更新渲染数据\r\n- **`setFont(font: cc.JsonAsset, textures: cc.Texture2D[]): void`**  动态设置字体\r\n- **`isVisible(index: number): boolean`**  根据字符下标判断此字符是否可见\r\n- **`setVisible(index: number, visible: boolean): void`**  根据字符下标设置字符是否可见\r\n- **`getColorExtraVertices(index: number): [cc.Color, cc.Color, cc.Color, cc.Color] | null`**  根据字符下标获取颜色顶点数据，顺序为[左下, 右下, 左上, 右上]\r\n- **`setColorExtraVertices(index: number, data: [cc.Color, cc.Color, cc.Color, cc.Color]): void`**  根据字符下标设置颜色顶点数据，会和节点颜色混合为最终的顶点颜色，顺序为[左下, 右下, 左上, 右上]\r\n- **`getPosVertices(index: number): [cc.Vec2, cc.Vec2, cc.Vec2, cc.Vec2] | null`**  根据字符下标获取坐标顶点数据，顺序为[左下, 右下, 左上, 右上]\r\n- **`setPosVertices(index: number, data: [cc.Vec2, cc.Vec2, cc.Vec2, cc.Vec2]): void`**  根据字符下标设置坐标顶点数据，顺序为[左下, 右下, 左上, 右上]\r\n\r\n#### \u003ca id=\"example\"\u003e\u003c/a\u003eExample\r\n- 高效实现打字机效果：不必随时间每次都更新字符串，这样会导致每次更新字符串时顶点数据重新计算一次，浪费性能。\r\n\r\n    ![image](./docs/images/showcase2.gif)\u003c/br\u003e\r\n    ```typescript\r\n    // 更新文本后立即更新一次渲染数据，后续根据此渲染数据进行操作\r\n    // 所有顶点动画效果都可参考此方式进行扩展\r\n    this.text1.string = \"这 是 一 段 测 试 文 字\";\r\n    this.text1.forceUpdateRenderData();\r\n    // 先隐藏所有字符\r\n    for (let i = 0; i \u003c this.text1.string.length; i++) {\r\n        this.text1.setVisible(i, false);\r\n    }\r\n    for (let i = 0; i \u003c this.text1.string.length; i++) {\r\n        // 逐个字符显示，并且过滤掉空格等不需要渲染的字符\r\n        this.text1.setVisible(i, true);\r\n        if (!this.text1.isVisible(i)) {\r\n            continue;\r\n        }\r\n        await this.waitCmpt(this, 0.1);\r\n    }\r\n    ```\r\n    \r\n    再更进一步，通过控制顶点颜色数据，逐顶点透明渐变\r\n\r\n    ![image](./docs/images/showcase3.gif)\u003c/br\u003e\r\n\r\n    ```typescript\r\n    public alpha: number = 0;\r\n    private async anim3(): Promise\u003cvoid\u003e {\r\n        this.text3.string = \"这 是 一 段 测 试 文 字\";\r\n        this.text3.updateRenderData(true);\r\n        for (let i = 0; i \u003c this.text3.string.length; i++) {\r\n            this.text3.setVisible(i, false);\r\n        }\r\n        let time = 0.5;\r\n        for (let i = 0; i \u003c this.text3.string.length; i++) {\r\n            this.text3.setVisible(i, true);\r\n            if (!this.text3.isVisible(i)) {\r\n                continue;\r\n            }\r\n            this.text3.setVisible(i, false);\r\n            let result = this.text3.getColorExtraVertices(i);\r\n            this.alpha = 0;\r\n            tween\u003cMain\u003e(this)\r\n                .to(time / 2, { alpha: 255 }, {\r\n                    onUpdate: () =\u003e {\r\n                        result[0].a = this.alpha;\r\n                        result[2].a = this.alpha;\r\n                        this.text3.setColorExtraVertices(i, result);\r\n                    }\r\n                })\r\n                .call(() =\u003e {\r\n                    this.alpha = 0;\r\n                })\r\n                .to(time / 2, { alpha: 255 }, {\r\n                    onUpdate: () =\u003e {\r\n                        result[1].a = this.alpha;\r\n                        result[3].a = this.alpha;\r\n                        this.text3.setColorExtraVertices(i, result);\r\n                    }\r\n                })\r\n                .start();\r\n\r\n            await this.waitCmpt(this, time);\r\n        }\r\n    }\r\n    ```\r\n\r\n    再换一种方式，通过控制顶点数据，让字符逐个跃出\r\n\r\n    ![image](./docs/images/showcase4.gif)\u003c/br\u003e\r\n\r\n    ```typescript\r\n    public _fScale: number = 1;\r\n    public _xOffset: number = 0;\r\n    private async anim1(): Promise\u003cvoid\u003e {\r\n        await this.waitCmpt(this, 1);\r\n        this.text1.string = \"这 是 一 段 测 试 文 字\";\r\n        this.text1.updateRenderData(true);\r\n        for (let i = 0; i \u003c this.text1.string.length; i++) {\r\n            this.text1.setVisible(i, false);\r\n        }\r\n        for (let i = 0; i \u003c this.text1.string.length; i++) {\r\n            this.text1.setVisible(i, true);\r\n            if (!this.text1.isVisible(i)) {\r\n                continue;\r\n            }\r\n            let result: Vec3[] = this.text1.getPosVertices(i);\r\n            let center = new Vec3();\r\n            center.x = (result[0].x + result[1].x + result[2].x + result[3].x) / 4;\r\n            center.y = (result[0].y + result[1].y + result[2].y + result[3].y) / 4;\r\n            this._xOffset = -50;\r\n\r\n            let updateCall = () =\u003e {\r\n                let copy: Vec3[] = [];\r\n                copy.push(result[0].clone());\r\n                copy.push(result[1].clone());\r\n                copy.push(result[2].clone());\r\n                copy.push(result[3].clone());\r\n                for (let j = 0; j \u003c 4; j++) {\r\n                    let delta: Vec3 = new Vec3();\r\n                    Vec3.subtract(delta, copy[j], center);\r\n                    delta.multiplyScalar(this._fScale).add(new Vec3(this._xOffset, 0));\r\n                    Vec3.add(copy[j], center, delta);\r\n                }\r\n                this.text1.setPosVertices(i, copy as any);\r\n            }\r\n\r\n            tween\u003cMain\u003e(this)\r\n                .to(0.1, { _fScale: 2, _xOffset: -15 }, { onUpdate: updateCall })\r\n                .to(0.1, { _fScale: 1, _xOffset: 0 }, { onUpdate: updateCall })\r\n                .start();\r\n            await this.waitCmpt(this, 0.2);\r\n        }\r\n    }\r\n    ```\r\n\r\n## \u003ca id=\"richtext\"\u003e\u003c/a\u003e富文本\r\n![image](./docs/images/richtext.png)\u003c/br\u003e\r\n如需使用富文本请使用**TmpRichText**组件，除粗体标签外支持全部Cocos的RichText组件的标签，且拓展支持了所有TextMeshPro具备的效果。\r\n\r\n- 复杂文本情况下draw call会少于Cocos的RichText组件\r\n- 内部对图片节点与文本节点做了分层处理，进一步减少了draw call\r\n\r\n**支持标签**\r\n| 名称 | 描述 | 示例 | 注意事项 |\r\n| :-: | :-: | :-: | :-: |\r\n| size | 字体渲染大小，大小值必须是一个整数 | \\\u003csize=30\\\u003eenlarge me\\\u003c/size\\\u003e | Size值必须使用等号赋值 |\r\n| color | 字体顶点颜色，颜色值可以是内置颜色，比如 white、black 等，也可以使用 16 进制颜色值，比如 #ff0000 表示红色 | \\\u003ccolor=#ff0000\\\u003eRed Text\\\u003c/color\\\u003e |  |\r\n| cg | 启用字体颜色渐变，指定四个顶点的额外颜色，会与顶点色混合 | \\\u003ccg lb=#f90000 rb=#f90000 lt=#0019f7 ​rt=#0019f7\\\u003ecolor gradient\\\u003c/cg\\\u003e | 默认值参考TextMeshPro组件 |\r\n| face | 文本主体颜色、厚度、柔和度 | \\\u003cface color=#f00000 dilate=0.5 softness=0.01\\\u003eface\\\u003c/face\\\u003e | 默认值和取值范围请参考TextMeshPro组件face相关属性 |\r\n| i | 斜体 | \\\u003ci\\\u003eThis text will be rendered as italic\\\u003c/i\\\u003e |  |\r\n| u | 启用下划线，可指定下划线的偏移 | \\\u003cu=8\\\u003eThis text will have a underline\\\u003c/u\\\u003e | 等号后面的值即下划线偏移值，默认值参考TextMeshPro组件underline相关属性 |\r\n| s | 启用删除线，可指定删除线的偏移  | \\\u003cs=8\\\u003eThis text will have a strikethrough\\\u003c/s\\\u003e | 等号后面的值即删除线偏移值，默认值参考TextMeshPro组件strikethrough相关属性 |\r\n| outline | 字体的描边颜色和描边宽度 | \\\u003coutline color=red thickness=0.15\\\u003eA label with outline\\\u003c/outline\\\u003e | 默认值和取值范围请参考TextMeshPro组件outline相关属性 |\r\n| underlay | 字体的阴影颜色、偏移、厚度、柔和度 | \\\u003cunderlay color=#00ff00 x=0.001 y=-0.001 dilate=0.5 softness=0.3\\\u003eunderlay\\\u003c/underlay\\\u003e | 默认值和取值范围请参考TextMeshPro组件underlay相关属性 |\r\n| glow | 字体辉光效果颜色、偏移、厚度 | \\\u003cglow color=#0ff0ff inner=0.2 outer=0.4\\\u003e\\\u003ccolor=#000000\\\u003eglow\\\u003c/color\\\u003e\\\u003c/glow\\\u003e | 默认值和取值范围请参考TextMeshPro组件glow相关属性 |\r\n| on | 指定一个点击事件处理函数，当点击该 Tag 所在文本内容时，会调用该事件响应函数 | \\\u003con click=\"handler\"\\\u003e click me! \\\u003c/on\\\u003e | 除了 on 标签可以添加 click 属性，color 和 size 标签也可以添加，比如 \\\u003csize=10 click=\"handler2\"\\\u003eclick me\\\u003c/size\\\u003e |\r\n| param | 当点击事件触发时，可以在回调函数的第二个参数获取该数值 | \\\u003con click=\"handler\" param=\"test\"\\\u003e click me! \\\u003c/on\\\u003e | 依赖 click 事件 |\r\n| br | 插入一个空行 | \\\u003cbr/\\\u003e | 注意：\\\u003cbr\\\u003e\\\u003c/br\\\u003e 和 \\\u003cbr\\\u003e 都是不支持的。 |\r\n| img | 给富文本添加图文混排功能，img 的 src 属性必须是 ImageAtlas 图集里面的一个有效的 spriteframe 名称 | \\\u003cimg src='emoji1' click='handler' height=50 width=50 align=center /\\\u003e | 规则与Cocos的RichText组件一致 |\r\n\r\n## \u003ca id=\"note\"\u003e\u003c/a\u003e注意事项\r\n- 切勿将字体纹理打入图集中\r\n- 暂不提供控制下划线与删除线的顶点数据\r\n- 个人时间精力有限，难免会出现疏漏，使用前请自行充分测试","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleeyip%2Fcocos-text-mesh-pro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleeyip%2Fcocos-text-mesh-pro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleeyip%2Fcocos-text-mesh-pro/lists"}