In this section, we will introduce the technical details of how Resident NFT are generated, as well as how we store a fully animated NFT on the Sui blockchain.
Randomness
The skin color of the resident is determined by the main color and secondary color. By randomly generating the main color, we can have a vast array of residents with diverse skin tones!
Colors sets total combination:
By controlling the offset of RGB color values, we can ensure that each randomly generated main color and secondary color falls within a controllable range. This ensures that the skin tones of the generated residents will not be too strange.
/// A code snippet that generate random color
let r_m = makeRandomRGB(random_key, object_bytes,b"r_m");
let g_m = makeRandomRGB(random_key, object_bytes,b"g_m");
let b_m = makeRandomRGB(random_key, object_bytes,b"b_m");
let contrast_color = rgbToHex(255-r_m, 255-g_m, 255-b_m);
(rgbToHex(r_m,g_m,b_m), rgbToHex(r_s,g_s,b_s), contrast_color)
Image storage
We use SVG as the format for storing NFTs. By leveraging Sui's object display feature, we can associate the Resident object ID with the SVG image that needs to be displayed.
We will use this resident NFT as an example.
We can break down the aforementioned SVG into two parts:
Frame: Indicates the numerical values of the Resident's attributes, as well as the rarity represented by corresponding colors.
Animation: Represents the animated part of the Resident.
Frame
In the frame, the properties with corresponding numerical values and the rarity colors are rendered based on the Resident's object fields.
Level - 1
Rating - 9999
Rarity - UR
Frame Border - #E50C0C (UR Color)
/// A code snippet that associates the properties of object fields with SVG
public fun create_display<T: key>(publisher: &Publisher, identity_index: u64, ctx: &mut TxContext){
let keys = vector[
utf8(b"image_url"),
utf8(b"Lv."),
utf8(b"rating"),
utf8(b"identity"),
];
let image_metadata = identity_source::generateSVG(identity_index,b"{rarity_color}", b"{level}", b"{rarity}", b"{rating}", b"{main_color}", b"{sec_color}", b"{contrast_color}");
let values = vector[
utf8(image_metadata),
utf8(b"{level}"),
utf8(b"{rating}"),
utf8(b"{identity}"),
];
let display = display::new_with_fields<T>(
publisher, keys, values, ctx
);
display::update_version(&mut display);
transfer::public_transfer(display, tx_context::sender(ctx));
}
Additionally, we have published a corresponding contract module for each type of Resident identity to store the compressed frame images corresponding to that identity (see the Animation below).
/// A simple code snippet of dungeon_resource
module dungeon_resource_arcane_trickster::svg {
use std::vector;
const Path1:vector<u8> = b"...";
// {{rarity-color}}
const Path2:vector<u8> = b"...";
// {{level}}
const Path3:vector<u8> = b"..."
// {{rarity-string}} N R SR SSR UR
const Path4:vector<u8> = b"...";
// {{rarity-color}}
const Path5:vector<u8> = b"...";
// {{rating}}
const Path6:vector<u8> = b"...";
// {{rarity-color}}
const Path7:vector<u8> = b"...";
// {{sec_color}}
const Path8:vector<u8> = b"...";
// {{main_color}}
const Path9:vector<u8> = b"...";
public fun generateSVG(rarity_color:vector<u8>, ..., sec_color:vector<u8>):vector<u8>{
let metadata = Path1;
vector::append(&mut metadata, rarity_color);
vector::append(&mut metadata, Path2);
.
.
.
vector::append(&mut metadata, main_color);
vector::append(&mut metadata, Path9);
metadata
}
}
Animation
As you can see, the idle animation of the Resident is actually composed of 4 frames of images. By controlling with CSS within the SVG, the animation effect of the Resident can be created from these four frames.
@keyframes animation {
from {
background-position-x:0;
}
to {
background-position-x:-128px;
}
}
We have created a unique compression method to compress these frame images, maximizing the reduction in file size, allowing them to be fully stored on the blockchain at a low cost.