• Pedro Civa Wiltuschnig

Dundas App

Updated: May 3


The South Dundas Tourism App is a project being developed in Unity by a team of 10, including me and my colleagues from St. Lawrence College in the Game Programming course.

The purpose of the app to make people more aware of the offerings at different locations in the South Dundas region.

The goal of the users is to discover new locations in the local area, collect badges and unlock minigames (probably in augmented reality) in which their score will then be added to the Leaderboards.

My first task was to implement a TTS (Text to speech) system into the app, which has proven to be harder than I thought, there are several plugins for TTS for Unity out there, most are simply wrappers for the native Text-to-Speech functions on the device, unfortunately most of them only work with old versions of Unity, the most reliable ones are usually Pay-Per-Seat where the developers create their own framework and API for the service.



After Some research I found out we can do TTS using an URL from google translate to download the speech directly in MPEG format, as such:

https://translate.google.com/translate_tts?ie=UTF-8&total=1&idx=0&textlen=32&client=tw-ob&q=Text To Speak Here&tl=Lang Here

Unity text to speech

In code we can then specify the text and language to be used simply by appending a string to the URL, we can then send a Web Request to the link and fetch the audio clip.



I began by creating the data class for the QR scan which basically holds data about the QR code we scan. The next thing was to create a UserData class which as you might've already guessed holds data about the user, such as a list of unlocked achievements, badges and high scores in the minigames the user will be able to play, as well as the BadgeData class.


My next and more interesting task was to create a way so that all the achievements are loaded at runtime at the start of the game. For that I worked together with one of my peers we worked together for creating the scriptable objects class for each of the achievements, scriptable objects are an awesome way to avoid repeating code. The SO script serves as a template for an object, all it does is hold information. Here's an example:

[CreateAssetMenu(fileName = "Achievements", menuName = "Achievements/achievement")]
public class Achievement : ScriptableObject
{
    public string achName;
    public string achDescription;

    public Sprite achImage;

    //Added this, necessary for grabbing with the AchievementDisplay.cs
    public Sprite achBackgroundImg;
}

Next we create the scriptable component itself and replace the variables with the data we want, like this:







Then we bring it to life by having a script that references this SO and then assigns all the variables accordingly like so:

public class AchievementDisplay : MonoBehaviour
{
    public Achievement achievement; // Achievement scriptable object

    //Our info
    public Text achNameText;
    public Text achDescriptionText;

    public Image achBG;
    public Image achImage;

    void Start()
    {
        //Assigning the variables at start
        achNameText.text = achievement.achName;
        achDescriptionText.text = achievement.achDescription;

        achImage.sprite = achievement.achImage;
        achBG.sprite = achievement.achBackgroundImg;
    }
}

Enough about scriptable objects, as for my task of loading all of the achievements at runtime I created a class for the functionality, what it does is, at Start, it loops through all the achievements SO stored in the Achievement Manager created by one of my peers, it then assigns this achievement to a temp game object and instantiates it to the screen, and it repeats the process until it looped through all the achievements. the code looks something like this:

public GameObject achvPrefab;

void Start()
{
	foreach(Achievement achv in 
	AchievementManager.Instance.achievements)
		{
		//Creates a clone of the prefab
		GameObject instance = Instantiate(achvPrefab);	
		//Adding achievement
		instance.GetComponent<AchievementDisplay>().achv=achv;  					 
		}
}

Final result:


For the third week of the development sprint our tasks were to work on the minigames for each of the locations featured in the app, more specifically, the GDD (Game Design Document), the location I chose was Upper Canada Village, a beautiful place in Canada where you can experience how life was like in the 19th century, original buildings and all.

So I though what could be better than a simple farming game that everyone can play.

The game mechanics are simple, you're a farmer who has plant, raise animals and then harvest your plantations and sell your animals in order to get money and improve your farm so that you can have more resources t


o build a better farm, as simple as that, not too deep of a gameplay experience as the purpose of the minigames is to be short and fun.


As for technical development of the main app I worked on implementing the functionality for the buttons in one of the screens where the user has access to social media links, as well as toggling some of the app sound functionalities.

It was Simple stuff in general, the buttons that direct you to websites use the same method (on click, enter url passed as argument) and the toggles are simple bools with to switch the functionalities on or off, those bools are checked in their respective managers.

I worked alongside my peers responsible for those managers to ensure they had the methods I needed to make the buttons work properly.




For the fourth week my main task was to populate the audio manager script with methods for playing specific SFX and songs, those clips are stored in a public List of clips to be populated on the editor. The implementation is very simple.

Make sure the sounds bool is true and the audio source is not playing yet. Then set the audio source clip to one clip from our list specified by the index.



But how do we would know which index number corresponds to which clip? We use enums, they are perfect for a situation like this. For example:




Each one of those corresponds to a number in ascending order, we can then access the index by a much more readable way.

I also implemented the animations for the user setting buttons, changing sprites and position on user click. The animation was created using the Unity animator tool which allows you to easily animate any game object on the game and it consists on playing the animation (change button position) and then changing the sprite, when the user presses the button based on the current sprite, so If the current sprite is 'Off' play the animation and change it to on. It looks something like this:

And the final result:


Moving on, as I had finished all my tasks I focused on assisting two of my peers Rory Lacayo and William Lafleur on their tasks. I helped Rory on hooking up some functionality for displaying information about a badge when clicking on it and in making sure the text is always spawned on the same offset positions for all badges. William's problem was a little bit more interesting and it was to figure out a way to ensure all buttons sprites are initialized correctly based on the information stored on the app settings save files.

The way those save files work In Unity are key value pairs. Will was trying to initialize a button storing a key string, corresponding to the name of the button, and a Sprite as the value, that makes sense, but the best more readable way to do it would be to simply store a bool, if false load the 'Off' sprite from the script, otherwise load the 'On' sprite.


Week 5 was a pretty busy week, I did a lot of little tasks and organized some things on the project but the main goal was to create an animation and some cool sounds and effects for when the the user unlocks a new badge through QR Scanning.

The animation was made using the Unity "Animation" window, a very straight forward tool, where you can change any elements of the object you are animating, such as the position, rotation, materials, etc.., create and add animation curves and add calls to animation events.


This is what the animation looks like without any special effects:

The next step was to add special effects, in this case I wanted to add some nice fireworks, and its colors based on the textures of the badge. Since the badge texture are not yet implemented I decided to make my fireworks with 3 colors only, red green and blue, randomly chosen.


The fireworks were created using a powerful Special Effects system, the Unity Particle System, I won't go over the steps for creating the fireworks as they are many, what I can say is that with a lot of trial and error, and experimenting one can get really good a it and create all kinds of nice effects, from simple smoke to explosions and tornados. Unity recently release they're newest system called VFX Graph which allows for even better looking and complex effects, unfortunately it still lacks in documentation so It is harder to work it it, it also requires the project to be set up to work with URP or HDRP pipelines, which is too overkill for a simple mobile app.


The fireworks are triggered by an animation event, the animation with the special effects look like this:



The last step was to add sound effects for the fireworks and some cheering to celebrate the unlock. The cheering was triggered through a simple animation event on the start of the animation, the most challenging part was the sound for the fireworks, they are 2 effects, one for take off and another one for the explosion, as there is no easy way to identify when those new particles are being created I had to count the number of trail particles (the rocket) currently on the screen, when they start (particleCount > 0), I play the take off sound, when they finish (particleCount back to 0) I play the explosion sound.


All left to do now is to add the badge texture from the badge scriptable object when the animation starts, for this I will be working together with my colleague Sean and Nick next week to figure out a way to link those 2 together, until next week.


This week I worked on some minor adjustments on the app, organized some folders but most importantly I made sure that when the Text to Speech is enabled by the user the app will play the audio description of the button first and then if the user presses again it will execute its functionality. It sounds simple but it was a tricky task, the OnClick() event (which all buttons have in Unity) is called whenever the button gets pressed, and there is no way around it. So in order to play the audio on the first time, I had to create a custom event called "When Clicked()" and call it under certain conditions, instead of using the default OnClick().

That's how they compare:



It turned out to be fairly simple and the good part about it is that Unity has a very nice interface for implementing systems, so other members of the team can easily use the new functionality I created.

It is then possible to subscribe methods via the editor or on the script itself wit the += overloaded operator.






On the following days our team chose 3 of the all the minigame ideas to actually be featured in the map. One of the winners was the Fishing Game, thought by my teammate William Lafleur. Our team consists of William, me and Rory.

I was responsible for designing the UML diagram, me and William discussed the best way to do it since the was the one that came up with the game idea. Basically a diagram for stubbing out some of the classes and the logic that we are gonna need for the game. The game is simple, catch a fish, level up, unlock customizables and equipments and discover new facts about the area around St. Lawrence river.

The bulk of the scripts are the managers, since I decided not to use the same managers of the app to keep things organized. The rest is basically some scriptable objects for achievements and challenges, scripts to hold methods for buttons and basic game mechanics. I am sure more things will come up on the way though, they always do.

Here is what the diagram looks like:




For this post I am condensing everything I did for the last 2 weeks in this post as last week I was very busy with different things and didn't have time to post.

A the beginning of last week I was eager to start developing the minigame so I got all of my tasks related to the main app done as soon as possible.

My last task consisted into downloading all of the Text To Speech sound clips when the application is initialized for the very first time and saving the files locally so we don't have to download each clip every time we click an object. It sounds easy enough, but it has proven to be a challenge. But once I grasped the concept, it was very simple to implement. Basically, we send a web request to the TTS clip link and in return we get an array of bytes (the clip data). We save that locally only once. Then all we have to do is, every time the app starts, we load the clips using the Unity method www.GetAudioClip() for each clip and store them into an array. That was it for my main app tasks, now for the exciting stuff !


Me and William Lafleur made a lot of progress in the course of these 2 weeks. I will write a breakdown of the main things I've done so far because if I were to go into detail I would end up writing pages and pages.


In the course of 11 days of development I accomplished:

1- Stubbed out all of the managers.

2- Created all of the scriptable objects for fish, baits, rods, etc...

3- Hooked up and implemented all of the button related to the main game mechanic (no menus)

4- Designed the main game loop mechanic :

This one deserves some explanation, the main game mechanic consists on casting the line, waiting for the fish, after some time a catch button will appear, the player has to click it within a certain amount of time in order not to lose the fish, then a minigame will appear.


The minigame was inspired on the fishing minigame of Stardew Valley where the player has to maintain the bar within the fish and fill the progress bar until it catches. It may not seem like much, but it was a lot of work to get it working as intended.


How it works in code (very basically): The first thing is choosing a fish, first we generate a random value and then we select based on each fish rarity level. We then set the fish data, this is all the information that is gonna show up in the pop up when the fish is caught after winning the minigame (see it in the first gif below). We also apply any difficulty multiplier this fish has, if any. This can make the minigame harder or easier.


In code the minigame script has methods for trying to catch the fish, which is called every second after the player casts the line.

The last line is a counter, if no fish is caught within a certain amount of time, the player has to cast the line again.






Important Notes: The minigame is fully customizable on the inspector, settings like fish movement speed, fish movement is constant or randomized, progress bar fill and un-fill speed, game over timer when progress bar is empty, and others!



Credits to William for making the animations buttons and background!


5- Created animation for losing the minigame. The animation looks like this:

Credits for MikeDoesWeb for providing the script for shaking the object!


6- Added sounds for all button clicks.

7- Added background sounds, music and SFX's as well as atmosphere sounds like rain and water sounds.

8- Created a placeholder particle effect for when catching the fish (you can see it in the first image), the plan is to have its color change based on the fish rarity.

9- Populated the Audio Manager, including a very nice fade In and Out sound method, at this point also the Minigame Manager and the Gameplay Manager are basically complete.

10- Added fish rarity level on the fish scriptable object and implemented it on the minigame. Every time the player casts a line a fish is chosen from the list. The more rare the fish the harder it is for it to show up.

11- Added headers and tooltip attributes for variables description on the editor to make it more understandable for developers.


These were the main things I've done so far, all in all, I made sure the design is as future proof as it can be if we decide to add more features in the future.


I will also share some very nice features created by Will and which I helped to implement:

The rain effect :


The customization screen:


It is important to note that me and my team were communicating a lot throughout this whole process to ensure we were on the same page and for helping each other out.


Next week I'll be working on implementing the different modifiers for the baits and rods as well as fixing small bugs and adding a couple more features, until then!


On the ninth week of development most of the work I did was adding small features to the game, I mostly assisted my peer Rory Lacayo on completing his task which was to add and hook up some new UI elements to the game including creating a new main menu screen.


The small feature I worked on was preparing the scripts to be able to handle our future, yet unnamed "Fish List" which is basically a window that displays all the fish that have already been caught by the player, the fish name, how many the player has caught, and its rarity. The caught fish will appear in colors and the uncaught fish will appear in grey scale. This is more or less what the screen will look like. The fish are organize by rarity, from least rare (top) to rarest (bottom).

The number of fish caught and other information still needs to be hooked up to the screen.

All the data discussed above comes from the fish scriptable object. Data such as the number of fish caught are updated whenever the player catches the fish. I also added a global counter for the total number of fish caught, which gets loaded and updated on our Minigame Controller script, like so:


The current fish index is where in the main fish list is the fish we are trying to catch.

It is being set on the Gameplay Manager each time we try to catch a fish.


As a suggestion of our course coordinator James Dupuis, I assisted Will on replacing the old 'Catch' button for something more challenging. Now instead of just pressing the button and starting the minigame, the player has to win the first minigame in order to progress to the main one, that allows for actually catching the fish. That is what it looks like:



On week 10 I focused on making some small adjustments to the game and fix some things I thought had to be fixed, those include:

  1. Making the player bar and the fish icon start at a fixed position every time we start the catch fish minigame.

  2. Fixed a small bug where the minigame would shake after losing and starting again.

  3. The minigame now freezes when the player loses and then performs the animation.

  4. Fixed a small bug where the tap minigame would disappear after being active for some time.

  5. Compressed all the audio files on the project to save space.

  6. Set up code for unlocking the fish and also made sure only unlocked fish can be caught.

  7. Hooked up the fish stats screen.


I also fixed a problem where the minigame would behave in a weird way in different resolutions. This is due to the fact that the player bar works with physics, basically a Rigidbody.AddForce2d every time we press the "Bump" button, but for some reason the higher the resolution the faster the bar would move and it felt more sluggish. To fix this problem I had to tweak the player bar mass and the gravity scale to compensate for the different resolution. This is what the code looks like :




The second important fix was changing the way the fish was being selected. The previous code would work when we have 1 item to be chosen over a random value, but when there is more than one element, each one with different probabilities things start to fall apart. That's why after some research I came across the term Random weighed selection which does exactly what I described. This is what the code looks like now:




First we initialize the random value to be based from 0 to the sum of all the fish probability, after that on every iteration of the loop if the value is not selected ( if randomValue < rarity )

we update the max number, decreasing it so now it ranges from 0 to (max probability - n), where n is the catch probability of the fish that wasn't selected.


Finally, I've had a meeting with my peers in the project, Will and Rory where we discussed the next steps to take and features we should add for the next sprint of development.


That was it for this week. I did all of this in preparation for the next biggest task to come, which is implementing the challenges, including UI and code. Coming next week !



Week 11 of development was not as productive as I would've liked, it was busy for me so I did not have the chance to implement everything I wanted to but I got a decent amount of work done. As I mention on last week's post, I started working on implementing the challenges.

The first thing I did was create the UI for the challenges screen. It basically consists of a canvas with a scrollable are and a grid for housing the challenges. The challenges themselves are prefabs that serve as a container for loading all the information such as the description, progress and the reward, this is all loaded from the scriptable object I created.

For loading the challenges I could've gone the easier way and made a bunch of prefabs for each challenge and manually add them on the scene, and every time we would have a new challenge I would have to insert it manually again, but one of the things I enjoy the most is creating tools and code to make things dynamic, so I made a script for loading the challenges dynamically.


In a few words, I loop through all the challenges scriptable objects, create a game object and load all the challenge info into them, then I set the game object as a child of our challenge window and some UI components take care of the rest (such as properly positioning all the challenges together) for me. I also store all these prefabs in a list so we can keep track of them. Here is what the script looks like:



Another cool feature is being able to claim the challenges reward, that can be a bait, or a rod. The way it works is in each challenge we also have a reference for a scriptable object, which is the object related to that specific item (reward). All we do when the player presses the "Claim" button is grab that reference and set a member variable called "isUnlocked" to true and this is automatically update the unlocked items.

This is how the challenges are looking right now:


I have also created other methods for updating the challenge progression and etc... but I haven't had the chance to test them yet. So I will be posting that as well as the particle effects for unlocking challenges next week.


In addition to that, I have also done some minor tasks, I've helped the Ghost Hunt minigame team, Sean and Brigitte as they were behind in the development sprint for this week. I've implemented the Sound Manager for the game as well as hooking up the buttons and sliders while me and Will worked together to figure out what would be the best way to save the data, considering they have multiple scenes in their game, and that can sometimes change the dynamic of things a bit. I have also slightly improved the UI manager, it now handles scenes from an enum, rather than a hard coded string. Like so:




The last thing I did this week was assist my peer Rory Lacayo on the camera transition animation for when the user starts playing our game, the code consists of changing the camera position in the Y coordinate * deltaTime * velocity, until it reaches the specified position, it is fairly simple so I won't be posting the code for that. This is what the animation ended up looking like:






On week 12 of development, as our minigame is in very good shape already, I mostly focused on getting some major tasks done for other teams that were a little behind on the development sprint and got some minor tasks done for our minigame as well.


For our minigame I've helped my peer Rory Lacayo create an intuitive FTUE for our players, I fixed a minor bug where the fish counter on the player starts would go up even when the player caught garbage. In addition to that I've also added a trash counter for the fish stats. My second addition as adding an popup and some SFX for notifying the user when completing a challenge.



Challenge complete pop up:


Trash Counter:


That was it for our minigame, now for the Ghost Hunt minigame.

For this minigame I implemented, with some assistance from Brigitte for helping me understand how the game flow should be, the random spawning of props throughout the map, so they're in different locations on every playthrough. This task was fairly simple, but the main problem was that sometimes the props would get too clustered together, the other problem was the rendering order which would make the props look weird if smaller props were rendered on top of bigger props etc...

To fix that, after speaking with my program coordinator James Dupuis, we've decided to create layers on the canvas hierarchy so that we can have more control over where things are getting spawned and their rendering order while still maintaining a certain amount of randomization.

I basically instantiate each prop and actor and increase the layer order index, so for each layer we spawn 1 prop and 1 actor, where the actor should always be rendered on top of the prop on the same layer. The order of rendering goes: Layer 1 is rendered behind layer 2, and so on.


Here's an example:


Next task was to randomize the actor's movement, simple enough since I've used the same logic as in our Beach fishing "Catch fish minigame" ( see week 7&8 ). Basically we set a destination for the actor in 1 axis, in this case the X axis, and update his position every frame, once we reach that position, randomize again, and so on.


This took care of the rendering, spawning and movement of the objects, the last thing now is randomizing the actor and props sprites, so they don't look the same every time.

What I did in this case was shuffling the actor and prop lists themselves on the Start method and then loop them normally. Another approach would be to randomize the list index, but I didn't wanna have to deal with keeping track of repeated indexes and randomizing the index number every time we iterate the loop for spawning the objects.




In week 13 of development, I mostly focused on fixing small bugs and polishing some parts of the codebase for our minigame, removing unused code and comments, I also created and implemented more challenges, they are essential since that's how the player will unlock new equipment. The new challenges are: Win the minigame in less than 5 seconds, Catch all types of fish at least once, Catch all types of fish at least once.


The rest of my work was focused on the Ghost Hunt minigame, more specifically the curtain animation, where every time the player wins or loses the game has to reset, and that's done in the meantime while the curtain is closed. The animation was made using Unity's animator tool, after planning and discussing with my peer Brigitte exactly what was to be done, this is how the animation ended up looking like:



The spawning and de-spawning of all the objects on the scene are being done via a method call:

You might've noticed the '0 references' pop-up above the method name, that's because this method is being called via a very useful feature called Animation Events, as long as this script is attached to the same game object that has the animator component, I can call the method on any animation frame, like so:



Note how the event is being called in the middle of the animation, that is, in this case, when the curtains are closed, that's where we want to reset the level.


Week 14 is the last week of development, as we are close to finishing there are no more features to add, only bugs to fix, so I spent the whole week fixing bugs.

Those include :

  • Removing unused button from the main app

  • Fixing text to speech for badges and the "Play Game" button

  • Fixing actors spawned all always the same on the Ghost Hunt minigame

  • Fixing the ghost so we can't be caught through props and actors on the Ghost Hunt minigame.

  • Fix ghost being rendered behind everything

To elaborate on the last two bug a little bit, this is a comparison of how the game was (left) and how it is after I fixed the bugs (right). Ignore the fireworks on the right, that's a bug to be fixed.





The reason why the ghost would be rendered on top of everything else is because the way the render order works is in layers in the hierarchy, and the ghost was not being assigned to any layer, thus making it render behind everything else.

My fix to that problem with the help of my program coordinator James Dupuis, was to assign the ghost to the right layer depending on its Y position on the stage at runtime.



Observe on the hierarchy how the ghost is parented to a new layer every time.


That was it for bug fixing, in addition to that, I also noticed there were lots of unused assets in the project, which is a very common thing for people to forget, especially at the end of the projects. I removed all of the unused files with the help of an asset called "Asset Cleaner Pro" as of now it is priced at $30 CAD but it is super helpful, effective, and intuitive and well worth the price in my opinion if you're looking to decrease a couple of MB's from your project.

Finally, Will and Rory, my peers for the minigame, discussed the overall shape of the minigame and some more challenges we could add, they're important since that is how the player will unlock new gear on the game. After we went through all of that, I implemented the challenges and basically finished my work on the minigame.

I am very happy with the shape of the app overall and I will be posting a link for the app on the Play Store and App store as soon as it is available!