In this two-part tutorial, we’ll build a scene that checks an API for weather data from a specific location of your choice (from the real world) and uses that data to render simulate the weather in the metaverse! Part one will focus on how to leverage external JavaScript libraries and APIs in a Decentraland scene. Part two of this tutorial will show how to create raindrops and snowflakes as particle systems, which gets pretty interesting.

My name is Nico Earnshaw, and I take care of the Decentraland documentation. That means everything you can find in docs.decentraland.org. If you have any feedback about our docs, please make a pull request or create an issue in the GitHub repo!

In Decentraland scenes we use TypeScript, which is based on JavaScript. So if you’re already familiar with JavaScript, we want you to know that you can do pretty much anything you’re used to doing with JavaScript, including using any of the JavaScript libraries that you know and love.

That also means you can make REST API calls to all your favourite APIs, just as you could if you were developing a website.

If you’re looking for inspiration, check out ProgrammableWeb, they have a large curated archive of APIs of all kinds. You can find APIs that relate to IOT, AI services, finance, and all sorts of other cool things.

In this tutorial, we’re going to integrate our scene with the Weather Unlocked API, since it’s relatively simple to use and reasonably well documented.

You can follow along with this tutorial by forking the scene code in my GitHub repo here.

You can also explore a finished version of this scene that’s running live on a server here.

Call the API from the browser #

To use the Weather Unlocked API, you first need to register to get your own API key. This key will identify the HTTP calls that come from your “app” (your Decentraland scene, in this case). They use keys to track usage per app because their API is only free up to certain usage limits. The free tier of the API lets us make 25,000 calls per day, which is probably fine for what we want to do.

To create a new account, click Start a free trial and then Signup for free under “Weather Data”. Then complete and submit the registration form.

Once you have an account and are logged in, check your account’s admin page to take note of your account’s app_id and key. You need to include these in the URL each time you call the weather API.

By reading their API documentation, we can tell that it’s quite easy to query the current weather at a specific latitude and longitude, which is all we need for our scene.

For example, I can query the weather from Buenos Aires via this request:

http://api.weatherunlocked.com/api/current/-34.55,%20-58.46?app_id=<app_id>&app_key=<key>

Replace <app_id> and the <key> with your own app_id and key and try it out in a web browser. You can also replace the latitude and longitude with the location of your own home or any other place you prefer. Check the latitude and longitude on Google Maps to get exact geographic coordinates.

After trying this URL in your browser, you should see it return some raw XML data like what we see above. Out of all that data, we only really care about the value of weather_description, which in this case is “mostly cloudy”.

Now that we know the API works and how to use it, it’s time to send the same HTTP request but from our Decentraland scene.

Call the API from your scene #

There are a lot of JavaScript libraries that make it easy to make API calls, and this article gives a good summary of some of the main libraries available. I decided to use Axios, but any of the others listed should work, too.

Before we can use this library, we need to install it on our scene folder. Note that if you already had this library installed in your local machine, you still need to install it in the scene folder so that it’s deployed together with the scene. Everything needed to run your scene must be deployed to Decentraland with your scene’s content.

To install the library, open a command prompt, navigate to your scene’s folder, and run this command:

npm install axios

When I ran this, I got a warning at the end of the installation telling me the dependency ajv was missing. This is apparently required by Axios, so I went ahead and installed ajv in my scene’s folder via:

npm install ajv

Now in our scene’s scene.tsx file, we can import axios by including this line:

const axios = require('axios');

Let’s write some minimal code in our scene to use this API:

import * as DCL from 'decentraland-api'

const axios = require('axios')
const appId : string = '<your app ID>'
const APIkey : string = '<your API key>'
const lat : string = '-34.55'
const lon: string = '-58.46'
const callUrl : string = 'http://api.weatherunlocked.com/api/current/' + lat + ',%20' + lon + '?app_id=' + appId + '&app_key=' + APIkey

export default class HouseScene extends DCL.ScriptableScene {
  sceneDidMount() {
    setInterval(this.getWeather(), 1000000)
  }

  getWeather() {
    axios.get(callUrl)
      .then( (response:any) => {
        console.log(response.data.wx_desc)
      })
      .catch( (error:any) => {
        console.log(error)
      });
  }

  async render() {
    return <scene/>
  }
}

Note: for this to work, you need to replace both the appId and the APIkey variables with strings holding your API credentials.

The first thing we’re doing here is building a URL from several variables. This makes it easy to change the parameters of the API call by changing those variables in the first lines. We then concatenate all these bits of URL into a single string that looks just like the URL we tried in our browser, and can retrieve it for our API call with axios.get().

In the sceneDidMount() function, we’re starting a time-based loop to regularly call getWeather(). I’m not sure how many zeros I added in the looping period, remember these are milliseconds, so we can add quite a few of them… weather doesn’t change all that often.

Finally, the API call returns an object that holds all of the data that we saw printed on our browser when we tried the URL before. The data returned by this function is in JSON format rather than in XML, which makes its components a lot easier to access. Remember, we only care about the weather description, which is kept in the field wx_desc, so we log it by referring to response.data.wx_desc.

Let’s run a preview of our scene by running dcl start in our scene folder!

If we open the JavaScript console on the browser tab that runs the scene (if you’re using Chrome, go to View > Developer > JavaScript Console) we should see that the weather description is logged there.

Mapping API responses to states #

Looking at the API documentation of Weather Unlocked, I realize that the weather description has a LOT of possible values… 48 to be precise, wow! I never knew there was so much you could say about the weather. Some examples of possible weather descriptions are: “Patchy light drizzle”, “Moderate or heavy sleet showers”, or “Thundery outbreaks possible”.

That’s a lot more detail than we care about for this tutorial.

Let’s keep our code simple, we don’t want to have different instructions for each of those 48 response options. We’ll make a list of the distinct weather states that we will simulate in our scene, and then we’ll then map all the different possible descriptions to those states.

Let’s create a new enum that lists the possible weather states:

export enum Weather {
  sun,
  clouds,
  rain,
  heavyRain,
  snow,
  storm
}

Custom enums are a handy feature of TypeScript that help to set variables that can only hold specific values, it’s almost like defining a custom type. Using enums makes your code a lot more readable, and your code editor will suggest the possible values to you as you type, preventing you from using the wrong value

We’ll keep track of a weather variable in the scene state that uses this enum as a type:

export interface IState{
  weather: Weather
}

export default class HouseScene extends DCL.ScriptableScene<any, IState>{
  state: IState = {
    weather: Weather.sun,
  }

  (...)

Let’s modify the getWeather() function so that after getting a response from the API, it pushes the weather description string that this returned to a function called mapWeather().

getWeather() {
  console.log('getting new weather')
  axios
  .get(callUrl)
  .then((response: any) => {
      console.log(response.data.wx_desc)
      let weather: Weather
      weather = this.mapWeather(response.data.wx_desc)
      if (weather == this.state.weather) {
        return
      }
      this.setState({ weather: weather})

    })
  .catch((error: any) => {
    console.log(error)
  })
}

If the new simplified weather is the same as what we already had in our state, the function returns and does nothing, otherwise we set the state with the new weather value.

Here are the contents of the function that matches the weather description to the options in our custom enum.

mapWeather(weather: string) {
  let simpleWeather: Weather

  if (weather.match(/(thunder)/gi)) {
    simpleWeather = Weather.storm
  } else if (weather.match(/(snow|ice)/gi)) {
    simpleWeather = Weather.snow
  } else if (weather.match(/(heavy|torrential)/gi)) {
    simpleWeather = Weather.heavyRain
  } else if (weather.match(/(rain|drizzle|shower)/gi)) {
    simpleWeather = Weather.rain
  } else if (weather.match(/(cloud|overcast|fog|mist)/gi)) {
    simpleWeather = Weather.clouds
  } else {
    simpleWeather = Weather.sun
  }

  return simpleWeather
}

This function matches the input against a series of regular expressions, one for each of the possible values of our Weather enum. To write these regular expressions, I got some help from a handy online tool called Regexr that lets me test the expressions on custom inputs. As I wrote the regex expressions, I tested them against the entire list of possible responses from the Weather Unlocked API docs.

This set of regex expressions covers almost all cases well, except for “blizzard”.

If a blizzard occurs, then our scene will stay sunny.

That would be easy to fix in the code, but I don’t expect it to be a problem if we’re getting weather data from Buenos Aires. We’ve only had light snow like twice in the past 50 years, blizzards are far from being something to worry about.

If you live at the North Pole, maybe you’ll want to add blizzards to your scene after you finish the tutorial.

Reproducing the weather in your scene #

Great, we now have a scene that can read stuff from an API. Next, we want it to do something with that information, we want the state to manifest itself in a visible way.

We’ll start by showing either white clouds, dark clouds, or no clouds at all depending on the weather. I found a couple of nice 3D models of clouds that we can use on Google Poly: this one by Poly by Google and this other cloud by Geoffrey Bell, both licensed under CC BY.

I slightly modified the stormy cloud to remove the lightning. In our next tutorial we’ll be dealing with the lightning bolts separately in a more fun, and dramatic, way.

Let’s change our scene’s code so that it renders different clouds depending on the scene state. We’re also including a function that renders a nice cozy house, to make it more immersive

async render() {
  switch (this.state.weather) {
    case Weather.sun:
      return (
        <scene>
          {this.renderHouse()}
        </scene>
      )
    case Weather.clouds:
      return (
        <scene>
          {this.renderClouds('white')}
          {this.renderHouse()}
        </scene>
      )
    case Weather.rain:
      return (
        <scene>
          {this.renderClouds('white')}
          {this.renderHouse()}
        </scene>
      )
    case Weather.heavyRain:
      return (
        <scene>
          {this.renderClouds('dark')}
          {this.renderHouse()}
        </scene>
      )
    case Weather.snow:
      return (
        <scene>
          {this.renderClouds('dark')}
          {this.renderHouse()}
        </scene>
      )
    case Weather.storm:
      return (
        <scene>
          {this.renderClouds('dark')}
          {this.renderHouse()}
        </scene>
      )
  }
}

renderClouds(cloudType: string) {
  switch (cloudType) {
    case 'dark':
      return (
        <gltf-model
          src="models/dark-cloud.gltf"
          position={{ x: 5, y: 8, z: 5 }}
          scale={4.5}
        />
      )
    case 'white':
      return (
        <gltf-model
          src="models/clouds.gltf"
          position={{ x: 9, y: 4, z: 1 }}
          scale={3.5}
        />
      )
  }
}

renderHouse() {
  return (
    <gltf-model
      src="models/House.gltf"
      position={{ x: 5, y: 0, z: 5 }}
    />
  )
}

Here the render function is checking the value of the weather in the scene state, and based on that, it calls the renderClouds function passing it different parameters to show one of our two models. If the weather is sunny, it just returns an empty <scene> object.

This code might all seem a little redundant at this point, as several weather conditions are rendering the same clouds. We will change this in the second part of this tutorial, when we’ll add raindrops, snowflakes, and thunder (when applicable).

Testing the scene #

Let’s save these changes in our scene.tsx file and switch back to our browser. The scene should reload automatically with our latest changes.

Awesome! Today is a cloudy day, and we can see clouds in our scene!

We don’t want to wait weeks or months to see the full range of the weather for our test. We can easily fake different weather conditions by defining another variable at the start of our file, passing it directly to the mapWeather() function.

let fakeWeather: string | null = 'heavy rain'

(...)

if (fakeWeather) {
  weather = this.mapWeather(fakeWeather)
} else {
  axios
    .get(callUrl)
    .then((response: any) => {
      console.log(response.data.wx_desc)
      weather = this.mapWeather(response.data.wx_desc)

      (...)

If fakeWeather is null, then we’ll call the weather API to get our value. Otherwise, we’ll just use this manually inserted value. I’m setting the type of fakeWeather to string|null so that it can be either one or the other.

Final thoughts #

In Part 1, we wanted to show you a simple example of what you can do with external web APIs. There are many more exciting ways you could use APIs!

For example, maybe there’s some real time statistic that you really care about, and you think being able to explore it as a landscape will be a strong way to convey its magnitude.

Or maybe you want your scene to charge users (more about that in an upcoming blog post!) and you want prices in MANA to be updated based on market movements. Whichever the case, there are APIs for everything, and your Decentraland scenes can take advantage of this power in any way you can imagine!

Stay tuned for Part 2 next week, when we’ll be adding some more dramatic weather simulations to our scene!

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