Laya.Script 使用说明

概述

Script 是所有脚本组件的父类,继承自 Component。与 Component 不同,Script 专门用于编写游戏逻辑,提供了丰富的生命周期方法和事件响应方法。

在 LayaAir 的 ECS(实体-组件-系统)架构中,Script 类承担了组件和系统的双重职责:

  • 组件部分:通过属性字段(使用 @property 装饰器)存储数据
  • 系统部分:通过生命周期方法和事件方法处理逻辑

Script 与 Component 的区别

特性 Component Script
用途 通用组件基类 专门用于编写游戏逻辑脚本
owner 类型 Node Sprite | Sprite3D
事件方法 支持鼠标、键盘、物理等事件方法
装饰器支持 基础支持 完整的 @regClass、@property 支持

装饰器

@regClass()

将脚本类注册到 IDE,使其可以在编辑器中使用:

const { regClass } = Laya;

@regClass()
export class MyScript extends Laya.Script {
    // 脚本内容
}

@property()

将属性暴露到 IDE 属性面板:

const { regClass, property } = Laya;

@regClass()
export class MyScript extends Laya.Script {
    @property(Number)
    public speed: number = 10;

    @property(String)
    public playerName: string = "Player";

    @property(Laya.Sprite3D)
    public target: Laya.Sprite3D;
}

API 参考

属性

属性 类型 说明
owner Sprite | Sprite3D 脚本所属的节点(只读)

生命周期方法

方法 调用时机
onAdded(): void 被添加到节点后调用(即使节点未激活也会调用)
onReset(): void 重置组件参数到默认值,实现此方法后组件可回收到对象池
onAwake(): void 组件被激活后执行,此时所有节点和组件均已创建完毕,只执行一次
onEnable(): void 组件被启用后执行,比如节点被添加到舞台后
onStart(): void 第一次执行 onUpdate 之前执行,只会执行一次
onUpdate(): void 每帧更新时执行
onLateUpdate(): void 每帧更新时执行,在 onUpdate 之后
onPreRender(): void 渲染之前执行
onPostRender(): void 渲染之后执行
onDisable(): void 组件被禁用时执行,比如节点从舞台移除后
onDestroy(): void 手动调用节点销毁时执行

鼠标事件方法

方法 说明
onMouseDown(evt: Event): void 鼠标按下时执行
onMouseUp(evt: Event): void 鼠标抬起时执行
onRightMouseDown(evt: Event): void 鼠标右键或中键按下时执行
onRightMouseUp(evt: Event): void 鼠标右键或中键抬起时执行
onMouseMove(evt: Event): void 鼠标在节点上移动时执行
onMouseOver(evt: Event): void 鼠标进入节点时执行
onMouseOut(evt: Event): void 鼠标离开节点时执行
onMouseDrag(evt: Event): void 鼠标按住物体拖拽时执行
onMouseDragEnd(evt: Event): void 鼠标拖拽结束后执行
onMouseClick(evt: Event): void 鼠标点击时执行
onMouseDoubleClick(evt: Event): void 鼠标双击时执行
onMouseRightClick(evt: Event): void 鼠标右键点击时执行

键盘事件方法

方法 说明
onKeyDown(evt: Event): void 键盘按下时执行
onKeyPress(evt: Event): void 键盘产生一个字符时执行
onKeyUp(evt: Event): void 键盘抬起时执行

3D 物理碰撞器事件

方法 说明
onCollisionEnter(other: Collider): void 开始碰撞时执行,仅执行一次
onCollisionStay(other: Collider): void 持续碰撞时执行,每帧都执行
onCollisionExit(other: Collider): void 结束碰撞时执行,仅执行一次

3D/2D 物理触发器事件

方法 说明
onTriggerEnter(other: Collider): void 开始触发时执行,仅执行一次
onTriggerStay(other: Collider): void 持续触发时执行,每帧都执行(2D 传感器不支持)
onTriggerExit(other: Collider): void 结束触发时执行,仅执行一次

脚本生命周期流程

添加组件
    │
    ▼
┌─────────────┐
│  onAdded()  │  添加到节点时(即使未激活)
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  onAwake()  │  只执行一次
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  onStart()  │  只执行一次
└──────┬──────┘
       │
       ▼
◄────────┼────────►
│        │        │
│   ┌────┴────┐   │
│   │onEnable()│◄──┼──► onDisable()
│   └────┬────┘   │
│        │        │
└───────►│◄───────┘
        │
        ▼
   ┌─────────┐
   │onUpdate()│◄──── 每帧循环
   └────┬────┘
        │
        ▼
┌───────────────┐
│onLateUpdate() │
└───────┬───────┘
        │
        ▼
┌───────────────┐
│ onPreRender() │
└───────────────┘
        │
        ▼
      [ 渲染 ]
        │
        ▼
┌───────────────┐
│onPostRender() │
└───────────────┘

销毁时: onDestroy()

基本用法

1. 创建基础脚本

const { regClass } = Laya;

@regClass()
export class MyScript extends Laya.Script {
    public speed: number = 10;

    onAwake(): void {
        console.log("脚本已激活");
    }

    onUpdate(): void {
        // 每帧执行
    }
}

2. 添加脚本到节点

// 2D 节点
const sprite = new Laya.Sprite();
sprite.addComponent(MyScript);
Laya.stage.addChild(sprite);

// 3D 节点
const sprite3D = new Laya.Sprite3D();
sprite3D.addComponent(MyScript);
scene3D.addChild(sprite3D);

3. 使用属性装饰器

const { regClass, property } = Laya;

@regClass()
export class PlayerScript extends Laya.Script {
    @property(Number)
    public moveSpeed: number = 5;

    @property(String)
    public playerName: string = "Hero";

    @property({ type: Laya.Sprite3D })
    public target: Laya.Sprite3D;

    @property({ type: Laya.Prefab })
    public bulletPrefab: Laya.Prefab;

    onAwake(): void {
        console.log("玩家:", this.playerName);
        console.log("速度:", this.moveSpeed);
    }
}

完整示例

示例1: 旋转控制脚本

const { regClass, property } = Laya;

@regClass()
export class RotationScript extends Laya.Script {
    @property(Number)
    public rotationSpeed: number = 90;

    private _autoRotateSpeed: Laya.Vector3 = new Laya.Vector3(0, 0.25, 0);
    private _isDragging: boolean = false;
    private _lastMouseX: number = 0;
    private _rotateDelta: Laya.Vector3 = new Laya.Vector3();

    onAwake(): void {
        Laya.stage.on(Laya.Event.MOUSE_DOWN, this, this.onMouseDown);
        Laya.stage.on(Laya.Event.MOUSE_UP, this, this.onMouseUp);
    }

    onMouseDown(): void {
        this._isDragging = true;
        this._lastMouseX = Laya.stage.mouseX;
    }

    onMouseUp(): void {
        this._isDragging = false;
    }

    onUpdate(): void {
        const sprite3D = this.owner as Laya.Sprite3D;

        if (this._isDragging) {
            const deltaX = Laya.stage.mouseX - this._lastMouseX;
            this._rotateDelta.setValue(0, deltaX * 0.2, 0);
            sprite3D.transform.rotate(this._rotateDelta, false, false);
            this._lastMouseX = Laya.stage.mouseX;
        } else {
            sprite3D.transform.rotate(this._autoRotateSpeed, false, false);
        }
    }

    onDestroy(): void {
        Laya.stage.off(Laya.Event.MOUSE_DOWN, this, this.onMouseDown);
        Laya.stage.off(Laya.Event.MOUSE_UP, this, this.onMouseUp);
    }
}

示例2: 鼠标拖拽脚本

const { regClass } = Laya;

@regClass()
export class DragScript extends Laya.Script {
    private _offsetX: number = 0;
    private _offsetY: number = 0;

    onEnable(): void {
        console.log("拖拽脚本已启用");
    }

    onMouseDown(evt: Laya.Event): void {
        const sprite = this.owner as Laya.Sprite;
        this._offsetX = sprite.x - evt.stageX;
        this._offsetY = sprite.y - evt.stageY;
        sprite.startDrag();
    }

    onMouseDrag(evt: Laya.Event): void {
        const sprite = this.owner as Laya.Sprite;
        sprite.pos(evt.stageX + this._offsetX, evt.stageY + this._offsetY);
    }

    onMouseUp(evt: Laya.Event): void {
        const sprite = this.owner as Laya.Sprite;
        sprite.stopDrag();
    }

    onDisable(): void {
        console.log("拖拽脚本已禁用");
    }
}

示例3: 键盘控制脚本

const { regClass, property } = Laya;

@regClass()
export class KeyboardControlScript extends Laya.Script {
    @property(Number)
    public moveSpeed: number = 200;

    private _moveX: number = 0;
    private _moveY: number = 0;

    onKeyDown(evt: Laya.Event): void {
        const code = evt["keyCode"];
        switch (code) {
            case 37: // 左
            case 65: // A
                this._moveX = -1;
                break;
            case 39: // 右
            case 68: // D
                this._moveX = 1;
                break;
            case 38: // 上
            case 87: // W
                this._moveY = -1;
                break;
            case 40: // 下
            case 83: // S
                this._moveY = 1;
                break;
        }
    }

    onKeyUp(evt: Laya.Event): void {
        const code = evt["keyCode"];
        switch (code) {
            case 37: case 39: case 65: case 68:
                this._moveX = 0;
                break;
            case 38: case 40: case 87: case 83:
                this._moveY = 0;
                break;
        }
    }

    onUpdate(): void {
        const sprite = this.owner as Laya.Sprite;
        const delta = Laya.timer.delta * 0.001;

        sprite.x += this._moveX * this.moveSpeed * delta;
        sprite.y += this._moveY * this.moveSpeed * delta;
    }
}

示例4: 生命周期演示脚本

const { regClass } = Laya;

@regClass()
export class LifeCycleDemoScript extends Laya.Script {
    private _log(tag: string, message: string): void {
        const time = new Date().toLocaleTimeString();
        console.log(`[${time}] [${tag}] ${message}`);
    }

    onAdded(): void {
        this._log("onAdded", "脚本已添加到节点");
    }

    onAwake(): void {
        this._log("onAwake", "脚本已激活,只执行一次");
    }

    onEnable(): void {
        this._log("onEnable", "脚本已启用");
    }

    onStart(): void {
        this._log("onStart", "第一次更新前执行,只执行一次");
    }

    onUpdate(): void {
        // 每帧执行,为避免刷屏不打印日志
    }

    onLateUpdate(): void {
        // 每帧在 onUpdate 之后执行
    }

    onPreRender(): void {
        // 渲染前执行
    }

    onPostRender(): void {
        // 渲染后执行
    }

    onDisable(): void {
        this._log("onDisable", "脚本已禁用");
    }

    onDestroy(): void {
        this._log("onDestroy", "脚本已销毁");
    }
}

示例5: 3D 物理碰撞器脚本

注意:onCollisionEnter/Stay/Exit 仅适用于 3D 物理碰撞器,且碰撞器的"是否为触发器"选项未勾选时才会触发。此类碰撞会产生实际的物理阻挡效果。

const { regClass } = Laya;

@regClass()
export class CollisionScript extends Laya.Script {
    /**
     * 3D 物理碰撞开始时调用
     * @param collision 碰撞信息
     */
    onCollisionEnter(collision: Laya.Collision): void {
        const otherOwner = collision.other.owner;
        console.log("碰撞开始,对方物体:", otherOwner.name);
    }

    /** 持续碰撞时每帧调用 */
    onCollisionStay(collision: Laya.Collision): void {
        // 持续碰撞中,可以在这里处理持续接触的逻辑
    }

    /** 碰撞结束时调用 */
    onCollisionExit(collision: Laya.Collision): void {
        const otherOwner = collision.other.owner;
        console.log("碰撞结束,对方物体:", otherOwner.name);
    }
}

使用方法:

  1. 在 3D 场景中选择一个 Sprite3D 节点
  2. 添加 PhysicsCollider 组件
  3. 确保"是否为触发器"未勾选
  4. 添加上述 CollisionScript 脚本
  5. 确保项目已启用物理引擎模块(项目设置 → 物理模块)

示例6: 触发器脚本(2D/3D通用)

注意:onTriggerEnter/Stay/Exit 用于触发器事件。勾选碰撞器的"是否为触发器"选项后,只会触发事件但不会产生实际的物理阻挡效果。

const { regClass } = Laya;

@regClass()
export class TriggerScript extends Laya.Script {
    /**
     * 进入触发区域时调用
     * @param other 对方的碰撞器组件
     * @param self 自身的碰撞器组件(可选)
     * @param contact 接触点信息(可选)
     */
    onTriggerEnter(other: any, self?: any, contact?: any): void {
        // other 类型:3D 是 PhysicsColliderComponent,2D 是 ColliderBase
        const otherOwner = other.owner;
        console.log("进入触发区域:", otherOwner.name);
    }

    /** 持续在触发区域内时每帧调用(注意:2D 传感器模式下不触发此方法) */
    onTriggerStay(other: any, self?: any, contact?: any): void {
        // 持续在触发区域内
    }

    /** 离开触发区域时调用 */
    onTriggerExit(other: any, self?: any, contact?: any): void {
        const otherOwner = other.owner;
        console.log("离开触发区域:", otherOwner.name);
    }
}

重要提示:

  • 2D 物理启用传感器(isSensor = true)后,onTriggerStay 不会被触发
  • 3D 触发器和 2D 传感器的区别:触发器不产生物理阻挡,只检测碰撞并触发事件

示例7: 组件属性引用

const { regClass, property } = Laya;

@regClass()
export class ReferenceScript extends Laya.Script {
    // 引用 2D 节点
    @property({ type: Laya.Sprite })
    public targetSprite: Laya.Sprite;

    // 引用 3D 节点
    @property({ type: Laya.Sprite3D })
    public targetSprite3D: Laya.Sprite3D;

    // 引用组件
    @property({ type: Laya.Animator })
    public animator: Laya.Animator;

    // 引用 Prefab
    @property({ type: Laya.Prefab })
    public enemyPrefab: Laya.Prefab;

    onAwake(): void {
        // 使用引用的节点
        if (this.targetSprite) {
            this.targetSprite.alpha = 0.5;
        }

        // 使用引用的组件
        if (this.animator) {
            this.animator.play("run");
        }

        // 实例化 Prefab
        if (this.enemyPrefab) {
            const enemy = this.enemyPrefab.create() as Laya.Sprite3D;
            (this.owner as Laya.Sprite3D).addChild(enemy);
        }
    }
}

示例8: 使用对象池的脚本

const { regClass } = Laya;

@regClass()
export class BulletScript extends Laya.Script {
    public speed: number = 500;
    public direction: Laya.Vector2 = new Laya.Vector2(0, -1);
    public damage: number = 10;

    private _lifeTime: number = 0;
    private _maxLifeTime: number = 3; // 3秒后销毁

    onEnable(): void {
        this._lifeTime = 0;
        console.log("子弹已启用");
    }

    onUpdate(): void {
        const sprite = this.owner as Laya.Sprite;
        const delta = Laya.timer.delta * 0.001;

        // 移动
        sprite.x += this.direction.x * this.speed * delta;
        sprite.y += this.direction.y * this.speed * delta;

        // 更新生存时间
        this._lifeTime += delta;
        if (this._lifeTime >= this._maxLifeTime) {
            this.recoverToPool();
        }

        // 检查是否超出屏幕
        if (sprite.y < -50 || sprite.y > Laya.stage.height + 50) {
            this.recoverToPool();
        }
    }

    onDisable(): void {
        console.log("子弹已禁用,回收到对象池");
    }

    // 重置方法(对象池复用时调用)
    onReset(): void {
        this._lifeTime = 0;
        this.damage = 10;
        const sprite = this.owner as Laya.Sprite;
        sprite.pos(0, 0);
    }

    private recoverToPool(): void {
        // 回收到对象池
        Laya.Pool.recover("Bullet", this.owner);
    }
}

注意事项

1. owner 类型

Scriptowner 属性类型为 Sprite | Sprite3D,使用时需要类型判断:

const sprite = this.owner as Laya.Sprite;  // 2D
const sprite3D = this.owner as Laya.Sprite3D;  // 3D

2. onUpdate 性能

避免在 onUpdate 中进行大循环或频繁调用 getComponent

// ❌ 不推荐
onUpdate(): void {
    const comp = this.owner.getComponent(SomeComponent);  // 每帧查询
}

// ✅ 推荐
private _comp: SomeComponent;

onAwake(): void {
    this._comp = this.owner.getComponent(SomeComponent);  // 只查询一次
}

onUpdate(): void {
    // 使用缓存的组件
}

3. 事件方法注册

脚本中的事件方法(如 onMouseDown)无需手动注册,引擎会自动调用:

// ✅ 自动调用,无需注册
onMouseDown(evt: Laya.Event): void {
    console.log("鼠标按下");
}

// ❌ 不要手动注册这类方法
// Laya.stage.on(Laya.Event.MOUSE_DOWN, this, this.onMouseDown);

4. 2D 物理传感器注意事项

当 2D 物理碰撞体启用传感器时,onTriggerStay 事件不会被触发。

5. onKeyPress 限制

onKeyPress 只在产生字符时触发(如字母、数字),功能键(如 F1-F12、方向键)不会触发此方法。

6. 装饰器必须成对使用

// ✅ 正确
const { regClass, property } = Laya;

@regClass()
export class MyScript extends Laya.Script {
    @property(Number)
    public speed: number = 10;
}

// ❌ 错误 - 缺少解构
@regClass()
export class MyScript extends Laya.Script {
    @property(Number)  // ReferenceError: property is not defined
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐