使用3D精灵(Sprite3D)
Sprite3D 是3D的基本节点对象,就像Sprite是2D的基本节点对象一样,是LayaAir3D中所有3D节点的父类,包含很多3D精灵基本的功能属性,除此之外还是所有3D组件和脚本的容器。
在LayaAir 3.0编辑器中,我们可以通过鼠标右键来创建一个Sprite3D,如动图1-1所示
![]()
(动图1-1)
创建好的Sprite3D,在编辑器场景中,Scene3D下会是一个空节点,如图1-2所示
![]()
(图1-2)
基本上我们创建一个Sprite3D空节点的最重要的目的是作为一个根节点,一个项目中会有很多不同功能的根节点,来方面我们管理。另外也可以用Sprite3D来添加组件,下面我们会详细介绍Sprite3D的用途。
二、属性和功能
Section titled “二、属性和功能”![]()
(图2-1)
图2-1中,可以看到Sprite3D作为一个最基础的3D节点,都具备哪些属性,其它更丰富的3D节点都具备这些属性
2.1 节点的名字
Section titled “2.1 节点的名字”![]()
(图2-2)
图2-2中,任何一个节点都有名字,通过名字可以方便的来查找一个节点下是否存在某个名字的子节点,通常会调用如下方法来获取子节点 getChildByName(“xxx”)
/** * 根据子节点的名字,获取子节点对象。 * @param name 子节点的名字。 * @return 节点对象。 */ getChildByName<T extends Node = ChildType<this>>(name: string, classType?: new (...args: any[]) => T): T { return this.getChild(name, classType); }当我们找到子节点时,也可以对节点做一些基础操作,比如删除掉自己方法 removeSelf()
/** * 从父容器删除自己,如已经被删除不会抛出异常。 * @return 当前节点( Node )对象。 */ removeSelf(): Node { this._parent && this._parent.removeChild(this); return this; }具体一个Sprite3D都有哪些基础方法,可以去参考Node节点的基础属性。
2.2 是否激活
Section titled “2.2 是否激活”![]()
(图2-3)
图2-3中,任何节点,都可以通过勾选是否激活来作用于场景中,当不激活某个节点时,下面的所有子节点也会随着根节点处于不激活状态。当然我们也可以通过代码来控制
/** * 获取自身是否激活。 * @return 自身是否激活。 */ get active(): boolean
/** * 设置是否激活。 * @param value 是否激活。 */ set active(value: boolean)2.3 是否为静态
Section titled “2.3 是否为静态”![]()
(图2-4)
图2-4所示,是否为静态。在游戏场景中,每一个Sprite3D都有静态或者动态的两种状态,当一个物体标记为static后,就确保这个物体在游戏场景中为静态的、不会移动的物体,进而在游戏的运行过程中让游戏有更加流畅的运行体验。一般而言,在场景中完全静止的物体标记成static是比较好的。这个静止指在游戏运行过程中没有发生移动或者缩放或者旋转等,一般将用于烘焙的节点标记为static。
标记成 static 往往是为了提高效率,比如静态物体的碰撞检测要更快,同时多个使用同一材质的静态物体在绘制的时候只使用一个drawcall,进而节省了CPU。在光照计算上,static的物体也更快一些。
另外,当勾选Static时,编辑器会询问是否同时改变所有子对象的静态标志,如图2-5所示
![]()
(图2-5)
当然也可以用代码来控制
/** * 是否为静态。 */ get isStatic(): boolean
/** * 是否设为静态。 */ set isStatic(value: boolean)2.4 蒙版层
Section titled “2.4 蒙版层”![]()
(图2-6)
图2-6中,是精灵所在的图层 Layer。渲染相机可进行可视遮罩层的控制,对精灵的渲染与否进行控制,可以参考摄像机文档,其中有对设置不同物体蒙版层后,摄像机选择不同蒙版层的示例。如动图2-7所示
![]()
(动图2-7)
如图2-8所示,通过点击Layer,可以选择编辑器已经自定义好的Default,Layer1等等,也可以再次编辑Layer层
![]()
(图2-8)
也可以通过代码来设置
/** * 蒙版层。 */ get layer(): number
set layer(value: number)
//添加显示图层(为相机添加一个蒙版) this.camera.addLayer(5);2.5 变换
Section titled “2.5 变换”变换组件确定场景中每个精灵的位置,旋转和缩放。同时和Node节点一样具备子父节点的关联关系,使变换操作更灵活,每个精灵都有一个Transform3D。
![]()
(图2-9)
我们也可以在编辑器里手动调整精灵的变换
(图2-10)
(图2-11)
图2-10用来调整精灵的旋转,图2-11用来调整精灵的缩放
(图2-12)
图2-12可同时用来调整精灵的位置,旋转和缩放。
通过代码,来做变换
//平移this.position1.setValue(-1.5, 0, 0.0);this.sprite3d.translate(this.position1);//旋转this.rotate1.setValue(0, 60, 0);this.sprite3d.rotate(this.rotate1, false, false);//缩放var scale = this.sprite3d.localScale;scale.setValue(0.1, 0.1, 0.1);this.sprite3d.localScale = scale;三、子父级关系
Section titled “三、子父级关系”Sprite3D继承自Node节点,是LayaAir3D中所有3D对象的父类。通过一些示例,我们可以了解子父级关系。
![]()
(动图3-1)
通过点击按钮,让父节点移动,同时可以看到子节点也跟随移动
this.layaMonkeyParent.transform.translate(new Laya.Vector3(-0.2, 0, 0);![]()
(动图3-2)
通过点击按钮,让子节点移动,可以看到父节点并没有移动
this.layaMonkeySon.transform.translate(new Laya.Vector3(-0.2, 0, 0);关于克隆,LayaAir3D中提供了几种接口。比较常用的是clone方法
/** * 克隆。 * @return 克隆副本。 */ clone(): Node { var dstSprite3D: Node = Sprite3D._createSprite3DInstance(this); Sprite3D._parseSprite3DInstance(this, dstSprite3D, this, dstSprite3D); return dstSprite3D; }![]()
(动图4-1)
通过代码来克隆一个猴子
//克隆一个猴子let sp = this.layaMonkeyParent.clone() as Laya.Sprite3D;//设置克隆猴子的坐标sp.transform.position = new Laya.Vector3(1,0,0);//添加到场景中this.scene.addChild(sp);其次,这里详细讲解下Sprite3D中特有的克隆接口instantiate
/** * 创建精灵的克隆实例。 * @param original 原始精灵。 * @param parent 父节点。 * @param worldPositionStays 是否保持自身世界变换。 * @param position 世界位置,worldPositionStays为false时生效。 * @param rotation 世界旋转,worldPositionStays为false时生效。 * @return 克隆实例。 */ static instantiate(original: Sprite3D, parent: Node = null, worldPositionStays: boolean = true, position: Vector3 = null, rotation: Quaternion = null): Sprite3D { var destSprite3D: Sprite3D = (<Sprite3D>original.clone()); (parent) && (parent.addChild(destSprite3D)); var transform: Transform3D = destSprite3D.transform; if (worldPositionStays) { var worldMatrix: Matrix4x4 = transform.worldMatrix; original.transform.worldMatrix.cloneTo(worldMatrix); transform.worldMatrix = worldMatrix; } else { (position) && (transform.position = position); (rotation) && (transform.rotation = rotation); } return destSprite3D; }-
original :原始精灵。
-
parent:父节点。
-
worldPositionStays:是否保持自身世界变换。
-
position:世界位置,worldPositionStays为false时生效。
-
rotation:世界旋转,worldPositionStays为false时生效。
该方法可以在克隆时携带父节点信息,世界位置信息和世界旋转信息。
![]()
(动图4-2)
通过代码来instantiate克隆一个猴子
//sprite3d的instantiate克隆方法let layaMonkey_clone1 = Laya.Sprite3D.instantiate(this.layaMonkeyParent, this.scene, false, new Laya.Vector3(-2, 0, 0), new Laya.Quaternion(0, -90, 0));this.scene.addChild(layaMonkey_clone1);五、添加组件
Section titled “五、添加组件”LayaAir中,可以对任何3D对象添加Component 组件,如动图5-1所示
![]()
(动图5-1)
Component 组件,是附加到所有3D对象的内容的基类。
通过代码也可以添加组件,需要物体使用addComponent方法
//添加Rigidbody3D组件let rigidBody = this.layaMonkeyParent.addComponent(Laya.Rigidbody3D) as Laya.Rigidbody3D;//创建盒子形状碰撞器var boxShape = new Laya.BoxColliderShape(1, 1, 1);//设置盒子的碰撞形状rigidBody.colliderShape = boxShape;//重力为向下-10rigidBody.gravity = new Laya.Vector3(0,-10,0);![]()
(动图5-2)
如动图5-2,添加 Rigidbody3D 组件,并且设置重力的效果(需要在“项目设置”中勾选物理相关的“引擎模块”)。
六、添加脚本
Section titled “六、添加脚本”在3D世界的开发中,脚本类会在很多地方被使用,给Sprite3D添加一个脚本。如动图6-1所示。
(动图6-1)
我们可以给Main Camera添加一个通过鼠标和键盘来控制摄像机的脚本,这个脚本在实际开发过程中非常的实用,图6-2中,显示摄像机添加了这个脚本。
(图6-2)
脚本的代码为:
const { regClass, property } = Laya;
@regClass()export class CameraMoveScript extends Laya.Script3D {
/** @private */ protected _tempVector3: Laya.Vector3 = new Laya.Vector3(); protected lastMouseX: number = 0; protected lastMouseY: number = 0; protected yawPitchRoll: Laya.Vector3 = new Laya.Vector3(); protected resultRotation: Laya.Quaternion = new Laya.Quaternion(); protected tempRotationZ: Laya.Quaternion = new Laya.Quaternion(); protected tempRotationX: Laya.Quaternion = new Laya.Quaternion(); protected tempRotationY: Laya.Quaternion = new Laya.Quaternion(); protected isMouseDown: boolean = false; protected rotaionSpeed: number = 0.00006; protected camera: Laya.BaseCamera = new Laya.Camera; protected scene: Laya.Scene3D = new Laya.Scene3D;
speed: number = 0.01;
constructor() { super(); }
/** * @private */ protected _updateRotation(): void { if (Math.abs(this.yawPitchRoll.y) < 1.50) { Laya.Quaternion.createFromYawPitchRoll(this.yawPitchRoll.x, this.yawPitchRoll.y, this.yawPitchRoll.z, this.tempRotationZ); this.tempRotationZ.cloneTo(this.camera.transform.localRotation); this.camera.transform.localRotation = this.camera.transform.localRotation; } }
/** * 监听鼠标事件 */ onAwake(): void { Laya.stage.on(Laya.Event.MOUSE_DOWN, this, this.mouseDown); Laya.stage.on(Laya.Event.MOUSE_UP, this, this.mouseUp); this.camera = (<Laya.Camera>this.owner); }
/** * 监听键盘事件 */ onUpdate(): void { var elapsedTime: number = Laya.timer.delta; if (!isNaN(this.lastMouseX) && !isNaN(this.lastMouseY) && this.isMouseDown) { Laya.InputManager.hasKeyDown(87) && this.moveForward(-this.speed * elapsedTime);//W Laya.InputManager.hasKeyDown(83) && this.moveForward(this.speed * elapsedTime);//S Laya.InputManager.hasKeyDown(65) && this.moveRight(-this.speed * elapsedTime);//A Laya.InputManager.hasKeyDown(68) && this.moveRight(this.speed * elapsedTime);//D Laya.InputManager.hasKeyDown(81) && this.moveVertical(this.speed * elapsedTime);//Q Laya.InputManager.hasKeyDown(69) && this.moveVertical(-this.speed * elapsedTime);//E
var offsetX: number = Laya.stage.mouseX - this.lastMouseX; var offsetY: number = Laya.stage.mouseY - this.lastMouseY;
var yprElem: Laya.Vector3 = this.yawPitchRoll; yprElem.x -= offsetX * this.rotaionSpeed * elapsedTime; yprElem.y -= offsetY * this.rotaionSpeed * elapsedTime; this._updateRotation(); } this.lastMouseX = Laya.stage.mouseX; this.lastMouseY = Laya.stage.mouseY; }
/** * 释放监听 */ onDestroy(): void { Laya.stage.off(Laya.Event.MOUSE_DOWN, this, this.mouseDown); Laya.stage.off(Laya.Event.MOUSE_UP, this, this.mouseUp); }
protected mouseDown(e: Laya.Event): void { this.camera.transform.localRotation.getYawPitchRoll(this.yawPitchRoll); this.lastMouseX = Laya.stage.mouseX; this.lastMouseY = Laya.stage.mouseY; this.isMouseDown = true; }
protected mouseUp(e: Laya.Event): void { this.isMouseDown = false; }
protected mouseOut(e: Laya.Event): void { this.isMouseDown = false; }
/** * 向前移动。 * @param distance 移动距离。 */ moveForward(distance: number): void { this._tempVector3.x = this._tempVector3.y = 0; this._tempVector3.z = distance; this.camera.transform.translate(this._tempVector3); }
/** * 向右移动。 * @param distance 移动距离。 */ moveRight(distance: number): void { this._tempVector3.y = this._tempVector3.z = 0; this._tempVector3.x = distance; this.camera.transform.translate(this._tempVector3); }
/** * 向上移动。 * @param distance 移动距离。 */ moveVertical(distance: number): void { this._tempVector3.x = this._tempVector3.z = 0; this._tempVector3.y = distance; this.camera.transform.translate(this._tempVector3, false); }
}运行时效果如动图6-3所示
(动图6-3)
七、各种渲染精灵简介
Section titled “七、各种渲染精灵简介”这里的渲染精灵都是通过组件实现
7.1 Mesh精灵
Section titled “7.1 Mesh精灵”Mesh精灵是引擎中最常用的 静态网格精灵,可根据于美术软件预制作或自定义的网格数据渲染模型。例如图7-1场景中的各种建筑模型、山体岩石一般均为Mesh精灵。
(图7-1)
通常我们在实践中会创建简单的Mesh网格3D对象,如图7-2所示
(图7-2)
7.2 SkinnedMesh精灵
Section titled “7.2 SkinnedMesh精灵”SkinnedMesh精灵是引擎中的 蒙皮动画网格精灵,可根据美术软件预制作的网格数据和Animator组件的动画数据产生动作形变。常用于带蒙皮动画的角色、怪物等。和Mesh精灵相比具产生网格形变的能力。
(图7-3)
7.3 Particle 精灵
Section titled “7.3 Particle 精灵”Particle 是引擎中的 3D粒子精灵(渲染组件类为 ShurikenParticleRenderer),可根据美术人员编辑的粒子参数产生奇妙炫酷的粒子特效。一般用于各种角色技能特效或火焰、烟雾等场景特效。
(动图7-4)
7.4 PixelLine 精灵
Section titled “7.4 PixelLine 精灵”PixelLine 是引擎中的 像素线精灵,可根据脚本的绘制数据产生任意颜色的像素线。一般用于场景中的引导效果或项目调试数据用途。
(图7-5)
7.5 Trail 精灵
Section titled “7.5 Trail 精灵”Trail 是引擎中的 拖尾精灵,可根据精灵运动轨迹产生动态的拖尾特效。一般用于各种轨迹拖尾效果的展示。
(动图7-6)