-
Notifications
You must be signed in to change notification settings - Fork 4
Space Shooter Tutorial
This tutorial will show you how to use the NEAR SDK in an existing space shooter game.
- Clone the project from here: https://github.com/svntax/near-space-shooter
- Open the project using the C# Mono version of Godot 3.3.3.
- Have Godot generate the C# project files by adding a C# script (you can delete it afterwards). See here for more info.
You should now have a simple space shooter game working.
TODO screenshot here
TODO
The project already comes with the NEAR SDK, so the rest of the tutorial will be about implementation.
The game is missing a high scores screen, so let's add one. First, we can duplicate one of the existing buttons in the main menu, rename it, and set up a new function in MainMenu.gd
for its pressed()
signal.
TODO screenshot of new high scores button
Next, let's create a new scene for the high scores screen, name it HighScoresMenu
, and save it in the root project folder, the same place where the main menu scene is saved. We'll also create a new script for it with the same name, which later on will handle fetching the high scores from NEAR.
TODO screenshot of new files for high score menu
Now we can go back to the MainMenu.gd
script, and make the game change to this new scene when the high scores button is pressed. The script should now look something like the following:
extends Node2D
func _on_StartButton_pressed():
get_tree().change_scene("res://Gameplay.tscn")
func _on_ExitButton_pressed():
get_tree().quit()
func _on_HighScoresButton_pressed():
get_tree().change_scene("res://HighScoresMenu.tscn")
We can start setting up the high scores menu by adding backgrounds, text labels, buttons, and other necessary UI elements. At this point, it's up to you how you want to set up the menu. As long as there's a button to go back and a table or container of some sort with rows for the high scores, the setup will work fine.
Here is how I'll be setting up the scene:
TODO screenshot of high scores menu with UI elements
I copied the background and header label from the main menu, added a button for going back to the main menu, and then added a new GridContainer
node with 2 columns and two placeholder Label
nodes for the player's name and score. These label nodes have their horizontal size flags set to Fill and Expand in order to make them take up the entire width of the column. The player label is left-aligned, and the score label is right-aligned.
Now we can start working on the high scores script. To start, we need to connect to the NEAR network. In our MainMenu.gd
script, let's check if a connection exists, and if not, connect to the testnet.
extends Node2D
var config = {
"network_id": "testnet",
"node_url": "https://rpc.testnet.near.org",
"wallet_url": "https://wallet.testnet.near.org",
}
func _ready():
if Near.near_connection == null:
Near.start_connection(config)
func _on_StartButton_pressed():
get_tree().change_scene("res://Gameplay.tscn")
func _on_ExitButton_pressed():
get_tree().quit()
func _on_HighScoresButton_pressed():
get_tree().change_scene("res://HighScoresMenu.tscn")
Then in our high scores menu script, we'll call the getScores()
method from our smart contract to retrieve the high scores. The return value will be a JSON string, which when parsed, will be an array of dictionaries, each with a username
and value
field. Our strategy here is to use the placeholder label nodes as templates by hiding them, duplicating them and setting their respective values for each score in the array, and at the end, add them to the grid container node.
The HighScoresMenu.gd
script should now look something like the following, including a new function for the back button:
extends Node2D
const CONTRACT_NAME = "name of your smart contract here"
onready var scores_grid = $ScoresGrid
onready var player_name_label = $ScoresGrid/PlayerName
onready var player_score_label = $ScoresGrid/PlayerScore
func _ready():
# First, hide the placeholder labels
player_name_label.hide()
player_score_label.hide()
# Next, fetch the high scores and create new labels for each score
var result = Near.call_view_method(CONTRACT_NAME, "getScores")
if result is GDScriptFunctionState:
result = yield(result, "completed")
if result.has("error"):
pass # Error handling here
else:
var data = result.data
var json_data = JSON.parse(data)
var high_scores: Array = json_data.result
for score in high_scores:
var name_label = player_name_label.duplicate()
name_label.set_text(score.username)
scores_grid.add_child(name_label)
name_label.show()
var score_label = player_score_label.duplicate()
score_label.set_text(str(score.value))
scores_grid.add_child(score_label)
score_label.show()
func _on_BackButton_pressed():
get_tree().change_scene("res://MainMenu.tscn")
Now when you run the game and go to the high scores screen, you should see something like this:
TODO screenshot of high scores after being fetched
If for any reason, the game fails to retrieve the high scores, we can display a message in the middle telling the user. To do so, you can add a new Label
node, place it in the middle, set its text to a message like "Error: Failed to get high scores", and hide it by default. Then, in our script, show this label if an error occurred.
...
if result.has("error"):
$MessageLabel.show()
else:
...
In order to let users sign in with their NEAR wallet, we can add a login button in the main menu with that functionality.
First, let's duplicate one of the existing buttons, drag it outside of the Buttons container, move it to a corner, rename it, and set up a new function for its pressed()
signal. We can also add a new Label
node to display the user's name when logged in.
TODO screenshot of new login button
Next, in our MainMenu.gd
script, after connecting to the NEAR network, we have to create a new WalletConnection
, set up functions for the user_signed_in
and user_signed_out
signals, and make sure the login button and username label have the correct text set if the user is already logged in.
extends Node2D
onready var login_button = $LoginButton
onready var player_name_label = $PlayerNameLabel
var config = {
"network_id": "testnet",
"node_url": "https://rpc.testnet.near.org",
"wallet_url": "https://wallet.testnet.near.org",
}
var wallet_connection
func _ready():
player_name_label.hide()
if Near.near_connection == null:
Near.start_connection(config)
wallet_connection = WalletConnection.new(Near.near_connection)
wallet_connection.connect("user_signed_in", self, "_on_user_signed_in")
wallet_connection.connect("user_signed_out", self, "_on_user_signed_out")
if wallet_connection.is_signed_in():
_on_user_signed_in(wallet_connection)
func _on_user_signed_in(wallet: WalletConnection):
login_button.set_text("Sign Out")
player_name_label.show()
player_name_label.set_text(wallet.get_account_id())
func _on_user_signed_out(wallet: WalletConnection):
login_button.set_text("Sign In")
player_name_label.hide()
func _on_StartButton_pressed():
get_tree().change_scene("res://Gameplay.tscn")
func _on_ExitButton_pressed():
get_tree().quit()
func _on_HighScoresButton_pressed():
get_tree().change_scene("res://HighScoresMenu.tscn")
func _on_LoginButton_pressed():
pass # Replace with function body.
Then, we can crate a new constant at the top of the script for the smart contract's name, and in _on_LoginButton_pressed()
, call either sign_in()
or sign_out()
depending on whether the user is signed in or not.
const CONTRACT_NAME = "name of your smart contract here"
# ... other code ...
func _on_LoginButton_pressed():
if wallet_connection.is_signed_in():
wallet_connection.sign_out()
else:
wallet_connection.sign_in(CONTRACT_NAME)
Now, when you run the game and click on "Sign In", you'll be redirected to the NEAR web wallet, where you can authorize the game to create a new access key, and once you're done, you can close the browser tab and go back to the game.
TODO