A marketplace is an excellent feature in games, allowing players to exchange items with each other. It provides a way for players to earn in-game currency by selling items they no longer need and upgrade their equipment by purchasing items from others.
In our previous tutorial, we built an inventory page for our multiplayer UI. Now that players have items in their inventory, we can add a marketplace where players can buy and sell items within the game.
To achieve this, we will extend the existing Express server with new API endpoints that handle inventory transactions, allowing players to manage their items within the marketplace.
Source location
You can find the complete sample source within the ${Gameface package}/Samples/uiresources/UITutorials/MultiplayerUI directory.
src folder contains the UI source code.
api folder contains the Express server source code.
Ensure to run npm i before testing the sample.
Refer to the README.md file in this directory for information on running the sample locally or previewing it without building or starting any processes.
Getting started - Backend
To enable users to interact with the marketplace, we need to update our server to handle requests for buying and selling items.
User money
First, we need to ensure every user has a starting balance to participate in the marketplace. To do this, we’ll update the user schema in our database to include a field for their in-game currency.
Now that we can store money in the database, we’ll initialize each user with starting funds when they register or log in to the game.
In this code, the generateUserData method will be executed when a new user registers, initializing their money along with the randomly generated items as explained in the previous tutorial.
Extending the items schema
To enable items to be listed for sale, we need to add a few additional properties to the item schema in our database. This will make it easier to develop the UI for the marketplace later.
Since our sample ensures that each item is unique for each user, multiple users can’t own the same item at once. That said, it’s straightforward to modify the schema of the items by adding additional properties.
We’ll introduce fields to indicate whether an item is up for sale, the seller’s name and ID, and the price the seller wants for the item.
Fetching items available for sale
To allow users to sell items in the marketplace, we first need to retrieve the items available in their inventory. These are items that are not currently marked for sale (forSale property is false).
We will create a new backend route that will handle fetching these items:
The getUserItemsForSale method will return the list of items in the user’s inventory that are not yet listed for sale.
Fetching items currently for Sale
To display items currently listed in the marketplace, we need to retrieve them from the backend. We’ll create a new endpoint to fetch all the items that have been marked for sale.
The getItemsForSale method will return all items marked as available for sale, which can then be displayed in the UI.
This method also supports filters that will allow us to show all items for sale, just the current user’s items (when filterUserItems is passed), or items not owned by the current user (when filterWithoutUserItems is passed).
Cancellling an item from listing
If a user wants to remove an item from the marketplace, we need to support this operation by resetting the price, soldById, soldByName, and forSale fields for the item.
First, we’ll define an API endpoint to handle this:
The cancelItem method will reset the relevant fields and save the item:
Selling an item
To list an item for sale in the marketplace, we’ll create a new API endpoint that allows users to submit such a request.
The sellItem method will handle the data sent in the request, which includes the seller’s name, ID, and the price the user wants to set for the item. It will also validate the request to ensure all necessary data is provided.
Buying an item
After implementing the logic for listing and removing items for sale, it’s time to enable users to purchase items from the marketplace. To achieve this, we’ll create a new API endpoint for handling item purchase requests.
The buyItem method will retrieve information about the buyer and seller, update their items arrays, transfer ownership of the item, and mark that it is no longer for sale.
Notifying users for item purchase
To notify users when an item is purchased, we will use websockets. This allows us to send real-time updates to the UI.
First, we’ll define a method to return all active sockets on the backend.
Next, we’ll modify our buyItem method to send a notification to both the buyer and the seller when their account balance changes.
We’ll retrieve the active sockets using the getSockets method and emit a user-money-changed event to both the buyer and the seller if they are online. This event will be handled later in the UI.
Lastly, we can notify the seller with a socket event that their item has been purchased. The event server-notification-message will send a message containing the purchase details.
Getting started - Frontend
To create the UI for the marketplace, we will add a new Marketplace page where users can buy and sell items. However, before that, we need to display the user’s in-game currency on the UI.
Displaying the user’s money
To show the user’s current balance, we’ll create a context and provider to manage the user’s money state. This allows us to access the user’s balance throughout the UI without using state management libraries like Redux. It’s essential because later, we’ll use this information to check if the user has enough funds to purchase an item and display messages when they don’t.
We’ll set up a UserMoneyProvider that will store the user’s money state and a useUserMoney hook to access the balance from any component within the provider.
Next, we need to provide the context in the app. We’ll wrap it around the main routes in App.jsx:
Now we can display or modify the user’s money across the UI. Let’s update Home.jsx to show the user’s balance next to their name in the top-right corner.
We’ll handle a few additional tasks:
Access the state to set and display the user’s money using the useUserMoney hook.
Fetch the user’s balance when the component loads via the getMoneyData method.
Listen for real-time updates to the user’s money using WebSockets (user-money-changed event), which will allow the balance to update dynamically when a transaction occurs.
Reusing spatial navigation for the marketplace page
To enable spatial navigation on our marketplace page, we need to make it reusable. We’ll create a custom hook to initialize spatial navigation, which can be used on both the inventory and marketplace pages.
With this hook in place, we can now remove the initialization of spatial navigation from the inventory page.
Marketplace page
Now, we can start building the marketplace page, which will allow users to buy or sell items. First, we’ll create a wrapper for the marketplace page, including a menu for switching between views - either for buying or selling items. It will follow the same structure as the leaderboard, using NavLink for navigation and Outlet to display the active view. We’ll also display the selected item’s stats on the right, as we did in the inventory tutorial, using the ItemStats component.
With the marketplace page wrapper ready, we can add it to the main navigation in the App.jsx.
Each subroute of the marketplace directs to the MarketplaceItems component, which will render different data depending on whether the user is buying or selling. This is easily managed by retrieving the active route (either /marketplace or /marketplace/sell).
Displaying items in the marketplace
To display items on the marketplace page, we’ll create a MarketplaceItems component. We’ll start by fetching items that are either for sale or can be sold by the user, depending on the active route. The route will determine whether the component shows items for buying or selling. Using the pathname property from the useLocation hook, we can check the route and define a memoized variable using useMemo based on the route.
With the displayItemsForSale variable, we can now fetch the appropriate data. We’ll define state for the items and get the current user’s ID to fetch the correct items.
To render the itemsData, we’ll use a scrollable container and the ItemPreview component from the inventory implementation. A loader will be added until the data is fully loaded.
Adding filters for buying items
In the marketplace, we’ll add three buttons to filter the items when a player wants to buy. These filters will be helpful for players to view all available items, see only the items they are selling, or browse items they don’t own and can purchase.
We begin by defining an enum variable for the filter types:
Next, we initialize the active filter state, with the default value set to display all items:
To fetch filtered items from the database, we need to adjust the URL queries based on the selected filter.
Now we can create filter buttons in the UI, along with a simple “Refresh” button to manually update the item list:
Adding spatial navigation
As previously mentioned, we’ll incorporate spatial navigation into the marketplace page. This is especially convenient since we already created the useSpatialNavigation hook.
Removing the item after purchase, sell, or cancellation
When an item is bought, sold, or canceled by the user, the item list should update. To avoid making an extra server request for the full item list, we can trigger a custom event to remove the item from the itemsData state. This custom event will be triggered by the BuyItemForm or SellItemForm components, which will be defined later.
Adding new elements to the ItemStats component
Since the ItemStats component is used on both the inventory and marketplace pages, we need to add elements that allow users to sell, buy, or cancel items in the marketplace.
We’ll achieve this by checking the current route and conditionally rendering the BuyItemForm or SellItemForm components, which will be defined later.
BuyItemForm component
This component adds extra details to the item stats, such as:
The seller’s name
The item’s price
Displays a warning if the user doesn’t have enough funds, using the useUserMoney hook created earlier.
A button to either buy the item or cancel the sale (if the logged-in user is the seller).
The StatItem component is reused to keep the layout consistent with the ItemStats component.
SellItemform component
This component enables users to sell items on the marketplace.
We also use the gameface-text-field component for creating a numeric input field for the item’s price, as Gameface doesn’t natively support number inputs. This component restricts input to numbers.
Notify active users for item purchases
To keep players informed when one of their items is purchased in the marketplace, we’ll add a toast notification for users who are online. This ensures that players are immediately notified when a transaction occurs.
The toast notification will be triggered when the server emits a server-notification-message socket event, which will be handled by the frontend. We’ll subscribe to this event on the home page.
Next, we render the gameface-toast component:
Resources
For the item icons, we used the free Basic RPG Item Icons from this resource.