Creating a Global Signal System in Godot

If you’ve worked in Godot long enough, you’ll have encountered the signal pattern. This pattern has one part of the code providing signals that it emits, which other parts of the code can listen for and react to. It’s very useful for keeping the parts of your codebase separated by concern, thereby preventing explicit dependencies, while still allowing for communication between different systems. This pattern is commonly used with UI elements, which have no bearing on how the game systems work, but still need to know when certain events happen. With signals, the UI nodes can listen for when specific game events occur, then take the data from those signals and use it to update their visuals.

In Godot, this pattern is implemented through the use of a Node’s signal keyword, emit_signal and connect methods, and a callback function. Example follows:


# Some example node script
extends Node
signal an_awesome_signal

func an_awesome_function():
  emit_signal('an_awesome_signal', 'data that the listeners receive')

func another_awesome_function():
  connect('an_awesome_signal', self, '_on_An_awesome_signal')

func _on_An_awesome_signal(data):
  print(data) # 'data that the listeners receive'

It is considered good Godot practice to name your listener callbacks after the signal they are responding to, prefixed with _on_ and with the first letter of the signal name capitalized.

Of course, you don’t have to just connect to signals within your node. Any node that is in the same scene as another node can connect to that node’s signals and listen for them. As explained, connecting nodes to one another allows for coding systems that need to respond to certain game events, but without having to call externalNode.external_node_method() each time external_node_method needs to be run in response to something happening.

Godot’s signal implementation is great, but there is a caveat: it doesn’t provide a clean way to listen for nodes which exist outside of the current scene. Let’s go back to the UI example. UI nodes and their code are usually kept separate from game systems code (after all, game systems shouldn’t need to manage the UI), often by creating entire scenes which house a portion of some UI widget, like a health bar. But how does said health bar know when it needs to be updated? Given this health bar (let’s call it HealthBarUI) is separate from the systems which actually calculate an entity’s health, we can’t directly connect it to the health system.

One way to solve this problem is to use relative paths when connecting the signals, e.g. ../../../HealthBarUI. Obviously, this solution is very brittle. If you decide that HealthBarUI needs to be moved anywhere in the node tree, you’ll have to update the signal connection path accordingly. It’s not hard to imagine this becoming untenable when adding many nodes which are connected to other nodes outside of their scene tree; it’s a maintenance nightmare.

A better solution would be to create a global singleton which your nodes can connect to, instead, adding it to the global AutoLoads. This alleviates the burden of relative paths by providing a global singleton variable that is guaranteed to be accessible from every scene.

Many programmers will advise against using the Singleton pattern, as creating so-called “god objects” is an easy way to create messy, disorganized code that makes code reuse more difficult. I share this concern, but advocate that there are certain times where you want to have a global singleton, and I consider this one of them. As with all practices and patterns, use your best judgment when it comes to determining how to apply them to solve your systems design problems.

GDQuest gives a good example of this pattern in this article. Basically, for every signal which needs to be globally connected, you add that signal definition to the global singleton, connect your local nodes to the singleton, and call Singleton.emit_signal() whenever you need to emit the global version of that signal. While this pattern works, it obviously gets more complex with each signal that you need to add. It also creates a hard dependency on the singleton, which makes it harder to reuse your nodes in other places without the global singleton.

I would like to propose a different take on the global singleton solution. Instead of explicitly defining global signals inside of a globally-accessible singleton file, we can dynamically add signals and connectors to a GlobalSignal node through add_emitter and add_listener methods. Once a signal is registered, then whenever it is emitted by its node, any registered listeners of that signal will receive it and be able to respond to it, exactly the same as how signals normally work. We avoid a hard dependency on the GlobalSignal singleton because we’re just emitting signals the normal way. It’s a clean solution that takes advantage of how Godot’s signals work as much as possible.

Intrigued? Let me show you how it works.

If you want to skip to the final result, you can access the sample project here: https://github.com/Jantho1990/Godot-Global-Signal.

Also, this post was originally written for Godot 3.x. With Godot 4.0 being (finally) released, I did a quick conversion of the sample project to 4.0 and pushed it up in a separate branch. I won’t update the article with 4.0 versions of code at this time, but there aren’t too many changes, so it shouldn’t be too hard to follow and translate the differences.

Building the Basics

Let’s start by creating the file (I’m assuming you’ll have created a Godot project to work with, first). I’ve called it global_signal.gd. It should extend the basic Node. Once the file is created, we should add it to the global AutoLoads by going into Godot’s project settings, clicking the AutoLoad tab, then adding our script (with the variable name GlobalSignal).

This is how we will make GlobalSignal accessible from any scene’s code. Godot automatically loads any scripts in the AutoLoad section first and places them at the top of the game’s node hierarchy, where they can be accessed by their name.

With that out of the way, let’s start adding code to global_signal.gd. First, we need a way for GlobalSignal to know when a node has a signal that can be emitted globally. Let’s call these nodes emitters. This code should take care of adding emitters for GlobalSignal to keep track of:


# Keeps track of what signal emitters have been registered.
var _emitters = {}


# Register a signal with GlobalSignal, making it accessible to global listeners.
func add_emitter(signal_name: String, emitter: Object) -> void:
  var emitter_data = { 'object': emitter, 'object_id': emitter.get_instance_id() }
  if not _emitters.has(signal_name):
    _emitters[signal_name] = {}
  _emitters[signal_name][emitter.get_instance_id()] = emitter_data

Nothing too complex about this. We create a dictionary to store data for the emitter being added, check to see if we have an existing place to store signals with this name (and create a new dictionary to house them if not), then add it to the _emitters dictionary, storing it by signal name and instance id (the latter being a guaranteed unique key that is already part of the node, something we’ll be taking advantage of later).

We can now register emitters, but we also need a way to register listener nodes. After all, what’s the point of having a global signal if nothing can respond to it? The code for adding listeners is nearly identical to the code for adding emitters; we’re just storing things in a _listeners variable instead of _emitters.


# Keeps track of what listeners have been registered.
var _listeners = {}

# Adds a new global listener.
func add_listener(signal_name: String, listener: Object, method: String) -> void:
  var listener_data = { 'object': listener, 'object_id': listener.get_instance_id(), 'method': method }
  if not _listeners.has(signal_name):
    _listeners[signal_name] = {}
  _listeners[signal_name][listener.get_instance_id()] = listener_data

With that, we now have the ability to add emitters and listeners. What we don’t yet possess is a way to connect these emitters and listeners together. Normally, when using signals, we’d have the listener node connect() to the emitter node, specifying whatever signal it wants to connect to and the callback function which should be invoked (as well as the node where this callback function resides). We need to replicate this functionality here, but how do we ensure that a new emitter gets connected to all current and future listeners, and vice versa?

Simply put, every time we add a new emitter, we need to loop through GlobalSignal‘s listeners, find the ones which want to connect with that emitter’s signal, and perform the connection. The same is true for when we add a new listener: when a new listener is added, we need to loop through the registered emitters, find the ones whose signal matches the one the listener wants to listen to, and perform the connection. To abstract this process, let’s create a couple of functions to take care of this for us.


# Connect an emitter to existing listeners of its signal.
func _connect_emitter_to_listeners(signal_name: String, emitter: Object) -> void:
  var listeners = _listeners[signal_name]
  for listener in listeners.values():
    emitter.connect(signal_name, listener.object, listener.method)


# Connect a listener to emitters who emit the signal it's listening for.
func _connect_listener_to_emitters(signal_name: String, listener: Object, method: String) -> void:
  var emitters = _emitters[signal_name]
  for emitter in emitters.values():
    emitter.object.connect(signal_name, listener, method)

Now we need to modify our existing add functions to run these connector functions.


func add_emitter(signal_name: String, emitter: Object) -> void:
  # ...existing code

  if _listeners.has(signal_name):
    _connect_emitter_to_listeners(signal_name, emitter)


func add_listener(signal_name: String, listener: Object, method: String) -> void:
  # ...existing code

  if _emitters.has(signal_name):
    _connect_listener_to_emitters(signal_name, listener, method)

We first check to make sure an emitter/listener has already been defined before we try to connect to it. Godot doesn’t like it when you try to run code on objects that don’t exist. 😛

With that, the last thing we need to finish the basic implementation is to add a way for removing emitters and listeners when they no longer need to be connected. We can implement such functionality thusly:


# Remove registered emitter and disconnect any listeners connected to it.
func remove_emitter(signal_name: String, emitter: Object) -> void:
  if not _emitters.has(signal_name): return
  if not _emitters[signal_name].has(emitter.get_instance_id()): return  
    
  _emitters[signal_name].erase(emitter.get_instance_id())
    
  if _listeners.has(signal_name):
    for listener in _listeners[signal_name].values():
      if emitter.is_connected(signal_name, listener.object, listener.method):
        emitter.disconnect(signal_name, listener.object, listener.method)


# Remove registered listener and disconnect it from any emitters it was listening to.
func remove_listener(signal_name: String, listener: Object, method: String) -> void:
  if not _listeners.has(signal_name): return
  if not _listeners[signal_name].has(listener.get_instance_id()): return  
    
  _listeners[signal_name].erase(listener.get_instance_id())
    
  if _emitters.has(signal_name):
    for emitter in _emitters[signal_name].values():
      if emitter.object.is_connected(signal_name, listener, method):
        emitter.object.disconnect(signal_name, listener, method)

As with the add functions, the remove functions are both almost identical. We take an emitter (or listener), verify that it exists in our stored collection, and erase it from the collection. After that, we check to see if anything was connected to the thing being removed, and if so we go through all such connections and remove them.

That’s it for the basic implementation! We now have a functional GlobalSignal singleton that we can use to connect emitters and listeners dynamically, whenever we need to.

A Simple Test

Let’s create a simple test to verify that all this is working as intended.

This simple test is included in the sample project.

First, create a Node-based scene in your project. Then, add a LineEdit node and a Label node (along with whatever other Control nodes you want to add to make it appear the way you want), and create the following scripts to attach to them:


# TestLabel
extends Label


func _ready():
  GlobalSignal.add_listener('text_updated', self, '_on_Text_updated')


func _on_Text_updated(text_value: String):
  text = text_value


# TestLineEdit
extends LineEdit

signal text_updated(text_value)


func _ready():
  GlobalSignal.add_emitter('text_updated', self)
  connect('text_changed', self, '_on_Text_changed')


func _on_Text_changed(_value):
  emit_signal('text_updated', text)

You could also use the value argument for _on_Text_changed, instead of taking the direct value of text. It’s a matter of preference.

Assuming you’ve implemented the code from this tutorial correctly, when you run the scene, you should be able to type in the LineEdit node and see the values of the Label node update automatically. If it’s working, congratulations! If not, go back and look through the code samples to see what you might’ve missed, or download the sample project to compare it with yours.

Now, obviously, this is a contrived example. GlobalSignal would be overkill for solving such a simple scenario as the one presented in the test case. Hopefully, though, it illustrates how this approach would be useful for more complex scenarios, such as the HealthBarUI example described earlier. By making our global signal definition dynamic, we avoid having to make updates to GlobalSignal every time we need to add a new globally-accessible signal. We emit signals from the nodes, as you do normally; we just added a way for that signal to be listened to by nodes outside of the node’s scene tree. It’s powerful, flexible, and clean.

Resolving Edge Cases and Bugs

There are some hidden issues that we need to address, however. Let’s take a look at them and see how we can fix them.

Dealing with Destroyed Nodes

Let’s ask ourselves a hypothetical question: what would happen if a registered emitter or listener is destroyed? Say the node is freed by the parent (or the parent itself is freed). Would GlobalSignal know this node no longer exists? The answer is no, it wouldn’t. Subsequently, what would happen if we’re looping through our registered emitters/listeners and we try to access the destroyed node? Godot gets unhappy with us, and crashes.

How do we fix this? There are two approaches we could take:

  • We could poll our dictionaries of registered emitters and listeners every so often (say, once a second) to check and see if there’s any dead nodes, and remove any we find.
  • Alternatively, we could run that same check and destroy whenever we make a call to a function which needs to loop through the lists of emitters and listeners.

Of those two options, I prefer the latter. By only running the check when we explicitly need to loop through our emitters and listeners, we avoid needlessly running the check and thereby introducing additional processing time when we don’t know that it’s necessary (which is what would happen if we went with polling). Thus, we’re going to implement this only-when-necessary check in the four places that need it: namely, whenever we add or remove an emitter or listener.

There is an argument to be made that running the check as part of adding/removing emitters/listeners adds additional processing time when performing these functions. That’s true, but in practice I’ve found that the added time isn’t noticeable. That said, if your game is constantly creating and destroying nodes that need to be globally listened to, and it’s measurably impacting game performance, it may prove better to implement a poll-based solution. I’m just not going to do it as part of this tutorial.

First, let’s create a function that will both perform the check and remove the emitter/listener if it is determined it no longer exists.


# Checks stored listener or emitter data to see if it should be removed from its group, and purges if so.
# Returns true if the listener or emitter was purged, and false if it wasn't.
func _process_purge(data: Dictionary, group: Dictionary) -> bool:
  var object_exists = !!weakref(data.object).get_ref() and is_instance_valid(data.object)
  
  if !object_exists or data.object.get_instance_id() != data.object_id:
    group.erase(data.object_id)
    return true
  return false

First, we check all the possible ways that indicate that a node (or object, which is what a node is based on) no longer exists. weakref() checks to see if the object only exists by reference (aka has been destroyed and is pending removal from memory), and is_instance_valid is a built in Godot method that returns whether Godot thinks the instance no longer exists. I’ve found that I’ve needed both checks to verify whether or not the object truly exists.

You may want to abstract this object existence check into some kind of helper function that is made globally accessible. This is what I’ve done in my own implementation of GlobalSignal, but I chose to include it directly in this tutorial to avoid having to create another file exclusively to house that helper.

Even if we prove the object exists, we still need to check to make sure the stored instance id for the emitter/listener matches the current instance id of said object. If they don’t match, then it means the stored object is no longer the same as the one we registered (aka the reference to it changed).

If the object doesn’t exist, or if it’s not the same object as the one we registered, then we need to remove it from our dictionary. group is the collection we passed in for validation (this will be explained in more detail momentarily), and group.erase(data.object_id) deletes whatever value is stored at the key with the same name as data.object_id. If we’ve reached this point, we then return true. If we didn’t erase the object, we return false.

With our purge function defined, let’s go ahead and modify our add and remove functions to implement it:


func _connect_emitter_to_listeners(signal_name: String, emitter: Object) -> void:
  var listeners = _listeners[signal_name]
  for listener in listeners.values():
    if _process_purge(listener, listeners):
      continue
    emitter.connect(signal_name, listener.object, listener.method)


func _connect_listener_to_emitters(signal_name: String, listener: Object, method: String) -> void:
  var emitters = _emitters[signal_name]
  for emitter in emitters.values():
    if _process_purge(emitter, emitters):
      continue
    emitter.object.connect(signal_name, listener, method)


func remove_emitter(signal_name: String, emitter: Object) -> void:
  # ...existing code
    
  if _listeners.has(signal_name):
    for listener in _listeners[signal_name].values():
      if _process_purge(listener, _listeners[signal_name]):
        continue
      if emitter.is_connected(signal_name, listener.object, listener.method):
        emitter.disconnect(signal_name, listener.object, listener.method)


func remove_listener(signal_name: String, listener: Object, method: String) -> void:
  # ...existing code
    
  if _emitters.has(signal_name):
    for emitter in _emitters[signal_name].values():
      if _process_purge(emitter, _emitters[signal_name]):
        continue
      if emitter.object.is_connected(signal_name, listener, method):
        emitter.object.disconnect(signal_name, listener, method)

For each function, the only thing we’ve changed is adding the _process_purge() check before doing anything else with the emitters/listeners. Let’s examine what’s happening in _connect_emitter_to_listeners(), to detail the logic.

As we start looping through our dictionary of listeners (grouped by signal_name), we first call _process_purge(listener, listeners) in an if statement. From examining the code, listener is the current listener node (aka the object we want to verify exists) and listeners is the group of listeners for a particular signal_name. If _process_purge() returns true, that means the listener did not exist, so we continue to move on to the next stored listener. If _process_purge() returns false, then the listener does exist, and we can proceed with connecting the emitter to the listener.

The same thing happens for the other three functions, just with different values passed into _process_purge(), so I shan’t dissect them further. Hopefully, the examination of what happens in _connect_emitter_to_listeners() should make it clear how things work.

That’s one issue down. Let’s move on to the last issue that needs to be addressed before we can declare GlobalSignal complete.

Accessing an Emitter/Listener Before It’s Ready

Here’s another scenario to consider: what happens if we want to emit a globally-accessible signal during the _ready() call? You can try this out yourself by adding this line of code to TestLineEdit.gd, right after defining the global signal:


GlobalSignal.add_emitter('text_updated', self)
emit_signal('text_updated', 'text in _ready()')

We’d expect that, on starting our scene, our Label node should have the text set to “text in _ready()”. In practice, however, nothing happens. Why, though? We’ve established that we can use GlobalSignal to listen for nodes, so why doesn’t the connection in Label seem to be working?

To answer this question, let’s talk a little about Godot’s initialization process. When a scene is added to a scene tree (whether that be the root scene tree or a parent’s scene tree), the _ready() function is called on the lowermost child nodes, followed by the _ready() functions of the parents of those children, and so on and so forth. For sibling children (aka child nodes sharing the same parent), Godot calls them in tree order; in other words, Child 1 runs before Child 2. In our scene tree composition for the sample project, the LineEdit node comes before the Label node, which means the _ready() function in LineEdit runs first. Since Label is registering the global listener in its _ready() function, and that function is running after LineEdit‘s _ready() function, our text_updated signal gets emitted before the listener in Label is registered. In other words, the signal is being emitted too early.

How do we fix this? In our contrived example, we could move the Label to appear before the LineEdit, but then that changes where the two nodes are being rendered. Besides, basing things on _ready() order isn’t ideal. In the case where we want nodes in different scenes to listen for their signals, we can hardly keep track of when those nodes run their _ready() function, at least not without some complex mapping of your scene hierarchy that is painful to maintain.

The best to solve this problem is to provide some way to guarantee that, when emit_signal is called, that both the emitter and any listeners of it are ready to go. We’ll do this by adding a function called emit_signal_when_ready() which we call whenever we need to emit a signal and guarantee that any listeners for it that have been defined in _ready() functions are registered.

Unfortunately, we can’t override the existing emit_signal function itself to do this, because emit_signal uses variadic arguments (aka the ability to define any number of arguments to the function), which is something Godot does not allow for user-created functions. Therefore, we need to create a separate function for this.

We’ll need to add more than just the emit_signal_when_ready() function itself to make this functionality work, so I’ll go ahead and show all of the code which needs to be added, and then cover what’s going on in detail.


# Queue used for signals emitted with emit_signal_when_ready.
var _emit_queue = []

# Is false until after _ready() has been run.
var _gs_ready = false


# We only run this once, to process the _emit_queue. We disable processing afterwards.
func _process(_delta):
  if not _gs_ready:
    _make_ready()
    set_process(false)
    set_physics_process(false)


# Execute the ready process and initiate processing the emit queue.
func _make_ready() -> void:
  _gs_ready = true
  _process_emit_queue()


# Emits any queued signal emissions, then clears the emit queue.
func _process_emit_queue() -> void:
  for emitted_signal in _emit_queue:
    emitted_signal.args.push_front(emitted_signal.signal_name)
    emitted_signal.emitter.callv('emit_signal', emitted_signal.args)
  _emit_queue = []


# A variant of emit_signal that defers emitting the signal until the first physics process step.
# Useful when you want to emit a global signal during a _ready function and guarantee the emitter and listener are ready.
func emit_signal_when_ready(signal_name: String, args: Array, emitter: Object) -> void:
  if not _emitters.has(signal_name):
    push_error('GlobalSignal.emit_signal_when_ready: Signal is not registered with GlobalSignal (' + signal_name + ').')
    return
  
  if not _gs_ready:
    _emit_queue.push_back({ 'signal_name': signal_name, 'args': args, 'emitter': emitter })
  else:
    # GlobalSignal is ready, so just call emit_signal with the provided args.
    args.push_front(signal_name)
    emitter.callv('emit_signal', args)

That’s quite a lot to take in, so let’s break it down, starting with the two class members being added, _emit_queue and _gs_ready.

_emit_queue is a simple array that we’re going to use to keep track of any signals that have been marked as needing to be emitted when GlobalSignal decides everything is ready to go. _gs_ready is a variable that will be used to communicate when GlobalSignal considers everything ready.

I use _gs_ready instead of _ready to avoid giving a variable the same name as a class function. While I’ve found that Godot does allow you to do that, I consider it bad practice to have variables with the same name as functions; it’s confusing, and confusing code is hard to understand.

Next, let’s examine our call to _process() (a built-in Godot process that runs on every frame update):


# We only run this once, to process the _emit_queue. We disable processing afterwards.
func _process(_delta):
  if not _gs_ready:
    _make_ready()
    set_process(false)
    set_physics_process(false)

If _gs_ready is false (which is what we’ve defaulted it to), then we call _make_ready() and subsequently disable the process and physics process update steps. Since GlobalSignal doesn’t need to be run on updates, we can save processing time by disabling them once we’ve run _process() the first time. Additionally, since GlobalSignal is an AutoLoad, this _process() will be run shortly after the entire scene tree is loaded and ready to go.

Let’s check out what _make_ready() does:


# Execute the ready process and initiate processing the emit queue.
func _make_ready() -> void:
  _gs_ready = true
  _process_emit_queue()

The function sets _gs_ready to true, then calls _process_emit_queue(). By marking _gs_ready as true, it signals that GlobalSignal now considers things to be ready to go.

Moving on to _process_emit_queue():


# Emits any queued signal emissions, then clears the emit queue.
func _process_emit_queue() -> void:
  for emitted_signal in _emit_queue:
    emitted_signal.args.push_front(emitted_signal.signal_name)
    emitted_signal.emitter.callv('emit_signal', emitted_signal.args)
  _emit_queue = []

Here, we loop through the _emit_queue array, push the signal name to the front of the arguments array, and use callv to manually call the emit_signal() function on the emitter node, passing in the array of arguments (emit_signal() takes the signal’s name as the first argument, which is why we needed to make the signal name the first member of the arguments array) . When we’ve gone through all of the members of _emit_queue, we reset it to an empty array.

Finally, we come to the emit_signal_when_ready() function, itself:


# A variant of emit_signal that defers emitting the signal until the first process step.
# Useful when you want to emit a global signal during a _ready function and guarantee the emitter and listener are ready.
func emit_signal_when_ready(signal_name: String, args: Array, emitter: Object) -> void:
  if not _emitters.has(signal_name):
    push_error('GlobalSignal.emit_signal_when_ready: Signal is not registered with GlobalSignal (' + signal_name + ').')
    return
  
  if not _gs_ready:
    _emit_queue.push_back({ 'signal_name': signal_name, 'args': args, 'emitter': emitter })
  else:
    # GlobalSignal is ready, so just call emit_signal with the provided args.
    args.push_front(signal_name)
    emitter.callv('emit_signal', args)

First, we check to see if the signal we want to emit has been registered with GlobalSignal, and return early if it is not (with an error pushed to Godot’s console to tell us this scenario happened). Our next action depends on the value of _gs_ready. If it’s false (aka we aren’t ready), then we add a new entry to _emit_queue and pass in the signal name, arguments, and emitter node, all of which will be utilized during _process_emit_queue(). If it’s true, then we called this function after everything has been marked as ready; in that case, there’s no point in adding this to the emit queue, so we’ll just invoke emit_signal() and call it a day.

With that, GlobalSignal should now be able to handle dispatching signals and guaranteeing that the listeners defined during _ready() functions are registered. Let’s test this by changing our modification to TestLineEdit so it uses emit_signal_when_ready():


GlobalSignal.add_emitter('text_updated', self)
GlobalSignal.emit_signal_when_ready('text_updated', ['text in _ready()'], self)

Note that we need to convert our ‘text in _ready()’ argument to be wrapped in an array, since we need to pass an array of arguments to the function.

Also note that we have to pass in the emitter node, since we have to store that in order to call emit_signal() on it later.

If, when you run the scene, the Label node shows our text string, that means our changes worked! Now we can declare GlobalSignal done!

Using Global Signals

Congratulations! You now have a dynamic way to define globally-accessible signals that closely follows Godot’s natural signals implementation. Adding new global signals is easy, and doesn’t involve changing the GlobalSignal singleton at all.

At this point, you might wonder, “Why not convert all of my signals to be global signals?” That’s not necessarily a great idea. Most of the time, we want to keep our signals local, as when we start connecting code from disparate parts of our code base it can make it confusing to recall which things are connected to what. By keeping signals local whenever possible, we make dependencies clearer and make it harder to design bad code.

That’s one of the things I actually like about this approach to implementing global signals. We’re still emitting signals locally; we just choose which signals need to also be exposed globally. You can connect signals locally and globally, with the same signal definitions.

What are some good use cases? UI nodes, as mentioned before, are a great example of a good use case for this pattern. An achievements system needing to know when certain events occur is another possible use case. Generally, this pattern is best suited for when you have multiple “major” systems that need to talk to one another in an agnostic manner, while local signal connections are better for communication between the individual parts of a major system.

As with any pattern or best practice, GlobalSignal should be carefully considered as one of many solutions to your problem, and chosen if it proves to be the best fit.

One last time, here is the link to the sample project, if you didn’t build along with the tutorial, or just want something to compare your implementation against. (And if you are using Godot 4.0, here is the branch with that version of it!)

Hopefully, this approach to global signals helps you in your projects. I’ve certainly made great use of it in mine!

How to Add GDScript Syntax Highlighting to Your Blog

Recently, I decided to devote more time to writing blog posts, especially tutorials about things I’ve learned in my three-plus years of learning Godot and GDScript. When I went to write my first tutorial post, however, I discovered that there is no support for GDScript syntax highlighting in any of the code formatting plugins for WordPress. On top of that, Github’s Gists, which I’ve used to show syntax-highlighted code in the past, also does not support GDScript.

Syntax highlighting, for those who don’t know, is the colorful text and different font weights and decorations (aka bolded, italicized, and underlined text) that code editors show to indicate different functionalities of code. Using syntax highlighting to show what your code is doing is extremely helpful, if not critically important for making your code readable. Without syntax highlighting, it’s a lot more difficult to parse what a given block of code is doing, to the point where it feels unreadable. For tutorials, it’s especially important to make code as easy to understand as possible, and a critical part of that is including syntax highlighting.

Imagine trying to read a blog article where all the code samples were one giant block of monochromatic text, like this:

It’s a little tricky, isn’t it? Maybe you can read this particular example after a few moments, but what about 20-50 line examples of complex code? And what if you’re scanning back and forth between code samples, trying to parse how the whole thing works? It was clear to me that, if I wanted to write tutorials that were user-friendly, I needed to find a way to add support for GDScript syntax highlighting to my website.

After asking in the Godot Discord, I was pointed towards an implementation of GDScript in highlight.js, a JavaScript-based syntax highlighter. Some additional googling showed me how I could integrate highlight.js onto a website, and further research showed how to make changes to a WordPress website’s head file. Combining this information together, I was able to successfully integrate highlight.js and the GDscript extension for it onto my blog.

First, I went to Appearance -> Theme Editor and edited the header.php file, adding this snippet right below the wp_head() function call, to take care of downloading highlight.js:

<!-- Syntax highlighting for code blocks. -->
<link rel="stylesheet"
href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/default.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/highlight.min.js"></script>

Next, I went to the Github repo for hilightjs-gdscript and copied the contents of the gdscript.min.js file, adding it just below where I added the main highlight.js script tag:

<!-- Syntax highlighting for GDScript. -->
<script>hljs.registerLanguage("gdscript",function(){"use strict";var e=e||{};function r(e){return{aliases:["godot","gdscript"],keywords:{keyword:"and in not or self void as assert breakpoint class class_name extends is func setget signal tool yield const enum export onready static var break continue if elif else for pass return match while remote sync master puppet remotesync mastersync puppetsync",built_in:"Color8 ColorN abs acos asin atan atan2 bytes2var cartesian2polar ceil char clamp convert cos cosh db2linear decimals dectime deg2rad dict2inst ease exp floor fmod fposmod funcref get_stack hash inst2dict instance_from_id inverse_lerp is_equal_approx is_inf is_instance_valid is_nan is_zero_approx len lerp lerp_angle linear2db load log max min move_toward nearest_po2 ord parse_json polar2cartesian posmod pow preload print_stack push_error push_warning rad2deg rand_range rand_seed randf randi randomize range_lerp round seed sign sin sinh smoothstep sqrt step_decimals stepify str str2var tan tanh to_json type_exists typeof validate_json var2bytes var2str weakref wrapf wrapi bool int float String NodePath Vector2 Rect2 Transform2D Vector3 Rect3 Plane Quat Basis Transform Color RID Object NodePath Dictionary Array PoolByteArray PoolIntArray PoolRealArray PoolStringArray PoolVector2Array PoolVector3Array PoolColorArray",literal:"true false null"},contains:[e.NUMBER_MODE,e.HASH_COMMENT_MODE,{className:"comment",begin:/"""/,end:/"""/},e.QUOTE_STRING_MODE,{variants:[{className:"function",beginKeywords:"func"},{className:"class",beginKeywords:"class"}],end:/:/,contains:[e.UNDERSCORE_TITLE_MODE]}]}}return e.exports=function(e){e.registerLanguage("gdscript",r)},e.exports.definer=r,e.exports.definer||e.exports}());</script>

Imagine trying to read the above without syntax highlighting!

Finally, I added a call to initiate highlight.js, right below the highlightjs-gdscript code:

<script>hljs.highlightAll();</script>

And with that (omitting some trial and error on my part to figure out the above), I successfully got highlight.js to run on my WordPress blog, with syntax highlighting for GDscript!

I wasn’t happy with the out-of-the-box highlighting, though, so I decided to quickly throw together some custom styles, targeting the classes highlight.js injects. First, I went to Appearance -> Edit CSS in the WordPress menu settings and injected this CSS styling:

:root {
    --code-background: #000004;
    --default-font-color: #fbfef9;
    --keyword-color: #7e1946;
    --title-color: #a63446;
    --function-color: #0c6291;
    --string-color: #6ea735;
    --html-color: #a76e35;
}

code {
	border: none;
	padding: 0;
	font-size: 0.9em;
}

pre, code, code.hljs {
    background: var(--code-background);
    color: var(--default-font-color);
}

code.hljs .hljs-function,
code.hljs .hljs-function .hljs-keyword {
    color: var(--function-color);
}

code.hljs .hljs-keyword {
    color: var(--keyword-color);
}

code.hljs .hljs-title {
    color: var(--title-color);
}

code.hljs .hljs-string {
    color: var(--string-color);
}

code.hljs .hljs-name,
code.hljs .hljs-tag,
code.hljs .hljs-attr {
    color: var(--html-color);
}

I also made changes to other parts of the highlight.js syntax highlighting that weren’t for GDScript to make them work with my new color scheme.

I also noticed that highlight.js wasn’t consistently auto-detecting when I was using GDScript, so I converted my WordPress code block to an HTML block and created the code tags manually:

<pre class="wp-block-code"><code class="language-gdscript"></code></pre>

Finally, highlightjs-gdscript didn’t include support for print, so I quickly added that to my import of the dist file:

{ keyword:"and in not or self void as assert breakpoint class class_name extends is func setget signal tool yield const enum export onready static var break continue if elif else for pass return match while remote sync master puppet remotesync mastersync puppetsync print" }

The final result looks like this:

At least, that’s what it looked like at the time I wrote this blog post. Since I was just looking to throw something together quickly, I didn’t put too much thought into my color scheme, instead utilizing a randomly-generated color palette (https://coolors.co/a63446-fbfef9-0c6291-000004-7e1946) and some hue-shifting to get related colors that weren’t part of the generated palette. Below is what the syntax highlight looks like on today’s iteration of the color scheme:

func ready():
  var variable = some_function()
  print("Hopefully this syntax highlighting works!")

Anyway, that’s a quick overview on how I implemented GDScript syntax highlighting on a WordPress blog. I imagine much of this can be adapted to implement GDScript syntax highlighting on any site. Now, on to writing blog posts!

Hammertime Prototype – Phase 1 (Download)

Link to download a simple prototype project. Available for Windows only.

Hammertime Prototype Phase 1

Controls:
AWSD = Move/Jump
Right Mouse = Throw Hammer
Right Mouse (while hammer in air) = Teleport to Hammer's location
Left Mouse (while hammer in air) = Create platform
Left Click (on platform) = Select platform
Left Click (anywhere but a platform) = Deselect selected platform
q (while platform selected) = Delete platform
q (no platform selected) = Delete all platforms

If you need a goal, you can try to collect all the tomes. The main point of this prototype, however, is to play around with the movement mechanics.

A Retrospective on 2019

2019 has been a good year for me, personally, and for my family. I’m blessed to have a new job, a new home, and plenty of learning experiences under my belt. If there’s any constant I can identify that sums up my overall take, it is this:

If you fight hard, you’ll put yourself in position to take advantage of good fortune, and find it easier to overcome struggles.

I want to trace back through the events of the year and offer my thoughts and commentary on them. My hope is that you, dear reader, may find some nuggets of truthful wisdom from these words of mine to help you in your own journeys, whatever they may be.

2019: Continuing the Game Development Journey

The start of last year found me working as a full-stack PHP/JavaScript developer for a small company in Rochester, Minnesota. Coming off my first-ever game jam, Ludum Dare 43, I started planning for the next game jam I intended to participate in, Ludum Dare 44. Part of that preparation would include learning Godot, an open-source game engine. I had built my own game engine, in JavaScript, as part of my game-dev self-education in 2018, in order to get an understanding of how game engines work, and having acquired that knowledge, I now wanted to focus my time more on games-making over engine-craft. I chose Godot due to its being open-source, as well as having first-class Linux support (so I could develop games on my Ubuntu laptop).

I also started vlogging my game development progress on Instagram, under the handle AspiringGameDev. It’s linked in my social media sidebar, if you should want to check it out.

By the time April arrived, and with it the start of the next Ludum Dare, I felt I had learned enough knowledge to be at least adequate in making a Godot game. With my wife handling the artwork, we dove headlong into Ludum Dare 44. Frankly, I was less pleased with the end result, “Impact!”, than I had been with the Ludum Dare 43 entry, “Sanity Wars”. The tight three-day deadline for Ludum Dare resulted in us being forced to cut out a lot of content we had planned to put in, and the resulting gameplay felt lackluster and boring. Impact! was judged accordingly, finishing with a worse score than Sanity Wars.

No matter, though. It was a learning experience all the same, and at this stage of my game development career all experience is good experience.

For the next few months, I started to work on small game experiments, practicing my craft. The first of these was an attempt to implement a mechanic I’ve always wanted to work with: free-form wall climbing, akin to that of a gecko. I spent about a month’s worth of time (minus work and family time) trying to make this work the way I’d envisioned. Alas, though I was able to get the wall-climbing aspect of the mechanic working, I found myself struggling to implement a smooth, intuitive way for the test character to climb from wall to ceiling. In the end, I chose to put the project aside, for now, in favor of working on a new experiment.

The next project I tackled was creating a top-down orientation game. Up to this point, most of what I’ve developed game-wise used a side-scrolling perspective, as this was what I was most familiar with. I wanted the experience of making a top-down game, as this would allow me to eventually make RPG-like games with exploration, narrative, and questing. I had just finished implementing some map mechanics, and was getting ready to work on dialogue systems, when something happened that would instigate a major force of change in my life.

On July 1st, 2019, I was laid off from my job.

2019: The Quest for Gainful Employment

Obviously, the layoff caught me by surprise, and my focus immediately shifted from game development to finding new employment. As part of that process, I needed a portfolio project to work on that could adequately showcase my programming proficiency. The JavaScript framework React has enjoyed considerable popularity the past few years, and though I did have a few small sample projects from my learning of how React worked, I believed I needed to create a more complex project with React if I wanted companies to take my React skills seriously (I had worked with vanilla JavaScript and Vue in previous jobs, so I had no professional React experience).

At the same time as I was considering this path, I heard of a site called Koji, a platform for users to create JavaScript game templates for other people to customize and publish, and they were looking to hire programmers to create templates for others to build upon. The idea came to me: what if I made a Koji game template in React? It’d be killing two birds with one stone: I’d get experience making a complex React project, and I’d be making a game I could potentially showcase to Koji. I had the perfect game in mind to make, too: Hangman. In the back of my mind, I’d always wanted to attempt making a Hangman game someday, but had not yet had the chance to act upon it.

It was settled: my portfolio project would be React Hangman. In between job applications and company interviews, I’d work on this portfolio project. Frankly, it was harder than I expected. Not the game mechanics themselves; I implemented the basic Hangman mechanics over the course of a weekend. The difficulty lay in adding all the other features necessary to make this feel like a smooth, polished game: save state, user menus, animation, artwork for the gallows, responsiveness at multiple screen sizes, bug-squashing…

Near the end of July, I got the prototype for the game solidified enough that I felt comfortable showing it to potential employers and contracting partners at in-person interviews. As it happened, I had several on-site interviews the last week of July, and where those interviews involved React I showcased React Hangman as example of what I could do. One of these interviews was with Best Buy, up in the Twin Cities (for those outside of Minnesota, that is the Minneapolis/St. Paul metropolitan area). They liked what they saw, I presume, for at the end of the week they made a contract offer to me (through the contracting agency filling the position), which I gladly accepted.

2019: The Move and the Collateral

I was no longer jobless, but now a new challenge arose: I needed to move from Rochester to the Twin Cities as soon as possible. Cue another month and a half of searching for places to live in the Twin Cities (we settled on a townhome rental in Apple Valley) combined with packing and moving, all around three weeks of having to commute three hours (minus traffic) to Best Buy’s corporate headquarters. During that time, I was also preparing a talk with a friend of mine around a UI tool called Framer X, which we gave at a Rochester coworking space called Collider.

Finally, around the end of September, things started settling down and I could start figuring out what personal projects to tackle next. The next game jam I was planning to take part in, Github’s Gameoff Jam, was due to start on November 1st, and I had wanted some time to experiment more with Godot before it began. Instead, however, I chose to finish the React Hangman project, since I felt it was close to being done. Plus, I wanted the experience of actually finishing and releasing a project.

So I spent the next few weeks running through my list of bugs to fix and features to implement. Yes, the next few weeks. Remember when I said I felt close to being done? That small amount of polish and bug-fixing took weeks of off-work time to finish. It taught me a valuable lesson: polish is not cheap. It’s not something you can just throw on a game and call it good. You have to account for significant development time making a game look and feel good.

The effort paid off. I had multiple comments from people to the effect that this was the best hangman game they’d ever played. One person commented on how smooth the game felt. My two previous Ludum Dare games lacked this level of polish and “juice” (a game-dev term for stuff that makes games feel good), and it was evident, even for a simple game of hangman, how much a difference polish really makes.

Fun fact: the music I used for React Hangman was originally an old song of mine that I threw in as a placeholder; I’d intended to compose my own music for the final release. People liked the temp music so much, though, that I chose to keep it as the release song.

This entire time, I had neglected to really unpack all the stuff in my home office and organize it the way I wanted to, so after releasing React Hangman I dedicated another week solely to this purpose. I’m pleased with how the end result turned out.

Now, only days remained until the start of Github Gameoff jam. I wished there was more time to spend on game experiments, but the time had been spent how it needed to be. Plus, I felt the experience of finishing React Hangman would help me in developing whatever effort would come from the Gameoff jam.

2019: Github Gameoff Jam (Or, How I Spent My November)

November 1st rolled up, and the theme for the jam was revealed: “Leaps and Bounds”. From now, Rebecca and I had an entire month to develop a game. I thought that would be more than enough time to get the job done.

Narrator: It was not.

My thoughts turned to puzzling over the theme. With a name such as “leaps and bounds”, I thought platformer would be an obvious choice of genre. I had spent a lot of time making platformers, however, and I felt the need to come up with at least some kind of twist to keep things interesting. In the end, we settled on the idea of a side-scrolling real-time strategy game, tentatively titled “Rabbit Trails”, in which the player’s goal was to place various gizmos to help colonies (“groups”) of rabbits make it from one location to another. I also decided that I wanted to add some kind of story to the game, having wanted to do so in previous game jams and run out of time to implement. The overall flavor was that the player was working for a company that catches rabbits and sells them for research, and interacts with a couple of company employees during the course of a game mission.

With the plan in mind, I started work on implementing the game’s core mechanics. I had never attempted anything close to a real-time strategy game before, so there were numerous foundational systems I needed to build: unit selection (and deselection), unit building, unit placement with validation, mouse camera movement, dialogue system… Looking back, that was a ridiculous number of features that I needed to create, and mostly from scratch at that (I used Godot Open Dialogue as the base for my dialogue system).

Unsurprisingly, these things took me a long time to develop, and before I knew it we were at the last week of the competition, and while many of the core systems were almost in place, there were still plenty of game-breaking bugs that needed to be resolved. No game content had yet been written, no sound effects had been created (and, on top of that, I’d have to implement spatial sound so players couldn’t hear the sounds of units halfway across the map where the camera wasn’t even looking), no game music composed…the scope of remaining work was daunting. Our son went to stay with the grandparents for the whole week, but I still had to work around my day job (contracting with a retail company means working Thanksgiving week, after all).

There was nothing for it but to tackle the challenge head-on. Rebecca cranked out needed artwork, while I steadily plowed through my list of bugs and features needed to make an MVP (minimum viable product, the bare minimum necessary to play the game). The days passed by slowly and quickly at the same time, and as time ran slowly out I cut more and more things out of the list for that MVP, including sound effects and music. It wasn’t until the night of the second to last day that I finally had a working prototype with full gameplay implemented. I spent the remainder of my time creating a tutorial stage, along with as many additional stages as I could manage. That turned out to be just one.

When time came to submit our project, I uploaded builds for my game (Windows, Linux, and web) on itch.io. When I went to submit the project to the jam, however, I found that the form for submission was gone. I was minutes too late.

We’d missed the deadline. What’s more, when I tried to open one of my builds to test (I’d been in such a rush I uploaded the builds without testing them first) and discovered that every single one of them crashed on load. This didn’t happen in any of my development builds, only on the releases. Oddly enough, I didn’t feel crushed, as one might expect after seeing a month’s worth of effort end up for naught. Truth be told, I was emotionally and mentally exhausted; practically every moment of free time I’d had the entire month had been spent solely on developing Rabbit Trails, and I felt relieved that the pressure was finally gone.

2019: Rest, Relaxation, and Reflection

Ultimately I chose to spend the entire month of December resting and recuperating. From the start of July onward, I really had not had a moment to really recover from the strain I placed myself under, what with the job hunting, the move, and then multiple subsequent projects I needed to finish, culminating with the game jam. I’d spent those months denying myself relaxation in dedication to getting work done; I resolved to spend December doing the opposite, favoring rest over rigor.

A month later, I think I made the right choice. I spent time (a lot of it) playing Remedy’s Control, a brilliant game. I watched some movies, something I hadn’t had time for in months. I started reading again. Not just fiction, but also books on game development theory, to percolate my mentality and kick around theories and ideas. I came to a realization that I’ve spent so much time on learning the programming aspects of game development that I’ve neglected the art of actually, you know, creating games. It’s a malady I intend to remedy.

2020: The Future

So ends my tale of yesteryear. What are my plans? I intend to keep vlogging, but I want to figure out how to present game development material that isn’t solely related to programming or the progress of personal projects. It’s not just for the sake of content; I myself need to focus outside of the programming side of things and embrace all the aspects of making games.

Speaking of making games…after my experience with the last jam, I had an epiphany of sorts. I was treating game jams as kickstarters to get me to make and finish some kind of project. What was really happening, though, was that the enforced time limits resulted in me not really getting to practice the aspects of game development I really needed to learn — crafting good gameplay, implementing juice, storytelling, etc. — and, without exception, each game jam project resulted in spending most of my time implementing features and not actually building much atop those features. Therefore, I have resolved to not participate in more game jams, at least for the near future.

Don’t get me wrong; I’m still going to work on games. Instead of enforcing arbitrary time limits and grinding myself to the bone to release what I can, I plan to focus on making playable prototypes, soliciting feedback, and ultimately settling on one to fully develop to completion for release. That’s been my goal since the start of my game development career, to make and release a game. After two years of focusing on learning, it’s time to start doing. We’ll see how this approach pays off.

I still have my day job as a web developer, and I don’t see that changing anytime soon. I still have my family, and I’ll still do my best to manage the balancing act of developing side projects and being a good husband and father.

And, somehow, someway, I want to find a way to introduce more time consuming content, instead of creating it. If I don’t rejuvenate myself, how can I energize the people I want to experience my work? Of course, this is easier said than done; my ambition far exceeds the physical limits of calendar days, and developing a game is going to take plenty of time. I don’t yet know how I can squeeze more time without sacrificing sleep (an act I find myself less capable of doing as the years pass by). But I want to find a way to make it work.

2019 was a good year, and I’m grateful for all that God has blessed me with, in opportunity and well-being. Here’s to an even better 2020. I’ll keep fighting hard, so that I can be ready to take advantage of any opportunity which comes my way.

I also want to blog more, especially some of the Godot stuff I’ve learned from the efforts of the Github Gameoff Jam. The project may not have been finished, but it was by no means a wasted experience!

React Hangman Koji Template Released

If you follow me on Instagram, you probably know that I have been working on a game template for Koji for the past few months. It started out as a portfolio project for me when I was looking for a job, and even after I got the job I decided I wanted to finish it and release it as a template for other Koji users to build their own apps on top of.

Well, today is the day of the first public release! Go play it here: https://withkoji.com/~Jantho1990/react-hangman

It’s intended to be a template, but the base app is fully playable in its own right!

I invite you to play the game and leave a comment on how you felt about it. Any feedback is helpful!

Bumbling Dwarves

I discovered Koji recently. Basically, it’s a service where coders create templates of projects, including games, for other people to download and customize in whatever ways the programmer allows them to do. Not only that, but the end user can also access the code for the template and make changes to that as they desire.

Figuring I’d give the service a try, I signed up for an account and spent tonight crafting my first game on the platform, a Match-3 clone based on their starter template called Bumbling Dwarves. You can click the link to play, or play it in the embed below!

Working with Koji was pleasant. Honestly, the majority of the time I spent on this project was restyling the open-source artwork I used for this project, as the original images clashed with each other. I wound up coloring the dwarf sprites to be visually distinct, and I softened the lights on the background image.

Using the template was easy, and it made everything simple to customize, just based on the template alone, and for the few code customizations I made (such as changing the color of the selection box) I had full access to the source code (the template was written in React).

The primary way you interact with the code is through Koji’s online editor, which is based on the Monaco editor (the same one that powers VS Code). They give you two ways to interact with the settings files: a visual editor which functions like a form, and the underlying JSON file that you can change in the editor. When I was finished making my customizations (including custom sounds and music), I filled out their short publication form, hit the publish button, and after a few minutes the project was published on https://withkoji.com/, where their projects are hosted.

I can definitely see how this perpetuates Koji’s mission of making app development and MVP prototyping fast, easy, and accessible even to non-coders. If not for those pesky images, I could easily have deployed Bumbling Dwarves in an hour or two.

This was a fun experience overall, and I’m thinking about messing around with Koji more in the near future. At the very least, I’ll be keeping tabs on this cool, interesting service.

If you want to check out other Koji projects from other users, visit https://withkoji.com/

Godot Node Selection Square Getting Fixed

In Godot 3.0, when you create a scene or a node, a 64×64 selection square appears around it by default. You cannot change the size of this selection square, and resizing it changes the scale of the scene/node, rather than resizing it.

To get the portal placed on the ground, I have to monkey around with offsets in the instanced scene itself. Ugh.

It gets particularly annoying when you are trying to place said scene/node instead of a snap-grid, and the 64×64 area not only clashes visually with your grid, it makes it hard to determine where the position actually is! It’s been a source of frustration for me as I learn how to make games using Godot.

Sprite is 32×32, but selection square is larger, so I can’t place the start position on the ground. Grr.

The good news it that this seems to be one of the things getting changed for Godot 3.1. While looking to see if there are ways to work around this quirk of Godot, I came across this change being merged into Godot’s master branch: https://github.com/godotengine/godot/pull/17502

Nice!

Basically, it resizes the selection rectangle to fit the dimensions of a sized child, and in cases where there is no sized child it uses a crosshair centered on the actual spawning position of the entity. This looks like it’ll resolve my gripes with the selection square quite well.

Godot 3.1 is currently in beta, so hopefully this will be released soon! Because the release seems imminent, I’ll just keep putting up with the selection square until 3.1 officially comes out.

Post-Mortem Ludum Dare 43

Outside my home office window, fluffy flakes of snow drifted down from the night sky, coating the ground in white, hiding the dreary brownish grass. It was a picturesque scene, and I allowed myself a brief moment to enjoy it; but then my focus turned back to the task before me: crafting the finishing touches of a sample game UI. A last practice project, in preparation for the challenging journey I was about to undertake.

In less than an hour, Ludum Dare 43 — a video game competition wherein participants design, develop, and deploy a video game in 72 hours — would begin, and I wanted to be sure my mind was primed and ready to roll the moment the competition began.

Before you continue further, dear reader, let me forewarn you: this is no mere post, simply detailing the development of a video game. This is a tale of hopes and dreams, of fear and despair; a tale of lessons learned, best laid plans, and desperate decisions; perhaps most of all, it is a tale of one man’s journey to stare dread fate in the eye and dare to succeed.

This, then, is the tale of my experience creating Sanity Wars for Ludum Dare 43, in all its horror and glory. Sit down, buckle up, and hold on.

The Beginning

I’d reserved today — November 30th, a Friday — and the following Monday and Tuesday off on PTO, in preparation for this weekend competition. I’d spent nearly a year teaching myself how to create video games, and now was when I felt my skills were sufficiently advanced enough to tackle a real challenge: Ludum Dare, a legendary video game competition where participants are given 72 hours to create a brand-new video game.

Though unconventional, I planned to enter the contest with a custom JavaScript-based engine that I coded myself, from its humble beginnings as a tutorial engine to its current state, with my unique experiments and needs added to it. It was general enough that I felt comfortable putting it to the test in the fires of competition.

This was an important moment for me. One of my dreams has been to create and release a professional video game, and to me this contest felt like the perfect opportunity to test not only my skill, but also my resolve. How would it feel to channel my creative and intellectual effort into making a video game? Would I enjoy the process enough to commit to wanting to make a full-fledged game later on down the road? Could I make a game that people would enjoy playing? Over the next three days, I reckoned, I’d find out the answers to these questions.

Let the Games Begin! (Friday Night)

At 8 p.m. CDT, the theme for Ludum Dare 43, voted on by its participants, was revealed: “Sacrifices Must Be Made”. I was pleased; this was one of the options I’d been voting for. As a creator, I’ve always been partial to the thematic and narrative drama sacrifice offers, and I knew I’d be able to come up with something that would fit this theme.

Pacing back and forth in my home office to get my creative juices flowing, I hashed through various different possibilities. Eventually, I settled on a general idea for the signature game mechanic: a side-scrolling platformer where the player used a resource called “sanity” to cast spells — but their sanity was also their health bar, and casting too many spells would deplete their sanity below zero, causing the player to die.

Additionally, players would not be able to access these spells at the start of the game; they’d instead start with something called a “sanity buff” which increased sanity recharge rate. If the player chose to sacrifice these buffs, they’d gain access to spells, but also decrease the rate at which their sanity recharged.

The narrative theme I created to support this mechanic was that the player would be fighting a malevolent being named the Dread Overlord, who held the entire world under the sway of terror, and that the player’s narrative goal would be to weaken the Dread Overlord’s iron grip, bringing salvation to the world. In this, the game also served as a loose allegory to the struggle with bipolar disorder, a struggle I am intimately familiar with, and that further endeared me to the idea. It was, I felt, a perfect fit with the sacrificial nature of the theme, as well as the sanity-casting and buff-sacrificing mechanics.

Little did I know of the irony this narrative theme would present later on in the competition.

With my initial planning made, I sat at my desk and immediately began prototyping my sanity-casting mechanic. It so happened that I already had a spell-casting mechanic built from a previous experiment. Using this as a base, I managed to hammer out a working prototype of the mechanic by early morning. The player’s hit points, or HP, would be directly tied to their mana — or, as I was calling it, “sanity”. Spells could not be accessed until their hotkey was held down for a long-enough period of time — aka a “sacrificed” sanity buff. Reducing sanity to below 0 would trigger the player death state, though I had yet to code what that actually looked like.

I also came up with a tentative title for the game: “Sanity Wars”. It wasn’t a perfect title, but I’d spent half an hour brainstorming possible titles, and this was the one that felt the least cheesy. I figured I’d revisit it later and come up with something better, near the end of the competition.

Feeling satisfied with the current state of things, I decided to call it a night and get some sleep. My confidence at this point felt solid; I believed that I had enough time to take this vision I’d come up with and hammer it out. Soon, I was fast asleep.

Time Management Issues (Saturday)

Around 8-9 a.m. CST, I woke up to a breakfast prepared by my wonderful, amazing wife, and I enjoyed the family meal before returning to the office to begin the day’s work on my game. The first thing I did was to finish implementing player death, which didn’t take much time. I just tied the player HP stat to mirror the sanity level.

Now that I felt I had the “core” of the game implemented, I thought about what to tackle next. I’d noticed the tileset I’d been working with included sloped tiles, but my engine didn’t have support for moving on a sloped tile. So…why not knock that out real quick?

Three hours later, I had a very buggy implementation of slopes; try as I might, I simply could not get the physics of walking on the slope to work correctly. At that point, I finally asked myself the question that I should have asked myself before I started working on slopes: Is this a feature I really need in my game?

The answer was glaringly obvious: No, it did not. I had been developing just fine with block tiles and jumping around, and while I loved the idea of including slopes, they were far from essential for my game to work. Thus…I nixed the idea and moved on. Three hours, down the drain, over a needless feature…

Lesson Learned: Question whether the feature you’re planning to add is necessary before writing code for it.

I decided my next goal should be to implement an enemy. Hitherto, all I had was a player and the sanity mechanic for casting spells. There needed to be at least one enemy, ideally more, for the player to contend with.

I decided I wanted to create a flying enemy, to avoid having to deal with determining where a ground character could move. After perusing OpenGameArt.com for a considerable period of time, I found a floating eyeball animation that looked delightfully menacing. With that as my art base, I created a floating eyeball entity, which would fly at the player, then back away to a random distance once they got too close, then either wander around for a bit or immediately descend upon the player again. When they were close enough to the player, they would trigger a sanity drain, thus “attacking” the player. It took another few hours, but it was complete and I had it integrated into Tiled (the tilemap editor I was using), so I could place floating eyeballs anywhere I wanted.

By then, the hour was getting late. I took a short break, to ponder my next moves. That was when it finally dawned on me: I had a mechanic, I had a player, I had an enemy…but I had no core game loop. In other words, I had no goal for the player to attain, no objectives to attain along the way. All I had was a map with a sanity caster and a few floating eyeballs. That wasn’t a game, that was a setup.

What was the player’s goal? In order for my game to truly be a game, I needed to answer that question, fast, and then implement the bare minimum necessary to make that goal playable.

Lesson Learned: Hash out the core loop for the game right away. Maybe it will change later, but at least have an initial iteration.

After some brainstorming — I can’t recall exactly how long — I settled on the idea that the player needed to survive and find a Final Exit, but this wouldn’t be considered a true victory unless the player first found some Objective; both the Final Exit and the Objective would be randomly spawned in one of a series of maps. Each map would be connected linearly by portals. I felt these things should all be doable within a day or less, and now that it was past 1 a.m. of the next morning I decided to go to bed now. Hopefully, this would give me the energy needed to bang things out quickly.

Making the World (Sunday)

The next morning, I woke up, scarfed down a quick breakfast, then headed back into my home office to start implementing the Map Portals mechanic. Up until now, I had been working with isolated test maps. In theory, I should be able to write code which would create two portals on a map, each linking to a different map, as determined by the load order in my map configuration file.

I spontaneously decided that I wanted the player to be spawned randomly as well, and that furthermore I wanted the player to spawn only in a map corner. Thinking that this shouldn’t be too hard, I got to work designing and implementing the code that would let me do this.

It took more work than I thought it would. I not only needed to restrict where on the start map a player could spawn, I also needed to check and make sure the spawn location was actually habitable by the player (in other words, not inside a solid tile, like a wall) and directly above a ground area (so the player wouldn’t spawn at the top of a tall map in mid-air). After several hours, I had the mechanic working, and proceeded to work on the Map Portals.

A lot of the spawning logic I used for the player also applied to spawning Map Portals, so I made use of copy-pasta and removed the map corner restriction. I then needed to add logic to prevent map portals from spawning on top of each other (or the player), as well as logic to prevent portals from spawning too close to each other.

Another several hours later, I had the portal spawns complete. Now, for the challenging part: enabling the player to press a key in front of one of these portals, and be teleported to the correct corresponding portal on the linked map.

It turned out to be even more complicated than I expected. First, I had to set up a system by which two portal objects could be “linked” together so that they’d always send the player to the correct location. Then I had to load the next map’s data into the game while swapping out the old map data (but not deleting it, because I’d need to restore it when the player returned to that map). The player also needed to be teleported to the receiving portal’s coordinates, which involved resetting the camera to focus immediately on the receiving portal’s location and preventing the player from accidentally teleporting back to their original location by holding the action key a fraction of a second too long. There also needed to be logic to determine where the Final Exit would be spawned, and only render the object when the player was on the chosen map. Finally, any additional entities (enemies, the Objective) on one map needed to be removed from the game loop, then put back in when the player returned to that map.

In short, I needed to make maps containing bad guys, portals, tomes, and the exit.

I spent the entire rest of the day just implementing all this logic. Along the way, I decided, instead of a single Objective, to create multiple Objective pickups (which I ultimately decided to call Tomes, giving them a bookish sprite), spawning one on each map, and that the player had to collect all of them to get the “best” ending; anything less would result in achieving a subpar ending. I also wound up having to write a custom events handler to run dispatches at the end of the current rendering cycle as part of making these portals work, as well as a WorldMap handler specifically to handle the metadata outside of each individual map.

Working feverishly, hour after hour, I finally had everything functioning as intended, except for enemy persistence. By now, it was nearly two in the morning. I had intended on composing an original song for this game, but some part of me warned myself that I might not have time for that on the final day, so I listened to a few of my older compositions and picked the best-fitting one as my backup soundtrack.

Lesson Learned: Things will take longer than expected, so account for that when planning.

Utterly exhausted, I collapsed into bed and tried to fall asleep. I tossed and turned, and I growled mentally at my body for being so stubborn…but sleep continued to evade me. At that point, thoughts flooded into my mind about how every minute I spent awake in bed would lead to one less minute of sleep I’d get, because I absolutely could not afford to sleep in late this time, like I did the other days.

After hours of this dreadful torture, I did finally fall asleep, but not for very long.

Dread Realizations (Monday)

At 8:00 a.m., the alarm blared and yanked me out of my fitful slumber. Compared to the other days, my mind was besot with grog and lack of clarity from the get-go. I plodded to the fridge, grabbed an energy drink, and started downing it. I had no time to think, no time to reflect; I had to finish enemy persistence, and whatever other little things I’d forgotten, to at least get the game to a playable state.

I thrust myself into the chair before my laptop and got to work. In another couple of hours, I got the enemies to correctly persist across maps. I also implemented a maximum enemy amount per map, and a rate which enemies would spawn/respawn into the map. I also threw in some simple ending screens to test the end-game conditions.

It was past noon by the time I got all this working. Meanwhile, my wife was playtesting builds of the game on another machine, and I would need to run back out there multiple times and pull down the latest builds for her to test with.

At this point, with the core loop finally in place, I decided to start looking around for final art assets. The ones I’d been testing with weren’t bad, but they didn’t quite fit together, and it bugged me enough that I chose to divert time into searching for new assets. Hours passed, and before I knew it the time was 4:00 p.m., and all I had to show for it was a single tileset and new sprites for the character.

The deadline was at 8:00 p.m. In short, I now had four hours to create the actual levels, create music, create sound effects, add story text and cinematics, upload the game to a server, test everything…that’s when it finally hit me: I’m not going to be able to release my full envisioning of the game.

It was at this point that my bipolar symptoms, hitherto under control, started to flare up strongly. Dark thoughts filled my mind about how badly I’d executed this game, how unlikely it was that I would even be able to get it finished. Every tiny little mistake I’d made, from the botched slopes to the overly-long search for a new tileset, flooded into my mind.

“You’re not good enough for this,” my brain whispered to me. “You did your best, but it wasn’t good enough and you’re going to fail.”

I kept trying to push forward with making levels. Nearly an hour later, I only had a single map that looked somewhat decent, and my mental anguish only intensified with each passing minute. There were now entire minutes where I’d find myself paralyzed with derision, my mind crushed under the anguishing weight of despair, my body unwilling to even move a single muscle. It had been a long, long time since I’d felt such intense levels of depression, and this scared me most of all. If this is what it’s going to be like every time, how can I justify making games in the future?

Sanity Wars, indeed. The imagined narrative of my game — of which I had yet to even write a single line of text — proved utterly ironic as the villain of my game threatened to consume the game’s creator.

Somehow, despite mostly working in fits and spurts, I kept forcing myself onward, even as my depression screamed at me about the futility of such gestures. My wife helped out by creating a map of her own, and I threw together a few additional barebones maps with a few randomly-drawn platforms. I found a few open-source sound effects to combine with my sfxr-generated effects I’d already put into the game, and I also took the backup music I’d selected the night before and added it as the game’s soundtrack.

Somehow, with less than two hours until the deadline, I wound up with a playable game. It was nowhere near the game I’d envisioned it to be when I started the competition; compared to that, it was abhorrent, abominable, awful garbage. Yet…it was still a game, and it was playable.

The Dread Overlord was still doing his best to crush my mentality and inconvenience my efforts, but, nevertheless, I slogged on. At this point, I had steeled my resolve. It was going to be a crap game, it wasn’t going to capture the theme nearly as well as I’d planned, and it was surely not going to do well…but I was going to finish and release it, flaws be damned!

The Final Push (Monday Night)

Now that I had something to release, it was time to ensure that the game did get released. Although it felt a little early–I still had an hour and a half of time–I decided to go ahead and set up the release platform for my game, so I wouldn’t be scrambling to do it at the literal final hour. As this was a web-based game, my plan was to host this as an AWS S3 website, where I wouldn’t have to deal with setting up and hosting a server, or learning some other company’s setup for their own hosting service. My decision to tackle this now would prove fortunate.

I mentioned previously that I’d built my game-engine based on a tutorial book. Said tutorial book gave instructions on how to set up a dev server called Budo for use with the testing environment…but, it turned out, had not provided any instruction on how to actually deploy the finished app to production. Scrambling, I rapid-researched how Budo ran under the hood, discovered it was using Browserify, and figured out how to set up Browserify to deploy a production build of my game script. After a few other snafus, I managed to correctly set up a deployment script.

Now it was time to create the S3 bucket-site and copy my production assets into it. In my depression-paralyzed state, I screwed up my first deployment attempt and spent almost half an hour trying to fix my AWS permissions before finally opting to blast the first site and make a new one. The second time through, I set the configuration correctly, and the deployment worked as intended. With less than an hour to go before the deadline, I at least knew I could reliably deploy the game.

I used every minute of that last hour to add as much polish as I dared get away with. By now, the adrenaline of deadline crunch was overcoming the dread weight of my depression, so I was singularly focused on trying to make my game at least not absolutely terrible. I converted my test ending screens into actual ending screens, so that I could at least get some of the story’s context into the game. My wife helped by writing the actual dialogue for the ending scenes, while I crafted the introductory scene establishing the game’s tone. We got the final words in, and deployed, at literally the last possible second, at 8:00 p.m.

At the end of the deadline, Ludum Dare allowed for a single hour to get the game deployed, and to fix any last-minute bugs that come up during this process. I honestly don’t recall much of what I did at this time, other than fixing a few things. At the end of it, I belatedly marked the game as “Unfinished” as I crafted the submission post. It only seemed fair, with the game in a barely-publishable state, to mark it as such. When I made my submission post, however, another fellow Ludum Dare-r messaged me, advising me to mark the game as “Jam” instead of “Unfinished”, pointing out that hardly anyone was going to play a marked-unfinished game, and I thus wouldn’t get any feedback on where I could improve. Deciding that I could at least treat this as an improvement opportunity, I took the person’s advice and changed my submission type to “Jam”.

Shortly after I did this, 9:00 p.m. hit. The competition was truly over, and now no changes were allowed. I collapsed onto the living room couch, utterly exhausted and in a foul state of mind. My wife tried to cheer me up, to focus on the fact that I had actually finished and released the game. I wasn’t quite ready to rejoice over that fact, especially since I found myself dreading the horrible reviews which would surely come.

But she was right: I had finished the game. It was a poor excuse of a game, in my mind, but I had actually done it. I’d built a game in three days, and on my first-ever attempt at such a feat. It wasn’t at the standard of quality that I hold myself to, but it was something.

Ultimately, I opted to push all thoughts of self-review and criticism out of my mind until I had gotten a good night’s rest. Fumbling my way through my regular nightly routines, I fell asleep mere minutes after tumbling onto the bed. It was a deep, deep slumber.

The Judgment

I didn’t get out of bed until nearly 10:00 a.m. the next morning. Immediately I noticed an improvement in my mood; sleep had seemed to help tremendously. At the least, I could finally acknowledge to myself that, yes, I had finished a game, and take some pride in that fact.

Lesson Learned: Never underestimate the value of good sleep. Working rested makes a huge difference not only in how you feel, but in the quality of work you output.

Now that the development part of the competition was over, each participant in the jam was expected to play each other’s games, rate them, and provide feedback. I hopped onto my desktop and started checking out the various games. I played a lot of clever implementations of the sacrifice theme: as a game mechanic, as part of a narrative, as the goal for the game…the creativity from the other developers was on full display.

All the while, I kept checking back on my own game. To my surprise, people were not only playing the game…they didn’t completely hate it. Sure, there were critiques on numerous aspects of the game, many of which I was expecting; but there were also plenty of positive comments about some things which they thought I’d done well: the choice of game genre, the incorporation of spellcasting, the visuals and audio… I hadn’t been expecting anything positive, so these comments pleasantly surprised me.

Over the course of the month of December, I continued to play other games (though not as many as I’d have liked, due to work obligations and the holiday season) and give my own ratings and feedback. Other people continued to play my own game and leave their feedback.

At last, at the beginning of January, the ratings period was officially over, and the scores were released for every game. I logged onto the site, and found my results:

The final results from my debut game entry, Sanity Wars.

With my own numbers known, I went to check on the overall stats for the jam, from which I’d learn how high my score truly was:

The overall statistics from Ludum Dare 43.

There were 2,514 submissions. Of these, 1,494 had received enough ratings (20 or more) to be awarded an official ranking.

Thus, my final placement: 842nd out of 1,494. Right in the middle of the pack.

To me, the final score was both surprising and expected. Expected, because this was within the middling range where I felt I’d wind up with my first-ever game entry. Surprised, because I never expected this flawed, imperfect game to score so highly. It still amazes me even now, as I type this retrospective.

It just goes to show: Never give up, even when all hope seems lost. This adage is a mantra that I try to live by, contending with my bipolar disorder, and time and time again I find it to be a true maxim. With my emotions screaming failure, I persevered and found triumph.

I Am Now a Video Game Developer

It’s official, now. Having designed, built, and released a video game, one which people were able to play, I am now a video game developer. It feels good to say that, after all the learning and preparation I put in to get to this point.

Overall, this competition was an incredible experience for me, in many ways. I set out to make my first-ever game, and I not only succeeded, I did better than I expected of my entry. Though I still wish I had been able to release a more polished game, the experience I’ve gained was invaluable, and will prove critical to my future endeavors to publish a full-fledged video game.

Among the things I learned: coding a game engine from scratch is hard. I mean, I knew that, and I knew that the main reason I did build an engine was to learn how things worked from a general perspective, but it gave me a whole new appreciation for the work game engine developers put in to make usable, reliable engines for others to use. Although I like tinkering with systems and how things work under the hood, Sanity Wars drove home a well-worn adage in the games industry: make games, not engines.

I also think I tried too hard to come up with a complex narrative for a game that, ultimately, did not need much narrative involvement. Presenting an allegory of the bipolar struggle may be neat for short stories, but it didn’t translate well into a game. I’m a narrative-oriented person, so I like creating complex narratives; however, by focusing too hard on cleverly implementing sacrifice in Sanity Wars‘ narrative, I instead sacrificed the fun of the game mechanics. In the end, I felt neither the narrative nor the spell sacrifice was well-implemented, and I could have done both more simply by highlighting the sacrificial nature of tying spellcasting to your health.

My goal with taking part in Ludum Dare was to gain experience, and in that regard I achieved success. Most of all, I learned that I can, indeed, create video games, and that this is something I want to keep doing, depression be damned

To put it in a melodramatic way…I faced the Dread Overlord, and conquered him

What’s Next?

I am currently working on rebuilding Sanity Wars in Godot, an open-source game engine, and comparing the experience to coding everything by hand. I suspect it might ease many of the pain points I had developing my game and engine.

Why did I choose Godot, over something more familiar to my background, such as PhaserJS? Well, I read plenty of good things about its ease of use, and the node-based architecture is very similar to the structure I used in my own engine. Plus, I thought it’d be nice to have a GUI to work with, instead of coding everything by hand. I plan on releasing the Godot version of Sanity Wars once I’ve finished the port.

The next Ludum Dare is at the end of April. I’ve already made sure to take time off for the dates of that contest. I look forward to taking on whatever challenges it throws my way, with the goal of releasing a more polished game.

As for my dream of making a professional video game? That dream is alive and well, and as I continue to learn and grow my skills, my confidence that this dream can be realized continues to grow. And as long as I keep moving forward, and don’t give up, I have no doubt that one day this will no longer be a dream.

It will be reality.

You can play the Ludum Dare version of Sanity Wars here.

Sample Project: Star-Rating (Vue)

This is part two of a multi-part series about React and Vue.
Part 1: React
Part 2: Vue

As I have been learning React lately, I’ve also been learning Vue. I currently use Vue at work, but I wanted to make a sample project for myself. I’ve been interested in doing a comparison between React and Vue, so I’ve chosen to build a Vue version of the Star-Rating app I built in React. The app is small and simple: you have a rating represented as a line of stars, and you can change how the star rating is calculated via an inputs component.

For the final result, click this link.

This tutorial presumes a basic knowledge of HTML, CSS, and JS, along with some basic knowledge of how to develop using a command line interface (or CLI) and how to use Node Package Manager (or NPM). If you’re unfamiliar with any of these things, feel free to make an online search for whatever you don’t know.

Vue makes some use of ES6 syntax, and I try to write my code using ES6 as well. I’ll try to link to definitions of ES6 syntax when I initially use them, but I won’t take especial time to explain them in-depth. If you want a quick overview of ES6, you can read this.

Table of Contents

Setting Up
Looking Around
Creating Our First Component
Rendering Stars with FontAwesome
Calculating How Many Stars to Render
Rendering the Stars
Vue Slots
Finishing the First Component
Introducing a Second Component
Rendering the Input
Adding the Other Inputs
Better Validation
Final Touches
Conclusion

Setting Up #

For this tutorial, I chose to use vue-cli, a command-line tool that will set up a boilerplate Vue single-page application, similar to create-react-app. If you don’t have it already, you can install it via npm install -g [email protected].

I’ve specified a specific version number because, as of this post, vue-cli V3 is in beta and will be released soon. It works differently from V2, so to follow this tutorial you’ll need a V2 version of the tool.

To set up a project, navigate in the terminal to the directory you want to make the project in (for me, this is ~/jdev/projects), and then enter this command:

vue init webpack star-rating-vue

Right away, you’ll be presented with a text interface that asks you a number of questions regarding how you want to set up your project. But, before we get to that, let’s look at the command we just entered. The first part is vue, calling the tool; the second part is init, which tells the tool to start up the project; and the fourth part, star-rating-vue, is the directory our app is going to be saved in. So what is the third part, webpack, for? The way vue-cli V2 works is by using pre-built templates, and which template you choose determines what kinds of goodies you start out with. In this case, we’re using the “webpack” template, which will set up a full Webpack-based project, as well as a few other things:

vue-loader
Allows us to write single-file components (we’ll get to that momentarily).
hot-reload
Allows us to see changes to our app on save.
linting
Helps enforce proper coding conventions by marking up your code editor.
testing
Sets up a unit testing framework. (We won’t be using tests in this tutorial.)
css extraction
Pulls our CSS out of single-file components (we’ll get to that momentarily, too).

If you look on the official page for vue-cli, you’ll find some other “official” templates you could use, instead. There are also third-party templates; just search online for “vue third-party templates” and you’ll be bound to find some.

Now that we’ve taken a brief overview of what templates are, let’s get back to the actual setup. The command line tool will present you with a set of options, one at a time. For most of the options, you’ll need to type in either y or n and hit enter to make your choice; some are multiple choice, and a few involve typing up strings.

I’ll go over each option briefly:

  • Project name: “name-of-your-project” (requires URL-friendly characters, so no spaces; also, no capital letters allowed)
  • Project description: “Whatever description you feel like including.”
  • Author: “Your Name <[email protected]>” (if you have a global Git user, this should be autofilled).</[email protected]>
  • Vue build: Here, you’ll have two options: Runtime + Compiler and Runtime-only.
    • Runtime + Compiler allows more flexibility in how you build your projects, such as passing in templates as a string (from an AJAX call, for example), but it makes the final build larger (by as much as 30%).
    • Runtime-only is a lighter build, and performs slightly faster, but imposes some restrictions on how you can set up your project.

    We shouldn’t need any functionality from the compiler for this project, so let’s select Runtime-only.

  • Install vue-router? vue-router is useful for setting up something resembling url navigation within an SPA. We won’t be doing that here, so you can choose n.
  • Use ESLint to lint your code? If you care about following style guides, choose y, and then pick the linter you want to use in the subsequent section. Otherwise, choose n.
  • Set up unit tests: As mentioned earlier, we won’t be setting up unit tests here, so choose n.
  • Setup e2e tests with Nightwatch? Nightwatch is a testing tool that allows you to render the app in full, then set up “scenarios” of user actions to test how your app acts under those circumstances. We will not be doing that here, so you can choose n.
  • Should we run `npm install` for you after the project has been created? Since we would need to do this anyway, go ahead and choose Yes, use NPM. If you prefer Yarn, you can choose that instead.

After that last choice, vue-cli will get to work setting up your app with the choices you’ve made. It may take a while, depending on your internet speed, but in a minute or two your boilerplate app should be generated and ready for you to work with. When it’s finished, start the development server by cd-ing into the project directory and typing npm run dev. Open a browser and navigate to localhost:8080; if you see the Vue logo and some links, the dev server is successfully running!

If you don’t want to use vue-cli, you can import Vue directly, via a CDN, locally-hosted script, or NPM install. Check out the official docs for more information. For this tutorial, however, I’ll be assuming you’re using vue-cli.

Looking Around #

Now that we’ve (finally) got the barebones project set up, open the project in your preferred code editor.

If you use VS Code, you can spawn a new editor window from the project directory by cd-ing into it, then typing code . (you may need to explicitly enable this if you use a Mac). Other code editors likely support this functionality as well, though the commands you use may be different.

You should see a number of directories and files already set up. Let’s look in the src directory and open up the file called main.js. It should look something like this:

This file sets up the root Vue instance. All the other components in our application will be kept under this root instance. We’re importing Vue itself, as well as a file called App (which we will be examining shortly). There’s a quick line disabling the production tip. Finally, we create a new Vue instance, and pass in a config object with two settings:

  • el: '#app' This tells Vue what element from the HTML document to bind the instance to. The original element will be replaced with our rendered application. (You can see this element by looking in the `index.html` file in the project root directory.)
  • render: h => h(App) This tells the instance’s render function what should be rendered, via passing in a callback function that handles the actual rendering. In this case, we’re rendering the App file imported at the top of the page.

Let’s dig into that second option a little bit. The code is using an ES6 arrow function, with a single argument: h. What’s h? It’s an alias for the createElement function, the letter itself short for “hyperscript” (you can check this Github issue comment for a deeper explanation of both the render prop and the meaning of h). h is then called, and App is passed in and rendered.

So what is App doing? Let’s find out:

Note the .vue extension. This is what Vue calls a Single File Component. A single file component allows you to write your HTML, JavaScript, and CSS for that component in the same file. For applications comprised of multiple, modular parts, this can be very useful for keeping markup, logic, and styles associated with a single component located in one place. We will be creating our own single file components (henceforth just “components”) soon enough, but first let’s dig into the default App component, section by section.

First, the HTML:

The entire section is wrapped in a template tag. This gives Vue access to the markup contained within; the tag itself will not be rendered. Next, you have a single div with an id of app. This is not the same app element that the root Vue instance replaced; it merely shares the same id. Finally, we have an image tag — the Vue logo — and a strange-looking, self-closing tag named HelloWorld. This is another component, imported into App and being rendered below the logo.

All elements in your component must be wrapped by a single container element; here, the #app div.

Next, in the javascript section:

This contains the JavaScript code for the component. First, you’ll see the HelloWorld component being imported from the components directory. Next, the code exports an object as default, containing two properties:

  • name The name assigned to this component. While not strictly required (except in a few cases), it’s good practice to always give your components names.
  • components This tells the component instance what custom components should be expected. This is how we can write <HelloWorld/> in our template and have Vue replace it with the template from the HelloWorld component.

Finally, there’s the styles section:

This is just regular ol’ CSS, styling the #app element. During development, these styles will be added to the <head>, but because our Webpack template includes CSS extraction, the styles in this section will get pulled out and added to a single CSS file during the build stage.

That does it for App. Now let’s look at the other component included in the boilerplate — the HelloWorld component.

As you may have noticed, the attributes for the a tags in your local file are split onto separate lines. This is considered by Vue’s style guide to be best practice. I’ve chosen to condense the tags back onto one line for my gist for the sake of brevity, but in general I agree with this philosophy.

It’s another single file component. The HTML template contains a couple unordered lists of links and some h2 tags titling them, as well as an h1 with {{ msg }}. If you’re looking at the app in the browser, however, the h1 says “Welcome to Your Vue.js App”. What’s going on?

Let’s look in the script section to find out:

There isn’t much here. We have the component’s name, “HelloWorld”. There are no components being passed in; however, we do have a method called data, which returns an object. This object contains one property: msg, whose value is set to “Welcome to Your Vue.js App”. Hey, that’s what the h1 says on the rendered page! That’s where it comes from. Anything you set in a Vue component’s data method becomes available in your template as a variable. You can output the values of those variables using a pair of curly braces.

Why is data a method returning an object, instead of just being a property set as an object? If you have multiple instances of the same component on a page, each component’s properties are shared with each other by reference. Thus, if data were an object, updating the data on one component would update that piece of data for every instance of that component. To avoid this, we just make data a method that returns a fresh object each time it is called.

The docs provide additional explanation, as well as an example.

Lastly, let’s take a look at the style section for this component:

Again, it’s just CSS, but this time there’s a scoped attribute in the style tag. This means the CSS in this component will only be applied to styles within that component. How does it do this? If you go to the web page and do a dev inspection on one of the links, you’ll notice there’s a data attribute attached with a long, random string (prefixed with data-v-) as its value. Vue sets the CSS rules in HelloWorld to target both the rule specified in the code and the value of this data attribute. Thus, the styles in this component will not affect anything outside of its scope.

You are still able to affect the element styles in this component from the outside, but be aware that, since the data attribute specification adds specificity, you may need to be more explicit with your rule declarations than usual to override the component’s styles.

Whew! For a boilerplate example, that was a lot of stuff to look over. Hopefully, this overview gave you a basic idea of how Vue works; the rest of what we need to know, we’ll pick up as we go along. Let’s start making our Star-Rating app by creating our first component!

Creating Our First Component #

In the “components” directory, create a new file and name it StarRating.vue. Here’s the structure we’ll need to start with:

In the template section, we’ve just added a single div with a class of “star-rating”, which will serve as the container element Vue requires. That’s all we’ll add, for the moment; we’ll get to adding stars shortly.

In the script section, we’re exporting a default object with two properties: name, and props. Props are pieces of data that a component expects to be passed down from a parent component. We’ll be setting up App to do that, but for now we’ll just set up defaults to work with. We’ll also add a type property to each prop, which allows Vue to detect when a prop gets passed down that doesn’t match the type specified, and warn you in the developer console.

The style section is currently empty. Eventually, we’ll put in the styles specific to the StarRating component, but first let’s have something to actually render.

Rendering Stars With FontAwesome #

If you read the React version of this sample project, you’ll remember that we chose an icon set called FontAwesome, imported some star icons into our app via a series of NPM libraries, and wrote some logic to control how to render those icons. We’re going to do the same thing here, but instead of using react-fontawesome, we’ll be using vue-fontawesome.

npm i --save npm i --save @fortawesome/fontawesome-svg-core @fortawesome/fontawesome-free-solid @fortawesome/fontawesome-free-regular @fortawesome/vue-fontawesome

To summarize, we’re installing Fontawesome, two sets of Fontawesome icons, and a package which provides FontAwesome Vue components. Once the packages have finished installing, we need to import them into our project.

First, in App.vue, replace the HelloWorld import with this:

The first statement is importing the main fontawesome library. The next two import statements are using multiple import syntax to extract specific parts of the fontawesome-free-solid and fontawesome-free-regular icon libraries. For the latter, we are also assigning aliases to the imports, since they share names with the solid counterparts. Finally, we use fontawesome.library.add() to make these four icons globally available for all our other components to use.

We could also have just imported all of a library’s icons — with, say, import solid from '@fortawesome/fontawesome-free-solid' — and added all the icons globally. If you know you only need a small set of icons, however, it’s best to just import the ones you need.

Next, let’s go back to StarRating and add one more import:

Similarly to how we imported parts of the icon libraries, we are importing parts of the @fortawesome/vue-fontawesome library — specifically, FontAwesomeIcon and FontAwesomeLayers. Soon, we’ll be using these to render our star ratings. First, though, we need to tell our StarRating component to make these FontAwesome components available to the template, and that’s what we’re doing by adding the components property to our exported object, its members FontAwesomeIcon and FontAwesomeLayers.

With our FontAwesomeIcon component imported, let’s go ahead and update our template:

Inside the container div, we’ve added a new tag: font-awesome-icon. This is our FontAwesomeIcon component, and when Vue renders our app it will take this component tag and replace it with the component’s template, just as the HelloWorld component is being rendered into our default application. We’ve given it a class of “star”, and we’ve also set a property named icon, its value set to “star” as well. This icon property is a prop we are passing down to the Vue component, telling it which icon we’d like it to render.

The official Vue style guide strongly recommends naming custom components using multiple words, as well as naming your components using either all Pascal-case (FirstLetterCapitalized) or kebab-case (dashes-between-words), and I agree with their reasons. For our tutorial, I’ve chosen to use kebab-case, but ultimately it’s just a matter of preference.

Let’s go ahead and render our new component into the App.vue component file:

We’ve added an import for our StarRating component before the other imports (imports are asynchronous, so JavaScript will not execute any code until all imports have been processed, which means we don’t have to import StarRating after our FontAwesome imports). We’ve also removed the import for HelloWorld because we won’t be needing it. In components, we replace HelloWorld with StarRating. Finally, in template, we again swap HelloWorld for star-rating. If all goes well, upon save you should see a single black star rendered just below the Vue logo.

Calculating How Many Stars to Render #

If you played around with the final result at the beginning of the tutorial, you may have noticed three different types of stars: “full” stars, “half” stars, and “empty” stars. This is how I’ve chosen to represent the star ratings for this project.

Before we can render any of that, though, we need to calculate four things:

  • What is the maximum number of stars we should render?
  • How many full stars should we render?
  • How many half stars should we render?
  • How many empty stars should we render?

You may be wondering why it’s necessary to calculate a “maxStars” value. Sure, we could simply calculate the full, half, and empty star values without calculating a separate max value, but I’ve found that it makes the math simpler to just figure out the maximum number ahead of time and use it in calculations later, so that is what I’ve chosen to do.

Let’s update the StarRating component with the necessary code to perform our calculations:

To briefly explain each calculation:

  • maxStars(): We take the maxRating and divide it by the starRatio, then round the answer up to the next integer.
  • fullStars(): We take the rating and divide it by the starRatio, then round the answer down to the next integer.
  • halfStars(): We take the modulus (or remainder) of rating and starRatio, get half the value of starRatio, and compare the two using a ternary operator. If `x` is greater than or equal to `i`, we return one half-star; otherwise, we return no half-stars.
  • emptyStars(): We take maxStars and subtract from it fullStars and halfStars.

Note where we have placed these methods. These are what Vue calls “computed properties”. In other words, when you access these properties, whether in your template or other parts of your code, Vue will return the result of the function you’ve assigned to that computed property.

If you examine the code used for the emptyStars method, you can see that we’re using the other three computed properties to calculate its return value. The other three computed properties, in turn, are calculated based on the value of the props passed in to the StarRating component. If any of the props should change, Vue will recalculate the values of these computed properties, and any rendered data using these computed properties will also be updated; it all happens automatically.

Rendering the Stars #

Let’s see the computed properties in action. Modify the template thusly:

What we’ve done is add a directive to our font-awesome-icon component called v-for. This directive is telling Vue to render a component this many times, just like looping through a list of items. The text inside of the v-for directive is actually specific syntax that Vue will interpret similarly to a for-in loop. Vue lets you enumerate objects and arrays with this syntax, as well as ranges (which is what we’re using in this case).

Be sure to check out Vue’s official documentation on this subject, which shows you more specifically how to work with arrays and objects using v-for.

We’ve also added a key attribute, and you might have noticed the colon in front of it. What is this colon for? This is a shortcut for the directive v-bind, which binds an HTML attribute to a JavaScript expression. The value of key equals "`fs${fs}`", which is a template literal expression. To use ES5, "fs" + fs (which would also work here; I just prefer using ES6 when possible). When Vue reads this, it will parse the expression bound to :key as actual JavaScript.

You can read more about attributes here. If you follow the link, you’ll notice that the Vue examples use v-bind: instead of just the colon. That is the “full” way to write out binding syntax, but Vue allows you to use just the colon as a shortcut.

Why do we need key? Vue strongly recommends that you add a unique key to each item rendered by a v-for loop because it helps Vue track each item in its virtual DOM, which ultimately improves performance. The expression set in key will generate a string with the incrementing value of the variable fs (set in the value of v-for), which is enough to make this key unique. Since, technically, it isn’t required, we could choose to leave the key out, but it’s good practice to always add a key attribute when using v-for.

So what does all this actually do? If you’ve saved this code, you might have already noticed that the demo app in the browser has already updated to show two black stars instead of one. This is because of our v-for directive, fs in fullStars. The default prop value for rating is 5, and our default starRatio is 2. Those two props are used by the fullStars computed property to calculate its value, and in this case the result is 2. v-for then loops n in 2, resulting in rendering the font-awesome-icon component twice. Again, this happens automatically. You don’t have to write a separate function to handle the looping and rendering for you. Vue just takes care of it because you told it to via the v-for directive.

Now that we know how this works, let’s go ahead and add the template code to render our half stars and our empty stars:

The process for rendering our half stars and empty stars is nearly identical to the process for rendering full stars. We just add a FontAwesome component and use v-for to render as many of each component as dictated by the related computed properties. There are a few differences, however, which I’ll go over briefly.

In both cases, we need to generate unique keys for the key attribute, and to accomplish this we just change the variable to match what we use in the v-for directive and alter the string prefix. For the empty stars, "`es${es}`", and for half stars, "`hs${hs}`".

For the empty stars icon component, instead of just passing in an icon attribute, we’re binding the attribute to a JavaScript array: ['far', 'star']. This is because of the font-awesome-icon component’s interface. By default, passing in a simple icon attribute will render an icon from the solid library of icons, which is what we used to render our full stars. For empty stars, however, we want to render a different icon; specifically, one from the regular library that looks like the empty outline of a star. In order to tell the font-awesome-icon component to render an icon from the regular library, FontAwesomeVue expects us to pass in an array with two members. The first item is the abbreviation of the library we want to pull the icon from, in this case far; the second item is the name of the icon we want, in this case star. Since we’re passing in an actual array, that means we need to bind icon so we can send the configuration array to the component.

You could use ['fas', 'star'] to render our full star icons, instead of relying on the component’s default assumption, if you prefer.

The most significant difference is with how we’re rendering the half stars. Instead of a single font-awesome-icon, we’re rendering two, and we’ve wrapped both components with a font-awesome-layers component. This is because FontAwesome 5 (the version this tutorial is using) doesn’t have a “half-filled” star icon; instead, it has a literal half of a star: one that is “filled” (from solid), and one that is “empty” (from regular).

To get the half-filled star effect we’re looking for, we need to combine those two icons into one. The official way to do this in FontAwesome is to use Layering; in vue-fontawesome, this means using the font-awesome-layers component. Anything within the font-awesome-layers tags will get combined into the same space and be shown as though it were one icon.

Thus, we’ve set the two font-awesome-icon components to use the half-star icons; additionally, we pass in a flip attribute to the empty half-star so it’s reversed. The end result is something that looks like a half-filled star. Excellent!

After I started writing this tutorial, FontAwesome did come out with a half-filled star icon, so we could use that instead of my original solution. I’m not going to change this solution, however, because it also serves to teach about Vue slots — which is coming right up!

Vue Slots #

Let’s take a moment to further examine font-awesome-layers. Up until now, we’ve been using self-closing tags for our components, because it’s simpler to read and we had no need to use opening/closing tag syntax. With font-awesome-layers, however, we’re not only using opening and closing tags, we’re including other components inside of the tags. This is using an aspect of Vue components called slots. To explain what slots do, let’s use a quick example:

The above code (assuming path names and whatnot) results in rendering the following:

In the first component, called ExampleComponent, we basically have two parts: an h2 with the text “These are my children:”, and a ul containing a slot tag. What is this slot tag for? This is telling Vue that anything which is slotted into this component should go here. What does slotting mean, though? To answer that, let’s look at the second component.

The second component is called ExampleParent. In it, we have an h1 with the text “I’m the Parent”. What comes next is our first component, example-component, but in between the opening and closing tags of example-component are three li tags, each containing a human name. If you look at the results image, you’ll see that it renders “I’m the Parent” (from ExampleParent), followed by “These are my children” (from ExampleComponent), and lastly by the names (from ExampleParent). Vue is taking the li tags we enclosed within the example-component tags and inserting them in place of the slot tag we put in ExampleComponent. That is slotting.

If you’d like to follow along, you can copy-paste the code snippets into your components directory as separate Vue files, and then import ExampleParent into App, just like we did with StarRating. Don’t forget to remove it when you’re done with it!

Now that we have a better idea of what slots are, let’s look at the font-awesome-layers component again:

We are slotting two font-awesome-vue icons inside of the font-awesome-layers component. That layers component will then render FontAwesome’s layers container (basically a div container and some classes), so we can make those two half-star pieces look like a half-filled star.

Finishing the First Component #

Alright, we’ve added the code necessary to render all of our stars: full, half, and empty. The last thing we’ll do (for now) is add some styling in the style tags at the bottom of the StarRating component. The finished* code should look like this:

Following the steps outlined so far, you should now see the Vue logo with two and a half gold stars rendered beneath it. If you do, congratulations on making it this far! If not, you should review your code and compare it with what I’ve posted. Hopefully that should expose your problem and get you back on the right track. If not, feel free to submit an issue in the project repository!

Did you notice the asterisk on “finished”? There’s one more thing we’re going to take care of later, but we don’t need to worry about it right now.

Assuming things are working, let’s go ahead and pass a prop down to our component. From App.vue:

When you save the file, you should see the rendered stars change. Assuming you passed in a “7”, like I did, there should be three and a half stars rendered — three full, one half, and one empty. Vue took the rating number we passed down to the StarRating component and converted it to the star rating we now see. We didn’t have to do anything other than pass down that prop.

Of course, at the moment we can only change the rating by manually editing our code to change the value of the rating prop. That obviously isn’t a very user-friendly way to change the star rating, so we’re going to want to have an interface for us to enter the prop values we want and have StarRating update in response, as shown in the final result. To do that, we need to build a second component specifically for handling this interface…a RatingInputs component!

Introducing a Second Component #

To begin, let’s create a new file in the components directory, and call it RatingInputs.vue. Add the following code:

First, in the template, we have a single input for the rating, as well as a label for that input, all wrapped in a container div. Then, in the script section, we have the component’s name and our prop definitions (like we had in the StarRating component). Finally, we have the empty style tags at the bottom.

Next, we’ll take advantage of Vue’s form input bindings and wire up our input element to the component’s data:

We’ve added a directive to the input, v-model, and set it equal to rating_. In the script section, we’ve added a data function, which returns an object containing one property: rating_ = this.rating. What this is doing is initializing rating_ with the value of rating, a prop this component expects to be passed down (and if it isn’t, then it defaults to 5, as we set up in the props section). Because we’ve told Vue to “model” the value of rating_ on our rating input, Vue sets the input element’s value equal to the value of rating_; when we change the value in the input element, Vue then updates rating_ to equal the new value. Thus, Vue gives us two-way data binding without having to do extra work.

Why not just set the input’s value to be rating directly? Technically, Vue would allow us to do that; however, this is considered bad practice. The reason is because anything defined as a prop will have its value changed any time a new value is passed down to the prop via the parent component. Thus, as best practice, if we want to modify a prop’s value from the child component, we should instead create a property in the data object and initialize it with the prop’s value, then modify that data property instead of the prop.

There is no established nomenclature for how you should name your props and data values in cases like these. I’ve chosen to use rating as the prop name for easier reading when passing it in from the parent, and rating_ because Vue doesn’t allow you to start data fields with underscores, and this seemed the next best thing.

Rendering the Input #

Although our RatingInputs component is wired up with the rating input, we still don’t have a way to tell the StarRating component what that input’s value is. To do that, we’re going to need to use Vue’s events system. We’ll start by updating our RatingInputs component:

We’ve added another new directive to the input element: v-on. This tells Vue to listen for an event (it can be a browser or custom event) emitted by this element. :input tells Vue which event to listen for, and "handleRating" is the callback we want Vue to run on this event. In the script section, we’ve added a new property to the component object, called methods. This is where you store any functions you want your component to have access to.

You should use regular functions here instead of arrow functions. Vue automatically handles binding a component’s this to its methods, but it can’t do so for arrow functions because those use the this value of the calling function — which, in this case, won’t be the RatingInputs component, but Vue’s handler for methods.

In methods, we’ve added one function: handleRating, which does two things: first, it gets the value for rating_ from the component via destructuring assignment; second, we call this.$emit, which is a special component function to emit a custom event that only a direct parent component will be able to read. This $emit function takes, as its arguments, a string “rating-update” and an object literal set to {rating: rating_}. rating-update is the name of our custom event, and the object literal is an argument that will be passed in to any callbacks the parent App component has listening for the rating-update event.

Now, in App, let’s make some changes to listen for our custom event:

In the template section, we’ve removed the hard-corded value for :rating and changed it to be the value of the rating data field, which we add in the script section. We’ve also added the rating prop to our RatingInputs component, as well as @rating-update, the custom event that we’re listening for, and a handleRatingUpdate callback.

@ is Vue shorthand syntax for v-on:

We’ve added a methods object to the App component object, as well as the function handleRatingUpdate. It takes one argument, data, which is equal to the object literal we passed into the $emit function in the RatingInputs component. From data, we extract the rating property, and then set this.rating (the data field in App) to equal rating. Upon saving the file, we should now be able to edit the value of rating input element and update the rendered stars in real-time. If you’re able to do so, congratulations!

Adding the Other Inputs #

While playing with the ratings input, you’ll likely notice that you can add a higher input than 10, and a lower input than 0. While this doesn’t seem to affect anything visually, this is not ideal behavior. We want to limit the input so that the user can’t add a higher rating than the maximum, or a lower rating than the minimum. Also, it would be nice to be able to change the star ratio, or how many stars get rendered per rating point (e.g. render one star per rating point, rather than one-half). Let’s get on that.

Let’s start by adding additional inputs for the values outlined above: maxRating, minRating, and starRatio. The code for both RatingInputs and App is as follows:

As you can see, the majority of we needed to do is replicate the code we added to wire up rating, changing only the names. We use the same handleRating callback to process any change to the inputs and pass them up to the parent, and likewise handleRatingUpdate updates all the data fields at once. We then pass back the data in App as props to both StarRating and RatingInputs, and each component updates its own data accordingly.

There are a couple of additional changes. First, all of the input elements have min and max attributes set, which will show an error highlight should any of the values go below the minimum or above the maximum. There’s also a limit property, which is used to constrain minRating and maxRating; this is mainly to control how many stars can actually be rendered, as rendering more than a thousand begins to negatively impact performance.

Better Validation #

We still haven’t solved the problem of preventing someone from inputing illegal values into our data. There are a variety of conditions we’ll need to check, and we’re going to need to check them for both StarRating and RatingInputs. It seems like it would be a good idea to write a separate file that contains all this validation code, which we can import into whatever files need to use it.

Conveniently, I’ve already written such a library for the React version of this project. Since we’re validating the exact same set of data, we can just take that file as-is and plop it straight into our Vue project.

In your src directory, create a new directory and name it lib, then create a file called validate.js and copy-paste the following code into it:

The function we’re most interested in is the default export, which is a function that runs all the other validation functions in the file, returning false if any one of the checks fail, and only returning true if all the checks pass. All we have to do is pass in our data values.

We have the validation library, but now we need to integrate it with our app. Let’s start with the RatingInputs component.

The only place where we needed to make changes was the script file. First, we import the library. Then, in the methods section of the component, we’ve added a new method: anyAreEmpty. All this does is check any arguments passed into it and return true if any of them are equal to an empty string. In our handleRating method, we then use anyAreEmpty to check each of our input values to make sure they aren’t empty strings. Why did we do this? A more detailed explanation can be found in this section of the React tutorial, but the gist is that without doing this, there are scenarios where Vue (and React) won’t let you update the inputs at all.

Finally, we’ve wrapped our call to $emit inside of our validation function, inputIsValid, which takes all of our data values as an argument. We’ve also moved our number conversion to occur before we run the validation function; otherwise, we’d be passing strings into the validation functions, which are expecting numeric values. Only when the validation function returns true do we emit the rating-update event.

With RatingInputs taken care of, let’s turn to StarRating and make a small change there (this is what the asterisk from earlier was for).

Here, we’ve added a new property to the component, called beforeMount. This is one of Vue’s lifecycle hooks, which are basically functions Vue calls at specific stages of a component’s life. In this case, beforeMount will be called once, right before the component is rendered for the first time. We’re checking to see if our default props, being passed in from App, are valid, and if they aren’t we throw an error to let the developer know that they need to check the values being passed as props.

You might be wondering: Why bother with this validation at all? After all, this validation check isn’t what’s showing the errors to the user; the browser is handling that. All this is doing is preventing the rating-update event from being emitted when an illegal value is entered. But that’s the point: without this validation, Vue would emit the event, try to process the illegal values, and then error out in the process. We’d be wasting processing effort doing something we already know is not going to be valid. While that’s not as significant an issue for a small demo app such as this, there are plenty of cases where this would be a costly error. For example, say our update event triggered an AJAX request; we wouldn’t want to send an HTTP request whenever an illegal value is entered. By making sure we only update when our input values are legal, we ensure our code isn’t wasting resources and processing power.

While I’m not going to do this here, we could also use our validation library to trigger more specific error messages in response to the illegal inputs. Feel free to add this yourself, if you’d like!

Final Touches #

With this, all that’s really left to do for RatingInputs is add some styling. If you want to fully match the way my version of the app looks, add the following style section to RatingInputs:

We should also change the styling on App so that the visual structure looks better (up until now we’ve been using the default styles that were generated by Vue when we created the app). I also made slight changes to the App template. To match my version of the app, add the following style section to App and modify the App template to match mine:

Of course, if you want to style things differently, feel free to do so!

Conclusion #

Hopefully this was a fun app to build. I liked making a demo app that wasn’t a todo list. Vue felt intuitive to me, as a web developer, and I loved being able to use template files to keep everything relating to a single component in one file. In exchange, you do have to structure your app in a more specific way, but since I mostly agree with how Vue wants you to do things, I don’t consider this a bad thing.

If you want to view the entirety of my code, look up the project on my Github repo.

Questions? Feedback? Leave a comment!