Trying to randomize dice faces when it rolls

Godot Version

4.6.1.stable

Question

I’m trying to randomize what frames are shown on screen as the dice ‘rolls’. In the _on_roll_dice_pressed() method, I had the code that gets a random frame for each animatedsprite2D stored in an array, but that line of code doesn’t work for some reason.

extends Node2D

var num_of_columns: int = 2
var num_of_rows: int = 2
var dice_grid_size: int

var ghost_dice_bag
var placed_animated_sprites = []
var roll_balance: int
var total_balance: int

@onready var balance_amt_label: Label = $"../Main_CanvasLayer/VBoxContainer/HBoxContainer/Balance"
@onready var animated_sprite_2d: AnimatedSprite2D = $"../AnimatedSprite2D"
@onready var timer: Timer = $"../Timer"

# Called when the node enters the scene tree for the first time.
func _ready():
	places_dice()
	
func places_dice():
	ghost_dice_bag = Globals.dice_bag.duplicate()
	dice_grid()
	
	#Resets the array, so a new set of dice can be placed on the grid
	placed_animated_sprites.clear()
	
	for i in range(0, num_of_columns):
		for j in range(0, num_of_rows):
			
			#if statement runs only if the dice bag is not empty
			if ghost_dice_bag.is_empty() == false:
				var die_key = ghost_dice_bag.pick_random()
				var die_node = animated_sprite_2d.duplicate()
				die_node.animation = die_key
				die_node.visible = true
				placed_animated_sprites.append(die_node)
				add_child(die_node)
				
				#Places the dice in a grid formation
				die_node.position.y = 80 + ((i+1)*16)
				die_node.position.x = 600 + ((j+1)*16)
				
				#Ensures the same die doesn't get choosen twice
				ghost_dice_bag.erase(die_key)
				
				#Places the die on a random frame
				var frame_count = die_node.sprite_frames.get_frame_count(die_key)
				die_node.frame = randi_range(0, frame_count)

func dice_grid():
	if (num_of_columns * num_of_rows) > Globals.dice_bag.size():
		dice_grid_size = Globals.dice_bag.size()
	else:
		dice_grid_size = num_of_columns * num_of_rows

func _on_roll_dice_pressed():
	#Clears the dice from the screen. Without this, the dice sprite would be placed over each other
	for child in get_children():
		child.queue_free()
		
	#Places new dice
	places_dice()
	
	#Starts rolling animation of each dice placed
	timer.start()
	for key in placed_animated_sprites:
		#Places the die on a random frame
		var frame_count = key.sprite_frames.get_frame_count(key) #<---doesn't work
		key.frame = randi_range(0, frame_count)

func _on_timer_timeout():
	for key in placed_animated_sprites:
		key.stop()
		#Calculates 'income' from dice roll
		roll_balance += (key.frame + 1)
		
	#Adds 'income' to total balance
	total_balance += roll_balance
	
	#Resets dice roll 'income'
	roll_balance = 0
	
	#Updates balance shown on screen
	balance_amt_label.text = str(total_balance)

Below is the code I have for Globals:

extends Node

#Stores dice that are available for rolling, name of die must be the same name as its animation
var dice_bag = [
	"D2-plastic",
	"D6-gold",
]

You’re passing key, which is an AnimatedSprite2D, as an argument of get_frame_count() method, which expects a StringName of an animation.
You probably have a type mismatch error in the console telling you that.

I suggest you to start adding some type hints into your scripts, it will make it much easier to debug.

1 Like

Thank you!

So I implemented your solution did make the game work, but another issue came up. When I ‘roll dice’ button gets pressed, I just see the final frame of the AnimatedSprite2D onscreen. I did add a “print(key)” and I do see in the output the AnimatedSprite2D comments (ex. @AnimatedSprite2D@3:<AnimatedSprite2D#30819747335>). So the ‘while’ statement does seem to work but I’m seeing only the last frame on screen, instead of each randomized frame being replaced with the previous one (and hence, creating the illusion that the dice is rolling). Not sure how to fix this.

extends Node2D

var num_of_columns: int = 2
var num_of_rows: int = 2
var dice_grid_size: int

var ghost_dice_bag
var placed_animated_sprites = []
var roll_balance: int
var total_balance: int
var duration: float

@onready var balance_amt_label: Label = $"../Main_CanvasLayer/VBoxContainer/HBoxContainer/Balance"
@onready var animated_sprite_2d: AnimatedSprite2D = $"../AnimatedSprite2D"

# Called when the node enters the scene tree for the first time.
func _ready():
	places_dice()
	
func places_dice():
	ghost_dice_bag = Globals.dice_bag.duplicate()
	dice_grid()
	
	#Resets the array, so a new set of dice can be placed on the grid
	placed_animated_sprites.clear()
	
	for i in range(0, num_of_columns):
		for j in range(0, num_of_rows):
			
			#if statement runs only if the dice bag is not empty
			if ghost_dice_bag.is_empty() == false:
				var die_key = ghost_dice_bag.pick_random()
				var die_node = animated_sprite_2d.duplicate()
				die_node.animation = die_key
				die_node.visible = true
				placed_animated_sprites.append(die_node)
				add_child(die_node)
				
				#Places the dice in a grid formation
				die_node.position.y = 80 + ((i+1)*16)
				die_node.position.x = 600 + ((j+1)*16)
				
				#Ensures the same die doesn't get choosen twice
				ghost_dice_bag.erase(die_key)
				
				#Places the die on a random frame
				var frame_count = die_node.sprite_frames.get_frame_count(die_key)
				die_node.frame = randi_range(0, frame_count)

func dice_grid():
	if (num_of_columns * num_of_rows) > Globals.dice_bag.size():
		dice_grid_size = Globals.dice_bag.size()
	else:
		dice_grid_size = num_of_columns * num_of_rows

func _on_roll_dice_pressed():
	#Clears the dice from the screen. Without this, the dice sprite would be placed over each other
	for child in get_children():
		child.queue_free()
		
	#Places new dice
	places_dice()
	
	rolling_dice()
	calculate_income()

func rolling_dice():
	while duration < 1.0:
		for key in placed_animated_sprites:
			for die in Globals.dice_bag:
				#Places the die on a random frame
				var frame_count = key.sprite_frames.get_frame_count(die)
				print(key)
				key.frame = randi_range(0, frame_count)
		duration += 0.1
	
func calculate_income():
	for key in placed_animated_sprites:
		#Calculates 'income' from dice roll
		roll_balance += (key.frame + 1)
		
	#Adds 'income' to total balance
	total_balance += roll_balance
	
	#Resets dice roll 'income'
	roll_balance = 0
	
	#Updates balance shown on screen
	balance_amt_label.text = str(total_balance)

Your whole while is being executed within the same frame. You can add await get_tree().create_timer(0.1).timeout within your while loop to make it actually wait.
However, I would consider moving this to the _process() or creating a dedicated Timer instead of while and await.

Thank you. A combination of a timer and the _process method worked, appreciate it!

1 Like