Options
All
  • Public
  • Public/Protected
  • All
Menu

@byted/riko

编辑器扩展脚本使用指南

什么是 Riko 脚本

Riko是编辑器提供的code模式编辑能力,通过此脚本系统,能够实现游戏引擎对齐的游戏逻辑,脚本系统几乎覆盖了编辑器大部分的能力,方便会编程的同学开发高级效果及玩法。

同时脚本系统和编辑器可视化面板进行结合,通过脚本定义的属性,能够在编辑器内以可视化的方式展现,从而降低用户使用门槛。

结合互动组件,使用脚本能够扩展出复杂的逻辑交互,但只开放简单的属性给用户使用,从而实现复杂逻辑的封装,降低用户使用难度。

如何使用脚本开发

脚本需要继承至 Riko.Script,脚本有完整的生命周期,不同的生命周期有对应的函数及用途, 更多详细信息可参考:Riko.Script

Typescript 脚本示例

export default class Script extends Riko.Script {
onEnable(): void {
console.log('脚本被启用时触发,此时可以使用 this.target,但脚本还未被执行,可能会被触发多次');
}
onAwake(): void {
console.log('在时间轴运行到脚本位置时,脚本会被激活,此时脚本才开始被执行,激活只会被触发一次');
}
onEnter(): void {
console.log('脚本每次开始播放时触发,可能会被触发多次');
}
onLeave(): void {
console.log('脚本每次播放完毕后触发,可能会被触发多次');
}
onUpdate(): void {
console.log('脚本被激活后,会被每帧调用,即使场景被暂停,更新也会继续,注意避免不必要的计算');
}
onClick(): void {
console.log('鼠标或者手指在对象上点击时触发');
}
onMouseDown(): void {
console.log('鼠标或者手指在对象上按下时触发');
}
onMouseUp(): void {
console.log('鼠标或者手指在对象上抬起时触发');
}
onMouseMove(): void {
console.log('鼠标或者手指在对象上移动时触发');
}
onStageMouseDown(): void {
console.log('鼠标或者手指在舞台按下时触发');
}
onStageMouseUp(): void {
console.log('鼠标或者手指在舞台抬起时触发');
}
onStageMouseMove(): void {
console.log('鼠标或者手指在舞台上移动时触发');
}
onDisable(): void {
console.log('脚本被禁用时触发,比如调用 this.enabled = false');
}
onDestroy(): void {
console.log('脚本被销毁时触发,场景被销毁,脚本会跟随一起销毁');
}
}

Javascript 脚本示例(js 支持 es6 Class 语法)

class Script extends Riko.Script {
onEnable() {
console.log('脚本被启用时触发,此时可以使用 this.target,但脚本还未被执行,可能会被触发多次');
}
onAwake() {
console.log('在时间轴运行到脚本位置时,脚本会被激活,此时脚本才开始被执行,激活只会被触发一次');
}
// 其他和 ts 写法相同
}
exports.default = Script;

Riko 还提供了哪些能力

除了脚本的生命周期以外,在 Riko 内,还提供了一些常用的功能 比如:

  • Riko.stage

    // 获得整个游戏的舞台,比如获取舞台鼠标位置
    Riko.stage.mouseX
    Riko.stage.mouseY,
  • Riko.timer

    // 时间控制器,比如:
    // 定时执行函数
    Riko.timer.once(1000, () => {
    console.log('timeout');
    });
    // 休眠1秒
    await Riko.timer.sleep(1000);
    // 每帧执行函数
    Riko.timer.frameLoop(1, this.loop, this);
  • Riko.emit Riko.on Riko.off 发送、监听、移除全局事件

    // 监听全局事件,【注意】如果脚本被销毁,需要手动调用`off`函数移除
    Riko.on('获得金币', () => {
    console.log('金币+1');
    });
    // 派发全局事件
    Riko.emit('获得金币');

    // 移除全局事件监听
    Riko.off('获得金币');
  • Riko.Sprite 精灵对象

    // 使用 createNode 方法创建节点
    const node = Riko.createNode('Sprite');
    const node = Riko.createNode('Particle');

    // 精灵也可以直接创建
    const sp = new Riko.Sprite();
    sp.url = 'xxx.png';
    this.target?.addChild(sp);
  • Riko.tweenTo 缓动动画

    // 使用 Tween 实现动画效果
    Riko.tweenTo(this.target, 'x', 300);

    // 顺序执行动画,先缓动X,再缓动Y
    await Riko.tweenTo(this.target, 'x', 100);
    await Riko.tweenTo(this.target, 'y', 100);

    // 还可以使用 AlphaTo,ScaleTo,MoveTo,RotateTo,SizeTo 等快速实现动画效果
    new Riko.ScaleTo(this.target, 2, 2).play();
    new Riko.RotateTo(this.target, 360).play();
  • 更多参考 Riko 提供的能力,可参考

如何定义脚本属性,并在编辑器内可修改

脚本内声明的属性,会被自动推导成对应的输入框,在编辑器内显示,用户可以可视化修改脚本属性。

  • 属性定义默认会被编辑器识别,如果不希望在编辑器内显示,则可以通过下划线_标识,比如 _length,完整示例如下(支持中):
  • 通过 Riko.use 函数,可以定义特殊类型的属性及提供辅助信息的描述

完整的属性代码示例

export default class Example1 extends Riko.Script {
// 对应编辑器的数字输入框
x = 100;
// 对应编辑器的数字输入框,增加了更加友好的提示
y = Riko.useNumber({ name: '垂直位置', tooltip: '节点的垂直坐标' });
// 对应编辑器的Slider
scale = Riko.useSlider({ name: '缩放', default: 1, min: 0, max: 10 });
// 对应编辑器的选择框
selected = true;
// 对应编辑器的字符串输入框
nickName = 'xxx';
// 对应编辑器的节点选择框
node = Riko.useNode();
// 对应编辑器的图片资源选择框
imageUrl = Riko.useRes({ type: 'Image' });
// 对应编辑器的声音资源选择框
soundUrl = Riko.useRes({ type: 'Sound' });
// 对应编辑器的视频资源选择框
videoUrl = Riko.useRes({ type: 'Video' });
// 对应编辑器的粒子资源选择框
particleUrl = Riko.useRes({ type: 'Particle' });
// 对应编辑器的序列帧资源选择框
frameUrl = Riko.useRes({ type: 'FrameAnime' });
// 对应编辑器的Lottie动画资源选择框
lottieUrl = Riko.useRes({ type: 'Lottie' });
// 对应编辑器的互动组件资源选择框
compUrl = Riko.useRes({ type: 'Component' });
// 对应编辑器的颜色选择框
color = Riko.useColor();
// 对应编辑器的下拉选择框
select = Riko.useSelect({
options: [
{ label: '选项1', value: '选择1' },
{ label: '选项2', value: '选择2' },
],
});
// 对应编辑器的动画效果选择框
effect = Riko.useEffect();
// 对应编辑器的缓动曲线
ease = Riko.useEase();
// 对应编辑器的触发事件
event = Riko.useEvent({ name: '胜利' });
// 对应编辑器的数组列表
array = Riko.useArray({ name: '图片列表', defaultItem: Riko.useRes({ type: 'Image' }) });
// 对应编辑器的分组列表
object = Riko.useObject({
default: {
speed: Riko.useNumber(),
icon: Riko.useRes({ type: 'Image' }),
node: Riko.useNode(),
},
});
// 使用下划线开头,则不会在编辑器显示
_length = 10;

async onAwake(): Promise<void> {
// 打印输出参数
console.log(
this.x,
this.y,
this.scale,
this.selected,
this.nickName,
this.node,
this.imageUrl,
this.soundUrl,
this.videoUrl,
this.particleUrl,
this.frameUrl,
this.lottieUrl,
this.compUrl,
this.color,
this.select,
this.effect,
this.ease,
this.event,
this.array,
this.object
);

// 根据输入创建精灵 或者使用 `new Riko.Sprite()`
if (this.imageUrl) {
const node = Riko.createNode('Sprite');
node.url = this.imageUrl;
node.pos(200, 0);
this.scene?.addChild(node);
}

// 根据输入创建视频
if (this.videoUrl) {
const node = Riko.createNode('Video');
node.url = this.videoUrl;
node.pos(200, 200);
node.play();
this.scene?.addChild(node);
}

// 根据输入创建粒子
if (this.particleUrl) {
const node = Riko.createNode('Particle');
node.url = this.particleUrl;
node.pos(200, 400);
node.play();
this.scene?.addChild(node);
}

// 根据输入创建序列帧动画
if (this.frameUrl) {
const node = Riko.createNode('FrameAnime');
node.url = this.frameUrl;
node.pos(200, 600);
node.play();
this.scene?.addChild(node);
}

// 根据输入创建 Lottie 动画
if (this.lottieUrl) {
const node = Riko.createNode('Lottie');
node.url = this.lottieUrl;
node.pos(200, 800);
node.play();
this.scene?.addChild(node);
}

// 根据输入创建互动组件
if (this.compUrl) {
const node = Riko.createNode('Component');
// 等待互动组件加载完毕,再播放,可以保证脚本被正确执行,如果无脚本,则可以忽略
await node.load(this.compUrl);
node.pos(300, 1200);
node.play();
this.scene?.addChild(node);
}

// 根据输入播放声音
if (this.soundUrl) {
Riko.SoundManager.playSound(this.soundUrl);
}

// 根据输入更改节点信息
if (this.node instanceof Riko.Sprite) {
// 位移动画
await Riko.tweenTo(this.node, 'x', this.node.x + 300, 1000, this.ease);
// 等待位移动画结束,播放缩放动画
new Riko.ScaleTo(this.node, 2, 2).play();
// 同时播放透明动画
new Riko.AlphaTo(this.node, 0).play();
}

// 递归获取场景下面某个节点
const node = this.scene?.getChildByName('节点名称', true) as Riko.Sprite;
if (node) node.alpha = 0.5;

// 播放动画
if (this.effect) {
// this.effect.target = this.target;
// this.effect.play();
}

// 执行事件
this.event.apply();

// 获取object内的资源
if (this.object.icon) {
const icon = new Riko.Sprite();
await icon.loadImage(this.object.icon);
this.scene?.addChild(icon);

// 增加物理属性
const rigidBody = icon.addRigidBody();
rigidBody.rigidBodyType = 'kinematic';
// 添加碰撞体
rigidBody.colliders.push({
type: 'box',
density: 1,
friction: 0.2,
restitution: 0,
isSensor: false,
x: 0,
y: 0,
width: 100,
height: 100,
});
// 设置速度
Riko.timer.once(1, () => rigidBody.setVelocity({ x: 0, y: 10 }));
}
}
}

更多可参考 Riko 的 use 系列方法

更新日期:2021.12.02