dot big bang

Developer API
Menu
All
  • Public
  • Public/Protected
  • All

Randomness can help break up a world or add variation to things. It's also great for determining loot, team distribution and a ton of other things.

Note: The Random API was added in UserScript v0.1.1. You can set your script API version by clicking the script name and adjusting the dropdown.

Contents

  1. Basic Use
  2. Common Cases
  3. Noise

Basic Use

You can access random APIs through a couple of different paths. You have the TypeScript Math.random() method as well as the Random API offered by dotbigbang.

The Random class can be used as an instance or called statically. Using it as an instance allows you to have more control since you can set a seed.

Example 1 - Getting random values

It's pretty easy to get random values for use in your code. Some approaches are outlined below.

// Use TypeScript to get a number that's greater than or equal to 0 and less than 1.
const myNumber = Math.random();

// Returns true 10% of the time.
const oneInTen = Random.chance(1 / 10);

// Returns true 50% of the time.
const fiftyFifty = Random.flipCoin();

// Returns a valid random index.
const myArray = ["one", "two", "three"];
console.log(myArray[Random.index(myArray.length)]);

// Returns a number within a range.
const oneToTen = Random.numberInRange(1, 10);

// Returns a number within the range 1 - n for an n sided dice roll.
const d6 = Random.roll(6);

// Shuffles an array in place.
const myArray = ["one", "two", "three"];
Random.shuffle(myArray);

// Generates a random string with an optional array of characters to pick from.
// Defaults to an array containing a through z, A through Z, and 0 through 9.
const myString = Random.string(8, ["a", "b", "c"]);

// Returns a value from a set of weighted inputs.
const inputs = [
  { value: "apple", weight: 1 },
  { value: "potato", weight: 3 },
  { value: "banana", weight: 0.5 },
];

// Prints "potato" (Most likely), "apple" (Next likely) or "banana" (Least likely).
console.log(Random.weightedSample(inputs));

Common Cases

Below are some common use cases for random number generation.

Example 2 - Pick a random location in a circle

Getting a random position in a circle can be useful for things like path-finding, loot drops and a variety of other things.

start() {
    const position = this.getRandomPositionInCircle(50);
    console.log(`Random position in circle: ${position}`);
}

getRandomPositionInCircle(radius: number): Vector3 {
    // Get a random angle.
    const angle = Math.random() * 2 * Math.PI;

    // Get a random point on the radius.
    const hypotenuse = Math.sqrt(Math.random()) * radius;

    // Build and return the position.
    const adjacent = Math.cos(angle) * hypotenuse;
    const opposite = Math.sin(angle) * hypotenuse;

    return new Vector3(adjacent, 0, opposite);
}

Example 3 - Deterministic values

Deterministic values are very useful for replays, procedural level generation and any case where you want randomness, but want all players to experience the same thing.

// Create an instance of the Random class with a seed.
private _random = new Random(12345);

start() {
    // Print out 3 random values.
    for (let i = 1; i <= 3; i++) {
        console.log(`Roll ${i} = ${this._random.roll(20)}`);
    }

    // Reset.
    console.log("Resetting...");
    this._random.reset();

    // Print out the same 3 random values.
    for (let i = 1; i <= 3; i++) {
        console.log(`Roll ${i} = ${this._random.roll(20)}`);
    }
}

Noise

Noise is really useful for smooth random values over time, procedural generation or other things that benefit from a more volumetric random value.

Noise can be sampled in up to 4 dimensions and returns values between 0 - 1 based on where it is sampled from.

Just like the above example you can set the seed on an instance of Noise to get deterministic noise values.

Example 4 - Cubes moving in a wave pattern

Lets make a grid of cubes move up and down like they are a liquid surface. We will sample from coordinates in 2D noise over time to get the vertical values and apply that to each cube.

// The Voxel Object to display in a grid.
voxelObject = new VoxelObjectRef();
// The number of rows the grid has.
rows = 5;
// The number of columns the grid has.
columns = 5;
// The spacing between each Voxel Object.
spacing = 20;
// The distance to move up and down.
distance = 50;
// The scale of the noise sampling.
scale = 0.1;
// The speed of the motion.
speed = 0.5;

private _voxelObjectComponents = new Array<VoxelObjectComponent>();
private _noise: Noise = new Noise(NoiseKind.SMOOTH);
private _noiseOffset = 0;

start() {
    // If the Entity is networked, only the owner can modify components on it.
    if (!this.entity.isOwnedLocally) {
        return;
    }

    // Hide the original VoxelObjectComponent.
    const voxelObjectComponent = this.entity.getComponentByType(ComponentType.VoxelObject);
    if (voxelObjectComponent) {
        voxelObjectComponent.visible = false;
    }

    // Create a grid of VoxelObjectComponents to animate with the noise values.
    for (let i = 0; i < this.rows * this.columns; i++) {
        const voxelObjectComponent = new VoxelObjectComponent();
        voxelObjectComponent.voxelObject = this.voxelObject.get();
        voxelObjectComponent.offset.set(
            Math.floor(i / this.rows) * this.spacing - ((this.rows - 1) * this.spacing * 0.5),
            0,
            i % this.columns * this.spacing - ((this.columns - 1) * this.spacing * 0.5)
        );
        this.entity.addComponent(voxelObjectComponent);
        this._voxelObjectComponents.push(voxelObjectComponent);
    }
}

tick() {
    // If the Entity is networked, only the owner can modify components on it.
    if (!this.entity.isOwnedLocally) {
        return;
    }

    // Loop over each grid position and find the noise value. Apply it as a vertical offset.
    for (let i = 0; i < this.rows * this.columns; i++) {
        const yOffset = this._noise.get2D((Math.floor(i / this.rows) * this.scale) + this._noiseOffset, (i % this.columns) * this.scale);
        this._voxelObjectComponents[i].offset.setY(yOffset * this.distance);
    }

    // Increase the noise offset to move through the noise each frame.
    this._noiseOffset += this.game.frameDeltaTime * this.speed;
}