说明&使用
项目仅用于测试交流, 如有侵权, 请联系我删除.
引擎版本: LayaAir2.10.0
Github地址: LayaEffect
clone LayaEffect项目 → 打开 Lightning.scene 场景 → F6 编译运行
注:
由于laya引擎代码可能随着版本更新进行优化, 此项目目前只在2.10.0版本运行通过.
思路
● 通过 CanvasRenderingContext2D 绘制图案
● 通过 Laya.Sprite.graphics.drawImage 将图案显示在 Sprite 中
效果
实现
管理类
import { Lightning } from "./Lightning";
import { LightningCfg } from "./LightningCfg";
import { LightningVector } from "./LightningVector";
/** 闪电管理器 */
export class LightningMgr {
private static _ins: LightningMgr;
public static get ins() {
if (!this._ins) {
this._ins = new LightningMgr();
}
return this._ins;
}
private isInit: boolean = false;
private sprite: Laya.Sprite;
private canvas: Laya.HTMLCanvas;
private lightning: Lightning;
private lightningCfg: LightningCfg;
private isDrawing: boolean = true;
private points: LightningVector[] = [];
private posVec: LightningVector;
public init(sprite: Laya.Sprite) {
if (!this.isInit) {
this.isInit = true;
this.sprite = sprite;
this.canvas = new Laya.HTMLCanvas(true);
this.canvas.size(Laya.stage.width, Laya.stage.height);
this.lightningCfg = new LightningCfg();
this.lightning = new Lightning(this.lightningCfg);
this.posVec = new LightningVector(0, 0, 0, 0);
this.points.push(new LightningVector(0, 0, this.canvas.width / 2, this.canvas.height / 2));
this.points.push(new LightningVector(0, 0, 20, 20));
this.points.push(new LightningVector(0, 0, this.canvas.width / 2, 20));
this.points.push(new LightningVector(0, 0, this.canvas.width - 20, 20));
this.points.push(new LightningVector(0, 0, 20, this.canvas.height - 20));
this.points.push(new LightningVector(0, 0, this.canvas.width / 2, this.canvas.height - 20));
this.points.push(new LightningVector(0, 0, this.canvas.width - 20, this.canvas.height - 20));
}
}
public startTimer() {
if (this.isInit) {
this.stopTimer();
Laya.timer.frameLoop(1, this, this.update);
}
}
public stopTimer() {
Laya.timer.clearAll(this);
}
public beginDraw(posX: number, posY: number) {
if (this.isInit) {
this.isDrawing = true;
this.posVec.X = 0;
this.posVec.Y = 0;
this.posVec.X1 = posX;
this.posVec.Y1 = posY;
}
}
public pauseDraw() {
this.isDrawing = false;
}
private update() {
if (this.isDrawing) {
this.canvas.clear(); // 清空画布内容
this.points.forEach(lightningVec => {
this.lightning.cast(this.canvas.context as any, lightningVec, this.posVec);
});
this.sprite.graphics.drawImage(this.canvas.getTexture());
}
}
/** 清空画布内容 */
public clearCanvas() {
if (this.canvas) {
this.canvas.clear();
}
}
public destroy() {
this.stopTimer();
this.canvas.clear(); // 清空画布, 释放GPU空间占用
this.canvas.destroy(); // 销毁画布, 释放CPU空间占用
this.canvas = null;
this.sprite = null;
this.lightningCfg = null;
this.lightning = null;
this.points = [];
this.isInit = false;
}
}
配置类
/** 闪电配置 */
export class LightningCfg {
public segments: number = 40;
// 值越大, 固定点显示越多
public threshold: number = 0.5;
public circle = {
// 线尺寸
lineWidth: 1,
// 线颜色
strokeStyle: "#ffffff",
// 线阴影尺寸(外边框)
shadowBlur: 0.1,
// 线阴影颜色
shadowColor: "#006dff"
}
public line = {
// 线尺寸
lineWidth: 1,
// 线颜色
strokeStyle: "#ffffff",
// 线阴影尺寸(外边框)
shadowBlur: 5,
// 线阴影颜色
shadowColor: "#127bca"
}
}
闪电类
import { LightningCfg } from "./LightningCfg";
import { LightningVector } from "./LightningVector";
/** 闪电类 */
export class Lightning {
public config: LightningCfg;
constructor(cfg: LightningCfg) {
this.config = cfg;
}
public cast(context: CanvasRenderingContext2D, from: LightningVector, to: LightningVector) {
if (!from || !to) {
return;
}
// 主节点
let mainVector = new LightningVector(from.X1, from.Y1, to.X1, to.Y1);
// skip cas if not close enough
if (this.config.threshold && mainVector.length() > context.canvas.width * this.config.threshold) {
return;
}
// 两点间总长度
let mainLength = mainVector.length();
// 两点间长度/画布宽度
let posLenRateInCanvas = (mainLength / context.canvas.width)
// 使用的节点数
let usingSegmentCount = Math.floor(this.config.segments * posLenRateInCanvas);
// 每个节点间宽度
let perNodeDis = mainLength / usingSegmentCount;
let fromPos = from;
for (let i = 1; i <= usingSegmentCount; i++) {
// position in the main vector
let tempPos = mainVector.multiply((1 / usingSegmentCount) * i);
// add position noise
if (i != usingSegmentCount) {
tempPos.X1 += perNodeDis * Math.random();
tempPos.Y1 += perNodeDis * Math.random();
}
// new vector for segment
let toPos = new LightningVector(fromPos.X1, fromPos.Y1, tempPos.X1, tempPos.Y1);
// main line
this.line(context, toPos);
fromPos = toPos;
}
this.circle(context, from, posLenRateInCanvas);
this.circle(context, to, posLenRateInCanvas);
context.restore();
}
private circle(context: CanvasRenderingContext2D, pos: LightningVector, posLenRateInCanvas: number) {
let cfg = this.config;
context.shadowBlur = cfg.circle.shadowBlur;
context.shadowColor = cfg.circle.shadowColor;
context.strokeStyle = cfg.circle.strokeStyle;
context.lineWidth = cfg.circle.lineWidth;
context.beginPath();
context.arc(
pos.X1 + Math.random() * 10 * posLenRateInCanvas, // x
pos.Y1 + Math.random() * 10 * posLenRateInCanvas, // y
5, // radius 半径
0, // startAngle 开始角度
2 * Math.PI, // endAngle 结束角度
false // counterclockwise 逆时针方向
);
context.stroke();
}
private line(context: CanvasRenderingContext2D, vec: LightningVector) {
let cfg = this.config;
context.shadowBlur = cfg.line.shadowBlur; // 清除阴影
context.shadowColor = cfg.line.shadowColor;
context.strokeStyle = cfg.line.strokeStyle;
context.lineWidth = cfg.line.lineWidth;
context.beginPath();
context.moveTo(vec.X, vec.Y);
context.lineTo(vec.X1, vec.Y1);
// context.globalAlpha = c.Alpha;
context.stroke();
}
private random(min: number, max: number) {
return Math.floor(Math.random() * (max - min)) + min;
}
}
向量类
/** 闪电向量 */
export class LightningVector {
public X: number;
public Y: number;
public X1: number;
public Y1: number;
constructor(x, y, x1, y1) {
this.X = x;
this.Y = y;
this.X1 = x1;
this.Y1 = y1;
}
delX() {
return this.X1 - this.X;
}
delY() {
return this.Y1 - this.Y;
}
normalized() {
let len = this.length();
return new LightningVector(
this.X,
this.Y,
this.X + (this.delX() / len),
this.Y + (this.delY() / len)
);
}
length() {
return Math.sqrt(Math.pow(this.delX(), 2) + Math.pow(this.delY(), 2));
}
multiply(num: number) {
return new LightningVector(
this.X,
this.Y,
this.X + this.delX() * num,
this.Y + this.delY() * num
);
}
clone() {
return new LightningVector(this.X, this.Y, this.X1, this.Y1);
}
}
场景控制类
import { ui } from "../../ui/layaMaxUI";
import { LightningMgr } from "./LightningMgr";
export default class LightningSceneCtrl extends ui.canvas.LightningUI {
onAwake(): void {
console.log('onAwake');
this.addUIEvent();
}
onEnable(): void {
LightningMgr.ins.init(this.sp_lightning);
LightningMgr.ins.startTimer();
}
addUIEvent() {
Laya.stage.on(Laya.Event.MOUSE_DOWN, this, this.onHandler);
Laya.stage.on(Laya.Event.MOUSE_UP, this, this.onHandler);
Laya.stage.on(Laya.Event.MOUSE_MOVE, this, this.onHandler);
}
removeUIEvent() {
Laya.stage.off(Laya.Event.MOUSE_DOWN, this, this.onHandler);
Laya.stage.off(Laya.Event.MOUSE_UP, this, this.onHandler);
Laya.stage.off(Laya.Event.MOUSE_MOVE, this, this.onHandler);
}
onHandler(e: Laya.Event) {
switch (e.type) {
case Laya.Event.MOUSE_DOWN:
case Laya.Event.MOUSE_MOVE:
LightningMgr.ins.beginDraw(e.stageX, e.stageY);
break;
case Laya.Event.MOUSE_UP:
LightningMgr.ins.pauseDraw();
break;
}
}
onDestroy(): void {
this.removeUIEvent();
}
}
注:
● 将 LightningSceneCtrl 作为场景runtime控制类
● 可修改配置参数, 调节显示效果