Files

434 lines
16 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const { ccclass, property } = cc._decorator;
//材质球可设置的属性:
const diffuseTexture = "diffuseTexture";
const startColor = "startColor";
const endColor = "endColor";
/**
* 拖尾特效。
*
* 使用方法:
* 先 init 初始化(可以添加方法 onload ,直接调用 init这里的 init reset 是为了适配我的框架写的),再 setShape 设置横截面形状;
*
* 其他属性如柔软度、起始颜色、纹理等,可在属性面板中设置,也可以通过代码设置;
*
* 请尽量在设置横截面之前将曲面细分等级设置好,并避免在运行过程中修改细分等级,因为设置细分等级后,需要重新设置网格的顶点数据。
*
* 怎样让它动起来呢:
*
* (一)
* 由update方法自动更新时将挂载本脚本的节点与要跟随的目标节点放在同一父节点下拖尾节点坐标0,0,0角度0,0,0
* 设置跟随的目标节点,跟随目标节点的位置偏移;
*
* 注:默认的 update 方法已注释掉!要使用它请自行取消注释(在最底部)。
*
* (二)
* 自行使用代码更新时将挂载本脚本的节点放置在3D场景根节点下坐标0,0,0角度0,0,0
* 通过方法 moveTo 设置拖尾头部的世界坐标即可。
*
* 注:
* 需要设置拖尾横截面形状才能看到曲面;
*
*/
@ccclass
export default class TrailEffect extends cc.Component {
@property(cc.MeshRenderer)
protected meshRenderer: cc.MeshRenderer = null;
protected mesh: cc.Mesh = null;
protected mat: cc.Material = null;
protected initMat() {
this.mat = this.meshRenderer.getMaterial(0);
let c = [this.startColor.r / 255, this.startColor.g / 255, this.startColor.b / 255, this.startColor.a / 255];
this.mat.setProperty(startColor, c);
c = [this.endColor.r / 255, this.endColor.g / 255, this.endColor.b / 255, this.endColor.a / 255];
this.mat.setProperty(endColor, c);
if (!!this.diffuseTexture) {
this.mat.setProperty(diffuseTexture, this.diffuseTexture);
}
}
@property({
type: cc.Texture2D
})
protected diffuseTexture: cc.Texture2D = null;
/**设置拖尾纹理 */
public setTexture(t: cc.Texture2D) {
this.diffuseTexture = t;
this.mat.setProperty(diffuseTexture, this.diffuseTexture);
}
@property(cc.Color)
protected startColor: cc.Color = cc.Color.WHITE;
/**设置拖尾头部的颜色及透明度 */
public setStartColor(color: cc.Color) {
if (this.startColor.equals(color)) return;
this.startColor.set(color);
if (!!this.mat) {
let c = [this.startColor.r / 255, this.startColor.g / 255, this.startColor.b / 255, this.startColor.a / 255];
this.mat.setProperty(startColor, c);
}
}
@property(cc.Color)
protected endColor: cc.Color = cc.color(255, 255, 255, 0);
/**设置拖尾尾部的颜色及透明度 */
public setEndColor(color: cc.Color) {
if (this.endColor.equals(color)) return;
this.endColor.set(color);
if (!!this.mat) {
let c = [this.endColor.r / 255, this.endColor.g / 255, this.endColor.b / 255, this.endColor.a / 255];
this.mat.setProperty(endColor, c);
}
}
/**Z轴方向的曲面细分等级 */
@property(cc.Integer)
protected level: number = 5;
/**设置Z轴方向的曲面细分等级 */
public setLevel(level: number) {
if (this.level === level || level <= 0) return;
this.level = level;
if (this.polygon.length > 0) {
cc.warn("提示:修改细分等级后,将重新设置拖尾的顶点数据,请尽量在设置横截面之前设置好细分等级,并避免频繁修改。");
this.createVerts();
this.createMesh();
}
}
@property(cc.Integer)
/**拖尾的柔软度,值越大尾巴越柔软 */
protected soft: number = 5;
/**设置拖尾的柔软度值越大尾巴越柔软拉伸的越长默认值为5 */
public setSoft(soft: number) {
if (this.soft === soft || soft <= 0) return;
this.soft = soft;
}
/**是否跟随目标节点的角度 */
@property(cc.Boolean)
protected followAngle: boolean = false;
/**拖尾头部的坐标 */
protected startPosition: cc.Vec3 = cc.v3();
protected initPosition() {
this.startPosition = cc.v3();
}
/**网格顶点数据 */
protected verts: cc.Vec3[] = [];
/**拖尾横截面的顶点坐标x、y按逆时针排列 */
@property({
type: [cc.Vec2],
tooltip: "拖尾的横截面多边形的顶点坐标,该横截面为从 cc.v3(0, 0, 1) 位置看向XY平面时的形状顶点坐标按逆时针排列建议通过setShape在代码中设置。"
})
protected polygon: cc.Vec2[] = [];
/**横截面是否为闭合的多边形 */
@property({
tooltip: "横截面是否为闭合的多边形"
})
protected polygonClosed: boolean = false;
protected initPolygon() {
this.polygon = [];
}
protected resetPolygon() {
this.polygon = [];
this.polygonClosed = false;
}
/**
* 设置拖尾的横截面多边形的顶点坐标,该横截面为从 cc.v3(0, 0, 1) 位置看向XY平面时的形状顶点坐标按逆时针排列
* @param polygon 顶点坐标数组
* @param close 是否闭合的多边形
*/
public setShape(polygon: cc.Vec2[], close: boolean = false) {
this.polygon = [].concat(polygon);
this.polygonClosed = !!close;
this.createVerts();
this.createMesh();
}
/**根据截面形状和拖尾位置创建网格顶点 */
protected createVerts() {
//顶点的Z轴偏移为0
this.verts = [];
let nPolygon = this.polygon.length;
if (nPolygon > 2 && this.polygonClosed) {
for (let i = 0; i <= this.level; ++i) {
for (let j = 0; j < nPolygon; ++j) {
this.verts.push(cc.v3(this.polygon[j].x, this.polygon[j].y, 0).addSelf(this.startPosition));
}
this.verts.push(cc.v3(this.polygon[0].x, this.polygon[0].y, 0).addSelf(this.startPosition));
}
} else {
for (let i = 0; i <= this.level; ++i) {
for (let j = 0; j < nPolygon; ++j) {
this.verts.push(cc.v3(this.polygon[j].x, this.polygon[j].y, 0).addSelf(this.startPosition));
}
}
}
}
/**创建网格数据 */
protected createMesh() {
// let gfx = cc.renderer.renderEngine.gfx;
let gfx = cc.gfx;
// 定义顶点数据格式,只需要指明所需的属性,避免造成存储空间的浪费
var vfmtPosColor = new gfx.VertexFormat([
// 用户需要创建一个三维的盒子,所以需要三个值来保存位置信息
{ name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 3 },
{ name: gfx.ATTR_UV0, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
{ name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true },
]);
this.mesh = new cc.Mesh();
this.meshRenderer.mesh = this.mesh;
let mesh = this.mesh;
// 初始化网格信息
mesh.init(vfmtPosColor, this.verts.length, true);
// 修改 position 顶点数据
mesh.setVertices(gfx.ATTR_POSITION, this.verts);
// 修改 color 顶点数据
let colors = [];
for (let i = 0, c = this.verts.length; i < c; ++i) {
colors.push(cc.Color.WHITE);
}
mesh.setVertices(gfx.ATTR_COLOR, colors);
// 修改 uv 顶点数据
let uv: cc.Vec2[] = [];
let nPolygon = this.polygon.length;
if (nPolygon > 2 && this.polygonClosed) {
for (let i = 0; i <= this.level; ++i) {
let uv_x = 1 - i / this.level;
for (let j = 0; j <= nPolygon; ++j) {
uv.push(cc.v2(uv_x, j / nPolygon));
}
}
} else {
for (let i = 0; i <= this.level; ++i) {
let uv_x = 1 - i / this.level;
for (let j = 0; j < nPolygon; ++j) {
uv.push(cc.v2(uv_x, j / nPolygon));
}
}
}
mesh.setVertices(gfx.ATTR_UV0, uv);
// 修改索引数据
let frag: number[] = [];
if (nPolygon > 2 && this.polygonClosed) {
let p2 = nPolygon + 1;
let p3 = nPolygon + 2;
for (let i = 0; i < this.level; ++i) {
for (let j = 0; j < nPolygon; ++j) {
let index = i * nPolygon + i + j;
frag.push(index, index + 1, index + p3);
frag.push(index, index + p3, index + p2);
}
}
} else {
let p2 = nPolygon;
let p3 = nPolygon + 1;
let maxJ = nPolygon - 2;
for (let i = 0; i < this.level; ++i) {
for (let j = 0; j <= maxJ; ++j) {
let index = i * nPolygon + j;
frag.push(index, index + 1, index + p3);
frag.push(index, index + p3, index + p2);
}
}
}
mesh.setIndices(frag);
}
protected resetMesh() {
let nPolygon = this.polygon.length;
if (nPolygon > 2 && this.polygonClosed) {
for (let i = 0; i <= this.level; ++i) {
for (let j = 0; j < nPolygon; ++j) {
this.verts[i * nPolygon + i + j].set(cc.v3(this.polygon[j].x, this.polygon[j].y, 0).addSelf(this.startPosition));
}
this.verts[i * nPolygon + i + nPolygon].set(cc.v3(this.polygon[0].x, this.polygon[0].y, 0).addSelf(this.startPosition));
}
} else {
for (let i = 0; i <= this.level; ++i) {
for (let j = 0; j < nPolygon; ++j) {
this.verts[i * nPolygon + j].set(cc.v3(this.polygon[j].x, this.polygon[j].y, 0).addSelf(this.startPosition));
}
}
}
let gfx = cc.gfx;
this.mesh.setVertices(gfx.ATTR_POSITION, this.verts);
}
/**更新网格形状 */
protected updateMesh() {
let rate = this.soft == 0 ? 0.2 : (1 / this.soft);
let nPolygon = this.polygon.length;
if (nPolygon > 2 && this.polygonClosed) {
let offset = nPolygon + 1;
for (let i = 1; i <= this.level; ++i) {
let index = i * offset;
for (let j = 0; j <= nPolygon; ++j) {
let previousVert = this.verts[index - offset + j];
let nextVert = this.verts[index + j];
this.interpolationPos(nextVert, previousVert, rate);
}
}
} else {
let offset = nPolygon;
for (let i = 1; i <= this.level; ++i) {
let index = i * offset;
for (let j = 0; j < nPolygon; ++j) {
let previousVert = this.verts[index - offset + j];
let nextVert = this.verts[index + j];
this.interpolationPos(nextVert, previousVert, rate);
}
}
}
let gfx = cc.gfx;
this.mesh.setVertices(gfx.ATTR_POSITION, this.verts);
}
protected getInterpplation(a: number, b: number, r: number) {
return (b - a) * r;
}
protected interpolationPos(p1: cc.Vec3, p2: cc.Vec3, rate: number) {
p1.x += this.getInterpplation(p1.x, p2.x, rate);
p1.y += this.getInterpplation(p1.y, p2.y, rate);
p1.z += this.getInterpplation(p1.z, p2.z, rate);
}
public init() {
this.initPosition();
this.initPolygon();
this.initMat();
}
public reset() {
this.resetMesh();
this._playing = false;
}
public clear() {
}
protected _playing: boolean = false;
/**是否正在运行 */
public get playing() { return this._playing; }
/**
* 开始运行
* @param startPosition 拖尾头部坐标,将从该位置开始出现拖尾
* @param angle 拖尾初始角度(该功能暂未完成)
*/
public play(startPosition: cc.Vec3, angle?: cc.Vec3) {
if (this.polygon.length == 0) {
cc.warn("拖尾特效未设置横截面形状!");
return;
}
if (!this.target) {
cc.warn("拖尾未设置跟随的目标节点!");
return;
}
this._playing = true;
this.startPosition.set(startPosition);
this.resetMesh();
}
/**停止运行,拖尾将逐渐缩短消失 */
public stop() {
this._playing = false;
}
/**
* 设置拖尾头部的位置,可通过定时调用该方法驱动拖尾运行
* @param pos 头尾头部坐标
* @param angle 拖尾头部的方向默认为垂直朝向XY平面
*/
public moveTo(pos: cc.Vec3, angle?: cc.Vec3) {
this.startPosition = pos;
let maxIndex = this.polygon.length - 1;
if (undefined === angle) {
for (let i = maxIndex; i >= 0; --i) {
this.verts[i].x = this.startPosition.x + this.polygon[i].x;
this.verts[i].y = this.startPosition.y + this.polygon[i].y;
this.verts[i].z = this.startPosition.z;
}
if (maxIndex >= 2 && this.polygonClosed) {
maxIndex++;
this.verts[maxIndex].x = this.startPosition.x + this.polygon[0].x;
this.verts[maxIndex].y = this.startPosition.y + this.polygon[0].y;
this.verts[maxIndex].z = this.startPosition.z;
}
} else {
for (let i = maxIndex; i >= 0; --i) {
let offset = cc.v3(this.polygon[i].x, this.polygon[i].y, 0);
offset = this.rotatePos(offset, angle);
this.verts[i].x = this.startPosition.x + offset.x;
this.verts[i].y = this.startPosition.y + offset.y;
this.verts[i].z = this.startPosition.z + offset.z;
}
if (maxIndex >= 2 && this.polygonClosed) {
maxIndex++;
let offset = cc.v3(this.polygon[0].x, this.polygon[0].y, 0);
offset = this.rotatePos(offset, angle);
this.verts[maxIndex].x = this.startPosition.x + offset.x;
this.verts[maxIndex].y = this.startPosition.y + offset.y;
this.verts[maxIndex].z = this.startPosition.z + offset.z;
}
}
this.updateMesh();
}
protected rotatePos(p: cc.Vec3, angle: cc.Vec3): cc.Vec3 {
//旋转顺序Y-X-Z
let p1 = cc.v2(p.x, p.z);
this.rotateV2(p1, angle.y);
let p2 = cc.v2(p.y, p1.y);
this.rotateV2(p2, angle.x);
let p3 = cc.v2(p1.x, p2.x);
this.rotateV2(p3, angle.z);
p.x = p3.x;
p.y = p3.y;
p.z = p2.y;
return p;
}
protected rotateV2(p: cc.Vec2, angle: number) {
let radian = angle * 0.017453;
let sin = Math.sin(radian);
let cos = Math.cos(radian);
let x = p.x;
let y = p.y;
p.x = x * cos - y * sin;
p.y = x * sin + y * cos;
}
/**跟随的目标节点 */
protected target: cc.Node;
protected offset: cc.Vec3;
/**
* 设置拖尾跟随的目标节点
* @param target 目标节点需要与本节点在同一父节点下或者其所有父节点的坐标、角度为0,0,0缩放为1,1,1
* @param offset 拖尾的中心点相对目标节点的坐标的偏移量
*/
public setTarget(target: cc.Node, offset: cc.Vec3) {
this.target = target;
this.offset = offset.clone();
}
//自定义方法,针对实际需求优化:
public customUpdate(dt: number) {
if (!this._playing) {
this.moveTo(this.startPosition);
} else {
this.moveTo(cc.v3(0, this.target.y + 0.5, this.target.z));
}
}
//通用方法:
// public update(dt: number) {
// if (!this.playing || !this.target) return;
// let pos = cc.v3();
// this.target.getPosition(pos);
// pos.addSelf(this.offset);
// if (!this.followAngle) {
// this.moveTo(pos);
// } else {
// this.moveTo(pos, this.target.eulerAngles);
// }
// }
}