【前言】
最後这个 Deploy NFT 才是真正真正真正的大魔王,比我想像中还要难超级多,难到我现在都不知道前言要打什麽了。只能放上一些梗图娱乐自己…
【Deploy NFT Flow】
这边基本规划一下要把自己的 NFT 发行,并且在网页之中让大家 mint 的流程是如何。
Day | Class | Description |
---|---|---|
Day 25 | Layers Blending & MetaData | 0. Day 25~ Day 29 Deploy NFT 规划 |
1.产 MetaData + 合图 | ||
Day 26 | Structuring Smart Contract | 0. 介绍 Lazy Mint |
1. 建置智能合约 | ||
Day 27 | Deploy on Testnet | 0. 上传测试资料至 IPFS |
1. Testnet 和测试 Mint | ||
Day 28 | Deploy the Lazy Mint in Website | 0. 将 Mint 实作在网站中 |
1. 并且测试在网站中 Mint 的功能 | ||
Day 29 | Deploy on Mainnet | 0. 上链主网 |
1. Opensea Collection 调整 |
【转换战场 - config.js
】
好,这边因为 JavaScript 比较适合生产,套用在网页上也比较方便,所以我们把原本 Day 24 的功能从 Python 移到这里。
首先我们要有一个 config.js
来储存一些 common 的变数以及资料。
const fs = require("fs");
const width = 1000; // 图片的长宽
const height = 1000;
const dir = __dirname;
const baseImageUri = "..."; // 图片最後的地址,这我们明天再来讲
const startEditionFrom = 0; // 第一个 NFT 的编号
const endEditionAt = 999; // 最後一个 NFT 的编号
const editionSize = 1000; // 总生产数量
const description = ""; // NFT 上要呈现的介绍或叙述
const raceWeights = [ // 种族,这边因为只有一种种族所以就只有一个
{
value: "DNMS_Beta",
from: 0,
to: editionSize,
},
];
种族的全部部件资料,那因为我们只有一个叫做 DNMS_Beta
的种族,里面会有好几个图层,每个涂层里面又有数个不同款式的部件。像是我们有 13 种背景颜色,里面再放入一些相关数据。
const races = {
DNMS_Beta: {
name: "DNMS_Beta",
layers: [
//-------------------------------------------//
{
name: "background",
elements: [
{
id: 0,
name: "Indigo",
path: `${dir}/part_image/13-background/background_1_none_none_Indigo_2.png`,
shape: "None",
color: "None",
possibility: "2", // 稀有度
},
...
{
id: 12,
name: "BabyBlue",
path: `${dir}/part_image/13-background/background_13_none_none_BabyBlue_3.png`,
shape: "None",
color: "None",
possibility: "3",
},
],
position: { x: 0, y: 0 },
size: { width: width, height: height },
number: 13, // 总共有 13 种 background
}, {
name: "effect",
elements: [
{
id: 0,
name: "BlackRadical",
path: `${dir}/part_image/12-effect/effect_1_none_none_BlackRadical_5.png`,
shape: "None",
color: "None",
possibility: "5",
},
...
],
...
},
...
//-------------------------------------------//
],
},
};
这边把所有部件档案输出成 .json 的格式我是用 python 写的,因为重点是合图以及 MetaData 所以就不多加赘述了!
接下来依然是一些需要用到的变数,稀有度、创作者自留特定款式、分岛屿这些都是一些普通 NFT 没有的功能,多做了这些东西不知道多花我多少时间!
const poss = {
1: { "Rarity": "Mythic", "Possibility": "3.1~4.9" },
2: { "Rarity": "Legendary", "Possibility": "6.4~10.6" },
3: { "Rarity": "Epic", "Possibility": "12.5~19.4" },
4: { "Rarity": "Rare", "Possibility": "20.6~30.4" },
5: { "Rarity": "Normal", "Possibility": "33.2~40.8" }
} // 稀有度的加权
let Creater_Data = {
LU: { "dna": ["08", "00", "03", "32", "08", "02", "32", "08", "09", "06", "05", "20", "08"], "id": "0102" },
...
} // 创始者我们会 MINT 自订的款式,这边的 dna 等下会说到。
const island = ["Ace", "Wanderer", "Muse", "Da_Kine"] // Dino 会住在四个岛上。
module.exports = { // 将以上的所有变数都输出,在 index.js 里面会用到
width,
height,
baseImageUri,
editionSize,
description,
startEditionFrom,
endEditionAt,
races,
raceWeights,
poss,
Creater_Data,
island
};
【转换战场 - index.js
】
接下来就是主战场了,首先先把刚刚输出的变数都引入,还有宣告一些变数,还有我们主要拿来处理图片的 canvas
功能。
const fs = require("fs");
const { createCanvas, loadImage } = require("canvas");
const {
width,
...
} = require("./input/DinoConfig.js");
const console = require("console");
const canvas = createCanvas(width, height);
const ctx = canvas.getContext("2d");
var metadataList = []; // 全部的 Dino 的 MetaData
var attributesList = []; // 当前这只 Dino 拥有的属性
var dnaList = []; // 当前这只 Dino 拥有的 dna
在这边 dna
会是一个阵列,里面储存着十三个字串,分别代表着十三个图层我们取用了哪一个款式的部件。除了可以利用 dna
来快速取得部件,也可以用来判断有没有重复的部 Dino。
///////////////////////////////
// For DNA
///////////////////////////////
const createDna = (_races, _race) => {
let randNum = [];
let sh = "None";
let co = "None";
_races[_race].layers.forEach((layer) => {
let consider = Randomize(layer);
let index = Math.floor(Math.random() * parseInt(consider.length));
let randElementNum = consider[index];
// Start to decide shape here!!!
...
// Start to decide color here!!!
...
// 这两个部分其实就是跑一个 while 回圈直到 RANDOM 出来的款式符合同一组开合和皮肤颜色
randNum.push(randElementNum);
});
return randNum;
};
const isDnaUnique = (_DnaList = [], _dna = []) => {
let foundDna = _DnaList.find((i) => i.join("") === _dna.join(""));
return foundDna == undefined ? true : false;
};
这里主要的重点其实是要怎麽把权重考量进去。我们设定了每个部件的加权,如果是比较稀有的款式那他的加权就会比较少,那这样他出现的机率就会降低。
///////////////////////////////
// For Shuffle and Random
///////////////////////////////
function shuffle(array) {
// refference: https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle...
while (currentIndex != 0) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
const Randomize = (layer) => {
let total = layer.number;
let considerList = [];
for(let i = 0; i < total; i++){
let now_pos = poss[parseInt(layer.elements[i].possibility)].Possibility;
now_pos = now_pos.split("~");
now_pos = Math.floor(Math.random() * (now_pos[1] - now_pos[0] + 1)) + now_pos[1];
for (let j = 0; j < now_pos; j++){
considerList.push(layer.elements[i].id);
}
}
shuffle(considerList);
return considerList;
}
再来就是生产 MetaData 以及写档。
///////////////////////////////
// For MetaData
///////////////////////////////
const addMetadata = (_dna, _edition) => {
let dateTime = Date.now();
let tempMetadata = {
dna: _dna.join(""),
name: `#${_edition}`,
image: `${baseImageUri}/${_edition}.png`,
description: description,
id: _edition,
island: Born_Location(_dna),
birthday: dateTime,
attributes: attributesList,
};
metadataList.push(tempMetadata);
attributesList = [];
};
const addAttributes = (_element) => {
let selectedElement = _element.layer.selectedElement;
attributesList.push({
trait_type: _element.layer.name,
type_name: selectedElement.name,
rarity: poss[selectedElement.possibility].Rarity
});
};
const writeMetaData = (_data) => {
fs.writeFileSync("./output/_metadata.json", _data);
};
const saveMetaDataSingleFile = (_editionCount) => {
fs.writeFileSync(
`./output/${_editionCount}.json`,
JSON.stringify(metadataList.find((meta) => meta.id == _editionCount))
);
};
function Born_Location(arr) {
var born = 0;
for (var i = 0; i < arr.length; i++) {
born += parseInt(arr[i]);
};
return island[born % 4];
}
接下来是读取部件图片,合图,挂上标签,以及存档。
///////////////////////////////
// For Drawing
///////////////////////////////
// 存档
const saveImage = (_editionCount) => {
fs.writeFileSync(
`./output/${_editionCount}.png`,
canvas.toBuffer("image/png")
);
};
// 在 NFT 的左上角标上他的 id
const signImage = (_sig) => {
ctx.fillStyle = "#ffffff";
ctx.font = "bold 30pt Verdana";
ctx.textBaseline = "top";
ctx.textAlign = "left";
ctx.fillText(_sig, 40, 40);
};
// 读取部件图档
const loadLayerImg = async (_layer) => {
return new Promise(async (resolve) => {
const image = await loadImage(`${_layer.selectedElement.path}`);
resolve({ layer: _layer, loadedImage: image });
});
};
// 将当前图层铺上去
const drawElement = (_element) => {
ctx.drawImage(
_element.loadedImage,
_element.layer.position.x,
_element.layer.position.y,
_element.layer.size.width,
_element.layer.size.height
);
addAttributes(_element);
};
// 取出所选部件的资料
const constructLayerToDna = (_dna = [], _races = [], _race) => {
let mappedDnaToLayers = _races[_race].layers.map((layer, index) => {
let selectedElement = layer.elements.find((e) => e.id == parseInt(_dna[index]));
return {
name: layer.name,
position: layer.position,
size: layer.size,
number: layer.number,
selectedElement: selectedElement,
};
});
return mappedDnaToLayers;
};
等上面的函数都准备好了之後,就可以开始合图的!
///////////////////////////////
// startCreating
///////////////////////////////
// 因为我们只有一个种族,所以这个函数用不到
const getRace = (_editionCount) => {
let race = "No Race";
raceWeights.forEach((raceWeight) => {
// if (_editionCount >= raceWeight.from && _editionCount <= raceWeight.to) {
race = raceWeight.value;
// }
});
return race;
};
const startCreating = async () => {
writeMetaData("");
let editionCount = startEditionFrom;
// 跑一个回圈
while (editionCount <= endEditionAt) {
let race = getRace(editionCount);
// 决定 dna,如果是指定的 creator_id 要特别设定 dna
let newDna = [];
if(editionCount === parseInt(Creater_Data.LU.id)){
newDna = Creater_Data.LU.dna;
}
...
else{
newDna = createDna(races, race);
}
// 确认没有重复的 dna
if (isDnaUnique(dnaList, newDna)) {
let results = constructLayerToDna(newDna, races, race);
let loadedElements = []; //promise array
results.forEach((layer) => {
loadedElements.push(loadLayerImg(layer));
ReviseStatic(layer);
});
await Promise.all(loadedElements).then((elementArray) => {
ctx.clearRect(0, 0, width, height);
elementArray.forEach((element) => {
drawElement(element);
});
signImage(`#${editionCount}`);
saveImage(editionCount);
addMetadata(newDna, editionCount);
saveMetaDataSingleFile(editionCount);
// island = Born_Location(newDna);
let is = Born_Location(newDna);
console.log(
`Created DINO-ID: ${editionCount}, Race: ${race} with DNA: ${newDna} at Island: ${is}`
);
});
dnaList.push(newDna);
editionCount++;
} else {
console.log("DNA exists!");
}
}
writeMetaData(JSON.stringify(metadataList));
PrintStatic();
};
startCreating();
【小结】
这边改良自 HashLips 大大的 Project,真的非常感谢网路上有这麽又强又乐意分享的大神,有了很棒的 Base 之後我就可以加上很多自己想要加的东西!不过还是遇到超多 Bug,尤其是我又想了一堆奇奇怪怪的功能,真是拿石头砸自己脚。
明天开始会进到智能合约,真的是要替自己好好祈祷还有鼓励,好难啊!
【参考资料】
Code generative art for NFT in node.js part 1
今天开始就要来利用专题了解 GAS 的各项功能罗~ 说到要能优雅、或是狼狈的…总之要能够享受下午茶,...
了解盒模型後,就要为标签套上各种花样了,上一篇介绍了简单的套用方法,但这个套用方法其实不太好用! ...
推荐扩充套件 Color Highlight 这边跟大家推荐 Color Highlight 这个扩...
前言 在 OOP 的世界里,我们常常会听到高内聚(Cohesion),低耦合(Coupling),以...
Review 前一篇文章我们谈到了如何实作一个 Maybe Monad,而其主要的功能就是处理无值的...