CryptoZombies 游戏:冷却时间、等级和高效数据

本文主要讲解了如何在以太坊的 CryptoZombies 游戏中实现核心游戏机制,包括使用时间戳实现技能冷却,使用带参数的modifier来实现等级限制,以及如何通过for循环来高效检索玩家拥有的僵尸,避免gas消耗。通过这些机制,让游戏更具动态性和可玩性。

欢迎回来,加密技术构建者们!在我们的上一篇文章中,我们强化了我们的僵尸工厂,通过 Ownable 模式掌握了安全性,并通过结构体打包和 view 函数优化了 gas。但是一个安全高效的合约还不是一个游戏。游戏的发展在哪里?策略在哪里?你的僵尸变得更强大的感觉在哪里?

今天,我们正从架构转向行动。我们正在通过实现使 CryptoZombies 具有吸引力的核心游戏机制,将灵魂注入我们的不死军队:冷却时间、等级门控能力和高效的链上数据检索。

第一部分:在区块链上创造时间——实现冷却时间

在任何好的游戏中,强大的动作都不能被滥用。它们需要一个冷却期。由于 Solidity 的原生时间处理,在链上实现这一点非常简单。

1. 在 Solidity 中定义时间:

我们首先设置一个冷却期。使用 1 days 瞬间就能理解,而且比手动计算 86400 秒要干净得多。

uint cooldownTime = 1 days; // Solidity 原生理解这个

2. 更新 Zombie 结构体:

我们需要跟踪每个僵尸何时可以再次行动。我们在结构体中添加了一个 readyTime,使用 uint32 非常适合存储时间戳,并通过结构体打包(正如我们上次讨论的那样!)帮助优化 gas。

struct Zombie {
    string name;
    uint dna;
    uint32 level;
    uint32 readyTime; // 这将存储一个 Unix 时间戳
}

3. 构建冷却机制:

我们创建 internal 辅助函数来管理逻辑。这使我们的代码模块化和干净。

// 通过将 readyTime 设置为未来日期来启动冷却
function _triggerCooldown(Zombie storage _zombie) internal {
    _zombie.readyTime = uint32(now + cooldownTime);
}
// 检查冷却期是否结束
function _isReady(Zombie storage _zombie) internal view returns (bool) {
    return (_zombie.readyTime <= now);
}

它是如何工作的:在僵尸进食(feedAndMultiply)之后,我们调用 _triggerCooldown(myZombie)。在任何操作之前,我们检查 require(_isReady(myZombie))。这种简单的模式是我们游戏节奏的支柱,全部由区块链的时间戳驱动。

第二部分:升级你的代码——参数化修饰器

没有进度的游戏是什么?我们希望奖励玩家升级他们的僵尸,使其拥有新的能力。与其为每个级别编写一个单独的修饰器,不如创建一个单一、强大且可重用的工具。

制作 aboveLevel 修饰器:

此修饰器接受参数,使其非常灵活。

// _level 是我们传入的最低要求
// _zombieId 是我们正在检查的僵尸
modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _; // 如果为真,则继续。如果为假,则恢复。
}

解锁能力:

现在,将函数置于等级要求之后变得清晰而直观。

// 等级 2 解锁名称更改
function changeName(uint _zombieId, string calldata _newName) external aboveLevel(2, _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].name = _newName;
}
// 等级 20 解锁 DNA 更改——强大的终局能力
function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].dna = _newDna;
}

这种模式是代码可重用性的杰作。我们现在可以添加无数与任何级别相关的新能力,只需最少的简单代码。

第三部分:数据检索的禅宗——For 循环 vs. 存储

在这里,我们遇到了 Solidity 中最令人费解的事情之一。我们如何获得单个地址拥有的所有僵尸?直观上“高效”的解决方案实际上是一个消耗 gas 的陷阱。

诱人但错误的方式:

mapping (address => uint[]) public ownerToZombies; // 这是一个 gas 陷阱!

为什么这是一个陷阱?因为转移。从存储中间的数组中删除僵尸是一场消耗 gas 的噩梦。它需要移动所有后续元素,从而导致数十次昂贵的存储写入。对于大型军队来说,成本变得不可预测,并且可能是天文数字。

优雅的解决方案:按需迭代。

我们在免费的 view 函数中使用 for 循环。虽然这在传统编程中看起来效率低下,但它是链上的最佳解决方案。

function getZombiesByOwner(address _owner) external view returns (uint[] memory) {
    uint[] memory result = new uint[](ownerZombieCount[_owner]);
    uint counter = 0;
// 迭代现有的每个僵尸
    for (uint i = 0; i < zombies.length; i++) {
        // 检查所有者是否匹配
        if (zombieToOwner[i] == _owner) {
            // 如果匹配,则将其添加到我们的结果数组中
            result[counter] = i; // 'i' 是僵尸的 ID
            counter++;
        }
    }
    return result;
}

权衡:我们接受 view 函数(对用户免费)中的一次性计算成本(循环),以避免在 transfercreate 等状态更改函数期间产生大量且重复的存储成本。这是一种违反直觉的、区块链原生的思维方式,它定义了专家智能合约设计。

结论

我们已经从一个简单的数据存储转变为一个动态的游戏世界。我们的僵尸现在可以在计时器上运行,通过关卡升级并解锁新的能力,并且可以有效地查询,而不会增加存储成本。

这才是区块链开发的真正艺术所在:既是游戏设计师,又是 gas 经济学家。通过利用 Solidity 的原生特性并做出明智的架构权衡,你可以创建由安全、去中心化的区块链完全驱动的深度参与体验。

舞台已经搭建完毕。我们的僵尸已经过训练、升级和组织。他们已经准备好战斗了。在我们的下一章中,我们将把它们彼此释放,并构建任何伟大游戏的核心:战斗系统。

准备好迎接战争吧!

  • 原文链接: blog.blockmagnates.com/b...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
blockmagnates
blockmagnates
The New Crypto Publication on The Block