Skip to content

Commit

Permalink
Add a proper tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
jspahrsummers authored Aug 26, 2024
2 parents a2ec213 + 668b712 commit 088b6aa
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 46 deletions.
7 changes: 5 additions & 2 deletions actors/player.gd
Original file line number Diff line number Diff line change
Expand Up @@ -260,15 +260,18 @@ func _land() -> void:
landing.visibility_changed.connect(func() -> void:
landing.add_sibling(self.ship)
landing.queue_free()
self._depart_from_port())
self._depart_from_port(port))

self.landed.emit(self, port)

func _depart_from_port() -> void:
func _depart_from_port(port: Port) -> void:
self.message_log.clear()

self.calendar.pass_approximate_days(PORT_LANDING_APPROXIMATE_DAYS)
self._reset_controls()
self._reset_velocity()
self.takeoff_sound.play()
self.message_log.add_message("Departing from %s at %s." % [port.name, self.calendar.get_gst()], MessageLog.LONG_MESSAGE_LIFETIME, false)

func _reset_controls() -> void:
self.ship.rigid_body_thruster.throttle = 0.0
Expand Down
4 changes: 4 additions & 0 deletions mechanics/hyperspace/hyperspace_scene_switcher.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class_name HyperspaceSceneSwitcher
## The player.
@export var player: Player

@export var message_log: MessageLog

## The approximate number of days that should pass with each hyperspace jump.
const HYPERSPACE_APPROXIMATE_TRAVEL_DAYS = 3

Expand Down Expand Up @@ -55,6 +57,7 @@ func load_jump_destination() -> void:
var previous_system_instance := StarSystemInstance.star_system_instance_for_node(player_ship)
previous_system_instance.remove_child(player_ship)
self.remove_child(previous_system_instance)
self.message_log.clear()

var star_system_instance: StarSystemInstance = self._loaded_system_nodes.get(destination.name)
if star_system_instance == null:
Expand All @@ -73,6 +76,7 @@ func load_jump_destination() -> void:

self._hyperdrive_system.shift_jump_path()
self.jump_destination_loaded.emit(star_system_instance)
self.message_log.add_message("Arriving in the %s system at %s." % [destination.name, self.player.calendar.get_gst()], MessageLog.LONG_MESSAGE_LIFETIME, false)

func finish_jump() -> void:
assert(self._hyperdrive_system.jumping, "No jump in progress")
Expand Down
7 changes: 4 additions & 3 deletions mechanics/missions/mission_controller.gd
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,11 @@ func _fail_mission(mission: Mission, failure_status: Mission.Status = Mission.St
self.cargo_hold.remove_up_to(commodity, amount)

if failure_status == Mission.Status.FORFEITED:
self.message_log.add_message("Mission forfeited: %s" % mission.title)
self.message_log.add_message("Mission forfeited: %s" % mission.title, MessageLog.LONG_MESSAGE_LIFETIME)
self.mission_forfeited.emit(mission)
else:
self.message_log.add_message("Mission failed: %s" % mission.title)
# Hack: call deferred to ensure these notices appear after hyperjump/departure calendar messages.
self.message_log.add_message.call_deferred("Mission failed: %s" % mission.title, MessageLog.LONG_MESSAGE_LIFETIME)
self.mission_failed.emit(mission)

## Mark a mission as succeeded, and pay out the proceeds.
Expand All @@ -137,7 +138,7 @@ func _succeed_mission(mission: Mission) -> void:
# TODO: This can fail if the player lacks cargo space. Deposit currency in lieu of commodities?
trade_asset.add_up_to(amount, self.cargo_hold, self.bank_account)

self.message_log.add_message("Mission succeeded: %s" % mission.title)
self.message_log.add_message("Mission succeeded: %s" % mission.title, MessageLog.LONG_MESSAGE_LIFETIME)
self.mission_succeeded.emit(mission)

## Evaluates all missions for failure conditions.
Expand Down
157 changes: 157 additions & 0 deletions mechanics/tutorial/tutorial_window.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
extends Window

@export var label: RichTextLabel
@export var previous_button: Button
@export var next_button: Button

## The current stage of the tutorial.
##
## Note that these values are saved via [SaveGame], so be careful about backwards compatibility!
enum Stage {
INITIAL = 0,
SHIP_CONTROLS = 1,
LAND_ON_PLANET = 2,
OPEN_GALAXY_MAP = 3,
HYPERJUMP = 4,
FINAL = 1000,
}

var _stage: Stage = Stage.INITIAL:
set(value):
if _stage == value:
return

_stage = value
self._update()

func _ready() -> void:
self._update()

func _update() -> void:
self.previous_button.visible = self._stage != Stage.INITIAL
self.next_button.text = "DONE" if self._stage == Stage.FINAL else "NEXT"

match self._stage:
Stage.INITIAL:
self.label.text = """\
Welcome to your new ship! This tutorial will guide you through the basics of ship control, navigation, and interstellar travel. Let's begin!"""

Stage.SHIP_CONTROLS:
var control_scheme := UserPreferences.control_scheme
var thrust_key := self._get_action_binding("thrust")
var turn_left_key := self._get_action_binding("turn_left")
var turn_right_key := self._get_action_binding("turn_right")
var fire_key := self._get_action_binding("fire")

var movement_text: String
match UserPreferences.control_scheme:
UserPreferences.ControlScheme.RELATIVE:
movement_text = """\
- [color=yellow]{thrust_key}[/color]: Thrust forward
- [color=yellow]{turn_left_key}[/color]/[color=yellow]{turn_right_key}[/color]: Turn left/right""".format({
"thrust_key": thrust_key,
"turn_left_key": turn_left_key,
"turn_right_key": turn_right_key
})

UserPreferences.ControlScheme.ABSOLUTE:
movement_text = """\
- [color=yellow]Arrow keys[/color]: Move in any direction"""

self.label.text = """\
To maneuver your ship:
{movement_text}
- [color=yellow]{fire_key}[/color]: Fire weapons
Try moving and firing to get familiar with the controls.""".format({
"movement_text": movement_text,
"fire_key": fire_key
})

Stage.LAND_ON_PLANET:
var cycle_landing_target_key := self._get_action_binding("cycle_landing_target")
var land_key := self._get_action_binding("land")

self.label.text = """\
Stop at ports to refuel, trade, and obtain missions:
1. Click or press [color=yellow]{cycle_key}[/color] to target a planet, moon, or space station.
2. Approach your selected port and slow down.
3. Press [color=yellow]{land_key}[/color] to land.
Try landing on Earth now.""".format({
"cycle_key": cycle_landing_target_key,
"land_key": land_key
})

Stage.OPEN_GALAXY_MAP:
var toggle_galaxy_map_key := self._get_action_binding("toggle_galaxy_map")

self.label.text = """\
The galaxy awaits you beyond Sol! You can use the galaxy map to navigate:
1. Press [color=yellow]{map_key}[/color] to open the map.
2. Click a system to set as destination.
3. Click the X or press [color=yellow]{map_key}[/color] to close the map.
Select the neighboring system Thalassa from the galaxy map.""".format({
"map_key": toggle_galaxy_map_key
})

Stage.HYPERJUMP:
var jump_key := self._get_action_binding("jump")

self.label.text = """\
Now that you have selected Thalassa, you can initiate a hyperspace jump.
Press [color=yellow]{jump_key}[/color] to make the jump to Thalassa now.""".format({
"jump_key": jump_key
})

Stage.FINAL:
var toggle_tutorial_key := self._get_action_binding("toggle_tutorial")

self.label.text = """\
Congratulations, you're ready to head out on your own! Explore, trade, and complete missions as you travel the galaxy—and don't forget to refuel your hyperdrive!
Good luck, pilot.""".format({
"toggle_tutorial_key": toggle_tutorial_key
})

func _on_close_requested() -> void:
self.hide()

func _input(event: InputEvent) -> void:
if event.is_action_pressed("toggle_tutorial"):
self.get_viewport().set_input_as_handled()
self.hide()

func _get_action_binding(action: StringName) -> String:
return InputMap.action_get_events(action)[0].as_text()

## See [SaveGame].
func save_to_dict() -> Dictionary:
var result := {}
result["stage"] = self._stage
result["visible"] = self.visible
return result

## See [SaveGame].
func load_from_dict(dict: Dictionary) -> void:
self._stage = dict["stage"]
self.visible = dict["visible"]

func _on_previous_button_pressed() -> void:
assert(self._stage != Stage.INITIAL, "Previous button should not be clickable when on initial tutorial stage")

var stages := Stage.values()
var index := stages.find(self._stage)

assert(index != -1, "Could not find current tutorial stage")
self._stage = stages[index - 1]

func _on_next_button_pressed() -> void:
if self._stage == Stage.FINAL:
self.hide()
return

var stages := Stage.values()
var index := stages.find(self._stage)

assert(index != -1, "Could not find current tutorial stage")
self._stage = stages[index + 1]
57 changes: 57 additions & 0 deletions mechanics/tutorial/tutorial_window.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[gd_scene load_steps=3 format=3 uid="uid://dasi6qq5tfc10"]

[ext_resource type="Script" path="res://mechanics/tutorial/tutorial_window.gd" id="1_v4ja5"]
[ext_resource type="Material" uid="uid://cs5s3tb7vagsi" path="res://screens/shared_ui/premultiplied_canvas_material.tres" id="2_7hvyq"]

[node name="TutorialWindow" type="Window" node_paths=PackedStringArray("label", "previous_button", "next_button") groups=["saveable"]]
transparent_bg = true
title = "Tutorial"
position = Vector2i(250, 100)
size = Vector2i(570, 200)
transparent = true
script = ExtResource("1_v4ja5")
label = NodePath("MarginContainer/VBoxContainer/RichTextLabel")
previous_button = NodePath("MarginContainer/VBoxContainer/HBoxContainer/PreviousButton")
next_button = NodePath("MarginContainer/VBoxContainer/HBoxContainer/NextButton")

[node name="MarginContainer" type="MarginContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_top = 0
theme_override_constants/margin_right = 0

[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2

[node name="RichTextLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer"]
material = ExtResource("2_7hvyq")
layout_mode = 2
size_flags_vertical = 3
bbcode_enabled = true
text = "Welcome to your new ship! Let's get you acquainted with the basic controls."

[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 8
alignment = 2

[node name="PreviousButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "PREVIOUS"

[node name="NextButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 8
size_flags_vertical = 8
text = "NEXT"

[node name="Padding" type="Control" parent="MarginContainer/VBoxContainer/HBoxContainer"]
custom_minimum_size = Vector2(2, 2.08165e-12)
layout_mode = 2

[connection signal="close_requested" from="." to="." method="_on_close_requested"]
[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/PreviousButton" to="." method="_on_previous_button_pressed"]
[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/NextButton" to="." method="_on_next_button_pressed"]
5 changes: 5 additions & 0 deletions project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ toggle_mission_log={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":73,"key_label":0,"unicode":105,"echo":false,"script":null)
]
}
toggle_tutorial={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":47,"key_label":0,"unicode":63,"echo":false,"script":null)
]
}

[layer_names]

Expand Down
15 changes: 10 additions & 5 deletions screens/game/game.tscn
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[gd_scene load_steps=74 format=3 uid="uid://sunhu71swcs2"]
[gd_scene load_steps=75 format=3 uid="uid://sunhu71swcs2"]

[ext_resource type="Environment" uid="uid://b7t3opwr35xen" path="res://fx/environment.tres" id="1_pqled"]
[ext_resource type="Script" path="res://mechanics/hyperspace/hyperspace_scene_switcher.gd" id="4_yyrkm"]
Expand Down Expand Up @@ -54,6 +54,7 @@
[ext_resource type="Script" path="res://screens/game/hud/cargo_container.gd" id="44_e80r5"]
[ext_resource type="Script" path="res://screens/game/hud/time_label.gd" id="45_4kcdo"]
[ext_resource type="Script" path="res://mechanics/time/calendar.gd" id="46_cos1s"]
[ext_resource type="PackedScene" uid="uid://dasi6qq5tfc10" path="res://mechanics/tutorial/tutorial_window.tscn" id="55_0jyo3"]

[sub_resource type="Resource" id="Resource_p7sh0"]
resource_local_to_scene = true
Expand Down Expand Up @@ -102,7 +103,7 @@ script = ExtResource("11_ag0i7")
max_volume = 10.0
commodities = {}

[sub_resource type="Resource" id="Resource_c47w7"]
[sub_resource type="Resource" id="Resource_5nfrs"]
resource_local_to_scene = true
script = ExtResource("13_q2g24")
max_fuel = 6.0
Expand Down Expand Up @@ -164,9 +165,10 @@ texture_margin_bottom = 10.0

[node name="Game" type="Node3D"]

[node name="HyperspaceSceneSwitcher" type="Node3D" parent="." node_paths=PackedStringArray("player") groups=["saveable"]]
[node name="HyperspaceSceneSwitcher" type="Node3D" parent="." node_paths=PackedStringArray("player", "message_log") groups=["saveable"]]
script = ExtResource("4_yyrkm")
player = NodePath("Sol/PlayerCorvette/Player")
message_log = NodePath("../InGameGUI/MarginContainer/HBoxContainer/MessageLog")

[node name="Sol" parent="HyperspaceSceneSwitcher" instance=ExtResource("17_wjgyf")]

Expand All @@ -183,7 +185,7 @@ hull = SubResource("Resource_ffax2")
shield = SubResource("Resource_75c8j")
battery = SubResource("Resource_xpwk4")
cargo_hold = SubResource("Resource_bsv1l")
hyperdrive = SubResource("Resource_c47w7")
hyperdrive = SubResource("Resource_5nfrs")

[node name="CombatObject" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" index="3" node_paths=PackedStringArray("targeted_sound")]
targeted_sound = NodePath("../Player/TargetedSound")
Expand Down Expand Up @@ -311,13 +313,14 @@ gi_mode = 0
mesh = SubResource("QuadMesh_7oe51")
skeleton = NodePath("../..")

[node name="InGameGUI" type="CanvasLayer" parent="." node_paths=PackedStringArray("player")]
[node name="InGameGUI" type="CanvasLayer" parent="." node_paths=PackedStringArray("tutorial_window", "player")]
script = ExtResource("18_y74ml")
galaxy_map_scene = ExtResource("17_4w17v")
game_over_scene = ExtResource("17_yidh7")
exit_dialog_scene = ExtResource("18_hl4w3")
currency_trading_scene = ExtResource("25_57wc6")
mission_log_scene = ExtResource("26_fk202")
tutorial_window = NodePath("../TutorialWindow")
player = NodePath("../HyperspaceSceneSwitcher/Sol/PlayerCorvette/Player")

[node name="MarginContainer" type="MarginContainer" parent="InGameGUI"]
Expand Down Expand Up @@ -642,6 +645,8 @@ text = "30 FPS
horizontal_alignment = 2
script = ExtResource("37_e11x7")

[node name="TutorialWindow" parent="." instance=ExtResource("55_0jyo3")]

[connection signal="jump_destination_loaded" from="HyperspaceSceneSwitcher" to="HyperspaceSceneSwitcher/Sol/PlayerCorvette/Player" method="_on_jump_destination_loaded"]
[connection signal="jump_destination_loaded" from="HyperspaceSceneSwitcher" to="InGameGUI/MarginContainer/HBoxContainer/Sidebar/SystemName" method="_on_jump_destination_loaded"]
[connection signal="jump_path_changed" from="HyperspaceSceneSwitcher/Sol/PlayerCorvette/HyperdriveSystem" to="InGameGUI/MarginContainer/HBoxContainer/Sidebar/PanelContainer/VBoxContainer/JumpDestinationName" method="_on_jump_path_changed"]
Expand Down
3 changes: 3 additions & 0 deletions screens/game/in_game_gui.gd
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ extends CanvasLayer
@export var exit_dialog_scene: PackedScene
@export var currency_trading_scene: PackedScene
@export var mission_log_scene: PackedScene
@export var tutorial_window: Window
@export var player: Player

func _on_player_ship_destroyed(_player: Player) -> void:
Expand All @@ -26,6 +27,8 @@ func _unhandled_input(event: InputEvent) -> void:
mission_log.mission_controller = self.player.mission_controller
self.add_child(mission_log)
mission_log.show()
elif event.is_action_pressed("toggle_tutorial"):
self.tutorial_window.visible = not self.tutorial_window.visible
elif event.is_action_pressed("exit"):
self._instantiate_and_show_window(self.exit_dialog_scene)

Expand Down
Loading

0 comments on commit 088b6aa

Please sign in to comment.