In the lead up to our next Game Jam, commencing September 16, participants from our June Hackathon will be taking over the Decentraland blog and revealing their design and building secrets. This week’s guest blogger is Tak. You can find Tak at Discord or on Slack using @tak.
Hey, I’m Tak-John Cheung and I’m a 3D Artist. In the industry I’m what you call a generalist, in that I pretty much dabble around with all areas of a computer graphics pipeline.
I actually found out about Decentraland from a friend, who suggested I check it out as something that I might be interested in. Turns out he was right. Since then, Decentraland has become my new playground.
In the June Hackathon, I created something called Kabloom Farm, which came second in the Districts category. I’ll be back for the Game Jam on September 16 and gunning for a first prize this time.
Introduction
The Builder provides an easy way for anyone to design a scene without having to worry about creating their own art assets, however its real strength lies in the fact that you can export these scenes for further development, taking advantage of all the tools and utilities available within Decentraland's SDK.
Let’s begin with a simple garden scene created using the Builder. It features a fixed gate which means users will eventually be able to enter the garden without having to leap over the fence. By making use of the new Utils library, we'll quickly be able to have the gate open and close in response to the user's clicks and hopefully by the end of the tutorial, you'll be able to see that anyone can create a simple interactive scene with minimal effort.
Check out the final code for this tutorial here.
The set up
Make sure you have the latest version of the Decentraland SDK installed by running the following command in the terminal:
npm install -g decentraland
You will also need to download the Builder scene files from this link. Once downloaded, extract them to their own folder.
Feel free to use your own Builder scene to follow along. Just download your scene from the Builder itself and you will get a similar .zip file. You can use the steps below as a general guide.
Running the scene
Inside the terminal, run the following command in the directory where we extracted the Builder scene:
dcl start
This will install any missing dependencies and automatically open your default browser to preview the scene. This might take a few seconds to load but once done, you should see a pavilion situated in a small garden that's surrounded by wooden fences.
As mentioned earlier, users can't access the garden without having to jump over the fence so our aim is to have the gate open and close whenever the user clicks on it.
Transforming items
Let’s start by tidying up the gate so that it fills the gaps between the fences. One of the things that we can do in the code, that currently isn't possible in the Builder, is scaling items. To achieve this, we will modify the Transform
component of the gate by doing the following:
- Open
game.ts
that's located in the/src
directory - Scroll down until you find
fencePicketDoor_01
or just perform a search for the term - Modify the
Transform
component belonging to the gate
TIP: The code from the Builder can be really long as each item in the scene generates a new block of code. To make locating an item in the code easier, you can return to the Builder and hover your mouse cursor over an item you wish to search for within the "Item Catalog" and a tool tip will popup giving you the item’s name, which should be very similar to what’s used in the code, so you can use it as a hint when searching for it.
/// --- Adding Basic Interactivity to gate ---
const fencePicketDoor_01 = new Entity()
fencePicketDoor_01.setParent(scene)
const gltfShape_16 = new GLTFShape('models/FencePicketDoor_01/FencePicketDoor_01.glb')
fencePicketDoor_01.addComponentOrReplace(gltfShape_16)
// Scale and position the gate
const transform_31 = new Transform({
position: new Vector3(6.65, 0, 0.5),
rotation: Quaternion.Euler(0, -90, 0),
scale: new Vector3(1, 1, 1.5)
})
fencePicketDoor_01.addComponentOrReplace(transform_31)
engine.addEntity(fencePicketDoor_01)
You can see in the example above that we adjusted the scale
to values that work for us in this scene. If you’re working with your own scene, you may have to play around a bit to see what works for you. In short, the gate’s position and scale has been adjusted to fit the space in between the fences.
We've also replaced the Quaternion rotation values with its Euler equivalent, which will be important later on when it comes to rotating the gate, at least in the sense that we will be able to understand it better conceptually.
Tip: This online tool is a good help when converting from one measurement to the other quaternions.online.
Utils library
It’s nearly time to add some functionality to the Scene and for that we're going to be making use of the new Utils library, which includes a number of pre-built tools that simplifies the process of adding common functionality to your scene.
If you haven't yet exited the preview server, then do that before installing the Utils library, which can be done by running the following command in the terminal, in your scene's project folder:
npm install -g decentraland-ecs-utils
To import the library into the scene's script, add this line at the start of your game.ts
file.
import utils from ".../node_modules/decentraland-ecs-utils/index"
We will only be using a few of the available helpers in this library, but if you're interested in learning more then follow this link.
Toggle states
The first component from the Utils library we'll be using is the ToggleComponent
, which allows us to switch an entity between two possible states and in doing so, perform a function. The states are specified by the ToggleState
, which can either be On
or Off
. In the case of our gate, we’ll be opening the gate when the ToggleState
is On
and closing the gate when the ToggleState
is Off
, so each time the state changes, the gate will rotate to the position corresponding to one of these states.
The ToggleComponent
takes in two arguments:
-
startingState
: Starting state of the toggle (On
orOff
) -
onValueChangedCallback
: Function to call every time the toggle state changes
Seeing as the gate starts off being closed, we’ll set the startingState
to the value ToggleState.Off
. For our first test of the toggle, on the second argument we’ll add a very simple function, whenever the state changes, the function will just log a message to the console describing the action we would like to perform. This way we can check that the toggle is working as expected before implementing the action of opening and closing the gate.
NOTE: Whenever you see utils
. that’s an indication that we’re accessing a helper from the Utils library.
// Toggle gate to its open / close positions
fencePicketDoor_01.addComponent(
new utils.ToggleComponent(utils.ToggleState.Off, value => {
if (value == utils.ToggleState.On) {
log("Open")
} else {
log("Close")
}
})
)
Click behaviors
So far nothing happens when you click on the gate, we want it to switch states when clicked. We’ll add an OnClick
component to the gate with a function that toggles between the states each time it’s clicked.
// Listen for click on the gate and toggle its state
fencePicketDoor_01.addComponent(new OnClick(event => {
fencePicketDoor_01.getComponent(utils.ToggleComponent).toggle()
}))
The function that’s executed by the OnClick
component changes the ToggleState
back and forth between On
and Off
. Although you won’t be able to see any changes happening visually in the scene, if you open up your browser’s console you should see messages alternating between Open
and Close
every time you click on the gate.
TIP: Accessing the browser’s console varies between browsers. For Chrome you use the keyboard shortcut CTRL + SHIFT + J (on Windows) or CMD + OPTION + J (on Mac). You can also open it via the menu through View > Developer >JavaScript Console.
Rotating the gate
It's worth noting that the gate has already been rotated by -90
degrees in the y-axis when it was originally placed inside the Builder. Earlier we converted the Quaternion
values for the rotation to its Euler
equivalent, which now becomes our starting rotation. For our end rotation, we will need to rotate by another -90
degrees so that it opens inwards making its final y-axis value -180
degrees. Let’s define a couple of variables to reflect these values and we’ll call them startRot
and endRot
.
// Define start and end rotations for the gate
let startRot = Quaternion.Euler(0, -90, 0)
let endRot = Quaternion.Euler(0, -180, 0)
To help us rotate the gate we’re going to be using another component from the Utils library called RotateTransformComponent
, which lets us rotate an entity over a period of time from one orientation to another.
The RotateTransformComponent
takes in three arguments:
-
start
:Quaternion
for the start rotation -
end
:Quaternion
for the end rotation -
duration
: duration (in seconds) of the rotation
We can now delete the log
statements that were used as placeholders and instead have the ToggleComponent
add the appropriate RotateTransformComponent
to the gate each time it changes state. When the gate opens, we set the start and end rotation arguments as expected with our startRot
and endRot
but when the gate closes, the order is reversed, meaning our endRot
is now our starting rotation and the startRot
is our end rotation.
// Toggle gate to its open / closed positions
fencePicketDoor_01.addComponent(
new utils.ToggleComponent(utils.ToggleState.Off, value => {
if (value == utils.ToggleState.On) {
fencePicketDoor_01.addComponentOrReplace(
new utils.RotateTransformComponent(startRot, endRot, 0.5)
)
} else {
fencePicketDoor_01.addComponentOrReplace(
new utils.RotateTransformComponent(endRot, startRot, 0.5)
)
}
})
)
Here we went with a 0.5
second duration for the opening and closing of the gate, which feels fairly snappy but I encourage you to play around with these values so that you can choose whatever feels right.
Finishing touches
You may have spotted a slight issue when you tried to click on the gate again before it finished rotating. Instead of smoothly transitioning between the open and closed states, it snaps back to its starting position and begins rotating again. A simple fix is to only pay attention to clicks on the gate done after it has finished rotating. This can be done by comparing the gate’s y-axis rotation to see if it matches either startRot
or endRot
.
TIP: It’s sometimes useful to add an intermediate variable. Although not necessary, it can help shorten your code as well as making it more readable.
// Listen for click on the gate and toggle its state
fencePicketDoor_01.addComponent(
new OnClick(event => {
// Adding an intermediate variable
let doorRotY = fencePicketDoor_01.getComponent(Transform).rotation.y
// Check if gate is at its start or end positions before toggling
if (doorRotY == startRot.y || doorRotY == endRot.y)
fencePicketDoor_01.getComponent(utils.ToggleComponent).toggle()
})
)
Final thoughts
Try running the Scene. Everything should now be working as intended. If you were just reading along and would now like to explore the Scene then you can find it here.
Using the Builder to navigate around and position items in your scene is a lot more intuitive than doing it purely by code. Hopefully this guide has shown you the possibilities when both the Builder and the SDK are used together and how even a little interactivity can go a long way in adding interest to your Scene.
In the near future, Builder assets will be optimized to load more quickly inside of Decentraland, which provides another benefit to those who wish to develop their scenes in conjunction with the Builder.
Let the games begin
The Decentraland Game Jam offers the perfect opportunity to play around with the SDK. Having participated in the June Hackathon, I can assure you that it’s one of the best ways to learn, not just because you get to build something but because you do it alongside other community members.
I’ve already signed up for the September 16 kick-off and I look forward to seeing what you guys build.
See you in the Metaverse!
Top comments (0)