Random Library

Header image for Random Library

A helper library for various randomisation functions.

7 minute reading time of 1535 words (inc code)

Intro

A simple collection of utility functions, wrapped up in a class and rewritten for Godot 4.

It gives me easy access to repeatable random things - eg. lets me save the seed so that procedural generation can proceed to generate the same things the next time the player loads that level etc.

(And it’s also a lot more convenient to type something like instance.get_color() to get a random color, or especially instance.from_vector2(first, second) to get a random vector between two bounds.)

The Library

class_name Random

"""
interface to provide easy access to repeatable RNG helper functions
"""

var rng : RandomNumberGenerator : 
	set (_rng):
		rng = _rng
	get:
		if not rng:
			rng = RandomNumberGenerator.new() 
		return rng

func set_seed(_rseed : int = 0) -> void:
	rng.set_seed(_rseed) if _rseed != 0 else rng.randomize()

func get_seed() -> int:
	return rng.seed


### fundamental

func get_int(from : int = 0, to : int = 100) -> int:
	return rng.randi_range(from, to)

func get_float(from : float = 0.0, to : float = 1.0, can_invert : bool = false) -> float:
	var value = rng.randf_range(from, to)
	if can_invert and get_float() > 0.5:
		value = -value
	return value


### colors

func get_color() -> Color:
	return Color(get_float(), get_float(), get_float())

func get_color_on_gradient(gradient : Gradient) -> Color:
	return gradient.sample(get_float())


### dice / coin

func roll(n : int = 3, sides : int = 6) -> int:
	return range(n).reduce(func(total, _idx): return total + get_int(1, sides), 0)

func toss_coin() -> bool:
	return (get_int() < 50)


### vectors

func from_vector2(from : Vector2 = Vector2.ZERO, to : Vector2 = Vector2.ONE) -> Vector2:
	return Vector2(get_float(from.x, to.x), get_float(from.y, to.y))
func from_vector2i(from : Vector2i = Vector2i.ZERO, to : Vector2i = Vector2i.ONE) -> Vector2i:
	return Vector2i(get_int(from.x, to.x), get_int(from.y, to.y))
	
func from_vector3(from : Vector3 = Vector3.ZERO, to : Vector3 = Vector3.ONE) -> Vector3:
	return Vector3(get_float(from.x, to.x), get_float(from.y, to.y), get_float(from.z, to.z))
func from_vector3i(from : Vector3i = Vector3i.ZERO, to : Vector3i = Vector3i.ONE) -> Vector3i:
	return Vector3i(get_int(from.x, to.x), get_int(from.y, to.y), get_int(from.z, to.z))

# flip the arguments as it's often more useful to generate from 0->x and godot isn't capable of passing named arguments
func to_vector2(to : Vector2 = Vector2.ONE, from : Vector2 = Vector2.ZERO) -> Vector2:
	return from_vector2(from, to)
func to_vector2i(to : Vector2i = Vector2i.ONE, from : Vector2i = Vector2i.ZERO) -> Vector2i:
	return from_vector2i(from, to)

func to_vector3(to : Vector3 = Vector3.ONE, from : Vector3 = Vector3.ZERO) -> Vector3:
	return from_vector3(from, to)
func to_vector3i(to : Vector3i = Vector3i.ONE, from : Vector3i = Vector3i.ZERO) -> Vector3i:
	return from_vector3i(from, to)


### arrays / dicts 

func from_array(_array : Array):
	var idx = get_int(0, _array.size()-1)
	return _array[idx]

func from_dictionary(_dict : Dictionary):
	var idx = get_int(0, _dict.keys().size()-1)
	return _dict[_dict.keys()[idx]]

Test Script

And a quick test script to demonstrate - attach it any Node and check the console:

extends Node

var array = [1,2,"apple",4,5,"fish",7,8,9,"banana"]

func _ready() -> void:
  var test1 = Random.new()
  var test2 = Random.new()
  
  test1.set_seed(123)
  test_array("a", test1, 5)
  
  print("globally called random in the middle = %s\n" % randf())
  
  test_array("b", test1, 5)
  
  test2.set_seed(42)
  test_array("c", test2, 10)
  
  test1.set_seed(123)
  test_array("d", test1, 5)
  
  print("globally called random in the middle = %s\n" % randf())
  
  test2.set_seed(42)
  test_array("e", test2, 10)
  
  test_array("f", test1, 5)
  
func test_array(id : String, rng : Random, length : int = 3):
  print("--- test (%s) seed = %s ---" % [id, rng.get_seed()])
  var test = range(0, length).reduce(func(out, _idx): return "%s %s" % [out, rng.from_array(array)], "")
  print(test+"\n")

Running that test gives me output of:

--- test (a) seed = 123 ---
 5 banana 1 fish fish

globally called random in the middle = 0.8317563533783

--- test (b) seed = 123 ---
 5 5 banana 4 apple

--- test (c) seed = 42 ---
 8 9 1 fish apple 7 9 4 banana 2

--- test (d) seed = 123 ---
 5 banana 1 fish fish

globally called random in the middle = 0.49645981192589

--- test (e) seed = 42 ---
 8 9 1 fish apple 7 9 4 banana 2
 
--- test (f) seed = 123 ---
 5 5 banana 4 apple

Note that your own test output will be different from mine, but all your (a+d), (c+e) and (b+f) pairs should match.


Reply via email