פיתוח מערכת אנרגיה/כוח (Stamina) ל-GameFi
התמודדנו עם המשימה של יצירת מערכת אנרגיה על-רשת (on-chain) שאינה שורפת גז על התחדשות. הגישה הרגילה—עדכון אחסון כל שנייה—הורגת את הכלכלה והופכת את המשחק לבלתי ניתן למשחק. היישום שלנו משתמש בהערכה עצלה (lazy evaluation), המאפשרת חישוב אנרגיה נוכחית ללא כתיבה. עם ניסיון של למעלה מ-5 שנים, פיתחנו ארכיטקטורה המאזנת בין חיסכון בגז לבין הגנה מפני רמאות. מאמר זה מכסה מכניקות מפתח: התחדשות מבוססת זמן ללא כתיבות, קישור ל-NFT, אנרגיה סחירה, והגנה מפני רמאות.
מערכת אנרגיה מגבילה את פעילות השחקן. שחקנים מוציאים אנרגיה על פעולות (קרבות, חקלאות, יצירה), והאנרגיה מתחדשת עם הזמן או באמצעות רכישות. ב-Web2, זהו מונה פשוט במסד נתונים. ב-Web3, זהו משאב על-רשת, היוצר הזדמנויות (אנרגיה סחירה, התחדשות ניתנת לאימות) ובעיות (גז לכל עדכון, מניעת רמאות). ארכיטקטורה נכונה של מערכת האנרגיה היא אתגר הנדסי מרכזי ב-GameFi. יישום שגוי הופך את המשחק לבלתי ניתן למשחק (יותר מדי פעולות על-רשת) או פותח ניצולים (אנרגיה חינמית באמצעות מניפולציה).
כיצד הערכה עצלה מפחיתה עלויות גז?
הפתרון האינטואיטיבי: אחסון אנרגיה ב-mapping ועדכון כל שנייה. זה רע—טרנזקציות אינסופיות. הגישה הנכונה היא הערכה עצלה. אחסן לא את האנרגיה הנוכחית, אלא את רגע העדכון האחרון ואת הערך באותו רגע. אנרגיה נוכחית מחושבת תוך כדי תנועה בכל קריאה:
contract EnergySystem {
struct EnergyState {
uint128 storedEnergy;
uint64 lastUpdateTime;
uint64 maxEnergy;
}
mapping(address => EnergyState) private energyStates;
uint256 public constant REGEN_RATE = 1e18;
uint256 public constant MAX_ENERGY = 100e18;
function currentEnergy(address player) public view returns (uint256) {
EnergyState storage state = energyStates[player];
uint256 elapsed = block.timestamp - state.lastUpdateTime;
uint256 regenerated = elapsed * REGEN_RATE;
uint256 total = uint256(state.storedEnergy) + regenerated;
uint256 max = state.maxEnergy == 0 ? MAX_ENERGY : uint256(state.maxEnergy);
return total > max ? max : total;
}
function _updateEnergyState(address player) internal {
EnergyState storage state = energyStates[player];
state.storedEnergy = uint128(currentEnergy(player));
state.lastUpdateTime = uint64(block.timestamp);
}
function spendEnergy(address player, uint256 amount) internal {
uint256 current = currentEnergy(player);
require(current >= amount, "Insufficient energy");
_updateEnergyState(player);
energyStates[player].storedEnergy -= uint128(amount);
}
function addEnergy(address player, uint256 amount) internal {
_updateEnergyState(player);
uint256 max = energyStates[player].maxEnergy == 0 ? MAX_ENERGY : energyStates[player].maxEnergy;
uint256 newEnergy = uint256(energyStates[player].storedEnergy) + amount;
energyStates[player].storedEnergy = uint128(newEnergy > max ? max : newEnergy);
}
}הנקודה המרכזית: contract EnergySystem { struct EnergyState { uint128 storedEnergy; uint64 lastUpdateTime; uint64 maxEnergy; } mapping(address => EnergyState) private energyStates; uint256 public constant REGEN_RATE = 1e18; uint256 public constant MAX_ENERGY = 100e18; function currentEnergy(address player) public view returns (uint256) { EnergyState storage state = energyStates[player]; uint256 elapsed = block.timestamp - state.lastUpdateTime; uint256 regenerated = elapsed * REGEN_RATE; uint256 total = uint256(state.storedEnergy) + regenerated; uint256 max = state.maxEnergy == 0 ? MAX_ENERGY : uint256(state.maxEnergy); return total > max ? max : total; } function _updateEnergyState(address player) internal { EnergyState storage state = energyStates[player]; state.storedEnergy = uint128(currentEnergy(player)); state.lastUpdateTime = uint64(block.timestamp); } function spendEnergy(address player, uint256 amount) internal { uint256 current = currentEnergy(player); require(current >= amount, "Insufficient energy"); _updateEnergyState(player); energyStates[player].storedEnergy -= uint128(amount); } function addEnergy(address player, uint256 amount) internal { _updateEnergyState(player); uint256 max = energyStates[player].maxEnergy == 0 ? MAX_ENERGY : energyStates[player].maxEnergy; uint256 newEnergy = uint256(energyStates[player].storedEnergy) + amount; energyStates[player].storedEnergy = uint128(newEnergy > max ? max : newEnergy); } } היא פונקציית currentEnergy(), שאינה צורכת גז. עדכוני אחסון מתרחשים רק על view/spendEnergy—כלומר, על פעולות משחק בפועל. הערכה עצלה מפחיתה את צריכת הגז פי 10 בהשוואה לעדכוני אחסון קבועים. לפי תיעוד Solidity, מבנים דחוסים חוסכים בגז אחסון.
| פרמטר | הערכה עצלה | עדכון קבוע |
|---|---|---|
| פעולות כתיבה | 0 ברקע, רק על פעולה | כל שנייה (מיליוני TX) |
| עלות גז לפעולה | ~50,000 גז | ~100,000 גז + רקע |
| מורכבות יישום | בינונית | נמוכה |
| מתאים ל | GameFi עם תעבורה גבוהה | סימולציות פשוטות |
שימוש בהערכה עצלה מפחית את צריכת הגז ב-90% בהשוואה לגישות נאיביות. עבור משחק עם 10,000 משתמשים פעילים יומיים, זה יכול לחסוך למעלה מ-$120,000 בשנה בעמלות גז.
קישור אנרגיה ל-NFT
האנרגיה מקושרת ל-NFT ספציפי, לא לארנק EOA. זה חשוב: שחקן יכול להחזיק במספר דמויות עם אנרגיה עצמאית, ולסחור בדמויות יחד עם האנרגיה הנוכחית שלהן.
contract CharacterEnergySystem {
struct CharacterEnergy {
uint128 storedEnergy;
uint64 lastUpdate;
uint8 tier;
}
mapping(uint256 => CharacterEnergy) public characterEnergy;
function regenRateForTier(uint8 tier) public pure returns (uint256) {
if (tier == 3) return 3e18;
if (tier == 2) return 2e18;
return 1e18;
}
function maxEnergyForTier(uint8 tier) public pure returns (uint256) {
return 100e18 + uint256(tier) * 50e18;
}
function currentEnergy(uint256 tokenId) public view returns (uint256) {
CharacterEnergy storage ce = characterEnergy[tokenId];
uint8 tier = nftContract.getTier(tokenId);
uint256 elapsed = block.timestamp - ce.lastUpdate;
uint256 regen = elapsed * regenRateForTier(tier);
uint256 total = uint256(ce.storedEnergy) + regen;
uint256 max = maxEnergyForTier(tier);
return total > max ? max : total;
}
}כאשר NFT מועבר, האנרגיה נעה עם הדמות אוטומטית, מכיוון שהיא מאוחסנת ב-mapping לפי tokenId.
חשיבות ההגנה מפני רמאות
ללא הגנה, שחקנים יכולים לתמרן התחדשות באמצעות התקפות re-org או replay. הפתרון שלנו משתמש בפעולות חתומות עם nonce:
struct GameAction {
uint256 characterId;
uint256 actionType;
uint256 energyCost;
uint256 nonce;
uint256 deadline;
}
mapping(address => uint256) public actionNonces;
function executeAction(
GameAction calldata action,
bytes calldata serverSignature
) external {
bytes32 digest = _hashTypedData(action);
address signer = ECDSA.recover(digest, serverSignature);
require(signer == GAME_SERVER_SIGNER, "Invalid signature");
require(action.nonce == actionNonces[msg.sender], "Invalid nonce");
actionNonces[msg.sender]++;
require(block.timestamp <= action.deadline, "Expired");
spendEnergy(action.characterId, action.energyCost);
_processAction(action);
}בנוסף, אנו מציגים משתנה cooldown לפעולות תכופות:
mapping(uint256 => mapping(uint8 => uint256)) public lastActionTime;
uint256 public constant BOSS_FIGHT_COOLDOWN = 4 hours;
modifier withCooldown(uint256 charId, uint8 actionType, uint256 cooldown) {
require(
block.timestamp >= lastActionTime[charId][actionType] + cooldown,
"Action on cooldown"
);
_;
lastActionTime[charId][actionType] = block.timestamp;
}
function fightBoss(uint256 charId) external withCooldown(charId, ACTION_BOSS, BOSS_FIGHT_COOLDOWN) {
spendEnergy(charId, BOSS_FIGHT_ENERGY_COST);
// ...
} סיכונים של אנרגיית ERC-20
מודל מורכב יותר: טוקן ERC-20 נפרד כאנרגיה, שניתן לקנות/למכור. פשרה: שחקנים יכולים לקנות אנרגיה ב-DEX → סיכון pay-to-win. אם מקובל, אנרגיית ERC-20 מספקת ערך כלכלי; אם לא, האנרגיה צריכה להיות בלתי ניתנת להעברה (חשבונאות פנימית, לא טוקן).
מודל כלכלי: כיורים ומקורות
מערכת האנרגיה פועלת כרגולטור כלכלי. חשוב לאזן בין מקורות לכיורים. פרמטרים מומלצים:
| פרמטר | המלצות |
|---|---|
| קצב התחדשות | מילוי מ-0 למקסימום ב-8–12 שעות |
| אנרגיה מקסימלית | 1–3 סשנים של משחק של 2–3 שעות כל אחד |
| מילוי פרימיום | לא יותר מ-2–3 מילוי מלא ביום |
| מכפיל דרגה | מקסימום פי 2–3, לא יותר |
ציר זמן והערכות עלות
מערכת בסיסית (התחדשות עצלה, הוצאה, cooldowns): 2–3 שבועות, עלות משוערת $5,000–$10,000. מערכת מלאה (התחדשות מבוססת דרגה, טוקן אנרגיית ERC-20, אינטגרציית DEX, פעולות חתומות נגד רמאות, אנליטיקת דשבורד): 5–7 שבועות, עלות משוערת $15,000–$25,000. העלות מחושבת באופן אישי לאחר ניתוח המכניקות שלך.
מה כלול במסירה
- ביקורת על המכניקות הקיימות
- עיצוב חוזה חכם עם התחשבות באופטימיזציית גז
- פיתוח ובדיקות יחידה (Foundry עם vm.warp)
- פריסת חוזה ואימות
- אינטגרציה עם השרת האחורי של המשחק (ethers.js, viem)
- תיעוד API ודוגמאות שימוש
- תמיכה לאחר השחרור (חודש אחד)
- הדרכה לצוות הפיתוח שלך
- גישה לכל קוד המקור וסקריפטי הפריסה
מדריך יישום שלב אחר שלב
- נתח את מכניקות המשחק שלך והגדר פרמטרי אנרגיה (קצב התחדשות, אנרגיה מקסימלית, פעולות).
- עצב ארכיטקטורת חוזה חכם עם הערכה עצלה ואנרגיית ERC-20 אופציונלית.
- פתח ובדוק חוזים באמצעות Foundry (עם vm.warp לנסיעה בזמן).
- פרוס חוזים ל-testnet ואמת אבטחה עם ביקורת.
- שלב עם השרת האחורי של המשחק באמצעות ethers.js או viem.
- פרוס ל-mainnet ועקוב אחר שימוש בגז.
- בצע בדיקות בטא עם משתמשים והתאם פרמטרים.
דוגמת בדיקת התחדשות ב-Foundry
```solidity function test_energyRegenOverTime() public { uint256 tokenId = 1; vm.prank(player); game.spendAllEnergy(tokenId); assertEq(energy.currentEnergy(tokenId), 0); vm.warp(block.timestamp + 50); assertEq(energy.currentEnergy(tokenId), 50e18); vm.warp(block.timestamp + 200); assertEq(energy.currentEnergy(tokenId), 100e18); } ```אנו מבטיחים ללא reentrancy ומשתמשים בתבניות מוכחות (OpenZeppelin). הניסיון שלנו: 5+ שנים ב-Web3, 30+ פרויקטים מיושמים. צור קשר לייעוץ על מכניקות ה-GameFi שלך. הזמן פיתוח מערכת אנרגיה במפתח פתוח.







