NocTarot

Status: In Progress

Details

The Why


Here's a nutty idea: try making one game for 3 game jams. Is it feasable? No, but to me the most important aspect of a game jam is the inspiration (the results often varry). So, what do you get when you make a game about moths, tarot, and coffee: NocTarot. While there wasn't enough time to submit to all 3 jams, the team I worked with was able to submit to one, the Tarot Jam.

Out of the Dialog


Due to the heavy narative aspects of this game, I decided to use an existing plugin: Dialogic 2. While this is an excellent dialog system, it seems to be built for visual novel games. Since NocTarot's main mechanics are not dialog based, some bootstrapping needs to be applied to integrate Dialogic into our game.

The narative is our foundation, therefore every time we want the player to interact with the other game elements, we must first send out a signal to ensure the rest of our game knows what's going on. Dialogic has the ability to send signals with a string argument, and can be written into a Dialogic Timeline via text.


[signal arg="transition_kitchen"]

When dialogic recieves a dialog signal, it emits it's own godot signal called signal_event, which we handle with a custom function where we match each signal type and call a custom signal from the DialogUI class.


func _ready() -> void:
	Dialogic.signal_event.connect(_handle_text_signal)
	Dialogic.timeline_ended.connect(_handle_timeline_ended)


func _handle_text_signal(argument: String) -> void:
	var args = argument.split("_")
	var command = args[0]
	args.remove_at(0)
	
	match command:
		"transition":
			Dialogic.paused = true
			transition.emit(args)
		"activate":
			activate.emit(args)
		"deactivate":
			deactivate.emit(args)
		"enter":
			enter.emit()
		"exit":
			exit.emit()
		"check":
			check.emit(args)
		"client":
			client.emit(args)
		"training":
			training_ended.emit()

I know that I may have confused more than clarified, but remember that the main goal is to move from the Dialogic system to the rest of our game - all this is boilerplate to accomplish this.

Back into the Dialog


Eventually, we want to return the player to the dialog system; for this, we use a custom variable called next_chapter and a system of DialogueChecks. The next_chapter tells Dialogic which Timeline to run next, but how do we know when to run said Timeline? That's where the DialogueChecks class enters the scene. (Is Dialogue the correct spelling? Excellent question! I won't be taking any more questions.)

Before we go further, let's look at how we setup our Dialogic... logic: we set the next chapter, let our game know what the check for, then tell Dialogic that the current timeline has ended.


set {next_chapter} = "card_training_hover"
[signal arg="check_training_deck"]
[end_timeline]

The next_chapter is just a variable we can set here and read from outside Dialogic; the check_training_deck signal is more complicated. Ultimately, we need to process this signal as a check type, training sub-type, with deck as the value we're checking for. Below you can see the DialogueChecks class and it's inner workings.


class_name DialogueChecks


enum Types {
	NONE,
	DECK,
	HOVERED,
	SELECTED,
	FINALIZED,
	FORTUNE,
	DRINK,
}

static var Possible: Dictionary[Types, bool] ={
	Types.NONE: false,
	Types.DECK: false,
	Types.HOVERED: false,
	Types.SELECTED: false,
	Types.FINALIZED: false,
	Types.FORTUNE: false,
	Types.DRINK: false,
}

static var currentCheck: Types


static func set_valid(type: Types) -> void:
	Possible[type] = true


static func current_passed() -> bool:
	if Possible[currentCheck]:
		Possible[currentCheck] = false
		return true
	else:
		return false

Since there are many different places throughout our game code where a value might be checked, we use static functions and properties. When a check does pass, we call set_valid(...) on that check type. Back in our main scene, we run the current_passed() function on every frame; if true, we tell Dialogic to start the next_chapter.


func _process(_delta) -> void:
	if DialogueChecks.current_passed():
		dialogue_ui.start(Dialogic.VAR.next_chapter)

Status Report


While our team did submit a working prototype in time for the Tarot Jam, there's still plenty of bugs to be fixed and content to be added until this game feels more finished. A few people have agreed to continue working on it bit by bit. I for one want to improve the boilerplate - maybe it's as good as it can be, but I don't want to settle if a better approach could be built.