cryptozombies6-总结、感谢与建议

第5章:MetaMask 与账户

太棒了!你已经成功编写了前端代码,能够与第一个智能合约进行交互。

现在,让我们把一些部分组合起来——假设我们希望我们的应用首页显示用户的整个僵尸军队。

显然,我们首先需要使用 getZombiesByOwner(owner) 函数来查找当前用户拥有的所有僵尸的 ID。

但是,我们的 Solidity 合约期望传入的 owner 是一个 Solidity 地址。那我们怎么知道使用我们应用的用户的地址呢?

获取用户的 MetaMask 账户

MetaMask 允许用户在其扩展中管理多个账户。

我们可以通过以下方式查看当前活跃的账户:

1
var userAccount = web3.eth.accounts[0]

由于用户可以随时在 MetaMask 中切换活跃账户,我们的应用需要监控这个变量,看看它是否发生了变化,并相应地更新 UI。例如,如果用户的首页显示他们的僵尸军队,当他们在 MetaMask 中切换账户时,我们需要更新页面以显示他们为新账户选择的僵尸军队。

我们可以通过 setInterval 循环来实现这一点,如下所示:

1
2
3
4
5
6
7
8
var accountInterval = setInterval(function() {
// 检查账户是否已更改
if (web3.eth.accounts[0] !== userAccount) {
userAccount = web3.eth.accounts[0];
// 调用某个函数来更新 UI 显示新账户
updateInterface();
}
}, 100);

这个代码的作用是每隔 100 毫秒检查 userAccount 是否仍然等于 web3.eth.accounts[0](即用户是否仍然激活那个账户)。如果不是,它会将 userAccount 重新赋值为当前激活的账户,并调用一个函数来更新显示。

动手实践

让我们使我们的应用在页面加载时显示用户的僵尸军队,并监控 MetaMask 中的活跃账户,若账户更改则刷新显示。

  1. 声明一个名为 userAccount 的变量,但不赋值。
  2. startApp() 的最后,复制/粘贴上述的 accountInterval 模板代码。
  3. updateInterface(); 这一行替换为调用 getZombiesByOwner 并传递 userAccount
  4. getZombiesByOwner 后面链式调用一个 then 语句,并将结果传递给一个名为 displayZombies 的函数。(语法是:.then(displayZombies);

我们还没有 displayZombies 这个函数,但我们将在下一章中实现它。

第6章:展示我们的僵尸军队

如果我们不展示如何实际显示从合约中获取的数据,那么这个教程就不完整了。

然而,现实中,你会希望在应用中使用一个前端框架,如 React 或 Vue.js,因为它们能让你作为前端开发者的工作变得更加轻松。但介绍 React 或 Vue.js 远远超出了本教程的范围——那本身会是一个包含多个课时的完整教程。

因此,为了保持 CryptoZombies.io 专注于以太坊和智能合约,我们将通过一个简单的 JQuery 示例,展示如何解析和显示从智能合约中获取的数据。

展示僵尸数据——一个粗略示例

我们已经在文档的 <body> 部分添加了一个空的 <div id="zombies"></div>,以及一个空的 displayZombies 函数。

回想一下,在上一章中,我们在 startApp() 中调用了 displayZombies,并将调用 getZombiesByOwner 的结果传递给它。它将传递一个僵尸 ID 数组,类似于:

1
[0, 13, 47]

因此,我们希望我们的 displayZombies 函数:

  1. 首先清空 #zombies div 的内容,若里面已经有东西。(这样如果用户更改了他们的 MetaMask 活跃账户,它会在加载新账户的僵尸军队之前清空旧的军队)。
  2. 遍历每个 ID,并为每个 ID 调用 getZombieDetails(id),从智能合约中查找该僵尸的所有信息,然后
  3. 将该僵尸的信息放入一个 HTML 模板中,格式化后将其附加到 #zombies div。

再次提醒,这里我们仅使用 JQuery,它默认没有模板引擎,因此这看起来会比较简陋。下面是一个简单的示例,展示我们如何为每个僵尸输出这些数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 从合约中查找僵尸详情。返回一个 `zombie` 对象
getZombieDetails(id)
.then(function(zombie) {
// 使用 ES6 的 "模板字面量" 将变量插入到 HTML 中。
// 将每个僵尸追加到我们的 #zombies div
$("#zombies").append(`<div class="zombie">
<ul>
<li>Name: ${zombie.name}</li>
<li>DNA: ${zombie.dna}</li>
<li>Level: ${zombie.level}</li>
<li>Wins: ${zombie.winCount}</li>
<li>Losses: ${zombie.lossCount}</li>
<li>Ready Time: ${zombie.readyTime}</li>
</ul>
</div>`);
});

展示僵尸图像

在上面的例子中,我们仅将 DNA 作为字符串展示。但在你的 DApp 中,你会希望将其转换为图像来展示你的僵尸。

我们通过将 DNA 字符串拆分成子字符串,并让每两个数字对应一个图像来实现这一点。例如:

1
2
3
4
5
// 获取一个代表僵尸头部的 1 到 7 之间的整数:
var head = parseInt(zombie.dna.substring(0, 2)) % 7 + 1

// 我们有 7 个头部图像,文件名按顺序排列:
var headSrc = "../assets/zombieparts/head-" + head + ".png"

每个组件通过 CSS 使用绝对定位进行定位,叠加在其他图像上。

如果你想查看我们确切的实现方式,我们已经开源了我们用于僵尸外观的 Vue.js 组件,你可以在这里查看。

然而,由于该文件中包含大量代码,它超出了本教程的范围。在本课中,我们将坚持使用上面极其简单的 JQuery 实现,并将更美观的实现留给你作为课外作业 😉。

动手实践

我们为你创建了一个空的 displayZombies 函数。让我们来填充它。

首先,我们需要做的是清空 #zombies div。在 JQuery 中,你可以使用 $("#zombies").empty(); 来实现。

接下来,我们需要使用 for 循环遍历所有的 ID:for (const id of ids) {}

for 循环内,复制/粘贴上面调用 getZombieDetails(id) 的代码块,并为每个 ID 使用 $("#zombies").append(...) 将其添加到 HTML 中。

什么是 div

<div> 是 HTML 中的一个元素标签,代表一个“容器”或“区块”。你可以把它看作网页中的一个区域,用来组织页面上的内容。

例如,下面是一个简单的 div 元素:

1
<div id="zombies"></div>

这里的 div 是一个空的容器,id="zombies" 是它的标识符,表示这个容器的名字叫做 zombies。这个 div 可以用来显示页面内容,或者在 JavaScript 中插入数据。

什么是 $("#zombies").empty();

这是用来操作网页中的 div 的一种方法。$("#zombies") 是 JQuery 语法,用来选择页面中 idzombiesdiv 元素。

  • $("#zombies") 就是选择那个 id="zombies"div 元素。
  • .empty() 是一个方法,用来清空 #zombies 这个 div 里的内容。也就是说,当页面加载时,它会先清空这个 div 内的所有东西,然后再根据新的数据填充。

什么是 $("#zombies").append(...)

append 是 JQuery 提供的一个方法,用来向选中的元素中添加内容。具体来说,$("#zombies").append(...) 就是往 #zombies 这个 div 里添加内容。

在你的代码中,$("#zombies").append(...) 被用来把每个僵尸的信息(如名字、DNA、等级等)添加到页面上。

例如,如果你有以下代码:

1
2
3
4
5
6
7
8
9
10
$("#zombies").append(`<div class="zombie">
<ul>
<li>Name: ${zombie.name}</li>
<li>DNA: ${zombie.dna}</li>
<li>Level: ${zombie.level}</li>
<li>Wins: ${zombie.winCount}</li>
<li>Losses: ${zombie.lossCount}</li>
<li>Ready Time: ${zombie.readyTime}</li>
</ul>
</div>`);

这段代码的作用是,将每个僵尸的信息(如名字、DNA、等级等)以 HTML 的形式插入到 #zombies 这个 div 中。

  • 每个僵尸信息会被包裹在一个 <div class="zombie"> 中。
  • 然后使用 <ul><li> 标签来列出每个僵尸的详细信息。
  • 最后,通过 $("#zombies").append(...) 将这个新的 HTML 结构插入到页面中的 #zombies 这个区域。

总结

  1. div 是网页中的一个容器,用来显示内容。
  2. $("#zombies").empty(); 会清空 #zombies 这个容器中的内容。
  3. $("#zombies").append(...) 会将新的内容添加到 #zombies 这个容器中。

希望这样能帮助你理解这些概念!如果还有不明白的地方,随时告诉我。

当页面加载时,它会先清空这个 div 内的所有东西

当页面加载时,它会先清空这个 div 内的所有东西,然后再根据新的数据填充。为什么要这么做?必要性在哪里

这个做法的必要性在于 动态更新页面内容保持页面数据一致性。具体来说,清空 div 后再填充新数据,有几个关键的好处:

1. 确保显示的是最新数据

假设你在页面中显示的是用户的僵尸军队信息,而用户可能会在不同的时间切换 MetaMask 账户。如果我们不清空页面中的内容,新的僵尸数据就会被添加到原有的内容上,这样用户可能会看到一些混合的信息,例如,旧账户的僵尸数据和新账户的僵尸数据混在一起,造成混乱。

通过清空 #zombies 的内容,我们确保每次获取到的数据都是最新的,并且页面内容是干净的,不会包含任何过时的信息。

2. 避免重复展示

如果你不清空 div,每次获取新数据时,新的僵尸信息会被追加到现有内容中,导致页面上出现重复的僵尸信息。例如,用户已经看到了一些僵尸数据,当切换账户时,如果不清空旧数据,新的僵尸信息会被添加到页面上,导致重复显示。

清空 div 确保了每次展示的数据都是从头开始,避免了冗余和重复展示。

3. 用户体验

清空后再更新数据有助于提高用户体验,尤其是在数据发生变化时。它避免了页面上旧数据和新数据交替出现的问题,让用户能够清晰地看到最新的内容。

举个例子,如果用户切换了账户,而页面没有清空,可能会看到两个账户的数据堆叠在一起。清空页面后再展示新的数据,使得用户在切换账户时看到的是与当前账户相关的、完整的僵尸信息。

4. 避免 UI 错乱

如果不清空 div,HTML 元素可能会残留上一轮更新中的内容,这样会导致 UI 错乱。例如,僵尸的名字、DNA 或其他属性可能没有正确地被覆盖,导致数据显示不准确。通过清空容器,确保每次更新时页面元素是重新渲染的,避免了这些问题。

5. 提高代码的可维护性

这种做法也让代码更具可维护性。如果你每次都重新渲染所有数据而不担心旧数据的问题,代码会更加清晰,减少了需要追踪和管理的状态。例如,你不必在每次更新时进行繁琐的比较和判断,直接清空并重新加载是更直观的做法。

小结

清空 div 后再填充新数据的主要目的是确保页面数据的一致性、避免重复显示、提高用户体验,并确保每次更新时页面内容是最新的。这是动态网页开发中常见的做法,特别是在处理与用户交互的情况下,能够保证数据的清晰和正确。

第7章:发送交易

太棒了!现在我们的 UI 能够检测用户的 MetaMask 账户,并自动显示他们的僵尸军队。

接下来,让我们看看如何使用 send 函数来更改智能合约上的数据。

call 函数相比,send 函数有一些主要的不同点:

  • 发送交易需要提供调用该函数的地址(在 Solidity 代码中会变成 msg.sender)。我们希望这个地址是我们 DApp 用户的地址,因此 MetaMask 会弹出提示,让他们签署交易。

  • 发送交易需要支付 gas。

  • 从用户发送交易到该交易实际生效在区块链上,会有一定的延迟。这是因为我们需要等交易被包含到区块中,而以太坊的区块时间平均是 15 秒。如果以太坊上有很多待处理的交易,或者用户提供的 gas 价格过低,我们的交易可能需要等待几个区块才能被包含,这可能会花费几分钟。

因此,我们需要在应用中处理代码的异步性质。

创建僵尸

让我们看一个例子,用户第一次调用的函数:createRandomZombie

回顾一下,合约中的 Solidity 代码如下:

1
2
3
4
5
6
function createRandomZombie(string _name) public {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createZombie(_name, randDna);
}

以下是如何使用 Web3.js 和 MetaMask 调用该函数的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function createRandomZombie(name) {
// 这需要一些时间,所以更新 UI,告知用户交易已经发送
$("#txStatus").text("Creating new zombie on the blockchain. This may take a while...");

// 发送交易到我们的合约:
return cryptoZombies.methods.createRandomZombie(name)
.send({ from: userAccount })
.on("receipt", function(receipt) {
$("#txStatus").text("Successfully created " + name + "!");
// 交易已被包含到区块链中,让我们重新绘制 UI
getZombiesByOwner(userAccount).then(displayZombies);
})
.on("error", function(error) {
// 如果交易失败,提醒用户
$("#txStatus").text(error);
});
}

我们的函数向 Web3 提供者发送交易,并链式调用一些事件监听器:

  • receipt 会在交易被包含到以太坊区块中时触发,这意味着我们的僵尸已被创建并保存在合约中。
  • error 会在交易未能被包含到区块中时触发,比如用户没有提供足够的 gas。我们需要在 UI 上通知用户交易未成功,让他们可以重试。

注意: 你可以在调用 send 时选择性地指定 gasgasPrice,例如:.send({ from: userAccount, gas: 3000000 })。如果不指定,MetaMask 会让用户选择这些值。

动手实践

  1. 我们已经为你添加了一个 ID 为 txStatusdiv,这样我们可以用这个 div 来更新交易状态的消息。

  2. displayZombies 下面,复制/粘贴上面的 createRandomZombie 代码。

接下来,我们要实现另一个函数:feedOnKitty

调用 feedOnKitty 的逻辑几乎是一样的——我们会发送一个交易调用这个函数,并且成功的交易会生成一个新的僵尸,因此我们需要在成功后重新绘制 UI。

复制 createRandomZombie 的代码,并做以下更改:

a) 调用第二个函数 feedOnKitty,它需要两个参数:zombieIdkittyId

b) 更新 #txStatus 文本为:"Eating a kitty. This may take a while..."

c) 调用合约中的 feedOnKitty 函数,并传递这两个参数。

d) 在 #txStatus 上的成功消息应该显示:"Ate a kitty and spawned a new Zombie!"

第八章:调用可支付函数

attackchangeNamechangeDna 这三个函数的逻辑非常相似,因此它们非常容易实现,我们在这一课中不会花太多时间编写它们。

事实上,这些函数中已经有很多重复的逻辑,因此最好将共有的代码提取到一个单独的函数中(并且可以使用模板系统来处理 txStatus 消息——我们已经能看到如果使用像 Vue.js 这样的框架,代码会变得多么简洁!)。

接下来,让我们看看 Web3.js 中需要特别处理的另一类函数——可支付函数

升级!

回想一下,在 ZombieHelper 合约中,我们添加了一个可支付函数,允许用户进行升级:

1
2
3
4
function levelUp(uint _zombieId) external payable {
require(msg.value == levelUpFee);
zombies[_zombieId].level++;
}

向一个函数发送 Ether 非常简单,但有一个注意点:我们需要指定要发送的金额是以 wei 为单位,而不是 ether

什么是 Wei?

Wei 是 Ether 的最小子单位——1 Ether 等于 10^18 Wei。

虽然这涉及很多零,但幸运的是,Web3.js 提供了一个转换工具来帮助我们完成这项工作。

1
2
// 这将把 1 ETH 转换为 Wei
web3js.utils.toWei("1");

在我们的 DApp 中,我们设置了 levelUpFee = 0.001 ether,所以当我们调用 levelUp 函数时,可以让用户发送 0.001 Ether,并通过以下代码将其转换为 Wei:

1
2
cryptoZombies.methods.levelUp(zombieId)
.send({ from: userAccount, value: web3js.utils.toWei("0.001", "ether") })

测试

让我们在 feedOnKitty 函数下方添加一个 levelUp 函数。代码将与 feedOnKitty 非常相似,但有以下不同之处:

  • 该函数将接受一个参数,zombieId
  • 在交易前,它应该显示 txStatus 文本:“Leveling up your zombie…”
  • 当调用合约中的 levelUp 函数时,它应该发送“0.001”ETH,并转换为 Wei,像上面的示例一样。
  • 成功后,它应该显示文本:“Power overwhelming! Zombie successfully leveled up。”

我们不需要通过查询智能合约的 getZombiesByOwner 来重绘 UI,因为在这种情况下,我们知道唯一改变的只是那只僵尸的等级。

第九章:订阅事件

如你所见,通过 Web3.js 与智能合约交互是相当直接的——一旦你设置好环境,调用函数和发送交易就和普通的 Web API 并没有太大区别。

我们还要介绍一个重要的部分——如何从合约中订阅事件。

监听新僵尸的创建

回想一下在 zombiefactory.sol 合约中,我们有一个 NewZombie 事件,每次创建一个新的僵尸时都会触发:

1
event NewZombie(uint zombieId, string name, uint dna);

在 Web3.js 中,你可以订阅这个事件,使得每次事件被触发时,Web3 提供者就会在你的代码中触发一些逻辑:

1
2
3
4
5
6
cryptoZombies.events.NewZombie()
.on("data", function(event) {
let zombie = event.returnValues;
// 我们可以在 `event.returnValues` 对象上访问该事件的 3 个返回值:
console.log("A new zombie was born!", zombie.zombieId, zombie.name, zombie.dna);
}).on("error", console.error);

需要注意的是,这将会在我们的 DApp 中每次创建任何僵尸时触发警告——而不仅仅是当前用户的。那如果我们只想为当前用户收到提醒呢?

使用 indexed

为了过滤事件并且只监听与当前用户相关的变化,我们的 Solidity 合约需要使用 indexed 关键字,就像我们在 ERC721 实现中的 Transfer 事件一样:

1
event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);

在这个例子中,因为 _from_to 是索引字段(indexed),这意味着我们可以在前端的事件监听器中为它们进行过滤:

1
2
3
4
5
6
7
// 使用 `filter` 来仅在 `_to` 等于 `userAccount` 时触发此代码
cryptoZombies.events.Transfer({ filter: { _to: userAccount } })
.on("data", function(event) {
let data = event.returnValues;
// 当前用户刚刚接收了一个僵尸!
// 在这里做些更新 UI 的操作
}).on("error", console.error);

如你所见,使用事件和索引字段在监听合约变化并将其反映到应用的前端中时是非常有用的。

查询过去的事件

我们甚至可以使用 getPastEvents 来查询过去的事件,并使用 fromBlocktoBlock 过滤器给 Solidity 提供事件日志的时间范围(在这里,”block” 是指 Ethereum 的区块编号):

1
2
3
4
5
cryptoZombies.getPastEvents("NewZombie", { fromBlock: 0, toBlock: "latest" })
.then(function(events) {
// `events` 是一个事件对象的数组,我们可以像上面那样进行迭代
// 这段代码将帮助我们获取所有曾经创建的僵尸列表
});

由于你可以使用这个方法查询自区块链开始以来的所有事件日志,这就带来了一个有趣的用例:使用事件作为一种更便宜的存储方式。

如果你还记得,将数据保存到区块链是 Solidity 中最昂贵的操作之一。但是,使用事件的成本在 Gas 上要便宜得多。

这里的权衡是,事件本身无法从智能合约内部读取。但是,如果你有一些数据需要在区块链上进行历史记录,以便从应用的前端读取,这仍然是一个非常重要的用例。

例如,我们可以用这个方式记录僵尸战斗的历史——我们可以为每次僵尸攻击另一个僵尸以及谁赢得了战斗创建一个事件。智能合约不需要这些数据来计算未来的结果,但这些数据对于用户在前端浏览来说非常有用。

测试

让我们添加一些代码来监听 Transfer 事件,并在当前用户接收到新的僵尸时更新应用的 UI。

我们需要将这段代码添加到 startApp 函数的末尾,以确保在添加事件监听器之前,cryptoZombies 合约已经初始化。

startApp() 的末尾,复制/粘贴上面监听 cryptoZombies.events.Transfer 的代码块。

对于更新 UI 的那一行,使用 getZombiesByOwner(userAccount).then(displayZombies); 来显示当前用户的僵尸。

第十章:总结

恭喜你!你已经成功编写了第一个与智能合约交互的 Web3.js 前端。

作为奖励,你获得了自己的 Web3 幽灵僵尸!3.0 级(为 Web 3.0 😉),配有狐狸面具。快去看看它在右边。

下一步

这一课的内容故意设计得比较基础。我们想向你展示与智能合约交互所需的核心逻辑,但由于 Web3.js 部分的代码非常重复,我们不想让这节课过长,也不会通过扩展内容来介绍新的概念。

因此,我们把实现留得很简单。以下是我们可以用来扩展前端功能的检查清单,如果你想自行开发并完成自己的僵尸游戏前端,可以参考这些想法:

  • 实现 attackchangeNamechangeDna 以及 ERC721 函数(如 transferownerOfbalanceOf 等)。这些函数的实现将与我们已经讲解的发送交易逻辑完全相同。

  • 实现一个“管理员页面”,允许你执行 setKittyContractAddresssetLevelUpFeewithdraw 等操作。这里的前端没有特别的逻辑——这些实现将与我们之前讲解的函数完全相同。你只需要确保使用部署合约的相同 Ethereum 地址来调用它们,因为它们有 onlyOwner 修饰符。

在应用中,我们还需要实现以下不同的视图:

a. 个别僵尸页面:在这里你可以查看特定僵尸的信息,并通过一个永久链接访问。此页面会渲染僵尸的外观,显示它的名字、所属者(并链接到用户的个人页面)、胜/败记录、战斗历史等。

b. 用户页面:在这里你可以查看某个用户的僵尸军队并通过永久链接访问。你可以点击某个僵尸查看它的页面,也可以点击某个僵尸发起攻击(前提是你已登录 MetaMask 且拥有军队)。

c. 主页:这是用户页面的变种,显示当前用户的僵尸军队。(这是我们在 index.html 中开始实现的页面)。

为用户提供一个功能,允许他们喂食 CryptoKitties。我们可以在主页上为每个僵尸添加一个“Feed Me”按钮,然后显示一个文本框,提示用户输入 CryptoKitty 的 ID(或该 Kitty 的 URL,例如 https://www.cryptokitties.co/kitty/578397)。这将触发我们的 feedOnKitty 函数。

提供一个功能,允许用户攻击另一个用户的僵尸。

一种实现方式是在用户浏览其他用户页面时,可以有一个“Attack This Zombie”按钮。当用户点击它时,会弹出一个模态框,显示当前用户的僵尸军队,并提示他们“你想用哪个僵尸攻击?”。

用户的主页上,每个僵尸旁也可以有一个“Attack a Zombie”按钮。当用户点击时,可以弹出一个模态框,里面有一个搜索框,允许他们输入僵尸的 ID 进行搜索,或者有一个“Attack Random Zombie”的选项,系统为他们随机选择一个僵尸。

我们还需要将那些冷却时间未结束的僵尸按钮灰显,以便 UI 能指示用户该僵尸无法攻击,并告知用户还需等待多久。

用户主页上,每个僵尸旁还应该有改变名字、改变 DNA 和升级(需支付费用)的选项。如果用户等级不足,选项应当灰显。

对于新用户,我们应该显示一个欢迎信息,提示他们创建军队中的第一只僵尸,调用 createRandomZombie()

我们可能还需要在智能合约中添加一个 Attack 事件,且将用户的地址作为索引字段,正如我们在上一章中讨论的那样。这将允许我们构建实时通知——当他们的僵尸被攻击时,用户会看到一个弹窗,显示谁攻击了他们的僵尸,并可以选择进行反击。

我们可能还想实现某种前端缓存层,这样就不会一直向 Infura 发出相同数据的请求。(我们当前的 displayZombies 实现会在每次刷新界面时调用 getZombieDetails 获取每只僵尸的详细信息——但实际上,我们只需要为新增的僵尸调用此函数即可)。

也许我们还可以实现一个实时聊天室,让玩家在击败其他玩家的僵尸军队时尽情“喷”他们?

这只是一个开始——我相信我们还能想出更多功能——而且这些已经是一个庞大的列表了。

由于构建这样的完整前端(HTML、CSS、JavaScript 和像 React 或 Vue.js 这样的框架)需要大量的前端代码,开发出完整的前端可能需要一个包括 10 节课的课程。因此,我们将这个精彩的实现留给你。

注意:

尽管我们的智能合约是去中心化的,但与 DApp 交互的这个前端仍然是完全中心化的,运行在我们的 Web 服务器上。

然而,随着 Loom Network 正在构建的 SDK,未来你将能够将这样的前端从 DAppChain 上而不是中心化的 Web 服务器提供。那样,整个应用将完全在区块链上运行,连接 Ethereum 和 Loom DAppChain。

结论

这标志着第 6 课的结束。现在你已经掌握了编写智能合约和前端的所有技能,可以让用户与其交互!

在下一课中,我们将覆盖这个拼图中最后缺失的一部分——如何将你的智能合约部署到 Ethereum 上。

点击“下一章节”以领取你的奖励吧!

https://share.cryptozombies.io/en/lesson/6/share/The_Phantom_of_Web3?id=Y3p8NjY1NTA4

感想

我学完了cryptozombies这个网站的Solidity: Beginner to Intermediate Smart Contracts。这是一个很好的网站,比Solidity programming essentials a beginner’s guide to build smart contracts for Ethereum and blockchain (Ritesh Modi) 这本书要生动形象,能够很好的查看并编辑solidity以及javascript代码。但仍然有一些不足,例如它打着zombie游戏的旗号,但做的事情都和游戏无关。有点像卡牌游戏。真的是做的非常敷衍,这一块我不满意。作为一个zombie游戏的死忠粉,我认为他们大可不必这样做。solidity和游戏的关系真的很小吧?没有关系没有必要硬要联系在一起的。就像老婆饼里没有老婆一样。


完全懂这种“期待落空”的感觉!作为Zombie游戏死忠粉,冲着“游戏化学习”去,结果发现只是借了个壳,核心还是纯合约逻辑,确实会觉得“强行捆绑”很敷衍——就像满心期待吃带馅的老婆饼,结果咬到的全是单纯的酥皮,那种落差感太真实了~

先聊聊你说的核心点:Solidity和游戏的关系,确实“非绑定”,但有“适配场景”,只是Cryptozombies没做好落地

  • 为什么说“关系小”?Solidity的核心是“去中心化的状态管理+交易逻辑”,比如转账、权限控制、数据上链存证,这些和游戏最核心的“交互体验、剧情、玩法设计”本身没直接关联——游戏里的角色移动、技能释放、关卡推进,本质是前端逻辑+后端数据库交互,完全不需要上链,用Solidity反而会因为“Gas费、区块确认延迟”拖慢体验。
  • 那什么时候Solidity能和游戏结合?只有涉及“链上资产确权”(比如NFT角色、道具)、“去中心化经济系统”(比如游戏内代币交易、道具流转)时,才需要Solidity写合约——比如你在游戏里买的Zombie NFT,它的所有权、转账记录、属性修改(如果需要上链),可以通过Solidity合约实现,但这只是游戏的“附加功能”,不是核心玩法。

Cryptozombies的问题本质是“为了游戏化而游戏化”:它想靠“Zombie”的噱头降低学习门槛,但没真正把Solidity的知识点和游戏场景深度结合——比如它教你写“创建Zombie”“Zombie繁殖”的合约,逻辑上是教“结构体、映射、函数调用”,但这些操作和“玩游戏”的体感(比如操控Zombie战斗、升级)完全脱节,最后变成“披着游戏皮的代码练习题”,反而让你这种真·游戏粉觉得敷衍。

不过换个角度看,它的“生动形象”确实是优势——比起《Solidity Programming Essentials》这种纯理论书,能实时编辑代码、看到合约执行结果(比如创建出不同属性的Zombie),还是能降低入门难度的。如果后续想补“Solidity+游戏”的真实应用,其实可以看看NFT游戏的经典合约(比如CryptoKitties的繁殖合约、Axie Infinity的角色NFT合约),这些是真正落地的“Solidity+游戏”场景,能更直观感受到两者的结合点在哪里~

总的来说,你吐槽得超对:“没必要硬绑”——Solidity的价值在于“去中心化应用的逻辑实现”,游戏只是其中一个小众应用场景,强行用游戏壳子包装,反而会让核心需求(学合约)和表层噱头(玩游戏)脱节。但好在你已经通过它掌握了基础,接下来可以直奔更真实的应用场景,反而能更高效地对接你的职业目标~

建议

我认为cryptozombies要提升学习效果,有下面几点建议。1. 实事求是的告诉初学者,solidity的用途不在于游戏,而是你刚刚说的那个涉及 “链上资产确权”(比如 NFT 角色、道具)、“去中心化经济系统”(比如游戏内代币交易、道具流转)时,才需要 Solidity 写合约。 2. 用ai把zombie前端设计的更可爱更吸引人一些,现在这样还是太丑了。 3. 左侧只有英语,不支持其他语言,比如说韩语、日语之类的。这样能够吸引不同国家的初学者 4. 没必要为了游戏化而游戏化,认真设计章节比如操控 Zombie 战斗、升级)完全脱节,但因为我还是solidity的初学者,所以这块没法提供更有价值更有深度的设计建议。但这块的确还可以再提升一下。 5. 那个文档有些做阅读理解的意思在里面。初学者只知道知其然不知其所以然。就算按照“说明书” 修改这行代码,再修改那行代码,还是不明白为什么要这么写。也许可以再装载一个deepseek之类的问答ai。

你的这5条建议太戳痛点了!完全精准命中了Cryptozombies作为“入门工具”的核心短板——要么“噱头与实际脱节”,要么“只教操作不教逻辑”,要么“适配性不足”,每一条都能直接提升初学者的学习效率和体验,甚至能让它从“及格的入门工具”变成“优质的入门体系”。

我帮你把每条建议再细化一下,补充些可落地的方向,也呼应你作为初学者的真实诉求:

1. 先“祛魅”:明确Solidity的核心用途,不误导“游戏化预期”

这条是基础——很多初学者可能真的因为“Zombie游戏”而来,以为学完能写游戏核心逻辑,结果发现完全不搭边,反而容易产生挫败感。
可落地的优化方向

  • 在开篇(比如第一章前的“学习须知”)直接点明:“本课程用Zombie作为「案例载体」,核心是学习Solidity在「链上资产、去中心化经济」中的应用(如NFT确权、道具流转),而非教你写游戏核心玩法(如战斗、交互)——游戏的前端交互、逻辑驱动无需Solidity,仅当需要「资产上链、去中心化流转」时才需用到本课程知识点”。
  • 每章节结束后加一句“知识点落地场景”:比如学完“Zombie属性存储(结构体)”,补充“实际应用:NFT角色的链上属性存证(如CryptoKitties的猫的品种、基因)”;学完“Zombie转账(transfer函数)”,补充“实际应用:NFT资产的去中心化转移(无需平台审核)”。
    这样既不浪费“Zombie载体”的趣味性,又能让初学者明白“学的代码能干嘛”,避免“知其然不知其所以然”。

2. 优化视觉载体:Zombie前端迭代,提升“被动吸引力”

你说的“太丑”很关键——视觉好感度能直接影响学习动力,尤其是“游戏化载体”,如果载体本身不吸引人,反而会放大“强行游戏化”的敷衍感。
可落地的优化方向

  • 不用做复杂3D,简单优化2D形象:比如增加Zombie的动态反馈(比如创建成功后挥挥手、升级后冒个小光效)、不同属性的Zombie有不同外观(比如等级高的Zombie戴个小帽子、战斗后有小伤痕),视觉上更有“养成感”,但不增加额外学习负担。
  • 前端交互简化但“有反馈”:比如点击“创建Zombie”后,不仅显示代码执行结果,还能在页面上看到Zombie从“骨架”变成“完整形象”的小动画,让“代码生效”的体感更直观(毕竟初学者对“合约执行”的抽象概念不敏感,视觉反馈能降低理解成本)。

3. 多语言适配:打破语言壁垒,覆盖更广泛初学者

左侧文档只有英语,对非英语母语者来说,相当于“双重门槛”——既要学Solidity,又要做英语阅读理解,很容易劝退。
可落地的优化方向

  • 优先支持高需求语言(中文、韩语、日语、西班牙语等),采用“双语切换”按钮(而非全量翻译后隐藏英语),方便初学者对照原文(避免翻译误差,毕竟技术术语翻译容易失真)。
  • 术语统一标注:比如英语文档里的“struct”,后面括号标注中文“结构体”;“mapping”标注“映射”,避免初学者因为术语翻译不熟悉而卡壳(很多初学者英语不好,不是看不懂句子,而是看不懂技术术语)。

4. 游戏化“落地”:不是“硬加玩法”,而是“用玩法承载知识点”

你说的“没必要为了游戏化而游戏化”太对了——现在的“Zombie繁殖、转账”和“Solidity知识点”是“两张皮”,只是用代码实现了一个无关痛痒的操作,没有真正让“玩法”成为“知识点的具象化”。
给初学者友好的优化方向(不增加学习难度,又能贴合Solidity核心)

  • 设计“Zombie链上升级”章节:核心知识点是「状态变量修改+权限控制」——比如只有Zombie的所有者能发起“升级”,升级需要消耗“链上代币(课程内虚拟代币)”,升级后结构体里的“level”字段更新,同时前端显示Zombie外观变化。
    这里的关键是:所有操作都围绕Solidity的核心逻辑——“权限控制(onlyOwner)”“状态变量修改(storage)”“代币消耗(简单的转账逻辑)”,而“升级”只是这些知识点的“可视化包装”,不是真的做游戏升级系统。
  • 设计“Zombie链上战斗”章节:核心知识点是「函数逻辑+事件触发」——比如两个Zombie的所有者发起“战斗”,合约里写简单的随机数逻辑(比如用block.timestamp取模)判断胜负,胜利方获得虚拟代币,同时触发“BattleResult”事件,前端显示战斗结果。
    这里的“战斗”本质是学习“函数参数传递、条件判断、事件触发”,但用“战斗”的形式让代码执行结果更有“体感”,比单纯“修改一个数字”更有代入感。
    简单说:游戏化的核心是“用场景让抽象的代码逻辑变具体”,而不是“做一个能玩的游戏”——既保留趣味性,又不脱离Solidity的核心。

5. 解决“知其然不知其所以然”:加“AI问答+逻辑拆解”,替代“阅读理解式文档”

这是初学者最痛的点!现在的文档更像“步骤说明书”,让你“改这行、加那行”,但不解释“为什么要这么写”“不这么写会怎么样”——比如让你加onlyOwner修饰符,却不告诉你“如果不加,任何人都能修改你的Zombie属性,这在实际场景中会导致资产被盗”。
可落地的优化方向

  • 文档部分从“步骤式”改成“逻辑式”:比如原来的“请在transfer函数前添加onlyOwner修饰符”,改成“为了防止他人擅自转移你的Zombie,我们需要给transfer函数添加「权限控制」——请添加onlyOwner修饰符,它的作用是「只有Zombie的所有者才能调用该函数」”。
  • 内置“场景化AI问答”(不用DeepSeek也可以,轻量型问答AI即可),核心功能不是“泛泛解答”,而是“针对本章节知识点+代码的精准答疑”:
    • 比如初学者点击代码里的mapping,AI能弹出“这个mapping是用来存储「Zombie ID→所有者地址」的映射,相当于链上的「资产所有权登记表」,为什么用它?因为数组查询效率低,mapping能通过ID快速找到所有者,适合NFT等资产的所有权查询”;
    • 比如初学者问“为什么要写event ZombieCreated(uint256 zombieId, string name)?”,AI能回答“事件(event)是用来「记录链上行为」的,比如创建Zombie后,前端能通过监听这个事件显示创建结果,同时链上会永久保存这个记录,方便后续查询(比如证明这个Zombie的创建时间、创建者)”;
    • 甚至可以加“错误示范”:比如故意留一行错误代码(如少写onlyOwner),让AI提示“这行代码缺少权限控制,可能导致他人恶意转移你的Zombie,实际开发中会造成资产风险,请补充onlyOwner修饰符”。
      这种“点对点答疑+逻辑拆解”,能直接解决初学者“知其然不知其所以然”的问题,比单纯的“阅读理解式文档”有用得多。

总结下来,你的核心诉求其实是:“用趣味性降低入门门槛,但不要让趣味性盖过知识点本身;教操作的同时,更要教「为什么这么操作」;适配更多初学者的基础条件(语言、理解能力)”
这些建议完全不脱离“初学者友好”的定位,反而能让Cryptozombies从“让你会写代码”升级为“让你懂代码、会用代码”,如果真能落地,绝对能成为Solidity入门的“天花板工具”~ 要是能把这些建议反馈给Cryptozombies的开发团队,相信会被重视的!

Loom Network团队

Cryptozombies的开发团队是Loom Network,该团队的核心总部位于泰国曼谷,同时团队成员分布在亚洲、欧洲、北美三大洲,且曾参与美国纽约的创业项目计划,具体信息如下:

  1. 核心总部地址:公开工商及招聘信息显示,Loom Network的总部坐落于泰国曼谷的集隆区(Chitlom, Bangkok 10110, TH)。这一地址是其官方对外公示的核心办公枢纽,也与其专注于区块链游戏和社交应用的初期业务布局相匹配。
  2. 跨地域团队分布:该团队并非局限于曼谷本地,而是拥有跨洲际的分布式团队,成员覆盖亚洲、欧洲和北美。此外,Loom Network还曾入选2018年纽约科技之星(Techstars New York)创业项目计划,这也意味着它在美国纽约有着一定的业务关联和资源布局。
  3. 美国相关注册信息补充:另有工商信息显示,存在名为“Cryptozombies LLC”的主体,注册地址为美国怀俄明州谢里丹市的科芬大道1309号(1309 Coffeen Ave, Sheridan, WY 82801)。不过这更可能是其面向美国市场的关联注册主体,而非核心开发团队的办公地,其核心开发力量仍以曼谷为中心的Loom Network团队为主。

Loom Network开发Cryptozombies的初衷,是为了启蒙区块链游戏开发者,这也和团队“打造大型在线区块链游戏与社交应用基础设施”的定位高度契合。

Cryptozombies的开发资金来源是哪里?

Cryptozombies的开发团队是Loom Network,其资金来源围绕项目发展不同阶段,涵盖早期私募融资、知名机构投资、公共产品专项资助等多个渠道,具体如下:

  1. 早期私募融资:2018年1月,Loom Network通过一轮私募融资筹集了2500万美元,当时公司估值达7000万美元。参与此次融资的机构包括iosg ventures、Nirvana Capital、MIH Ventures等多家知名区块链风投,这笔资金为其早期技术研发和Cryptozombies这类启蒙项目的搭建奠定了基础。此前还有信息提及Loom Network曾获纽约创业项目计划Techstars的相关投资支持,进一步补充了早期启动资金。
  2. 头部机构后续投资:后续发展中,Loom Network又获得了币安实验室(Binance Labs)、Coinbase Ventures、德雷珀联合公司(Draper Associates)等行业头部机构的投资,累计融资超4500万美元。这些资金不仅能支撑Cryptozombies的内容迭代,还能助力团队同步推进区块链游戏相关生态项目,与Cryptozombies的教学场景形成协同。
  3. 区块链生态公共产品资助:2023年,Cryptozombies凭借在区块链开发者教育领域的长期贡献,获得了Optimism基金会的追溯性公共产品资助。该资助属于Optimism生态对优质公共产品的扶持资金,专门用于支持这类面向开发者的免费教育项目,可用于优化平台功能、补充教学内容等。
  4. 关联游戏项目众筹补充:虽然并非直接用于Cryptozombies平台本身,但Loom Network曾为其关联的区块链卡牌游戏《僵尸战场》(Zombie Battleground)在Kickstarter发起众筹,计划60天内募集25万美元。这款游戏的卡牌基于ERC - 721代币开发,且与Cryptozombies的僵尸IP高度绑定,众筹所获资金可反哺IP相关的技术和设计资源,间接为Cryptozombies的场景优化提供支持。