How i can use spitesheet for texure 3d model in Godot 4

Good evening. I want to write a script to randomize the texture of a 3D model (MeshInnstance) in Godot 4 using Spritesheet (each texture is 18 by 18 pixels in size, the width of the image with textures is 54, with a height of 18 pixels).

@onready var Material_texture_block = $"../Куб"  
@onready var Random_texture_block = $"."
var frames: int
func _ready() -> void:
  frames = int(texture.get_width() / region_rect.size.x)
  var random_index = randi_range(0, frames - 1)
  region_rect.position.x = float(random_index * region_rect.size.x)
  if Material_texture_block == null:
    return
    var material = Material_texture_block.get_surface_override_material(0)
  if material == null:
    material = StandardMaterial3D.new()  Material_texture_block.set_surface_override_material(0, material)
  if material is StandardMaterial3D:
    var image = texture.get_image()
    var cropped_image = Image.new()     cropped_image.copy_from(image) 
    var crop_rect = Rect2i(    int(region_rect.position.x),  int(region_rect.position.y),  int(region_rect.size.x),    int(region_rect.size.y)
    )
cropped_image.crop(crop_rect)
    var new_texture = ImageTexture.create_from_image(cropped_image)  
  material.albedo_texture = new_texture

At the moment, the previous texture image remains unchanged and does not change. How can I fix the script so that it works correctly?

You’re assigning texture to a material, but is that material being assigned to the model afterwards?

Actually, maybe it is? There’s some formatting going on with the code above.

Have you tried displaying your new texture to make sure you’re getting what you expect in new_texture?

If your UVs are set up correctly you can scale the texture coordinates.

@export var h_frames: int = 3
@export var v_frames: int = 1

func _ready() -> void:
	var material := StandardMaterial3D.new()

	# scale material based on frame count
	material.uv1_scale = Vector3(1.0/h_frames, 1.0/v_frames, 1.0)

	# picking random frames
	var random_h_frame: int = randi_range(0, h_frames)
	var random_v_frame: int = randi_range(0, v_frames)

	# offset determined by `frame * scale`
	var x_offset: float = random_h_frame * material.uv1_scale.x
	var y_offset: float = random_v_frame * material.uv1_scale.y
	material.uv1_offset = Vector3(x_offset, y_offset, 0.0)

Cropping a new image is a expensive operation. Though .crop should take a rect, it only takes a width and height. Hence “expected 2 arguments but recieved 1”, it wants two integers.

should this script be attached to the MeshInstance3D node or to the Node3D node?

which ever, the script does nothing on it’s own, you still have to fill in what to do with the material.


Thanks, but I don’t understand how I can write a texture assignment for a model.

It’ll be the model’s material’s texture, probably better to put script on one of the two you asked about, a sibling could produce strange results on-ready

# assuming on the root node "block_test"
material.albedo_texture = load("your texture file.png") # or through another @export

$Ky6.material_override = material

Thank you. I’m sorry, I’m still new to working in the Godot 4 3D project/ Do I need to write these lines in the MeshInstance script or still in the Sprite2D script?

In that example, since it’s using the path $Ky6 it assumes that your MeshInstance3D named “Ky6” is a child of the script, so the only node that could have that script is the root node “block_test”

If you change the path to $"../Ky6" then it could work on the Sprite2D script still, as that path goes up a parent (../) then down to a sibling (Ky6)

1 Like