Hello! Please choose your
desired language:
Dismiss

If you’ve been busy building interactive Decentraland scenes with our SDK, a LOT has changed in version 5.0! We believe these changes are for the better, but you will have to have to make some changes to the code in your scenes in order to make them compatible with version 5.0. This new style of building scenes is going to be a lot more straightforward and easier to pick up.

For those of you building your scenes as static XML content, worry not. You can ignore this article and the new SDK release. Nothing has changed on that front. If you’re curious, we’re working toward interpreting XML syntax as .ts code in the backend, but that shouldn’t affect how your scenes are written.

This article will help you migrate your code and ensure that your scenes take advantage of the entity-component-system

Also, to help you get started:

  • We’ve rewritten our documentation, check it out!
  • We published a reference page, where you can find details about all the components and functions exposed in the SDK
  • We published a number of example scenes. This includes rebuilding most of our old example scenes with the new SDK, so you can see how we went through those transformations for inspiration.
  • Take a look at this tutorial where we explain several of the main concepts that have changed with the new SDK. Stay tuned for even more tutorials!.

In this article, we won’t go too deep into general concepts. Instead, we’ll focus on how the new syntax is different and how you can port your old scenes to the new SDK. For part one, we’ll look at defining the entities and components of your scene. Part two will cover more advanced topics, giving you tips for migrating the interactive behaviors of your scene.

All of the 3D models, animations, and textures that were supported before are unchanged: you won’t need to remodel anything! In fact, the triangle limits are now higher for scenes spanning more than one parcel, so you’ll be able to add even more content.

All the values you used to position, rotate, and scale entities in your scenes should still produce the same results– you don’t need to rethink any of that either!

The high level differences

Let’s quickly go over some obvious things you’ll notice right away:

  • The main file is now a .ts instead of a .tsx file, and it’s found in an src folder inside your scene’s directory.
  • The scene’s main code is no longer enclosed inside a ScriptableScene class definition, and there are no more default methods like onSceneDidMount() or render(). The examples you’ll find below can be written straight in the game.ts file.
  • You no longer need to import the SDK or any of the basic components to your files – they are already implicitly imported. Only blockchain and audio modules need to be imported.
  • There are no entity types: all entities are generic. Their characteristics come from the components that you add to them.
  • Rotations are stored as quaternion rather than Euler angles.
  • The scene state object has been removed. Now the entities in the scene embody the scene data.
  • There are no more transition properties on entities: you now reposition entities frame by frame as they move.
  • IDs and keys are no longer needed on any entities.

You can read the SDK 5.0 announcement on our blog for a more in-depth look at these differences.

You’ll notice that the new version of the SDK sometimes requires slightly more verbose code, but it pays off when you realize that things are a lot more straightforward. This is especially true when it comes to coding dynamic behavior.

Below is an example of the simplest scene you could write with the old SDK. Notice how many layers of complexity you need to get through before you actually add the 3D model of your statue.

import * as DCL from 'metaverse-api'

export default class SampleScene extends DCL.ScriptableScene {
  async render() {
    return (
      <scene>
        <gltf-model src="models/statue.gltf" position={{ x:5, y:0, z:5 }} />
      </scene>
    )
  }
}

On the other hand, this is what that same scene looks like using version 5.0 of the SDK:

let statue = new Entity()
statue.add(new GLTFShape("models/statue.gltf"))
statue.add(new Transform({
 position: new Vector3(5, 0, 5)
}))
engine.addEntity(statue)

Primitive shapes

Since there are no more typed entities, to create a primitive you first create a generic entity and then you add a shape component to it along with any other components that are relevant.

Below is an example for how to add a cube:

Old code:

<box
  position={{ x: 5, y: 3, z: 5 }}
  rotation={{ x: 180, y: 90, z: 0 }}
  scale={0.5}
/>

New code:

let box = new Entity()
box.add( new BoxShape())
box.add( new Transform({
	position: new Vector3(5, 3, 5),
  	rotation: Quaternion.Euler(180, 90, 0),
  	scale: new Vector3(0.5, 0.5, 0.5)
}))
engine.addEntity(box)

You assign a different component type for each primitive shape, such as PlaneShape or SphereShape. See shape components for more details.

Notice that the position, rotation and scale of the entity are held in a Transform component. The same values that we had for these fields are still valid in the Transform component, and have the same results.

The only major difference is that rotation is now stored as a quaternion. You can use the Quaternion.Euler() method to pass values as Euler angles (the normal x, y, and z angles that go up to 360), and in that way keep using the same values from your old scene.

As before, collisions for entities with primitive shapes are enabled through the withCollisions property. This property now belongs to the shape component.

Old code:

<box
 withCollisions={true}
/>

New code:

let box = new Entity()
const bShape = new BoxShape()
bShape.withCollisions = true
box.add(bShape)
engine.addEntity(box)

Materials

Materials work pretty much the same as in previous versions of the SDK. You still define Materials and Basic Materials separately from the entities to which they’re applied.

All of the same properties and property names that you could add to materials in older versions are still supported, you just do so using a new syntax.

Below is an example of how to define a new material:

Old code:

<material
  id="myMaterial"
  albedoTexture = "materials/wood.png"
  roughness={0.5}
/>

New code:

let myMaterial = new Material()
myMaterial.albedoTexture = "materials/wood.png"
myMaterial.roughness= 0.5

Note that Materials and BasicMaterials are now components; they are no longer considered entities themselves. By treating them as components, we can add them to entities and keep using them in a way that’s similar to how we’ve used them in the past.

The examples below show how you apply a material to an entity:

Old code:

<box material = "#myMaterial" />

New code:

let box = new Entity()
box.add(myMaterial)

3D models

3D models in .glTF and .glb formats can be added in the same way as before. Again, the syntax changes, but there are no significant conceptual differences.

Old code:

<gltf-model
  src="models/myModel.gltf"
  position={{ x: 5, y: 3, z: 5 }}
  rotation={{ x: 180, y: 90, z: 0 }}
  scale={0.5}
/>

New code:

let myModel = new Entity()
myModel.add(new GLTFShape("models/myModel.gltf"))
myModel.add( new Transform({
	position: new Vector3(5, 3, 5),
  	rotation: Quaternion.Euler(180, 90, 0),
  	scale: new Vector3(0.5, 0.5, 0.5)
}))
engine.addEntity(myModel)

3D models are added through a GLTFShape component. This component includes a required parameter that points to the path of the 3D model file.

Like with primitives, the position, rotation and scale of the entity are all held in a separate Transform component. Again, the same values you used on older versions of the SDK will still work.

The only major difference is that rotation is now stored as a quaternion. You can use the Quaternion.Euler() method to pass values as Euler angles (the normal x, y and z angles that go up to 360), and in that way keep using the same values from your old scene.

Colliders for glTF models also work the same as before, they must be imported as part of the 3D model.

OBJ objects

3D objects in .obj format can also be added in a similar way, using an OBJShape component.

Old code:

<obj-model
  src="models/myModel.obj"
  position={{ x: 5, y: 3, z: 5 }}
  rotation={{ x: 180, y: 90, z: 0 }}
  scale={0.5}
>

New code:

let myModel = new Entity()
myModel.add(new OBJShape("models/myModel.gltf"))
myModel.add( new Transform({
	position: new Vector3(5, 3, 5),
  	rotation: Quaternion.Euler(180, 90, 0),
  	scale: new Vector3(0.5, 0.5, 0.5)
}))
engine.addEntity(myModel)

Animations

When using animations on a glTF (or glb) 3D model, we now create an AnimationClip object for each animation and add it to the GLTFShape component.

Old code:

<gltf-model
  src="models/shark_anim.gltf"
  skeletalAnimation={[
    {
      clip: "swim",
      playing: true
    },
    {
      clip: "bite",
      weight: 0.8,
loop: false,
      playing: false
    }
  ]}
/>

New code:

let shark = new Entity()
shark.add(new GLTFShape("models/myModel.gltf"))
engine.addEntity(shark)

const swimClip = new AnimationClip('swim')
const biteClip = new AnimationClip('bite', { weight: 0.8, loop: false })
shark.get(GLTFShape).addClip(swimClip)
shark.get(GLTFShape).addClip(biteClip)

swimClip.play()

The AnimationClip object can have optional parameters that are added when creating it.

TIP: You can now also set how fast to play an animation in the animation’s parameters.

If you don’t need to set any properties for the clip, then you can skip the steps of creating and adding the clip object to the component. The clip is created automatically (with default property values) when you first attempt to get it from the GLTFShape:

let shark = new Entity()
shark.add(new GLTFShape("models/myModel.gltf"))
engine.addEntity(shark)
let swimClip = shark.get(GLTFShape).getClip("swim")
let biteClip = shark.get(GLTFShape).getClip("bite")
swimClip.play()

To play or pause an animation, you can either run the play() and pause() functions, or you can set the .playing property to true or false.

// using the play() function
swimClip.play()
// using the playing property
swimClip.playing = true

Nested entities

To nest entities as children of other entities, use setParent().

Old code:

  <entity position={{ x: 0, y: 0, z: 1 }} rotation={{ x: 45, y: 0, z: 0 }}>
    <box position={{ x: 10, y: 0, z: 0 }} />
    <box position={{ x: 10, y: 10, z: 0 }} />
  </entity>

New code:

// create parent
let wrapper = new Entity()
wrapper.add(new Transform({
	position: new Vector3(0, 0, 1),
	rotation: Quaternion.Euler(45, 0, 0)
}))
engine.addEntity(wrapper)

// create first child
let box1 = new Entity()
box1.add(new BoxShape())
box1.add(new Transform({
	position: new Vector3(10, 0, 0)
}))
engine.addEntity(box1)

// create second child
let box2 = new Entity()
box2.add(new BoxShape())
box2.add(new Transform({
	position: new Vector3(10, 10, 0)
}))
engine.addEntity(box2)

// assign parent
box1.setParent(wrapper)
box2.setParent(wrapper)

When an entity is a child of another, its position, rotation, and scale are all inherited from the parent. This is also true in the new version of the SDK, so using the same values on each entity should produce in the same results as before.

There no longer is a <scene> node wrapping your whole scene, but if your scene used to have universal position, rotation, or scale values and you want to keep those, you can always recreate them via an entity that’s set as the parent of all others.

<scene position ={{ x: 5, y: 0, z: 5 }}>
  <box position={{ x: 10, y: 0, z: 0 }} />
</scene>
let sceneWrapper = new Entity()
sceneWrapper.add(new Transform({
	position: new Vector3(5, 0, 5)
}))
engine.addEntity(sceneWrapper)

let box = new Entity()
box.add(new BoxShape())
box.add(new Transform({
	position: new Vector3(10, 0, 0)
}))
box.setParent(sceneWrapper)
engine.addEntity(box)

Quaternions to Euler

As we already mentioned, the Transform component stores rotation data as a Quaternion. You can pass Euler angles instead, which are what previous versions of the SDK used, by using any of the following syntaxes:

// Create a transform with a predefined rotation in Euler angles
let myTransform = new Transform({rotation: Quaternion.Euler(0, 90, 0)})

// Use the .setEuler() function to set rotation
myTransform.rotation.setEuler(0, 90, 180)

// Set the `eulerAngles` field
myTransform.rotation.eulerAngles = new Vector3(0, 90, 0)

Click behavior

The onClick field in older versions of the SDK is now replaced by the OnClick component. This component can be added to any entity, and has exactly the same functionality as the onClick field. See (doc about clicks) for more details.

Old code:

<box
  onClick={() => console.log("Clicked!")}
/>

New code:

let box = new Entity()
box.add(new BoxShape())
box.add(
  new OnClick(e => {
   log("Clicked!")
})
engine.addEntity(box)

Final thoughts

The tips and examples in this blogpost should help you migrate all of the static content in your scenes to version 5.0.

When it comes to interactive and dynamic content, the migration process is less straightforward and requires some more thought. We’ll soon release part two of this migration guide that will cover those topics.

To see full examples of scenes built with the new SDK, check out the Example scenes page in the documentation!

Start Building!
Our SDK provides everything you need to start developing games and applications.
Get Started