Laya Canvas2D闪电特效

说明&使用

项目仅用于测试交流, 如有侵权, 请联系我删除.
引擎版本: 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控制类
● 可修改配置参数, 调节显示效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值