Hey @Karim-Akra how are you? Welcome to the forums!
You don’t need Verse Field Binding at all here, and that’s exactly why validation is rejecting your material.
Field Binding is meant for UMG widgets and view bindings, not for Materials. When you bind a material parameter, UEFN stores internal binding objects inside the “.uasset”, and those live in editor-only modules that never ship to Fortnite. So the validator refuses the asset. That’s your three errors.
Materials already expose their parameters to Verse on their own, through Asset Reflection. No binding needed.
Let me explain how to use that:
1. Set the Verse binding back to None on every parameter, then Save All. This matters, otherwise the disallowed reference stays baked in. If validation still complains, make a fresh material and rebuild the graph. Keep your Param nodes as they are, your UV math is already right.
2. Build Verse Code and open your “Assets.digest.verse”. Your material is in there:
NewFolder := module:
Mats := module:
Animated2DMat_material<scoped {...}> := class<scoped {...}>(material):
@editable
var FrameW<public>:float = external {}
@editable
var FrameX<public>:float = external {}
var Sampler2D<public>:texture = external {}
The fields are “var”, so you can write them at runtime. And each subfolder became a Verse module, so yours lives at “NewFolder.Mats.Animated2DMat_material”.
3. Folders generate “internal” modules, so that material is not reachable from a Verse file at your project root. You have to re-declare the chain, or you get around 50 cascade errors saying things like “This assignment expects a value of type false”, which point nowhere near the real cause:
NewFolder := module:
Mats<public> := module {}
Only “Mats” needs “public”, since “NewFolder” is already a sibling of your device file.
4. Instantiate the material, apply it once, then write the parameters:
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Folder-generated modules are <internal> by default, so a material inside
# Content/NewFolder/Mats is not reachable from a file at the project root.
# Re-declaring the module chain with <public> fixes it.
NewFolder := module:
Mats<public> := module {}
# One frame rect, for sheets that are NOT a uniform grid
sprite_frame := struct:
X : float = 0.0
Y : float = 0.0
W : float = 0.25
H : float = 0.25
spritesheet_syntax_test := class(creative_device):
@editable
TargetProp : creative_prop = creative_prop{}
# The material instance, created from the digest class
AnimMat : NewFolder.Mats.Animated2DMat_material = NewFolder.Mats.Animated2DMat_material{}
@editable
Columns : int = 4
@editable
Rows : int = 4
@editable
SecondsPerFrame : float = 0.25
# Frame rects, used by PlayFrameList below
Frames : []sprite_frame = array{sprite_frame{X := 0.0, Y := 0.0, W := 0.25, H := 0.25}, sprite_frame{X := 0.25, Y := 0.0, W := 0.25, H := 0.25}, sprite_frame{X := 0.5, Y := 0.0, W := 0.25, H := 0.25}}
OnBegin<override>()<suspends> : void =
# You only apply the material once
TargetProp.SetMaterial(AnimMat)
set AnimMat.FrameW = 0.25
set AnimMat.FrameH = 0.25
set AnimMat.FrameX = 0.50
set AnimMat.FrameY = 0.25
Print("Material applied")
# optional index overload
TargetProp.SetMaterial(AnimMat, ?Index := 0)
# Plays the frame list once
PlayFrameList()
Print("Frame list done")
# Then loops the uniform grid forever
Animate()
# Uniform grid, driven by two counters
Animate()<suspends> : void =
FrameW : float = 1.0 / (Columns * 1.0)
FrameH : float = 1.0 / (Rows * 1.0)
set AnimMat.FrameW = FrameW
set AnimMat.FrameH = FrameH
var Col : int = 0
var Row : int = 0
loop:
# int * float gives float
set AnimMat.FrameX = Col * FrameW
set AnimMat.FrameY = Row * FrameH
Print("Col={Col} Row={Row}")
set Col += 1
if (Col >= Columns):
set Col = 0
set Row += 1
if (Row >= Rows):
set Row = 0
Sleep(SecondsPerFrame)
# Non-uniform frames, one rect per entry
PlayFrameList()<suspends> : void =
for (Frame : Frames):
set AnimMat.FrameW = Frame.W
set AnimMat.FrameH = Frame.H
set AnimMat.FrameX = Frame.X
set AnimMat.FrameY = Frame.Y
Sleep(SecondsPerFrame)
“Animate()” is the uniform grid version. “PlayFrameList()” is for arbitrary rects, which is probably closer to what you want since you mentioned X, Y, Width and Height. Use whichever fits your sheet, and change “SecondsPerFrame” in the device details to speed it up or slow it down.
Two things about the math: Verse has no implicit int to float conversion, so you multiply by “1.0”, and integer division is failable, so it’s easier not to divide at all.
About Blend Mode
If you wire the sampler’s “A” into Opacity with Blend Mode on Translucent, which is correct for a real sprite sheet, but you test with a texture that has no useful alpha, the prop turns completely invisible and it looks like nothing works. The Verse side should be running fine, you just can’t see it. Use Opaque while testing, which is what the video below uses, and switch back to Translucent once your sheet is in.
A few extra things
- Types map like this: scalar to “float”, vector4 to “color” (RGB only, add a scalar if you need alpha), texture to “texture”. So you can swap “Sampler2D” at runtime and keep several sheets on one material.
- If your prop looks unchanged, check the material slot before anything else. “SetMaterial(AnimMat, ?Index := 1)” targets a different slot.
- The same works on a Scene Graph “mesh_component”, or on a “material_block” for UI.
- Follow Verse naming rules on asset and folder names, or they get skipped in the digest.
- The “Print” calls are there on purpose. If the logs keep advancing but the prop stays still, the problem is the material or the slot, not the Verse.
This is how it looks:
Hope this works for you! Let me know if you need more help!