Verbatim.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. import { Label } from 'cc';
  2. import { Logger } from './Logger';
  3. /*
  4. ----------------------------------------------- 基础用法
  5. const typewriter = new VerbatimEffect(this.label, {
  6. speed: 0.1, // 每个字符显示间隔
  7. delay: 1.0, // 开始前的延迟
  8. callbackDelay: 0.5 // 完成回调延迟
  9. });
  10. //添加文字
  11. typewriter.addText("第一段文字")
  12. .addText("第二段文字")
  13. .addText(["第三段文字", "第四段文字"]);
  14. //设置回调
  15. typewriter.onComplete(() => {
  16. Logger.log("所有文字显示完成");
  17. });
  18. //开始播放
  19. typewriter.start();
  20. ----------------------------------------------- 显示数组用法
  21. const verseArr = this.seleteData.verse.split(",");
  22. const typewriter = new VerbatimEffect(this.verseLabel1, {
  23. speed: 0.15,
  24. preserveSpaces: true // 保留空格
  25. });
  26. typewriter.addText(verseArr)
  27. .onTextComplete((text, index) => {
  28. Logger.log(`第${index + 1}句显示完成: ${text}`);
  29. })
  30. .onComplete(() => {
  31. Logger.log("所有诗句显示完成");
  32. })
  33. .start();
  34. ----------------------------------------------- 高阶用法
  35. const typewriter = new VerbatimEffect(this.label);
  36. typewriter.addText("Hello World!")
  37. .addText("This is Typewriter Effect.")
  38. .onChar((char) => {
  39. // 每个字符显示时的音效
  40. AudioManager.playTypingSound();
  41. })
  42. .onTextStart((text, index) => {
  43. Logger.log(`开始显示第${index + 1}段文字: ${text}`);
  44. })
  45. .onTextComplete((text, index) => {
  46. Logger.log(`第${index + 1}段文字显示完成: ${text}`);
  47. })
  48. .onComplete(() => {
  49. Logger.log("全部文字显示完成");
  50. })
  51. .start();
  52. */
  53. /**
  54. * 打字机效果配置选项
  55. */
  56. interface WriterOptions {
  57. /** 每个字符的显示间隔时间(秒)默认0.1 */
  58. speed?: number;
  59. /** 开始播放前的延迟时间(秒)默认0 */
  60. delay?: number;
  61. /** 播放完成后的回调延迟(秒)默认0 */
  62. callbackDelay?: number;
  63. /** 是否自动开始播放 默认true */
  64. autoStart?: boolean;
  65. /** 是否保留空格 默认false */
  66. preserveSpaces?: boolean;
  67. }
  68. /**
  69. * 打字机效果类 - 实现文字逐字显示效果
  70. */
  71. export class VerbatimEffect {
  72. private label: Label;
  73. private textQueue: string[] = [];
  74. private currentIndex: number = 0;
  75. private isPlaying: boolean = false;
  76. private options: WriterOptions;
  77. private onCompleteCallback: Function | null = null;
  78. private onCharCallback: ((char: string) => void) | null = null;
  79. private onTextStartCallback: ((text: string, index: number) => void) | null = null;
  80. private onTextCompleteCallback: ((text: string, index: number) => void) | null = null;
  81. /**
  82. * 构造函数
  83. * @param label 要显示文字的Label组件
  84. * @param options 配置选项
  85. */
  86. constructor(label: Label, options: WriterOptions = {}) {
  87. this.label = label;
  88. this.options = {
  89. speed: 0.1,
  90. delay: 0,
  91. callbackDelay: 0,
  92. autoStart: true,
  93. preserveSpaces: false,
  94. ...options
  95. };
  96. }
  97. /**
  98. * 添加要显示的文字
  99. * @param text 文字内容(可以是字符串或字符串数组)
  100. * @return 当前实例(支持链式调用)
  101. */
  102. public addText(text: string | string[]): VerbatimEffect {
  103. if (Array.isArray(text)) {
  104. this.textQueue.push(...text);
  105. } else {
  106. this.textQueue.push(text);
  107. }
  108. return this;
  109. }
  110. /**
  111. * 设置完成回调
  112. * @param callback 所有文字显示完成后的回调
  113. * @return 当前实例(支持链式调用)
  114. */
  115. public onComplete(callback: Function): VerbatimEffect {
  116. this.onCompleteCallback = callback;
  117. return this;
  118. }
  119. /**
  120. * 设置字符显示回调
  121. * @param callback 每个字符显示时的回调
  122. * @return 当前实例(支持链式调用)
  123. */
  124. public onChar(callback: (char: string) => void): VerbatimEffect {
  125. this.onCharCallback = callback;
  126. return this;
  127. }
  128. /**
  129. * 设置单个文本开始显示回调
  130. * @param callback 单个文本开始显示时的回调
  131. * @return 当前实例(支持链式调用)
  132. */
  133. public onTextStart(callback: (text: string, index: number) => void): VerbatimEffect {
  134. this.onTextStartCallback = callback;
  135. return this;
  136. }
  137. /**
  138. * 设置单个文本显示完成回调
  139. * @param callback 单个文本显示完成时的回调
  140. * @return 当前实例(支持链式调用)
  141. */
  142. public onTextComplete(callback: (text: string, index: number) => void): VerbatimEffect {
  143. this.onTextCompleteCallback = callback;
  144. return this;
  145. }
  146. /**
  147. * 开始播放文字
  148. */
  149. public start(): void {
  150. if (this.isPlaying || this.textQueue.length === 0) return;
  151. this.isPlaying = true;
  152. this.currentIndex = 0;
  153. this.label.node.active = true;
  154. this.label.string = '';
  155. if (this.options.delay && this.options.delay > 0) {
  156. this.label.scheduleOnce(() => this.playNextText(), this.options.delay);
  157. } else {
  158. this.playNextText();
  159. }
  160. }
  161. /**
  162. * 停止播放
  163. */
  164. public stop(): void {
  165. this.isPlaying = false;
  166. this.label.unscheduleAllCallbacks();
  167. }
  168. /**
  169. * 跳过当前文本,立即显示完整内容
  170. */
  171. public skipCurrent(): void {
  172. if (!this.isPlaying) return;
  173. this.label.unscheduleAllCallbacks();
  174. const currentText = this.textQueue[this.currentIndex];
  175. this.label.string = this.options.preserveSpaces ? currentText : currentText.replace(/\s/g, '');
  176. this.handleTextComplete(currentText, this.currentIndex);
  177. }
  178. /**
  179. * 跳过所有,立即显示所有完整内容
  180. */
  181. public skipAll(): void {
  182. if (!this.isPlaying) return;
  183. this.stop();
  184. let fullText = '';
  185. for (let i = this.currentIndex; i < this.textQueue.length; i++) {
  186. fullText += this.options.preserveSpaces
  187. ? this.textQueue[i]
  188. : this.textQueue[i].replace(/\s/g, '');
  189. }
  190. this.label.string = fullText;
  191. if (this.onCompleteCallback) {
  192. this.executeCallback(this.onCompleteCallback, this.options.callbackDelay);
  193. }
  194. }
  195. /**
  196. * 清空文字队列
  197. */
  198. public clear(): void {
  199. this.stop();
  200. this.textQueue = [];
  201. this.currentIndex = 0;
  202. }
  203. /**
  204. * 播放下一个文本(内部方法)
  205. */
  206. private playNextText(): void {
  207. if(!this.isPlaying || this.currentIndex >= this.textQueue.length) {
  208. this.isPlaying = false;
  209. if (this.onCompleteCallback) {
  210. this.executeCallback(this.onCompleteCallback, this.options.callbackDelay);
  211. }
  212. return;
  213. }
  214. const currentText = this.textQueue[this.currentIndex];
  215. const processedText = this.options.preserveSpaces ? currentText : currentText.replace(/\s/g, '');
  216. const chars = processedText.split('');
  217. let currentPos = 0;
  218. let displayText = '';
  219. //触发文本开始回调
  220. if(this.onTextStartCallback) {
  221. this.onTextStartCallback(currentText, this.currentIndex);
  222. }
  223. const updateFunc = () => {
  224. if (!this.isPlaying) return;
  225. const char = chars[currentPos];
  226. displayText += char;
  227. this.label.string = displayText;
  228. //触发字符回调
  229. if(this.onCharCallback) {
  230. this.onCharCallback(char);
  231. }
  232. if (++currentPos >= chars.length) {
  233. this.label.unschedule(updateFunc);
  234. this.handleTextComplete(currentText, this.currentIndex++);
  235. }
  236. };
  237. this.label.schedule(updateFunc, this.options.speed, chars.length - 1, 0);
  238. }
  239. /**
  240. * 处理文本完成(内部方法)
  241. */
  242. private handleTextComplete(text: string, index: number): void {
  243. //触发文本完成回调
  244. if (this.onTextCompleteCallback) {
  245. this.onTextCompleteCallback(text, index);
  246. }
  247. //播放下一个文本
  248. this.playNextText();
  249. }
  250. /**
  251. * 执行回调(带延迟)
  252. */
  253. private executeCallback(callback: Function, delay: number = 0): void {
  254. if (delay && delay > 0) {
  255. this.label.scheduleOnce(() => callback(), delay);
  256. } else {
  257. callback();
  258. }
  259. }
  260. }