RPG Building Basics - Encode Club Sui Series #5

In this fifth of six educational videos, Shayan walks through the basics of creating a role playing game on Sui.

RPG Building Basics - Encode Club Sui Series #5

In the fifth video of Encode Club’s Sui series, we show how to create characters and items, and how to make them interact, for a role playing game (RPG).

The Sui Foundation partnered with Encode Club to offer a series of six developer-focused videos. This series will range from the basics of Sui to tutorials about building smart contracts and working with objects in Sui Move.

Learning Highlights

Due to Sui Move’s object-centric programming model, as well as its scalability, Sui promises to be the first blockchain to truly deliver a web2 experience to web3. The forefront of this experience includes gaming. Games are both complex in nature to program, as well as requiring robust infrastructure to ensure a seamless experience for players. Thanks to the aforementioned two points, Sui is up to the challenge.

Let’s look at a coding example for an on-chain RPG in Sui Move. The following example is adapted from Sam Blackshear’s hero.move code.  

The Objects

/// Our hero!
struct Hero has key, store {
	id: UID,
	/// Hit points. If they go to zero, the hero can't do anything
	hp: u64,
	/// Experience of the hero. Begins at zero
	experience: u64,
	/// The hero's minimal inventory
	sword: Option<Sword>,
	/// An ID of the game user is playing
	game_id: ID,
}

The above code defines our playable character. As you can see from its fields, this Hero is comparable to other characters in RPGs. It has hit points (HP), experience, and an inventory.

/// The hero's trusty sword
struct Sword has key, store {
	id: UID,
	/// Constant set at creation. Acts as a multiplier on sword's strength.
	/// Swords with high magic are rarer (because they cost more).
	magic: u64,
	/// Sword grows in strength as we use it
	strength: u64,
	/// An ID of the game
	game_id: ID,
}

The above code shows our hero’s sword. Note that this sword has the key and store abilities. To recap from previous lessons in this series, key means it’s an ownable asset, and can exist in top-level storage. Move objects in this category can also be accessed from external APIs, creating Sui’s unique possibility of using items across multiple games. And store means that this object is freely wrappable and transferable.

/// A creature that the hero can slay to level up
struct Boar has key {
	id: UID,
	/// Hit points before the boar is slain
	hp: u64,
	/// Strength of this particular boar
	strength: u64,
	/// An ID of the game
	game_id: ID,
}

Above, we define a boar, non-playable character (NPC) or enemy in our game. Similar to other games of this genre, we can create NPCs for our Hero to fight and gain experience, or shop for items and receive quests.

The Action

/// Slay the `boar` with the `hero`'s sword, get experience.
/// Aborts if the hero has 0 HP or is not strong enough to slay the boar
public entry fun slay(
	game: &GameInfo, hero: &mut Hero, boar: Boar, ctx: &TxContext
) {
	check_id(game, hero.game_id);
	check_id(game, boar.game_id);
	let Boar { id: boar_id, strength: boar_strength, hp, game_id: _ } = boar;
	let hero_strength = hero_strength(hero);
	let boar_hp = hp;
	let hero_hp = hero.hp;
	// attack the boar with the sword until its HP goes to zero
	while (boar_hp > hero_strength) {
		// first, the hero attacks
		boar_hp = boar_hp - hero_strength;
		// then, the boar gets a turn to attack. if the boar would kill
		// the hero, abort--we can't let the boar win!
		assert!(hero_hp >= boar_strength , EBOAR_WON);
		hero_hp = hero_hp - boar_strength;
	};
	// hero takes their licks
	hero.hp = hero_hp;
	// hero gains experience proportional to the boar, sword grows in
	// strength by one (if hero is using a sword)
	hero.experience = hero.experience + hp;
	if (option::is_some(&hero.sword)) {
		level_up_sword(option::borrow_mut(&mut hero.sword), 1)
	};
	// let the world know about the hero's triumph by emitting an event!
	event::emit(BoarSlainEvent {
		slayer_address: tx_context::sender(ctx),
		hero: object::uid_to_inner(&hero.id),
		boar: object::uid_to_inner(&boar_id),
		game_id: id(game)
	});
	object::delete(boar_id);
}

The action shown in the above code describes the slay function. At a high level, this function first checks to make sure that the Hero and the Boar both belong to the same game instance. Then the duel between the Hero and the Boar takes place, with a check to ensure that the Hero’s HP cannot reach zero. Once the duel is complete, the hero gains experience proportional to the Boar, and the Hero’s Sword grows in strength by one (if the Hero is wielding a Sword). Finally, the function emits an event, BoarSlayEvent. Events in Sui Move let indexers track on-chain actions, an important means of achieving a universally recognized object state.

The code examples above give a brief excerpt of Sam’s hero.move code. This code serves as a valuable example for game builders on Sui and, as it’s open source, feel free to fork the repo and build your own game!

Watch the Whole Series

  1. What's Sui?
  2. Smart Contracts
  3. Creating Objects and NFTs
  4. Dynamic Fields and Collections
  5. RPG Building Basics
  6. Deploy a Game to the Blockchain