MotionTrail.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. class TrailData {
  2. x: number = 0;
  3. y: number = 0;
  4. dis: number = 0;
  5. cos: number = 0;
  6. sin: number = 0;
  7. }
  8. import { CCInteger, CurveRange, Director, DynamicAtlasManager, GradientRange, IAssembler, RenderData, SpriteFrame, UIRenderer, UITransform, Vec2, Vec3, __private, _decorator, director, v2 } from 'cc';
  9. const { ccclass, property, executeInEditMode, menu } = _decorator;
  10. @ccclass
  11. @menu('Comp/MotionTrail')
  12. @executeInEditMode()
  13. export default class MotionTrail extends UIRenderer {
  14. @property({ visible: false })
  15. public get color() {
  16. return this._color
  17. }
  18. @property
  19. private _spriteFrame: SpriteFrame = null;
  20. @property({ type: SpriteFrame, displayName: 'SpriteFrame' })
  21. get spriteFrame() { return this._spriteFrame; }
  22. set spriteFrame(value: SpriteFrame) {
  23. this._spriteFrame = value;
  24. }
  25. @property
  26. private _isWorldXY: boolean = true;
  27. @property({ displayName: '世界坐标', tooltip: '顶点坐标是世界坐标还是本地坐标' })
  28. get isWorldXY() { return this._isWorldXY; }
  29. set isWorldXY(value: boolean) {
  30. this._isWorldXY = value;
  31. }
  32. @property({ displayName: '偏移' })
  33. offset: Vec2 = v2(0, 0);
  34. @property
  35. private _length: number = 20;
  36. @property({ type: CCInteger, displayName: '拖尾长度' })
  37. get length() { return this._length; }
  38. private set length(value: number) {
  39. this._length = Math.max(value, 0);
  40. this.updateLength();
  41. this.updateWidth();
  42. this.resetPos();
  43. this.freshBuffer()
  44. }
  45. trailData: TrailData[] = [];
  46. @property({ type: CurveRange, displayName: '宽度变化', range: [0, 1] })
  47. _sizeCurveRange: CurveRange = new CurveRange();
  48. @property({ type: CurveRange, displayName: '宽度变化', range: [0, 1] })
  49. get sizeCurveRange() {
  50. return this._sizeCurveRange;
  51. }
  52. set sizeCurveRange(value) {
  53. this._sizeCurveRange = value;
  54. this.updateWidth()
  55. }
  56. @property({ type: GradientRange, displayName: '颜色变化' })
  57. _colorCurveRange: GradientRange = new GradientRange();
  58. @property({ type: GradientRange, displayName: '颜色变化' })
  59. get colorCurveRange() {
  60. return this._colorCurveRange;
  61. }
  62. set colorCurveRange(value) {
  63. this._colorCurveRange = value;
  64. this._updateColor()
  65. }
  66. protected _render(render: __private._cocos_2d_renderer_i_batcher__IBatcher): void {
  67. render.commitComp(this, this.renderData, this._spriteFrame, this._assembler, null);
  68. }
  69. protected _flushAssembler(): void {
  70. this._assembler = simple
  71. if (!this._renderData) {
  72. this._renderData = this.requestRenderData();;
  73. this._renderData.material = this.getRenderMaterial(0)
  74. this.freshBuffer()
  75. this.markForUpdateRenderData()
  76. if (this._spriteFrame) {
  77. this._assembler.updateUVs(this)
  78. }
  79. this._updateColor()
  80. }
  81. }
  82. //更新buffer
  83. protected freshBuffer() {
  84. let len = this.length;
  85. let count = 4 + (len - 2) * 2
  86. this._renderData.dataLength = count;
  87. this._renderData.resize(count, (count - 2) * 3);
  88. }
  89. private updateLength() {
  90. let trailLen = this.length;
  91. let sub = trailLen - this.trailData.length;
  92. if (sub > 0) {
  93. for (let i = 0; i < sub; ++i) {
  94. this.trailData.push(new TrailData());
  95. }
  96. } else if (sub < 0) {
  97. this.trailData.splice(trailLen);
  98. }
  99. }
  100. private updateWidth() {
  101. let data = this.trailData;
  102. let trailLen = this.length;
  103. let width = this.getComponent(UITransform).width / 2
  104. for (let i = 0; i < trailLen; ++i) {
  105. data[i].dis = this.sizeCurveRange.evaluate(i / (trailLen - 1), 0) * width;
  106. }
  107. }
  108. private resetPos() {
  109. let data = this.trailData;
  110. let tx = this.offset.x;
  111. let ty = this.offset.y;
  112. if (this.isWorldXY) {
  113. tx += this.node.worldPosition.x;
  114. ty += this.node.worldPosition.y;
  115. } else {
  116. tx += this.node.position.x;
  117. ty += this.node.position.y;
  118. }
  119. for (let i = 0, len = this.length; i < len; i++) {
  120. data[i].x = tx;
  121. data[i].y = ty;
  122. }
  123. }
  124. onLoad(): void {
  125. this.length = this._length;
  126. }
  127. onEnable(): void {
  128. super.onEnable()
  129. director.once(Director.EVENT_BEFORE_DRAW, this.resetPos, this)
  130. }
  131. protected _useVertexOpacity: boolean = true
  132. }
  133. const simple: IAssembler = {
  134. updateRenderData(trail: MotionTrail) {
  135. const frame = trail.spriteFrame;
  136. DynamicAtlasManager.instance.packToDynamicAtlas(trail, frame);
  137. this.updateUVs(trail);// dirty need
  138. //this.updateColor(sprite);// dirty need
  139. const renderData = trail.renderData;
  140. if (renderData && frame) {
  141. if (renderData.vertDirty) {
  142. this.updateVertexData(trail);
  143. }
  144. renderData.updateRenderData(trail, frame);
  145. }
  146. },
  147. updateWorldVerts(trail: MotionTrail) {
  148. const renderData = trail.renderData;
  149. const vData = renderData.chunk.vb;
  150. let len = trail.length
  151. if (len < 1) return
  152. let tx = 0, ty = 0;
  153. let data = trail.trailData;
  154. if (!trail.isWorldXY) {
  155. tx = trail.node.position.x;
  156. ty = trail.node.position.y;
  157. }
  158. let ax = 0, ay = 0
  159. let curIdx = 0;
  160. let tempData: TrailData = null
  161. let tempNextData: TrailData = null
  162. //
  163. for (let i = 0; i < len; ++i) {
  164. tempData = data[i];
  165. let sameIdx = i
  166. for (; sameIdx < len; ++sameIdx) {
  167. tempNextData = data[sameIdx];
  168. if (tempNextData.x != tempData.x || tempNextData.y != tempData.y) {
  169. break
  170. }
  171. }
  172. sameIdx -= 1
  173. if (sameIdx == i) {
  174. ax = tempData.x - tx;
  175. ay = tempData.y - ty;
  176. vData[curIdx] = ax + tempData.dis * tempData.sin;
  177. vData[curIdx + 1] = ay - tempData.dis * tempData.cos;
  178. vData[curIdx + 2] = 0;
  179. curIdx += renderData.floatStride;
  180. vData[curIdx] = ax - tempData.dis * tempData.sin;
  181. vData[curIdx + 1] = ay + tempData.dis * tempData.cos;
  182. vData[curIdx + 2] = 0;
  183. curIdx += renderData.floatStride;
  184. } else {
  185. tempData = data[sameIdx];
  186. ax = tempData.x - tx;
  187. ay = tempData.y - ty;
  188. vData[curIdx] = ax + tempData.dis * tempData.sin;
  189. vData[curIdx + 1] = ay - tempData.dis * tempData.cos;
  190. vData[curIdx + 2] = 0;
  191. curIdx += renderData.floatStride;
  192. vData[curIdx] = ax - tempData.dis * tempData.sin;
  193. vData[curIdx + 1] = ay + tempData.dis * tempData.cos;
  194. vData[curIdx + 2] = 0;
  195. curIdx += renderData.floatStride;
  196. for (; i < sameIdx; ++i) {
  197. vData[curIdx] = vData[curIdx - renderData.floatStride * 2];
  198. vData[curIdx + 1] = vData[curIdx - renderData.floatStride * 2 + 1];
  199. vData[curIdx + 2] = 0;
  200. curIdx += renderData.floatStride;
  201. vData[curIdx] = vData[curIdx - renderData.floatStride * 2];
  202. vData[curIdx + 1] = vData[curIdx - renderData.floatStride * 2 + 1];
  203. vData[curIdx + 2] = 0;
  204. curIdx += renderData.floatStride;
  205. }
  206. }
  207. }
  208. },
  209. fillBuffers(trail: MotionTrail) {
  210. if (trail.spriteFrame == null) return
  211. const renderData = trail.renderData!;
  212. const chunk = renderData.chunk;
  213. this.update(trail)
  214. this.updateWorldVerts(trail, chunk);
  215. // quick version
  216. const vidOrigin = chunk.vertexOffset;
  217. const meshBuffer = chunk.meshBuffer;
  218. const ib = chunk.meshBuffer.iData;
  219. let indexOffset = meshBuffer.indexOffset;
  220. let vid = vidOrigin;
  221. let len = trail.length - 1
  222. for (let i = 0; i < len; i++) {
  223. ib[indexOffset++] = vid + 0;
  224. ib[indexOffset++] = vid + 1;
  225. ib[indexOffset++] = vid + 2;
  226. ib[indexOffset++] = vid + 1;
  227. ib[indexOffset++] = vid + 3;
  228. ib[indexOffset++] = vid + 2;
  229. vid += 2;
  230. }
  231. meshBuffer.indexOffset += len * 6;
  232. },
  233. updateVertexData(trail: MotionTrail) {
  234. const renderData: RenderData | null = trail.renderData;
  235. renderData.vertDirty = true
  236. },
  237. updateUVs(trail: MotionTrail) {
  238. if (trail.spriteFrame == null) return
  239. const renderData = trail.renderData;
  240. const vData = renderData.chunk.vb;
  241. const uv = trail.spriteFrame.uv;
  242. const length = trail.length - 1;
  243. const uvStep = length == 0 ? 0 : 1 / length;
  244. let curIdx = 3;
  245. let subV = uv[7] - uv[1]
  246. for (let i = 0; i < length; i++) {
  247. vData[curIdx] = uv[0];
  248. vData[curIdx + 1] = uv[1] + subV * i * uvStep;
  249. curIdx += renderData.floatStride
  250. vData[curIdx] = uv[0];
  251. vData[curIdx + 1] = uv[3] + subV * i * uvStep;
  252. curIdx += renderData.floatStride
  253. }
  254. vData[curIdx] = uv[0];
  255. vData[curIdx + 1] = uv[7];
  256. curIdx += renderData.floatStride
  257. vData[curIdx] = uv[0]
  258. vData[curIdx + 1] = uv[5];
  259. curIdx += renderData.floatStride
  260. },
  261. updateColor(trail: MotionTrail) {
  262. const renderData = trail.renderData!;
  263. const vData = renderData.chunk.vb;
  264. const total = 4 + (trail.length - 2) * 2;
  265. const stepTotal = trail.length
  266. let stepIdx = -1
  267. let colorOffset = 5;
  268. let curC, random
  269. for (let i = 0; i < total; i++) {
  270. if (i % 2 == 0) {
  271. random = (trail.colorCurveRange.mode === GradientRange.Mode.TwoGradients || trail.colorCurveRange.mode === GradientRange.Mode.TwoColors) ? Math.random() : 0;
  272. curC = trail.colorCurveRange.evaluate(stepIdx / stepTotal, random)
  273. stepIdx++
  274. }
  275. vData[colorOffset] = curC.r / 255;
  276. vData[colorOffset + 1] = curC.g / 255;
  277. vData[colorOffset + 2] = curC.b / 255;
  278. vData[colorOffset + 3] = curC.a / 255;
  279. colorOffset += renderData.floatStride;
  280. }
  281. },
  282. update(trail: MotionTrail) {
  283. if (trail.length === 0) return;
  284. let data = trail.trailData;
  285. for (let i = trail.length - 1; i > 0; --i) {
  286. let cur = data[i], prev = data[i - 1];
  287. cur.x = prev.x;
  288. cur.y = prev.y;
  289. cur.sin = prev.sin;
  290. cur.cos = prev.cos;
  291. }
  292. let pos: Vec3 = null
  293. if (trail.isWorldXY) {
  294. pos = trail.node.worldPosition;
  295. } else {
  296. pos = trail.node.position;
  297. }
  298. let fristData = data[0];
  299. fristData.x = trail.offset.x + pos.x;
  300. fristData.y = trail.offset.y + pos.y;
  301. let secondData = data[1];
  302. let radian = Math.atan2(secondData.y - fristData.y, secondData.x - fristData.x);
  303. fristData.sin = Math.sin(radian)
  304. fristData.cos = Math.cos(radian)
  305. },
  306. updateOpacity(trail: MotionTrail, alpha: number) {
  307. const renderData = trail.renderData!;
  308. const vData = renderData.chunk.vb;
  309. const total = 4 + (trail.length - 2) * 2;
  310. const stepTotal = trail.length
  311. let stepIdx = -1
  312. let colorOffset = 5;
  313. let curC, random
  314. for (let i = 0; i < total; i++) {
  315. if (i % 2 == 0) {
  316. random = (trail.colorCurveRange.mode === GradientRange.Mode.TwoGradients || trail.colorCurveRange.mode === GradientRange.Mode.TwoColors) ? Math.random() : 0;
  317. curC = trail.colorCurveRange.evaluate(stepIdx / stepTotal, random)
  318. stepIdx++
  319. }
  320. vData[colorOffset + 3] = curC.a / 255 * alpha;
  321. colorOffset += renderData.floatStride;
  322. }
  323. }
  324. }