{"id":9695210,"url":"https://github.com/solana-developers/solana-game-preset","last_synced_at":"2025-04-14T23:42:44.649Z","repository":{"id":203596960,"uuid":"708808243","full_name":"solana-developers/solana-game-preset","owner":"solana-developers","description":"A npx preset with anchor program and multiple clients","archived":false,"fork":false,"pushed_at":"2025-03-12T17:54:51.000Z","size":4527,"stargazers_count":16,"open_issues_count":1,"forks_count":9,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-14T23:42:40.087Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/solana-developers.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-23T12:41:04.000Z","updated_at":"2025-04-09T10:27:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"790bf935-d50e-49ba-acb8-def613dc4db0","html_url":"https://github.com/solana-developers/solana-game-preset","commit_stats":null,"previous_names":["woody4618/solana_game_preset","solana-developers/solana_game_preset"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solana-developers%2Fsolana-game-preset","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solana-developers%2Fsolana-game-preset/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solana-developers%2Fsolana-game-preset/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solana-developers%2Fsolana-game-preset/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/solana-developers","download_url":"https://codeload.github.com/solana-developers/solana-game-preset/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248981259,"owners_count":21193143,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-05-15T12:02:18.502Z","updated_at":"2025-04-14T23:42:44.630Z","avatar_url":"https://github.com/solana-developers.png","language":"C#","funding_links":[],"categories":["C\\#"],"sub_categories":[],"readme":"# Solana Game Preset\n\nThis game is meant as a starter game for onchain games.\nThere is a JS and a Unity client for this game and both are talking to a Solana Anchor program.\n\nThis game uses gum session keys for auto approval of transactions.\nNote that neither the program nor session keys are audited. Use at your own risk.\n\nTo initialize the game preset with your own project name please use the command:\n\n```bash\nnpx create-solana-game \u003cname\u003e\n```\n\n# How to run this example\n\n## Quickstart\n\nThe unity client and the js client are both connected to the same program and should work out of the box connecting to the already deployed program.\n\n### Unity\n\nOpen the Unity project with Unity Version 2021.3.32.f1 (or similar), open the GameScene or LoginScene and hit play.\nUse the editor login button in the bottom left. If you cant get devnet sol you can copy your address from the console and use the faucet here: https://faucet.solana.com/ to request some sol.\n\n### Js Client\n\nTo start the js client open the project in visual studio code and run:\n\n```bash\ncd app\nyarn install\nyarn dev\n```\n\nTo start changing the program and connecting to your own program follow the steps below.\n\n## Installing Solana dependencies\n\nFollow the installation here: https://www.anchor-lang.com/docs/installation\nInstall the latest 1.16 solana version (1.17 is not supported yet)\nsh -c \"$(curl -sSfL https://release.solana.com/v1.16.18/install)\"\n\nAnchor program\n\n1. Install the [Anchor CLI](https://project-serum.github.io/anchor/getting-started/installation.html)\n2. `cd program` to end the program directory\n3. Run `anchor build` to build the program\n4. Run `anchor deploy` to deploy the program\n5. Copy the program id from the terminal into the lib.rs, anchor.toml and within the unity project in the AnchorService and if you use js in the anchor.ts file\n6. Build and deploy again\n\nNext js client\n\n1. Install [Node.js](https://nodejs.org/en/download/)\n2. Copy the program id into app/utils/anchor.ts\n3. `cd app` to end the app directory\n4. Run `yarn install` to install node modules\n5. Run `yarn dev` to start the client\n6. After doing changes to the anchor program make sure to copy over the types from the program into the client so you can use them. You can find the js types in the target/idl folder.\n\nUnity client\n\n1. Install [Unity](https://unity.com/)\n2. Open the MainScene\n3. Hit play\n4. After doing changes to the anchor program make sure to regenerate the C# client: https://solanacookbook.com/gaming/porting-anchor-to-unity.html#generating-the-client\n   Its done like this (after you have build the program):\n\n```bash\ncd program\ndotnet tool install Solana.Unity.Anchor.Tool \u003c- run once\ndotnet anchorgen -i target/idl/lumberjack.json -o target/idl/Lumberjack.cs\n```\n\nIf you are still on an old version that is not compatible with anchor 0.30.1 (\u003c0.2.13) please run:\n\n```bash\ndotnet tool update -g Solana.Unity.Anchor.Tool\n```\n\n(Replace lumberjack with the name of your program)\n\nthen copy the c# code into the unity project.\n\n## Connect to local host (optional)\n\nTo connect to local host from Unity add these links on the wallet holder game object:\nhttp://localhost:8899\nws://localhost:8900\n\n## Video walkthroughs\n\nHere are two videos explaining the energy logic and session keys:\nSession keys:\nhttps://www.youtube.com/watch?v=oKvWZoybv7Y\u0026t=17s\u0026ab_channel=Solana\nEnergy system:\nhttps://www.youtube.com/watch?v=YYQtRCXJBgs\u0026t=4s\u0026ab_channel=Solana\n\n# Project structure\n\nThe anchor project is structured like this:\n\nThe entry point is in the lib.rs file. Here we define the program id and the instructions.\nThe instructions are defined in the instructions folder.\nThe state is defined in the state folder.\n\nSo the calls arrive in the lib.rs file and are then forwarded to the instructions.\nThe instructions then call the state to get the data and update it.\n\n```shell\n├── src\n│   ├── instructions\n│   │   ├── chop_tree.rs\n│   │   ├── init_player.rs\n│   │   └── update_energy.rs\n│   ├── state\n│   │   ├── game_data.rs\n│   │   ├── mod.rs\n│   │   └── player_data.rs\n│   ├── lib.rs\n│   └── constants.rs\n│   └── errors.rs\n\n```\n\nThe project uses session keys (maintained by Magic Block) for auto approving transactions using an expiring token.\n\n# Energy System\n\nMany casual games in traditional gaming use energy systems. This is how you can build it on chain.\n\nIf you have no prior knowledge in solan and rust programming it is recommended to start with the Solana cookbook [Hello world example](\u003c[https://unity.com/](https://solanacookbook.com/gaming/hello-world.html#getting-started-with-your-first-solana-game)\u003e).\n\n## Anchor program\n\nHere we will build a program which refills energy over time which the player can then use to perform actions in the game.\nIn our example it will be a lumber jack which chops trees. Every tree will reward on wood and cost one energy.\n\n### Creating the player account\n\nFirst the player needs to create an account which saves the state of our player. Notice the last_login time which will save the current unix time stamp of the player he interacts with the program.\nLike this we will be able to calculate how much energy the player has at a certain point in time.  \nWe also have a value for wood which will store the wood the lumber jack chucks in the game.\n\n```rust\n\npub fn init_player(ctx: Context\u003cInitPlayer\u003e) -\u003e Result\u003c()\u003e {\n    ctx.accounts.player.energy = MAX_ENERGY;\n    ctx.accounts.player.last_login = Clock::get()?.unix_timestamp;\n    ctx.accounts.player.authority = ctx.accounts.signer.key();\n    Ok(())\n}\n\n#[derive(Accounts)]\npub struct InitPlayer\u003c'info\u003e {\n    #[account(\n        init,\n        payer = signer,\n        space = 1000, // 8+32+x+1+8+8+8 But taking 1000 to have space to expand easily.\n        seeds = [b\"player\".as_ref(), signer.key().as_ref()],\n        bump,\n    )]\n    pub player: Account\u003c'info, PlayerData\u003e,\n\n    #[account(\n        init_if_needed,\n        payer = signer,\n        space = 1000, // 8 + 8 for anchor account discriminator and the u64. Using 1000 to have space to expand easily.\n        seeds = [b\"gameData\".as_ref()],\n        bump,\n    )]\n    pub game_data: Account\u003c'info, GameData\u003e,\n\n    #[account(mut)]\n    pub signer: Signer\u003c'info\u003e,\n    pub system_program: Program\u003c'info, System\u003e,\n}\n```\n\n### Chopping trees\n\nThen whenever the player calls the chop_tree instruction we will check if the player has enough energy and reward him with one wood.\n\n```rust\n    #[error_code]\n    pub enum ErrorCode {\n        #[msg(\"Not enough energy\")]\n        NotEnoughEnergy,\n    }\n\n    pub fn chop_tree(mut ctx: Context\u003cChopTree\u003e) -\u003e Result\u003c()\u003e {\n        let account = \u0026mut ctx.accounts;\n        update_energy(account)?;\n\n        if ctx.accounts.player.energy == 0 {\n            return err!(ErrorCode::NotEnoughEnergy);\n        }\n\n        ctx.accounts.player.wood = ctx.accounts.player.wood + 1;\n        ctx.accounts.player.energy = ctx.accounts.player.energy - 1;\n        msg!(\"You chopped a tree and got 1 log. You have {} wood and {} energy left.\", ctx.accounts.player.wood, ctx.accounts.player.energy);\n        Ok(())\n    }\n```\n\n### Calculating the energy\n\nThe interesting part happens in the update_energy function. We check how much time has passed and calculate the energy that the player will have at the given time.\nThe same thing we will also do in the client. So we basically lazily update the energy instead of polling it all the time.\nThe is a common technic in game development.\n\n```rust\n\nconst TIME_TO_REFILL_ENERGY: i64 = 60;\nconst MAX_ENERGY: u64 = 10;\n\npub fn update_energy(\u0026mut self) -\u003e Result\u003c()\u003e {\n    // Get the current timestamp\n    let current_timestamp = Clock::get()?.unix_timestamp;\n\n    // Calculate the time passed since the last login\n    let mut time_passed: i64 = current_timestamp - self.last_login;\n\n    // Calculate the time spent refilling energy\n    let mut time_spent = 0;\n\n    while time_passed \u003e= TIME_TO_REFILL_ENERGY \u0026\u0026 self.energy \u003c MAX_ENERGY {\n        self.energy += 1;\n        time_passed -= TIME_TO_REFILL_ENERGY;\n        time_spent += TIME_TO_REFILL_ENERGY;\n    }\n\n    if self.energy \u003e= MAX_ENERGY {\n        self.last_login = current_timestamp;\n    } else {\n        self.last_login += time_spent;\n    }\n\n    Ok(())\n}\n```\n\n## Js client\n\n### Subscribe to account updates\n\nIt is possible to subscribe to account updates via a websocket. This get updates to this account pushed directly back to the client without the need to poll this data. This allows fast gameplay because the updates usually arrive after around 500ms.\n\n```js\nuseEffect(() =\u003e {\n  if (!publicKey) {\n    return;\n  }\n  const [pda] = PublicKey.findProgramAddressSync(\n    [Buffer.from(\"player\", \"utf8\"), publicKey.toBuffer()],\n    new PublicKey(LUMBERJACK_PROGRAM_ID)\n  );\n  try {\n    program.account.playerData.fetch(pda).then((data) =\u003e {\n      setGameState(data);\n    });\n  } catch (e) {\n    window.alert(\"No player data found, please init!\");\n  }\n\n  connection.onAccountChange(pda, (account) =\u003e {\n    setGameState(program.coder.accounts.decode(\"playerData\", account.data));\n  });\n}, [publicKey]);\n```\n\n### Calculate energy and show countdown\n\nIn the java script client we can then perform the same logic and show a countdown timer for the player so that he knows when the next energy will be available:\n\n```js\nconst interval = setInterval(async () =\u003e {\n    if (gameState == null || gameState.lastLogin == undefined || gameState.energy \u003e= 10) {\n        return;\n    }\n\n    const lastLoginTime = gameState.lastLogin * 1000;\n    const currentTime = Date.now();\n    const timePassed = (currentTime - lastLoginTime) / 1000;\n\n    while (timePassed \u003e TIME_TO_REFILL_ENERGY \u0026\u0026 gameState.energy \u003c MAX_ENERGY) {\n        gameState.energy++;\n        gameState.lastLogin += TIME_TO_REFILL_ENERGY;\n        timePassed -= TIME_TO_REFILL_ENERGY;\n    }\n\n    setTimePassed(timePassed);\n\n    const nextEnergyIn = Math.floor(TIME_TO_REFILL_ENERGY - timePassed);\n    setEnergyNextIn(nextEnergyIn \u003e 0 ? nextEnergyIn : 0);\n    }, 1000);\n\n    return () =\u003e clearInterval(interval);\n}, [gameState, timePassed]);\n\n...\n\n{(gameState \u0026\u0026 \u003cdiv className=\"flex flex-col items-center\"\u003e\n    {(\"Wood: \" + gameState.wood + \" Energy: \" + gameState.energy + \" Next energy in: \" + nextEnergyIn )}\n\u003c/div\u003e)}\n\n```\n\n## Unity client\n\nIn the Unity client everything interesting happens in the AnchorService.\nTo generate the client code you can follow the instructions here: https://solanacookbook.com/gaming/porting-anchor-to-unity.html#generating-the-client\n\n```bash\ncd program\ndotnet tool install Solana.Unity.Anchor.Tool \u003c- run once\ndotnet anchorgen -i target/idl/lumberjack.json -o target/idl/Lumberjack.cs\n```\n\n### Session keys\n\nSession keys is an optional component. What it does is creating a local key pair which is toped up with some sol which can be used to autoapprove transactions. The session token is only allowed on certain functions of the program and has an expiry of 23 hours. Then the player will get the sol back and can create a new session.\n\nWith this you can now build any energy based game and even if someone builds a bot for the game the most he can do is play optimally, which maybe even easier to achieve when playing normally depending on the logic of your game.\n\nThis game becomes even better when combined with the Token example from Solana Cookbook and you actually drop some spl token to the players.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolana-developers%2Fsolana-game-preset","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsolana-developers%2Fsolana-game-preset","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolana-developers%2Fsolana-game-preset/lists"}