Technical Journal
Concept Release
Sprint 1 - Initial Planning
Last updated 19 April 2021 at 14:15
Wednesday 7th - Tuesday 13th October 2020.
Initial Planning - Concept Release, Sprint 1.
Overview
Our primary focus this week lay on establishing the basis for our Team Project. This included getting to know each other, identifying suitable team roles, outlining a set of agreed-upon technologies, exploring our game ideas, setting up weekly meetings, and establishing suitable version control.
Output
My main outputs for this sprint are as follows:
- Create Shared Repository
- Outline Version Control Conventions
- Link Remote Repo
- Create Initial Script
Process
Create Shared Repository
I was tasked with creating a shared GitHub repository that would be used as a central point for version control point within our project. This process was more involved than simply creating a GitHub repo, as I needed to first look into the best practices for using version control with Unity. I came across an article that outlines various issues that may occur while using version control with Unity. Helpfully, this article also identifies suitable solutions for each problem and includes a step-by-step guide for its readers to follow. I set up the repo according to the article's instructions and assigned the role of 'contributor' to each member of our team (this means that we all can contribute to the repo as I currently have it flagged as private). I also decided to add John Loane to our repo, as he is in charge of overviewing Scrumwise and Git for the various Team Project teams.
Outline Version Control Conventions
During placement, I worked on a team that used very specific version control conventions during their daily operations. The various processes stood out to me as an organised and well-structured approach to handling version control. I decided to talk about this with my team and asked if they would be interested in applying similar techniques to our project. Thankfully, they were more than happy to agree, as they saw the obvious benefits of a well-managed system. I created a README file that outlines a list of various conventions that I had used in my work placement team. This includes comprehensive instructions for creating branches, fetching changes, and writing semantic and well-formatted commit messages. Each member could then use this file as a central point of reference while working with version control. As a result of these conventions, our repository should be kept clean and well-organised, while also avoiding any major merge conflicts between branches. It should also be noted that the README I created was a modified version of the README that was used on a project that I worked on during placement, and that the semantic commit message format was derived from the Angular commit message format seen here.
Link Remote Repo
I needed to link my local Unity project with the remote repo that I had set up. This was a relatively short process that just included adding a remote repo to my local project using the git remote add origin command. However, I needed to make sure that my .gitignore and .gitattribute files were working correctly, and that each other member of my team could clone the project without facing any problems. I staged and commit all files to check if git was correctly identifying my .gitignore file (I'm often unsure on Windows, as it is flagged as a .txt file in the explorer). Everything seemed to work as expected, and all relevant files were ignored. I then asked each member of my team to download Unity version 2019.4.12f1 to make sure we were all working from the same project version (this would prevent conflicts in Unity's ProjectSettings file). Once done, I asked everyone to download and clone the project from our repo using the git clone command (this was to make sure that all required files were uploaded to the repo, and that no requisite files were ignored by my .gitignore). Again, everything seemed to work as expected.
Create Initial Script
I offered to create a script ahead of our meeting with the lecturers on Tuesday. This would be used as a rough outline during our initial project pitch and would be supported by a set of accompanying slides that Kristine offered to create. I started by creating a set of the main topics that we wanted to hit - team roles, game idea, demographics, game mechanics, visual feel. I then outlined a set of bullet-point to further explain each topic. We met as a group to review and discuss the script which helped me to expand each bullet point further. We decided to loosely base our pitch around the script, but we would ultimately try to talk from the heart as it would appear more natural than if we tried to read from a page.
Issues
We were effectively a week behind the other groups as our team was only established on Wednesday. As such, we needed to overcome the initial barriers of working in a new team, so that we could start catching up with our classmates. We began by getting to know each other - we discussed our strengths and weaknesses, and we talked about what we wanted to learn from this team project. We were glad to realise that we work quite well as a team, and we seem to share a strong mutual respect for one another. Once comfortable, we were able to start working on our project.
Sprint 2 - Research & Development
Last updated 19 April 2021 at 14:17
Wednesday 14th - Tuesday 20th October 2020.
Research & Development - Concept Release, Sprint 2.
Overview
Our focus of this week primarily lay on the research of features and the development of our skills. It was pointed out by our lecturing panel that we have a rare quiet spell in the semester, and that we should use this time to full advantage to develop a strong foundation for our project.
Output
My main outputs for this sprint are as follows:
- Research Multiplayer
- Explore VR Motion Mechanics
Process
Research Multiplayer
I was tasked with researching multiplayer by exploring the feasibility of adding it into our game. I decided to look online for some tutorials and found a great video by YouTuber Valem which covers the basics of setting up multiplayer in VR. I began by creating a simple multiplayer test environment and added some basic VR functionality to it (allow the player to look around and view their hands in-game). I then import the Photon Pun package from Unity's Asset Store. Photon Pun is an SDK that enables multiplayer support on Unity. I opened the Photon Dashboard on my browser and created a networked application. Finally, I connected our game to the networked application in my dashboard via the 'App ID'.
To enable multiplayer, I added a NetworkPlayerSpawner script which allows multiple players to spawn in-game. To accompany this, I added a NetworkPlayer script that creates an avatar for each player. Through this, networked players can view and interact with each other in-game. I tested this locally first, by creating a build of the current scene. I then ran two concurrent instances of the game to ensure that multiple players were spawning. To further this, I invited a student in my class (Aaron Richardson) to test this functionality across a non-local network (I could not ask a team member, as I am the only student on my team who currently has access to a VR headset). To our amazement, this initial implementation of multiplayer seemed to have worked, and we were able to view and wave at each other in the scene.
I decided to add some additional features to enrich the multiplayer experience. I added cross-network animations so that networked players can view the animated state of each other's in-game hands (open, pinch, or clench), and I added voice chat support so that players can communicate. Again, I invited Aaron to test our game so I could be sure that it worked as expected across a non-local network.
Explore VR Motion Mechanics
My next challenge was to develop a solution that would translate the oar motion that a rower makes into in-game movement. This was a rather complex problem, so I decided to break it down into its component parts using a piece of paper beforehand. I decided that my best option would be to calculate the delta between the controllers' position at each frame, multiplied by the boats forward vector to ensure that only the forward and back movements were recorded. Calculating the distance between these two vectors returned a variable (handSpeed) which could be used to apply a force on the boat. I designed an accompanying control scheme that would allow the player to row forwards and backwards, while also giving them the ability to turn towards the left or the right and to also turn on the spot. I based my control scheme on the idea that the player can manipulate an oars state by squeezing the trigger and grip on the corresponding controller. For example, if the player squeezes both the grip and the trigger on each controller - they will row forwards. If the player releases the trigger on each controller - they will row backwards. If the player lets go of the trigger and grip on one controller (but not the other) - they will turn on the spot. These various combinations should enable the player to manipulate the boat in a multitude of ways.
Finally, I made some changes to the NetworkPlayer script and NetworkPlayer prefab to integrate my changes with our current implementation of multiplayer. This would allow multiple players to join the game across a local and non-local network to row their boats together in a scene.
Issues
I ran into some issues on Sunday when I realised that my animations weren't syncing across the network (bear in mind that I was testing this across a local network, where I was using one VR headset to control two instances of our game). After a considerable amount of time, I realized that by running tests in this way, I was effectively only interacting with one version of the game at a time - and not both games at the same time - and therefore, the animations would only be triggered in one instance of the game, and not the other. I decided to ask Aaron to test the game with me again, as I thought that maybe it would work if someone else controlled a version of the game using a separate VR headset. I discovered that my hypothesis was correct and that the animations were indeed syncing across the network. It wasn't time completely wasted though, as it enabled me to test some of the other functions with Aaron (see above, last paragraph in Research Multiplayer).
Sprint 3 - Prototype Outline
Last updated 19 April 2021 at 14:28
Wednesday 21st - Tuesday 27th October 2020.
Prototype Outline - Concept Release, Sprint 3.
Overview
Our focus has largely switched to providing a realistic experience for our target users - we are hoping to deliver a game that provides an immersive experience using gameplay that feels almost natural to an everyday rower. Much of my work this week focused on finding out more about rowing, and what the rowing community as a large would want from our product.
Output
My main outputs for this sprint are as follows:
- Follow-Up with Rowing Clubs
- Research Rowing Techniques
- Explore Feasibility of Achievements
Process
Follow Up with Rowing Clubs
Keelan sent emails out to various rowing clubs around Ireland trying to source feedback and insight from our target users on the game. Unfortunately, only one positive response was received from this initial correspondence - a lot less than what we originally expected. I decided to follow up on this by expanding our search to include rowing clubs in the UK. I also decided to create a questionnaire, as it would allow us to get quantitive feedback on various important aspects of our game (e.g. how important realistic water physics are, what type of environment would be most appealing, and how meaningful are each of our main mechanics). The results of this survey would allow us to form a stronger understanding of what our target user really wants.
We decided to also post the questionnaire on Reddit to gain feedback from a larger portion of the rowing community. We made sure to clear this with our lecturers before posting, as we were unsure if there would be legal or ethical issues involved with using and storing the responses we received. Under guidance from our lecturer, I added a disclaimer to the survey informing participants that their answers would be stored and possibly used to improve our project. I also added two further questions asking participants to confirm that they were at least 18 years old, and they consent to us storing and using their response to our questions. Once we were happy that the survey was ethical, I added a post under the r/Rowing sub-Reddit containing information about our game and a link to our questionnaire. We got great feedback from the community, with 37 participants in our survey, 14 of which provided their contact details to provide further input into the development of our game. We also got quite a lot of quantitative feedback on our Reddit post, which we will use to inform our game design decisions going forward.
Research Rowing Techniques
The goal of this research was to develop a firm understanding of various rowing techniques, with a focus on the rower's body movement and oar action. This would allow me to begin thinking about possible ways that we could design our game to accommodate these behaviours as closely as possible.
I began by looking at various online videos covering the basics of rowing and sculling. I spent much of this time trying to understand the four stages of movement - catch, finish, drive, and recovery. I primarily focused on the rower's body movements - how their knees and ankles align at the catch, and how they slightly (but quickly) drop their hands to bring their oars above water. It was for important me to get a good grasp of how the player expects to move in their boat, as we wanted to create an experience that feels natural to a rower.
I also looked at various oar techniques and movements that rowers use to become more efficient. At this stage, I was beginning to identify possible ways of implementing basic oar techniques in our game. For example, instead of just recording the translational movement of a VR controller, I could also record its orientation - this would allow me to determine if the player's oar is gliding along (oar is horizontally oriented), or pushing through (oar is vertically oriented) the water. I also spent some time covering more complex techniques, as well as learning how professional individual rowers and rowing teams practice together.
Explore Feasibility of Achievements
I spent some time initially deliberating how the achievement system would work - we had agreed that achievements would be displayed on the external body of a players' boat, so I would need to create a system that could store a set of slots for displaying achievements. Additionally, I would need to develop some solution that could share this information across the network. I began by creating a set of simple classes that would establish a basic achievement system for our game. The classes that I created were Player, Stats, Achievement, Achievement Display, and Achievement Tracker. However, I was having some trouble visualizing how these classes would integrate with our game. I soon realised that my current class architecture was far too complicated and that it needed to be reduced. I decided to scrap the Stats and Achievement Tracker classes, as they felt like unnecessary additions that were making the code more difficult to work with. I placed all of their functionality inside of the Player class instead (I could always extract this functionality back into separate classes at a later stage anyway). I was able to get a basic implementation of the achievement system working.
Achievements are currently in the form of small square cubes that become instantiated when a player is awarded an achievement. A script then searches for an open 'slot' on the player's boat to place the achievement (a slot is simply a game object with an attached transform). The instantiated achievement will then be set as a child of the game object slot - this ensures that the achievement is placed in the appropriate space on the player's boat.
However, I soon realised that achievements were not correctly syncing across the network - each player could view their own achievements, but could not see the achievements of any other player in their lobby. I spent a lot of time trying to identify and address the cause of this issue. Upon further inspection, it seemed that the underlying structure of our multiplayer system was flawed. I decided to spend some time next week figuring out a suitable solution for this.
Issues
I had some issues getting achievements to sync across the network correctly. It seemed that the in-game objects and prefabs aren't working as I had expected them to - I wanted a solution that would allow us to spawn and destroy prefab objects that were completely independent of the game scene. However, the current implementation of multiplayer meant that the player object was tied to in-game components and scripts. I decided to peel the issue back and realised that the underlying multiplayer structure needed to change. It was going to take some time to fix, but the time lost now would be worth the inevitable headaches later.
Sprint 4 - Prototype Design
Last updated 19 April 2021 at 14:27
Wednesday 28th October - Tuesday 3rd November 2020.
Prototype Design - Concept Release, Sprint 4.
Overview
My primary focus this week was on updating our multiplayer game system. Our current implementation of multiplayer would not be sustainable going forward, as the underlying architecture did not suit the style of our game. Additionally, I would need to add a basic implementation of sound so that we demonstrate our placeholder soundscape during the upcoming prototype presentation.
Output
My main outputs for this sprint are as follows:
- Basic Sound Implementation
- Update Multiplayer Game System
Process
Basic Sound Implementation
Looked online for some placeholder music and sound effects to add to our game. These were temporary assignments that I used only to test my progress. I found some music on Purple Planet - a site that I had used before - and downloaded one sound effect from Freesound. I then added an audio manager and sound class for handling and storing audio respectively (according to this tutorial by Brackeys). The audio manager contains a list of sounds - the sound class stores information about an audio clip (a reference to the audio clip, loop audio and play on awake booleans, etc.). I then created a prefab object containing an audio manager script - this allows us to add and edit the game sounds from one place, which avoids duplication. I added some functionality to the script which ensures that there is ever only one type of this component in any given scene (according to Unity's singleton pattern). I then ensured that the audio manager did not destroy on load - this makes the prefab persistent across scenes, meaning that constant audio (such as music) will contain to play even if the player switches to a new scene (e.g. switching from the lobby/menu scene to the game/world scene).
I then added a simple OnCollisionEnter check which plays a sound whenever the player collides with some object. I also added some functionality that examines the tag of that object - this may be useful later as it would allow us to determine what sound to play based on the type of object that a player has collided with. For example, we may want to play 'Sound A' when a player collides with another player, but we may want to play 'Sound B' when the player collides with rocks or debris. This added functionality means that we can easily expand our range of collision sound effects going forward.
Update Multiplayer Game System
I ran into some problems in week 3 when trying to sync achievements across the network. I soon realised that our current implementation of multiplayer was not appropriate for the game that we were building. For example, the player prefab was directly tied to various non-networked components that were stored in the scene (such as a boat controller script). This didn't make sense to me, and it caused complications when sending data across the network. I wanted to find a solution where a complete and independent player prefab could be instantiated across the network, as this would allow me to add or edit the player prefab without fear of otherwise breaking the code. I decided to spend this week updating our multiplayer game system, to give us a solid foundation to work on going forward.
I began by looking at various YouTube tutorials to see which implementation would work best for our game. A Multiplayer FPS in Unity tutorial seemed like an unlikely starting point, but the core underlying principles that the developer used closely matched with some of the behaviours we would need from our game. For example, we would need the ability to create and list various rooms for our players to join. We would also need to instantiate and destroy player prefabs across the network. This series of tutorials provided solutions for both of these problems.
I followed the tutorial series up to episode 6. At this point, the developer had created a menu system that allows the player to create, find, and join a room. Additionally, player prefabs would be spawned and destroyed as players joined and left a room. This was all of the functionality that I needed, so I got to work integrating those changes with our game.
However, I still had some issues syncing achievements across the network. The achievements would appear for each player, but they would not appear in the correct place. When an achievement is instantiated, it should be set to a child of the player prefab. However, Photon (our Multiplayer SDK) doesn't allow objects to be parented across the network. After some time, I realised that I would need to apply these changes using a networked function. I found a solution that involved creating a PUN RPC call that sends the objects PhotonView ID through to a networked function. Within that function, I could then parent the object as normal. I was relieved to see that this worked!
Issues
There were no issues of note this week.
Sprint 5 - Prototype Development
Last updated 19 April 2021 at 14:13
Wednesday 4th - Tuesday 10th November 2020.
Prototype Development - Concept Release, Sprint 5.
Overview
This week, our focus was on the development aspects of our prototype, such as modes, mechanics, and PowerPoint presentation. There was also an emphasis on sound design, level design, model design.
Output
My main outputs for this sprint are as follows:
- Refactor Code
- Singleplayer Implementation
- Consolidate Prototype
Process
Refactor Code
Updated member variables such that they follow best practices when developing in Unity - e.g. non-public fields should be tagged as [SerializeField] as to appear correctly in the editor. Also added some tooltips and range sliders to make the variables more interactive and easy-to-use in the editor. Additionally, refactored the player controller script - extracted stats and achievement tracker functionality in separate classes and consolidate the rower and boat controller classes into one 'player controller' class. Also cleaned up and deleted unused scripts and prefabs, and re-organised the assets folder.
Singleplayer Implementation
Implemented a system in which players can choose to play online (multiplayer) or offline (singleplayer). Players without an internet connection will automatically be entered into offline mode. When the player loads the game, they will be faced with a title menu. The title menu has options for singleplayer or multiplayer. If a player selects the multiplayer option, the system will check if they have an active connection to the internet. If not, an error will be shown and the user will be returned to the title menu. If the player does have an active internet connection, they will be connected to the server. If the player then leaves a room, they will be disconnected from the server and returned to the main menu.
Consolidate Prototype
Implemented Kristine and Keelan's changes with the current version of our game. Kristine created a race mode which allows players to race against each other. Keelan developed an environment in which the player can explore.
Issues
There were no issues of note this week.
Pre-Production Release
Sprint 6 - Implementation
Last updated 19 April 2021 at 14:12
Wednesday 11th - Tuesday 17th November 2020.
Implementation - Pre-Production Release, Sprint 1.
Overview
During this sprint, our focus was on improving the current implementation of our boat. This includes implementing realistic oars and improving the boats collision detection/response. This goal was later revised due to separate conversations with mentors and participants from our user group. The project direction changed from working with the VR space to developing a game that integrates directly the Concept2 performance monitor via Bluetooth. As such, the initial sprint goal of 'Implement Oars' was replaced with 'Research Bluetooth'.
Output
My main outputs for this sprint are as follows:
- Implement Oars
- Research Bluetooth
Process
Implement Oars
Created a simple test environment scene that would allow me to experiment with the implementation of oars separately from the rest of our game. Started to think about various ways of implementing oars - began by sketching out a simple diagram that I planned to implement using a custom made script. My initial idea was to freeze the x, y, and z rotation of the oars rigid body in place, while rotating the oar (around a fixed point) to track the position of the players left or right VR controller. I created a simple oar (using a stretched out cube) and added a pivot point. I then began developing a script that would allow me to move the oar. This proved quite difficult, so I spent some time researching looking for solutions online. I discovered that Unity has built-in joint components which allow objects to pivot around a fixed point. I decided to use this instead of implementing my own script, as it would reduce my overall workload. I also implemented a system which allows the player to 'grab onto' the end of the oar with their VR controller. After a bit of tweaking, I was able to get a simple system to work.
Next, I spent some time integrating my initial simple oar with the player controller/boat model. I then created a pair of pivots on the boat (one for each oar) that the oars could rotate around. I duplicated of the original oar and centred it on one of the pivots. Unfortunately for me, it wasn't just that easy - the joint didn't respond as expected, and the oar often broke free from its pivot, or jiggled around in frustration - back to the drawing board.
I sketched out the problem on a piece of paper. I decided to break the problem down by focusing on 2D positional tracking, rather than trying to tackle all 3 dimensions at once. I created a list of variables that I knew I would have immediate access to (such as the oar, the position of the pivot point, and the position of the object that I wanted to track). I then sketched the problem from a top-down perspective (by placing a dot on the page to represent the pivot point, a horizontal line through the pivot point to represent an oar, and a small square inline with the oar to represent the tracked object). I then worked out that I could use basic trigonometry to calculate the delta angle between the oar's current position and the tracked object's updated position, by creating a triangle between the pivot point and tracked objects initial and updated position, using the desired oars position as a hypotenuse - this allows me to determine the required angle of rotation that the oar is required to turn for re-alignment with the tracked object. Thus, I can now track the tracked objects Y position in from a top-down perspective in 2D. By projecting this into 3D, I can track the objects Z (previously Y in 2D) and Y position. However, I soon realised that this only works when the tracked object is moving along one axis, and not multiple. For example, the algorithm will work if you move the tracked object upwards and downwards, or side-to-side, but it will not work if you move it in both directions at the same time (i.e. diagonally).
After a lot of time and frustration, I realised that Unity has a built-in 'a.LookAt(b)' method, which turns object a to look at object b. All of the previous work that I had done was essentially replaced by one line of code! I could have saved myself a lot of time by looking at Unity's built-in functions. Regardless, I learned a lot about Unity's joints and constraints system, as well as working with trigonometry in 3D.
Research Bluetooth
Spent some time looking at various solutions for integrating our game with a Concept2 rowing machine via the performance monitors built-in Bluetooth. I looked at Concept2's performance monitors Bluetooth specification guide and discovered that they are using relatively new technology within Bluetooth known as Bluetooth Low Energy (BLE). BLE allows low-powered devices (such as the Concept2 performance monitor) to send out Bluetooth signals for connecting with nearby mobile devices - once within range, a mobile device can begin a BLE scan to detect nearby BLE broadcast signals. This allows mobile devices to integrate directly with Concept2 performance monitors.
Once a connection has been established, the mobile device can send requests directly to the performance monitor. The performance monitor will then return real-time data about the workout. For example, the mobile device can request the current distance that the user has covered (a full list of the commands may be found in the specification guide linked above).
I next needed to identify a means for capturing Bluetooth data within Unity. InTheHand have an open-source library on GitHub (32Feet) which provides a .NET interface for interacting with Bluetooth devices. Luckily, this also works with BLE devices, so it was just a matter of adding the packages to Unity. To do this, I installed the NuGet manager for Unity and used it to install the 32Feet library.
Currently, there are two issues - there is currently limited documentation on how to set up and scan for BLE devices using 32Feet's library, and Concept2's Bluetooth specification states that the PM only integrates with mobile devices. Therefore, I have to determine my own solution for integrating with BLE devices, and I will need to talk with my team about possibly moving our target platform to Android.
Issues
I wasted a lot of time trying to implement code that was pre-built into Unity. I over-engineered the problem and never thought to look inwards at the functionality that was already provided. However, I learned a lot by doing so, including about Unity's joints and constraints system, as well as about trigonometry in 3D. I also learned a lesson about over-estimating simple problems!
Sprint 7 - Mobile Migration
Last updated 19 April 2021 at 14:10
Wednesday 25th November - Monday 7th December 2020.
Mobile Migration - Pre-Production Release, Sprint 2.
Overview
The primary goal of this sprint was to migrate our project to Android mobile. This allows us to create a portable and user-friendly application that is usable at home or at the gym. Additionally, this provides us with access to Android's Bluetooth protocol which enables integration with a Concept2.
Output
My main outputs for this sprint are as follows:
- Migrate to Mobile
- Implement Map Camera
- Integration with Concept2
Process
Migrate to Mobile
We decided, based on user feedback, to move away from using VR in our game. Instead, we would move towards launching our game on mobile, to create more of an app-like experience. The core gameplay would remain the same, but the interaction medium would change. As such, we needed to migrate our project from using VR technologies to mobile technologies.
I began this process on Saturday and spent some time removing all VR-related content from our game. I removed the XR Interaction Toolkit from our project and build settings, and removed all XR Interaction Toolkit references from our scripts and scene. As a result of this, I was required to migrate various individual scripts (such as the PlayerController script and Oar script) that previously relied on various XR Interactions utilities to function. I decided to initially strip all XR-related code out - as this allowed me to run the game without compile errors - but also allowed me to test the feasibility of mobile migration before needlessly dedicating time into writing code that may be otherwise 'thrown out'. I also replaced the menu/UI XR Interaction with touch/click interaction to allow me to test our game on my phone. At this stage, I was able to successfully run our game and join a singleplayer/multiplayer lobby.
I upgraded our project version from 2019.4.12f1 to 2019.4.15f1 (LTS stable release at the time of update). I also added the Android Build Tools and iOS Build Tools modules. I then changed our project settings such that our game now builds as an Android project, rather than as a Windows project. I downloaded the Unity Remote 5 app on my phone - this would allow me to run my project through my phone via a USB cable. I connected the devices and built the project directly onto my android device - there is currently an app version of our game on my phone, which is pretty cool! Now, every time I press the 'play' button in-editor (provided that my phone is connected to my PC via USB, and Unity Remote is open), the game launches directly onto my phone. This will be really useful going forward for testing gameplay and performance. However, I cannot currently do anything in-game, since I removed all of the XR-related functionality from various scripts - it was time to go back and make our scripts more mobile-friendly.
We currently have no way of providing movement input to our game (we hope to pull data from a Concept2 Performance Monitor to drive our boat in the future). I decided to add a simple UI which includes buttons for moving our boat around the lake - this is a simple implementation, but it would allow us to test various elements/mechanics via our phones. I wondered about the best way for adding a HUD to our game - I wanted to create a system that abstracted all player-relevant data from our game scene, as this would allow us to keep all networked logic separate from in-game logic. I decided to add the HUD as a direct child of our Player prefab. This would mean that a separate HUD would spawn for each additional player in our game. This may seem like a problem at first (after all, multiple HUD's in one game seem like a really bad idea), but our multiplayer architecture allows us to choose what data we send across the network. Choosing not to send each individual HUD across the network means that each player will only have access to one HUD - their own. This keeps the game scene clean and modular. I added various buttons to the HUD which allows us to move the player boat around the scene (forward, back, left, right, rotate left, rotate right). At this point, I can launch our game via my phone, and move the boat around the lake using various on-screen buttons.
Implement Minimap
Implemented a minimap system that displays the player's current position from a top-down perspective (sort of like a mini-map in first-person shooters). I tested this out in a test environment first - I originally tested it with a basic player object to make sure that it worked in a simple scenario (rather than waste time trying to make it work in a complex scenario). I set up the object with a first-person controller script (for controlling the object) and a camera. I then placed another camera 50 units above the object and faced it down towards the player - this camera's view would be used to render the minimap. I then created a simple texture asset and assigned the output of the above camera to the input of the texture. As such, the camera would continuously render its view onto the texture.
I needed to assign the texture to a material before I could display it (this was a simple process of selecting the texture as the material's input). I was then able to assign the material to any object or surface within the world. For testing purposes, I just created a simple object to place the material on as this allowed me to quickly see if my solution had worked or not.
I then created a simple canvas that would act as the player's HUD (heads-up display), before placing the minimap material in the top right corner. As such, the player will now be able to see a minimap in the top right-hand corner of their screen. Once I was happy with my progress, I decided to test it out on our main level. However, I quickly noticed a problem - the camera was not high enough to go above the various cliffs, edges, and arches in our game. As a result, the map was inaccurate and would often clip through various objects (such as arches). As such, to ensure that the camera was capable of viewing everything below it, I needed to make it the highest point on the map. However, this caused another problem - the player was so far away that they had become invisible! To remedy this, I attached a large 2D sprite to the player character and set it such that only the minimap camera would render it. Thankfully, this worked! I was able to see both the map and a real-time 2D sprite representation of the player character on the minimap.
Integration with Concept2
To integrate with the Concept2 rowing machine, we would need to create a system that allows us to connect with BLE devices. BLE (Bluetooth Low Energy) is a wireless protocol that various low-powered devices use to send and receive data. I spent a lot of time over recent weeks trying to get Bluetooth working with Unity/C#, but I found myself getting nowhere. I noticed an Andoird/iOs BLE asset pack on the Unity asset store and decided that the cost for it outweighed the time that I would ultimately spend trying to create my own version (side note, this is another reason why it was useful for us to migrate to mobile, as it allows for the usage of an asset pack like this one). I bought the package, downloaded it, and looked through the various files, documentation, and examples to get a stronger understanding of how the system worked.
However, I ran into a problem - I didn't know if I owned any BLE devices; I definitely own Bluetooth devices, but I'm not sure which (if any) are BLE. So, I downloaded an app on my phone rfConnect that scans for nearby Bluetooth devices. This app gives you the added benefit of uncovering information about each scanned device (for example, if it is a BLE device or not). Unfortunately, the scanner didn't pick up any BLE devices. As such, I wasn't able to test any of their included sample scenes. The next day, at my sister's house, I decided to scan again. The app picked up a BLE device called 'Ace 2' - it turns out that my niece owns a Pink FitBit Ace 2 (a device that uses BLE). She doesn't use it often and allowed me to borrow it. This was a great step forward, as it allowed me to make some meaningful progress toward integrating with BLE devices.
I spent some time experimenting with the asset pack when I got home and realised that the game was still unable to pick up any devices. After some headache, I realised that the game must be built and launched through an Android device before it would successfully be able to communicate across BLE (this was just due to the nature of the asset pack, as it relies on built-in Android functionality to work). I built and launched our game on my phone, and was finally able to pick up and receive information about the FitBit through our game.
I finished this process by updating the scene to make it more user-friendly and edited the sample script to better suit our specific needs. I still currently have no access to an actual Concept2 Performance Monitor, but I was able to prove that I could integrate with it by connecting to a device that employs the same protocol. Additionally, I was in contact with software developers who work for Concept2 - they're excited about our project and decided that they would send me out a Performance Monitor of their own that I could use to test our game. This was another huge step forward, as I would essentially have persistent personal access to a device that I could test our game on - I just needed to wait for it to arrive in the post!
Issues
There were no issues of note this week.
Sprint 8 - Sensors & Data
Last updated 19 April 2021 at 14:06
Wednesday 8th - Monday 21st December 2020.
Sensors & Data - Pre-Production Release, Sprint 3.
Overview
The primary goal of this sprint was to create an interface for communicating with the Concept2 Performance Monitor. This should allow us to send commands to the Performance Monitor requesting specific data about the user's current workout. This data could then be translated and used to drive an in-game boat.
Output
My main outputs for this sprint are as follows:
- Sync Boat Seat Movement
- Create Concept2 Communication Interface
Process
Sync Boat Seat Movement
I created a simple script that allows the boat's seat and oars to track an empty game object. As such, these empty game objects will be attached to various points on the player character once we create and animate it in the next release. For example, the boat's sliding seat is set to track the 'Seat Track' game object - as the Seat Track object moves forward and back, the Boat Seat object follows it (tracking only occurs in the forward/backward axes to ensure that the seat stays at a fixed height). As such, this 'Seat Track' object could be attached to the player characters pelvis. Through this, the boat seat would always stay aligned with the player's pelvis, simulating its motion as the player moves back and forth. The same is true for each oar, their corresponding 'track' objects may be attached to each of the player characters hands. Through this, each oar would follow the player's corresponding hand position.
Create Concept2 Communication Interface
I previously used an imported Bluetooth package to create an interface for discovering and connecting with BLE devices. Taking this a step further, I would need to create a communication interface for connecting to a Concept2 Performance Monitor. Once connected, the communication interface should send commands to the performance monitor requesting real-time workout data. This data would then be processed and used to drive the users' boat in-game. As such, the communication interface would essentially be the games' bridge between the real world and the virtual world.
To test my progress, I would need at least need access to a performance monitor (PM), but preferably to a Concept2 rowing machine (Concept2 rowing machines each have their own performance monitor, so access to one would inherently result in access to its performance monitor). As such, I contacted the Duty Manager of DkIT Sport (our college gym) and enquired about gaining access to one of their Concept2 rowing machines for a few hours per week. He was happy to help out - he encouraged me to contact him any time that I wanted access to one of their machines (we agreed that I would bring my laptop with me to DkIT Sport and spend some time in a separate room testing out various features of our game). So far so good.
I spent a lot of time researching further into how other applications communicate with Concept2 Performance Monitors. I used the following articles and repositories for inspiration: PyRow by WeMakeWaves, ErgometerJS by Tijmen Van Gulik, and Concept2 Ergometer Interface by Micrological. I also reviewed the Concept2 Bluetooth LE Specification, and the Concept2 CSAFE Protocol Specification (I do not have a link to this as it was sent to me directly by Concept2 developers). I quickly realised how much of a mammoth task I had ahead of me - the specification documents were both long and arduous, and the repo's I reviewed used complex protocols and bit-manipulation algorithms that I did not yet fully understand. I decided that a good starting point would be to comb through the PyRow library to understand how they approach communication with the Concept2. I spent some time trying to translate the PyRow code from Python in C#. Through this, I learned a bit more about how the code worked. However, I was also aware that PyRow used the Performance Monitors USB interface, which meant that the underlying communication protocol may be different. Nevertheless, I started to gain an understanding of how the performance monitor communicates. Additionally, by cross-referencing PyRow code with the CSAFE Spec documentation, I was able to also draw conclusions on the purposes of otherwise esoteric sections of code (particularly in the read/write functions). I looked briefly at the ErogometerJS library, but admittedly it was very intimidating. I concluded that I would not have enough time in this sprint to develop a sufficient understanding of how the ErgometerJS library worked, so decided to leave it for another time.
I felt at this point that I had a firm understanding of how communication with the Performance Monitor worked. Unfortunately, the performance monitor that Concept2 sent to me did not arrive by the end of this sprint. This meant that I had no way of testing my progress, and thus had no way of completing this task. I just hoped that the performance monitor would arrive before Christmas - otherwise I may have to wait until the New Year.
Issues
I was unable to complete the Create Concept2 Communication Interface task due to not having access to a Performance Monitor. I rather foolishly thought that the one sent to me by Concept2 would arrive in good time during one of the busiest postal periods of the year! Admittedly, I was relieved - my busy assessment schedule meant that I wouldn't have time regardless. If anything, it was a good excuse to work on my other assignments.
Sprint 9 - Christmas Break
Last updated 19 April 2021 at 14:01
Wednesday 22nd December 2020 - Monday 11th January 2021.
Christmas Break - Pre-Production Release, Sprint 4.
Overview
The primary goal of this sprint was to spend some time finishing off the tasks that I had not completed by the end of Sprint 8.
Output
My main outputs for this sprint are as follows:
- Complete Concept2 Communication Interface
Process
Complete Concept2 Communication Interface
The Performance Monitor that the Concept2 developers sent me arrived in the post just before Christmas (the 23rd!). This meant that I would now be able to complete the Communication Interface task.
I started off by modifying one of the example scenes included in the Bluetooth package. The example included code for scanning, connecting, and writing/reading service characteristics to and from Bluetooth devices (side note - Bluetooth LE devices use the GATT protocol, which allows for the communication between devices using various services. Services may be thought of as specific data categories. Within each service, there are characteristics. Characteristics may be thought of as specific data points). I configured the code to scan for and connect with Concept2 Performance Monitor's using the Universally Unique Identifier code (UUID) outlined in the Concept2 Bluetooth LE Specification (C2 BLE). Once working, I wrote some code that would attempt to retrieve the manufacturer's name from the performance monitor (this would allow me to check if I could successfully communicate with the PM). To do so, I needed to retrieve the 'C2 manufacturer name string characteristic' from the 'C2 device information primary service'. Luckily, the example had a built-in method demonstrating how this worked. I was able to successfully reconfigure the code (by simply changing the default service and characteristic UUID's to match the outlined values in the C2 BLE document) to retrieve the manufacturer's name characteristic from the performance monitor. The data returned was represented as a series of bytes, so I used built-in C# methods to convert it into a string (BitConverter.toString()). The decrypted data simply read 'Concept2', but it was the value that I had expected, and it proved to me that the system had worked.
I decided to go a step further by changing the service and characteristic values to retrieve the 'C2 rowing general status characteristic' from the 'C2 rowing primary service'. This characteristic would return a 19-byte data packet containing data about the real-time rowing that was taking place. The data packet returned didn't make much sense - the 19 bytes of data were random values between 0-255. Additionally, the data was static since no actual rowing was taking place. To find out more, I would need to test our game by hooking up the Performance Monitor to a Concept2 rowing machine. Unfortunately, due to government restrictions, all gyms were forced to close. This meant that I would no longer have access to DkIT Sport's Concept2 rowing machine, and thus could not further test out our game. Back to the drawing board.
Issues
I felt overwhelmed by the sheer amount of work that would be involved with creating the communication interface. I had completely underestimated the complexity of this task, and it quickly became apparent that I would need to spend a lot of additional time trying to understand the intricate nature of communicating with the performance monitor. I was happy with the end result that I produced, but recent government restrictions (which included closing all gyms) meant that I had no way of testing my progress on a Concept2 rowing machine. As such, I had no idea if my code was complete, or if it needed to be changed entirely. There is a lot of uncertainty at this point, and that doesn't look likely to change any time soon.
Sprint 10 - Exam Week
Last updated 1 May 2021 at 16:49
Wednesday 13th - Monday 18th January 2021.
Exam Week - Pre-Production Release, Sprint 5.
Overview
Reduced workload sprint to allow additional time to prepare for and sit the exams which were taking place this week.
Output
My main outputs for this sprint are as follows:
- Regenerate Terrain
- Update Environment
Process
Regenerate Terrain
I was tasked with regenerating the terrain according to an updated game map that Kristine's developed. Our previous terrain was blocky and un-interesting, and we needed to develop an environment that would feel real and immersive to our players. I created a large terrain - 3000 square meters and added a 2D plane just above it. I assigned Kristine's map image to the 2D plane and used it to guide my progress as I began sculpting the terrain. I had played around with the terrain feature before, so I had a plan for how I wanted to approach this task - rather than just sculpting the terrain upwards, I decided to use a mix of techniques that would simulate how nature creates features such as rivers, mountains, and lakes. For example, I brought the terrain above water level and began carving into it to create rivers and lakes. Conversely, I began sculpting upwards to create mountains and mountain ridges. Additionally, I downloaded a preview Unity 'Terrain Tools' package which added a set of unique and interesting terrain brushes to create and add detail to the terrain.
Once a rough outline of the terrain was complete, I began to add detail to the various islands and cliffs around the scene. I used a mixture of tools and features (such as the smooth height feature, raise or lower terrain feature, and set height feature) to achieve this goal. This was taking longer than expected, so I talked with the team and we decided to focus only on a sub-section of the map for the first release - I proposed an island section that I had already been working on, and everyone agreed that it seemed like a suitable location.
I began texturing the island - this by far was the most difficult part. Creating realistic textures that didn't obviously repeat was going to be a challenge. I experimented with various terrain layers and terrain textures (rock layer, gravel layer, dirt layer, sand layer etc.), but I couldn't find a mix of textures that both worked well together, and didn't obviously repeat. I tried to break up the terrain by using the 'paint trees' and 'paint details' features to add trees and grass around the island. This wasn't working, however, as it just made the island look cheap and tacky. I talked with my team about the problems I was facing, and Kristine recommended a Unity project that she discovered one day while doing some research - she displayed it as a reference point and asked for our opinion on it. It instantly struck a chord with me, so I decided to download the project and take a look for myself. It contained exactly what we needed - a range of cliffs models that were both numerous and cohesive, terrain textures that worked well over large areas, and a water shader that beautifully complemented its surroundings. I got to work learning about how to export assets from one project and began importing them into ours. This took a substantial amount of time, as I needed to fix some issues that I encountered relating to various dependencies.
Update Environment
Once all of the assets were successfully imported, I began fleshing out our scene. I started by adding the water shader - this immediately gave our scene a visual boost. I then decided to spend some time re-texturing our terrain. I began by texturing the beach with a light sand texture that I found in the BoatAttack repo. I then began painting along the waterline - where the sand meets the water - in a darker coloured sand texture. This gives the appearance of the sand being wet. I was quite happy with how this effect looked - the water's transparency helped in this regard, as it allows us to see the sea bed when close to the shore.
Once the beach was complete, I began adding in some of the BoatAttack 'prop' assets (this included beach chairs and sun-shades, beach houses, kayaks, water jetty's, and water buoys). I wanted to create a 'private beach' scene that was enclosed by mountains to the rear. Additionally, I wanted to create a scene that felt both realistic and lived in. I began by placing chairs and sun-shades around the beach in postures and positions which I believe people would naturally leave them. I then added a line of beach houses along the back of the beach, looking out to the water. I used the same beach house model four times in a row to give the effect of leased properties along the beach. At the end of the four houses, I added a longer house to act as a 'reception' area. I then copied this style on the opposite side of the beach. The scene still looked bare, and I was struggling to get the sand to seam nicely with other texture layers, so I decided to use some assets from the BoatAttack repo to help fill out the scene (this immediately improved the scene, as it hid the texture seam from view, and allowed me to fill out a substantial area of the island). I then copied this general style around the remaining section of beach to develop the landscape further.
Sprint 11 - Consolidation
Last updated 9 May 2021 at 19:05
Thursday 20th January - Tuesday 1st February 2021.
Consolidation - Pre-Production Release, Sprint 6.
Overview
The main goal of this sprint was to consolidate all aspects of release 1 before presenting our product to users for feedback and testing.
Output
My main outputs for this sprint are as follows:
- Implement In-Game UI
- Implement Basic Nature Sounds
- Implement Basic Rowing Sounds
- Complete Concept2 Integration
- Create User Feedback Form
Process
Implement In-Game UI
I created a basic in-game user interface that displays the user's stats in real-time. This allows the user to view information about the current workout (such as distance rowed, time spent rowing, stroke power, drive length, and stroke rate). I decided to make use of our menu system to implement our HUD - rather than creating an entire separate canvas for the HUD, I decided to treat the HUD as a pseudo menu. That meant that I could utilize systems that I set up previously to open and close the HUD like it was a menu. For example, when the user opens a menu, I close their HUD - when the user closes a menu, I open their HUD. This allowed me to quickly toggle between UI states, based on what the user is currently looking at. I then created a simple semi-transparent box for each stat that I wanted to display. Inside each box, I placed a text label - this would be used to display the current value of a stat (for example, in the case of the distance stat, this might equal '400m'). I then simply connected the output of each stat to the input of each text label. Thus, the text labels on-screen will update to reflect the stats which are currently being sent from the performance monitor. Through this process, I also made the UI entirely responsive, such that it fits on most screen sizes and aspect ratios.
Implement Basic Nature Sounds
I used Audacity to create a simple background loop using various sounds that I sourced online (sources are outlined in the 'Media' section of my references page). I needed to ensure that the sound smoothly looped, so I used some audio tricks to edit the water sound such that it looped seamlessly back on itself (this process involved duplicating the audio clip, placing the two versions of the audio clip side-by-side, using the crossfade clips tool to create a smooth transition from the end of audio clip 1 to the start of audio clip 2, I then split the two clips apart and repeated the same process on the other end - thus creating a loop which smoothly transitions from its end back to its start). Once the loop was complete, I stretched it out to 10 minutes long and added various bird and wind effects at random points throughout the loop. Increasing the length of the loop to 10 minutes ensured that no obvious looping pattern would be heard by the user (i.e. a 1-minute loop would be very obvious, as the pattern in which the birds caw would repeat in an obvious manner). I then integrated this with Unity, by setting up a simple AudioManager class that automatically plays this loop when the level begins.
Implement Basic Rowing Sounds
I sourced an audio clip online of a person rowing across a lake. I downloaded the clip and began to edit it using Audacity. I isolated 5 sections of the clip in which the oars are splashing into and pushing through the water. I then edited each clip to suit our needs before exporting them separately (the editing process included cleaning up each clip and removing any background or otherwise unwanted noise). I then brought each clip into Unity and integrated them with the AudioManager class I set up earlier. Next, I updated the Oar script to play one of the 5 rowing clips at random each time an oar enters the water. Thus, the sounds of oars splashing through the water would now be heard as the user rows around the scene.
Complete Concept2 Integration
Over the past two weeks, I met with the duty manager of DkIT Sport on two separate occasions at their premises in Dundalk. Throughout this time, I worked on updating the interface that I developed for communicating with the Concept2 Performance Monitor. I have created a set of videos on my hardware integration page that explores this process in great detail (I have chosen not to document this via my journal, as the depth of complexity of this task cannot easily be described using words!). If you would like to see this process, you can check out my hardware integration page, here.
Create User Feedback Form
I created a user feedback form using Google forms. This would be used to collect feedback from our test users about the various features in our game. I added a section on Concept2 Integration (to measure how well our game integrates with their Concept2 machine), Game Performance (to measure how well our game performs on the users mobile device), User Interface (to measure how useful and accessible our user interface is), Gameplay (to measure how well our various modes and mechanics achieve their goals), and General (to gather any general feedback or comments from test users). I discussed the feedback form with my team, and made changes and updates based on their recommendations.
Sprint 12 - User Testing
Last updated 9 May 2021 at 19:04
Thursday 3rd February - Tuesday 8th February 2021.
User Testing - Pre-Production Release, Sprint 7.
Overview
The main goal of this sprint was to gather feedback from test users about the various functions and features in our game. My primary roles in this sprint were preparing our game for release, and sourcing test users.
Output
My main outputs for this sprint are as follows:
- Multiplayer Network Synchronisation
- Create User Testing Information Material
- Collect User Testers
- Publish Application
Process
Multiplayer Network Synchronisation
I needed to address a bug with networked players, as they seemed to zoom around the track much faster than they were actually moving. I discovered that this was caused by a conflict in the network system, where the network was trying to move the player to reflect their in-game position, while the route follower class attached to the player was trying to move the player around a track. This conflict caused the game to break, resulting in the player zooming around the track at a very high speed. This was fixed by removing the route follower component from networked players. I then developed a system that automatically assigns a translucent white material to networked players - visually separating them from the primary player. Additionally, I added a system that updates networked players minimap icons such that they now appear as red (instead of white), making it easier for the local player to distinguish their own icon from others. I then spent some time pair-programming with Kristine to test our game online with the goal of identifying any bugs which occur during multiplayer gameplay. We discovered various bugs (such as the multiplayer race not syncing correctly across the network, and the race not waiting for others players to join before beginning). Kristine then logged these bugs under GitHub issues, which we could then reference when addressing the bugs later (fixing these bugs would involve an entire re-design of the event system, which was not possible in the time we had remaining in this sprint, so it was pushed back to sprint 13).
Create User Testing Information Material
Created a detailed email that outlined how to access and install our game. I also provided additional links on how .apk files are installed, and what risks are involved with installing external .apk's (I wanted to be as transparent with our users as possible when asking them to install our game, as I didn't want to lose any of the trust that I gained). I assured test users that it was safe to download our game, and informed them that a 'this file may not be safe for download' warning will appear when trying to install our game. I explained that this was a totally normal consequence of downloading external applications, as Android automatically flags any external application (i.e., any application not downloaded via the Google Play Store) as unsafe. I also created two instructional videos which detail what our game is, and how to interact with and use our game. Each video can be viewed at the following links: GoRo Information Pt. 1, GoRo Information Pt. 2.
Collect User Testers
I created a Reddit post detailing our interest in recruiting test users for our game. We included a form in which users can express their interest by registering their email address. This allowed us to contact interested parties directly via email. I then sent an email to all registered test users with information on how to participate in user testing (this included requesting that all test users read our information leaflet and sign and return our consent form before we allow them to download and test our game). In total, 11 users registered to test, but only 6 returned consent forms.
Publish Game
I needed to publish our game online to a shared folder where test users would have the ability to download and install our game. As such, I first needed to create a stable build version of our game. I tested our game thoroughly (separate from a Concept2 machine) to ensure that all of our game functions and features worked as intended. I then tested this version of the application on a Concept2 rowing machine to ensure that our game still worked with the hardware as expected. Once I was happy that the version of our game worked as intended, I created a project build. I then uploaded this build to a shared DropBox folder. Finally, I linked this folder with our registered users and invited them to test our game.
Production
Sprint 13 - Re-Routing
Last updated 1 May 2021 at 16:48
Thursday 14th February - Tuesday 23rd February 2021.
Re-Routing - Production Release, Sprint 1.
Overview
The main goal of this sprint was to develop updated routes that spanned over larger areas. My role in this sprint involved updating the multiplayer race feature to make it more stable across the network.
Output
My main outputs for this sprint are as follows:
- Fix Multiplayer Race
- Tweak Multiplayer Race
Process
Fix Multiplayer Race
Through testing, Kristine and I discovered three large bugs with the multiplayer race. These bugs are as follows - the race start countdown did not sync correctly over the network, meaning that one player would start the race while the other still waiting for the countdown to end; the race did not wait for other players to join before beginning the race, which meant that the race would start with just one participant; the race did not sync correctly over the network, meaning that the players were essentially each taking part in an entirely separate race, and not one shared race. I decided to completely re-work our event system to tackle each of these bugs. To start this process, I decided to create a separate test scene which I could use to develop a reduced functionality 'test race'. I set up a simple scene that featured a 'starting line' volume (white), a 'finish line' volume (red), and a race trigger volume (green).
I then added multiplayer functionality to spawn a simple capsule player controller for each player. Then, using Kristine's previous work for reference, I built an updated Race and RaceManager class. The Race script would be attached to individual tracks within the game (as such, any track could become a race track by adding a Race script component). I created a simple state machine in the Race class which managed its current state. The four states in which a race can be are Forming (a player has started the race, and is waiting for players to join, or for the race to begin), Active (players are currently participating in a race), Resolving (a player has reached the finish line, and is waiting for others to complete the race), and Inactive (the race is currently inactive, and players are free to join or create a race on this track). When joining a race, the RaceManager will first check to see if it is either Inactive or Forming. In the case of a race being Inactive, the RaceManager will create a new race (changing its state to Forming) and will add the player to that race. In the case of a race forming, the RaceManager will add the player to the forming race. In every other case (e.g. when a race is in progress, or when the race is waiting to end), the player will be shown a message informing them that the track is currently busy. I tested this system using my laptop as a pseudo networked player, and made adjustments as necessary (this took quite a lot of time, because each update had to be made, pushed to git on my desktop, and then pulled back down on my laptop before the changes could be tested). Eventually, I had a stable working system that allowed me to sync all aspects of this test race across the network. I then spent some time integrating my changes to the work that Kristine previously set up. I then tested these changes using my laptop and made updates necessary to ensure the system was integrated correctly.
Tweak Multiplayer Race
I made some changes to the multiplayer race to make it more user-friendly. This includes creating a 'Race Start' notification which is sent to all other players currently loaded in the multiplayer room. As such, when a player sets up a race, every other player in the lobby will receive a message notifying them that a race is about to begin. This allows players time to join the race by opening their pause menu and selecting 'Start Race'. Also added a notification for the player who started the race, letting them know that the game is waiting for other players to join before the race begins. Finally, I added a system that causes the race to time-out once the first person crosses the finish line. Each subsequent person then has X amount of seconds to cross the line before the race will end. This timeout value is then reset as each person crosses the line. As such, once the first person crosses the line, the player in second has 20 seconds to complete the race before the race ultimately ends, if that person completes the race in 10 seconds, then the timer will reset and the person in third place will have another 20 seconds to complete the race. This system has been added to prevent situations in which players who have finished the race are left waiting for other players who have no intention of completing the race.
Sprint 14 - Time Trial
Last updated 1 May 2021 at 16:47
Thursday 25th February - Tuesday 9th March 2021.
Time Trial - Production Release, Sprint 2.
Overview
The main goal of this sprint was to create an updated version of our time trial mode. to allow players race against their previous best times around a given track. My role within this sprint was creating a system to enable this functionality, by developing a local storage algorithm and by updating the time trial script.
Output
My main outputs for this sprint are as follows:
- Expand Concept2 Stat Database
- Boathouse Hub Menu
- Create Toasts
- Update Time Trial
Process
Expand Concept2 Stat Database
I updated the StatsManager and BluetoothManager classes to extract additional stats from the Concept2 Performance Monitor (this includes split average time and drag factor, according to stats requested from our test users). I updated our in-game UI by adding a section at the top for displaying live workout stats. Previously, our stats were spread out across the screen and looked rather disjointed. This update pulled all of the stats together into one place at the top of the users' screen. Additionally, I added labels to each stat to make it more obvious what stats were being displayed (we received feedback from users suggesting that it was difficult to decipher what each stat represented). Next, I updated the remaining user interface by making the buttons and text larger. This made our UI more accessible on smaller devices (again, this was acted upon in response to feedback that we received from users). Finally, I created a real-time stat graph that displays live information about the users' current workout in a user-friendly graph. Users can view up to 15 seconds worth of workout data that updates every half second to provide them with a brief overview of their recent performance, allowing them to compare their current output relative to their recent form). Users can select which data they wish to see on the graph by clicking on a stat in the UI (users can select from a drag factor graph, a stroke rate graph, a speed graph, a power graph, or a pace graph).
Boathouse Hub Menu
I updated our main menu by developing a simple boathouse scene. I created a simple scaled-down terrain and added in our water. I then placed Keelan's boathouse in the scene. I used various models that Keelan created to make the scene come alive - I created a shelf and placed some trophies on top, and added oars around the scene. Next, I added a boat to the boathouse. I decided to place a camera in the boat so that the user feels like they are sitting in their boat while at the main menu. I felt that the menu looked very static, so I added a simple bob animation to the boat, which gently moved it up and down in the water. I also added basic animations to the oars which allow them to move around as the boat bobs. I got some feedback from the team about my composition, and Kristine suggested moving the camera out of the boat, and off to the side. I made the changes that she suggested during a team meeting one day, and we all agreed that the new update was much better. We got feedback from a test user about our composition, and they were very annoyed to see oars being placed on the ground. As such, I asked Keelan if he would develop a simple oar rack model that I could add to the scene. He was happy to help and soon sent me over a simple model. I added it to the scene and placed our oars into the rack.
Create Toasts
In the previous sprint, I created a system for sending players notifications when a race is about to begin. Meanwhile, Nathan was creating a simple design in photoshop that would pop up on the users' screen when the race is about to begin. This toast notification would provide details about the race (such as the track name, current participants, and lap count). Additionally, it would provide options for the user to join the race (represented as a tick), or dismiss the notification (represented as an x). I then created this design using Unity and added all of the required functionality. I began by creating an EventNotification class that would hold all of the relevant details about an EventNotifcation. This class also included various functions for joining the race and dismissing the notification. Next, I updated my race script to instantiate an EventNotification for each member currently loaded in the multiplayer game. As such, when a user creates a race, each other person in the multiplayer game will receive a notification inviting them to join the race. The user can then click on the tick to join that race, or click on the x to dismiss the notification. Finally, I updated the toasts for each of the race features to bring them in line with the other UI updates I made previously (this includes updating the size of font, and transparency of text boxes).
Update Time Trial
We decided to update our trial system - our previous trial system simply allowed the user to row around a given track. Once a user had completed their trip around the circuit, the time that it took them to complete the event would be displayed to them on screen. This was a simple implementation of the trial system and wasn't really exciting. So, we decided to update this system, by creating a save file on the players' device which stored their best time around each track that they have competed on (this would allow users to compare their trial time against their previous best time, and could be used as motivation to improve their performance). Going further, we decided to make this system more interactive by displaying an additional 'ghost version' of the player the moves around the track. This 'ghost' version of the player would be a separate entity and would represent their previous best time around a given track. The player can use this ghost as a performance metric to determine if they are improving on - or worsening - their time (based on whether they are ahead of or behind the ghost). As such, we would need to create a local storage system that would allow us to store data about the players' previous performance.
I began by outlining the structure that I would use for loading from (and saving to) files. I decided to use a basic comma-separated format, where the first column would contain the track name, and where the second column would contain the trial time (i.e., how long it took the player to complete this circuit). I then had a look online for tips on how to use file I/O with Android, Unity, and C#. I came across this article, which details a set of functions that I could use. Next, I began to work on a system that searches for a save file when the player begins a trial. If a save file is present, the system loads all data from that file and stores it in an array. Otherwise, the system does nothing (if there is no save file present, then it must mean that the player has not yet completed a trial). Once the save file is loaded, the system then searches for a row in that file which matches the track that the trial is being completed on. If a record for that track exists, the system then loads the time it took the player to complete that circuit and uses it to instantiate a player 'ghost'. The system then calculates the average speed that the ghost should move around the track, based on the length of the track, divided by the time that was stored on file. This average speed would then be fed into the instantiated ghost object, causing the ghost to move around the track at a constant speed. Now, the player could use this ghost as a performance indicator to determine if they are improving their time or not.
Sprint 15 - Production
Last updated 9 May 2021 at 19:05
Thursday 11th March - Tuesday 23rd March 2021.
Production - Production Release, Sprint 3.
Overview
The main goal of this sprint was to update and consolidate our prototype ahead of the release 2 deadline. My primary role in this sprint involved making updates to various aspects of our game, to improve its overall quality.
Output
My main outputs for this sprint are as follows:
- Update Ghost Trial
- Allow User to Set Nickname
- Update Leave Logic
- Menu Updates
Process
Update Ghost Trial
Our previous implementation of the trial algorithm moved a ghost around a track at a constant speed which represented the players previous best time around that track. This was calculated as an average speed, based upon the length of the track, divided by the time it took the player to complete the event. This was nice, as it allowed the player to compare their general current performance against their previous best performance by referring to their position in relation to the ghost. However, it was pointed out by the panel that it may be more desirable if the player could compare their performance directly against their previous best run, by allowing them to directly compare their actual position, rather than just a position which is determined by an overall average.
This would mean updating our algorithm such that we sampled the players' position at multiple time intervals throughout the event (the idea of this being that the players' position could be replicated by plotting their position around the track at the same time interval). The problem was, it would be very difficult to move the ghost smoothly around a track unless our time interval was very small. A large time interval (like 0.5) would make the ghost appear to jump from point to point). However, a small time interval (like 0.03) would result in far too many samples to be considered a reasonable option (for example, if we sampled the player' position every 0.03 seconds, we would have 2000 samples after only one minute!).
As such, I needed to come up with a different solution which would allow me to both accurately reflect the player's previous position, but also not bog down the game's performance. After some time, I realised that I could make use of the RouteFollower class that is attached to a player object. The RouteFollower class takes one input (a velocity) and uses that value to move the player around a track. I decided that (instead of sampling the player's position at each time interval), I could instead sample their speed. This would mean that I could then attach a RouteFollower component to the ghost prefab, and feed it speed values from the locally saved file. Additionally, the RouteFollower class moves an object around the track at a constant speed, depending on what speed value is currently passed into it. So, setting the ghost's speed to 1 would just mean that the ghost would move around the track at a speed of 1 (until this value was changed). As such, the ghost would move smoothly around the track rather than jumping from point to point as mentioned above. This meant that I could drastically reduce the sample interval, while still preserving smooth movement. However, I still needed to be conscious of the players update speed - if the player's RouteFollower updated quicker than the ghost's RouteFollower, then the player and ghost positions could quickly become out of sync. So, I decided to moderate the player's update speed to update that of the ghost (this update speed is controlled by an external SAMPLE_RATE constant variable).
With these changes in place, I needed to re-work our local storage system. Previously, our save file contained only two columns (one for the track name, and another for the trial time). I would now need to update this to include a list of speed samples, along with a count of the number of speed samples that were recorded (this is useful when reading the file back later). I then edited the read and write functions to incorporate these change, and began editing the trial script by adding a function which samples the player's speed SAMPLE_RATE times per second. At the end of a trial event, the system checks if the players current time (i.e, the time that it took them to complete the event) is less than the time that was stored locally. If so, the system then overwrites the players local file by updating the data which is associated with the current trial's track. Simple, but complicated!
Allow User to Set Nickname
Feedback from our users suggested that we allow them to set their own nicknames in-game - they wanted to be able to show off their nicknames in multiplayer. As such, I decided to add this feature by creating an 'Update Nickname' menu which is accessible through the multiplayer screen. I copied most of this menu's structure from the 'Create Room' menu, but added a label above the input box which displays the user's current nickname. I then added functionality in the GameManager class which extracts this input and assigns it to the player (our network system allows us to set a nickname for each player). As such, the player can now open the Update Nickname menu to set their own name. This nickname will then appear in-game to other networked players via multiplayer. If a player chooses not to set a name, the system will simply assign them a default name (Player 0123, for example).
Update Leave Logic
Our game had various bug ins it (some of which were pointed out by test users) - players could not leave an event after it had started, and players could not exit singleplayer or multiplayer withoutt closing the application. As such, we needed to update our game to address these issues. I began by focusing on the 'leave room' option which would allow players to exit from singleplayer or multiplayer game modes. This was a relatively quick fix, as I just needed to add functionality to a UI system that Kristine had previously set up (Kris set up a system that gives players the option to leave a room, and prompts them to confirm their choice before they can actually leave). I updated the leave logic accordingly (by de-loading the game scene, and re-loading the main scene), and addressed various small bugs which were causing the game to crash. When re-loading the main scene, the game was trying to re-instantiate already instanced 'DoNotDestroy' objects. To fix this problem, I removed these object from the main scene, and loaded them via a script instead (the script checks if a matching object is already present before attempting to instantiate another version).
Once the leave room logic was working, I began to work on the leave event problem. First, I added an option in the pause menu which allows the player to exit an event. However, this brought with it a problem - the 'leave event' button would appear in the pause menu regardless of whether the player was in an event or not. I decided to create a system that would allow me to cycle between various pause menu configurations, based on what the user was currently doing. I created two configurations - a regular pause menu and an event pause menu. The regular pause menu would work the same as usual (with options for resuming the game, starting an event, or exiting the lobby). The event menu would simply include two options - one for resuming the game, and another for exiting the event. Rather than have two completely separate pause menu's, I decided to have on large pause menu with all of the options for both states added into it. I could then turn off each option as necessary, depending on whatever state that the user is currently in (if the user is in an event, I could turn off all non-event-related pause menu options). To keep the pause menu clean, I decided to use a vertical button group to store all of the options. A button group works well in this situation because it moves the buttons inside of it closer to each other if there is any space left between them. As such, the button group automatically scales itself to fit however many options are displayed on-screen. Thus, I just needed to create a simple boolean function that activates and deactivates each individual option as the user cycles between game states.
UI Updates
We received feedback from the panel which suggested that we allow users to turn our UI options on and off (with the idea being that some users would prefer to see fewer stats on-screen than others). As such, I decided to split our stats UI into three sections - primary stats panel, additional stats panel, and stat graph panel. I wanted to create a system that would allow the user to quickly and easily toggle between various UI states while in-game. UI states would consist of various combinations of each panel being turned on or off - a 'no stats' state would have all panels turned off, while an 'all stats' state would have all three panels turned on. To achieve this goal, I created a simple function which would turn each panel on or off, based on some input state (the function takes three booleans values as an input, which is used to control the active state of each panel). So, for example, calling the function with input values of (true, true, false) would turn the first two panels on (the primary stats and additional stats panels), but would turn the third panel off (the stat graph panel). I then just hooked this function up to a state machine that cycles through a list of six possible states. Finally, I added a button to the user's HUD that triggers this function, allowing them to toggle between our various UI states.
We also received feedback from the panel suggesting that our UI was unclear and lacked overall cohesion (it was difficult to distinguish the stat label - i.e. 'meters' - from the stat value - i.e. '10'). As such, I decided to update the colour of the stat label to help it stand out. I then edited the transparency of the additional stats panel to visually separate it from the primary stats panel. I then added a simple white rule across the top of the additional stats panel to further outline this divide. Next, I noticed that the shapes we were using in our UI looked odd and unappealing (we were using the default shape until now, which was a rectangle with weirdly rounded edges). I replaced this shape with a straight-edged rectangle, and immediately noticed an improvement - our UI went from dull and boring to sharp and sleek.
Finally, we got feedback that it wasn't clear how to update our stats graph to display different stats. Our stat graph works by displaying information about a selected stat in real-time, however, it was unclear to users how they select other stats to be displayed. To select a different stat, users would have to click on that stat in the additional stats panel. However, there was no indication anywhere on the screen that these stats were interactable. As such, I decided to update the additional stats panel by adding a transparent black box behind the currently selected stat - this makes the currently selected stat stand out from the others, indicating to users that it is interactable.
Post-Production
Sprint 16 - Business Plan
Last updated 1 May 2021 at 16:44
Thursday 25th March - Tuesday 6th April 2021.
Business Plan - Post-Production Release, Sprint 1.
Overview
The main goal of this sprint was to spend time working on our business plan as part of our Entrepreneurship CA. My main role in this sprint involved updating the user's HUD and UI to create a more polished and feature-rich experience.
Output
My main outputs for this sprint are as follows:
- Split Routes
- Add HUD Billboards
- Update Event UI
- Update Race UI
Process
Split Routes
We received feedback from users that it felt weird having other players rowing on the exact same track as they were. Essentially, networked players would move around the same track as the local player, meaning that both players would be placed on top of one other at various points throughout the race (for example, if they were roughly the same distance around the track). I thought about creating a system that would spawn a separate route for each player in the game, but I soon realised that this would not work, as each player would need to move a different distance to complete one lap of the track (picture a running track, where players on the outside track need to run further than players on the inside track). After some brainstorming, I came up with a solution that uses an offset value to make it appear as if networked players are pushed off to the left or right side of a local player. Essentially, this would work by moving the visual aspects of networked players (such as their boat, oars, player tag, and avatar) to the left or to the right of their parent objects pivot point. Thus, creating an illusion that makes it seems as if networked players are on a different path, while in reality, both players are moving around the exact same track (you can picture this by visualising a pendulum moving around a track, where the swinging point of the pendulum represents the player's actual position, and where the pendulum represents the players offset position). A video demonstrating this system may be viewed on my multiplayer integration page.
Add HUD Billboards
Through testing, we realised that it was quite difficult to see other players in-game while they are far away. To address this, we decided to add a billboard above each player head to make them more easily identifiable. I began by attaching a simple canvas to the player prefab. Next, I added a semi-transparent white background to help it stand out. I then created a simple function in the PlayerController class that places the player's nickname in the billboard above their head (as such, players will be able to see who is in their multiplayer game by looking at other players nicknames). I decided to call this component 'Player Tag', as it is a tag that represents the player.
I then needed to convert this canvas into a billboard, which would turn to face the local player's camera at all times. I found a simple script online, which turns the selected object to face the forward transform of an input camera at all times. As such, I added this script to the player tag, and set up a simple function within the player controller class that automatically assigns the local players camera to the input camera of each networked player's 'Player Tag'. As a result of this, each networked players 'Player Tag' would always turn to face the local player's camera. Finally, I used culling to hide the local player's Player Tag from their own camera view, meaning that local players cannot see their own Player Tag. To get this working, I placed the Player Tag on a layer that is not rendered by the player's camera. I then added a simple function in the PlayerController class which changes the Player Tag's layer, if the PlayerController belongs to a networked player. Thus, the local player cannot see their own Player Tag, but they can see networked player's Player Tag.
Through testing, I realised that networked player's Player Tags would take up large portions of the screen while nearby the local player. As such, I needed to create a system that would cull player tags from view if too close to the player camera. Camera culling within Unity works by allowing the developer to specify a range in which objects are rendered. For example, a range of 0 (close) to 100 (far) will only render objects which are within the range of 0 units to 100 units away from the camera. As such, I would need to create a culling range that would hide player tags if they came too close to the player's camera (for example, 5 units away). However, by setting the main camera's close cull distance to 5, I was culling objects such as the player's boat, or their oars. As such, I would need a system that could specify two separate cull distances for each object (one for the entire world, and one for just the player tag).
I did a bit of research online, and came across this video by Unity which describes how to use camera overlays. Camera overlays essentially allow you to render multiple camera layers on top of one another. The benefit of using camera overlays is that you can specify separate cull range and render layers for each camera. As such, I could tell Camera A to render the entire world (except player tags) with a cull range of 0 to 6000, for example. Meanwhile, I could tell Camera B to only render player tags, but with a cull range of 5 to 6000. As such, Camera B would automatically cull any player tag that comes within 5 units of the player's main camera (a demo of this can be seen on my multiplayer integration page).
Update Event UI
Feedback from users suggested that we add stats to our event UI which would allow them to keep track of details such as speed and stroke rate. As such, we would need to update its general layout to create space for these additions. This was a welcome change, however, as our event UI currently felt out of sync with the other UI elements in our game. As such, I decided to completely overhaul its design to bring it more in line with everything else.
I began by removing the event icons, as they don't mix well with our clean and minimalist aesthetic. I then spent some time updating our progress bar, as I wasn't happy with how it was originally placed (it felt like our progress bar didn't fit into its container correctly). I decided to place the progress bar into a separate container component that uses a slightly darker background to the other event UI elements. This allowed me to both visually and physically separate the progress bar from the other event elements. As a result, the progress bar seems to fit better in its holding container. Next, I spent some time rearranging the event panel layout, to create more space for 'speed' and 'stroke rate' stats. I struggled to find a layout that I was happy with. Ultimately, I decided to copy the general design of the 'additional stats' panel, as this would at least keep a sense of consistency within our UI (overall, I was quite happy with how this turned out). Finally, I decided to move the progress bar panel above the event information panel.
Update Race UI
Our race UI wasn't properly synced across the network (i.e. the position display didn't accurately reflect the player's position in the race). As such, I needed to spend some time creating a system that would sync each players position across the network. I began by creating a function that keeps track of each players distance around the race track. This works on the principle that each player in the race saves their distance around the track in a local variable which is then used to determine who is in what position, based on how far each player is around the track. As such, each player would need to sync their distance across the network so that each other player could access this data.
This is accomplished through a PUN RPC call, which essentially is a function that is executed on every instance of a class, meaning that a PUN RPC call may be used to sync data across the network, by passing variables as parameters through each RPC function. imagine a scenario where 2 people need to share data across the network, both Player A and Player B only have local copies of each other. So, for Player B to receive updates about Player A, Player A would need to use a PUN RPC. When Player A calls a PUN RPC, that function is executed on both versions of Player A (Player A's version of Player A, and Player B's version of Player A). If Player A did not use a PUN RPC, that function would only be executed on Player A's version of Player A. So, as we can see, PUN RPC's are a simple and useful way of syncing data about components across the network.
As such, by syncing each player's distance across the network, we can easily access this data within the race class. I began by creating a data structure to map each player in the race to their current distance. I then create a list using each players distance, before sorting it in descending order to create a list of distances that represent what position each player is in, based on how far they are around the track (the player at position 100 is further around the track than the player at position 10, as such, they must be ahead of them in the race). I then use the map that I created earlier to link each distance with the player that it represents. Finally, I update the race UI by displaying the player's position, based on their index within the ordered distance list. This process is then repeated for each update in the race.
Additionally, I added to the above-outlined system by also displaying networked players race progress on the progress tracker (this allows players to easily see how far they have progressed around the track, relative to others around them). To do this, I added a separate progress tracker which is instantiated for each networked player. The progress tracker is then updated based on each networked players distance. I decided to bring this feature over to the trial mode also, allowing me to display the 'ghost players' progress around the track.
Sprint 17 - Apply Polish
Last updated 1 May 2021 at 16:46
Thursday 8th March - Tuesday 20th April 2021.
Apply Polish - Post-Production Release, Sprint 2.
Overview
The main goal of this sprint was to apply the finishing touches to our game ahead of our expo. My role within this sprint involved creating a more real experience for our users, by adding systems and features which make the game feel more like a real rowing experience.
Output
My main outputs for this sprint are as follows:
- Update Movement Algorithm
- Add Route Markers
- Refine Routes
- Add Particle Effects
Process
Update Movement Algorithm
Something that we have wanted to add from early in our project was a system that moves the player around our world at a rate that would accurately reflect the distance that they have rowed in real life (via their rowing machine). However, I was faced with various technical issues that prevented me from doing this earlier.
The primary issue that I faced was this - the RouteFollower class takes a velocity value as an input. This velocity value is then used to move the RouteFollower (which in our case, is a player) around a particular route at that input velocity. The Routefollower will continue to move around the route at a constant speed, depending on what its input velocity is (an input velocity of 0 means that the RouteFollower is stopped). As such, to edit the speed at which the RouteFollower moves around the track, you need to edit its input velocity value (this can be done at any time, even during gameplay). So, my first instinct was to directly tunnel the Concept2's speed value into the RouteFollower class - with my idea being that by using the Concept2's speed value (measured as m/s), I could accurately reflect the distance moved by the player (luck for us, Unity uses meters as it's distance unity, so to get this working, I would just need to update the speed value once every second). However, the Concept2's speed value does not return to 0 if the player stops rowing. For example, if the player is rowing at 4m/s and they choose to stop rowing, the Concept2 performance monitor will not update their speed value to 0m/s (this is to allow users take a break while rowing). As you can imagine, this wasn't going to work with our RouteFollower system, as players would continue to move around the track even if they stopped rowing. So, I looked at calculating a speed value based on the delta distance and delta time as extracted from the rowing machine (my idea being that the speed would then be 0 if the delta distance since the last update was equal to 0). However, this caused another issue, because the speed at which Unity updates is not in sync with the speed at which the Concept2 updates - and since I had no way of definitely knowing how often the Concept2 would sync (since it stops syncing to save battery if the user takes a break from rowing), I had no way of accurately calculating a delta distance based on a delta time (I could not use Unit's local delta time as it was out of sync with the Concept2, and I also could not use the Concept2's delta time because it would often equal 0).
So, I went back to the drawing board and realised that I could use a rigid body to make the boat slow down over time if the user is not rowing (this would solve my 'persistent speed' issue). However, it is not possible to use a system like our RouteFollower system on a rigid body object, as the physics of the rigid body clash with the maths of the movement system (i.e., you cannot tell Unity to move an object to a fixed and definite position, while also telling Unity to apply physics to that object - it is like telling the game engine to move an object, while also keeping it fixed in place). It looked hopeless at this stage, but after some time, I realised that the rigid body does not actually need to be attached to the player - I could just use the velocity value of the dynamic rigid body to update the input values of the RouteFollower class. As such, the game applies a force to an external rigid body as the user rows on their rowing machine. The velocity values of that rigid body are then used to edit the speed at which the player moves around the track. If no force is applied to the rigid body (i.e. because the user stopped rowing), then the rigid body will begin to slow down, causing the player to slow down (and eventually stop).
This was a system that we used for quite a while, but it was something that I was never really happy with. My main issue with this system was that it did not accurately reflect how far the player had actually rowed - the distance the player moves in-game is entirely separate from the distance they rowed in real-life (this is because there is no realistic way of modulating the forces applied to the rigid body such that its velocity is an accurate reflection of the players supposed real-world velocity). But, I had an idea - what if I change the RouteFollower class such that it takes a distance value as an input, rather than a speed value?I had been so caught up in trying to create a velocity value, that I never thought of other options (it sounds very straightforward typing this back right now, but it was a conundrum that I played with for weeks!). The updated RouteFollower class would use a distance value (which would be extracted from the Concept2) to directly map the players rowing distance to a point around the track. Of course, this would bring its own challenges - using a direct mapping of distance to position meant that the player would jump between points around the track, rather than smoothly moving from point to point. As such, I decided to develop a system that would smoothly move the player between two points, based on some pre-set time. For example, if we know that the player will update their distance every 2 seconds, we can tell the system to smoothly move their avatar from Position A to Position B over the course of those two seconds. By the time their avatar arrives at Position B, their distance value would have updated again, allowing the system to calculate their new destination, Position C. In essence, the player would lerp from point to point based on some pre-set value. As such, I created a simple system that expects a distance update from the Concept2 at least once every second (this gives the performance monitor enough time to send a new update to our game). As such, our updated movement algorithm now accurately moves the player around our tracks based on the distance values which we are extracting from their Concept2's. Also, since Unity uses meters as their based measurement, this meant that we could place objects in our scene (such as route markers) which allow users to see how far they have rowed in-game.
Refine Routes
By updating our movement algorithm, we were able to now change our routes such that they represent more meaningful distances (early user feedback suggested that we create set-distance routes, such as a 1km sprint, a 2km sprint, and a 5km sprint). It previously wasn't possible for us to meet this request, but my recent update to the movement algorithm enabled us to make the change. As such, I spent some time configuring our routes to bring them more in line with our user feedback (our race track is now 2km long, for example). Thus, to complete one lap on our race track, players will need to row 2000 meters on their rowing machine. As a final addition to our route system, I decided to place markers at various points around our track. I used a flag model that Keelan created to place a marker every 500 meters. I then added a black 2D sprite above each marker to create an icon that appears in the player's minimap. As such, players can see the route markers in-game, which allows them to roughly judge how far along the route they have rowed.
Add Particle Effects
As a final touch to our game, I decided to use particle effects to bring more life into the world. Feedback from the panel suggested that we add birds to our scene, as it was pointed out this small touch could really add to our overall presentation. I realised that the most cost-efficient way of doing this was to add particle effects to represent birds. I wasn't sure how the particle system worked within Unity, so took a quick look online, and came across this tutorial. This tutorial allowed me to add a basic implementation of birds to our scene. However, this tutorial was rather basic, so I decided to spend more time experimenting with various values to create a more suitable effect. I began by adding collisions to the particle effect which prevents particles from clipping directly through the world (previously, birds would fly directly through a cliff, or right into the water). I also edited the emission value (which controls how particles are emit over time), duration value (which controls how long the system waits before emitting more particles), lifetime value (how long each particle stays alive before being destroyed), and maximum particles value (the maximum number of particles which can be present at any one time) to control how birds appear in the world. I also created two separate particle volumes (where particles are spawned), and placed them such that the particles spawn close to areas in which the player rows (to ensure that the player sees the bird particles as they move around) and such that the player cannot see the particles being spawned (i.e. the particles spawn outside of the camera's view). As such, bird particles are randomly spawned into the game world at various points during gameplay.
I also created a water splash particle effect to make the rowers movements feel more realistic. I spent some time looking at various tutorials online to learn more about how the Unity particle system works (particularly this tutorial, which demonstrates how to edit emission values to create trails). I then spent some time experimenting with different values to create a water splash effect that is generated by the boat as it moves through the water. Finally, i created a slightly modified version of this effect and added to each oar to make it appear as if the oars were splashing through the water as player's row.
Sprint 18 - Expo
Last updated 2 May 2021 at 17:17
Thursday 22nd April - Monday 27th April 2021.
Expo - Post-Production Release, Sprint 3.
Overview
The main goal of this sprint was to complete and release the final version of our application. My primary role involved preparing promotional material ahead of the expo such as the presentation and our game trailer.
Output
My main outputs for this sprint are as follows:
- Record Live Demo
- Prepare Gameplay Content
- Consolidate Submission
Process
Record Live Demo
We decided to create a live pre-recorded demo of our application ahead of our final-year expo. The goal of this demo was to show off all of the features and functionalities that we added to our game. Andrew Nolan is a past Film & TV student that I met while taking part in an international project in Poland. I follow Andrew's filming page on Instagram, and I noticed one day that he posted an advertisement for his filming and editing services. I reached out to Andrew, and he was more than happy to record and edit a promo video for our project! This was greatly going to reduce the workload on us for the sprint. Regardless, I needed to find a suitable place where we could record the demo. DkIT Sport was the most suitable place, as both Andrew and Kristine live in Dundalk. As such, I reached out to Alan Watter from DkIT Sport to see if it was possible for us to record some footage. He was more than happy to help out. He arranged to meet us on Wednesday 21st April from 10.00 am to 11.30 am.
Before going to DkIT Sport, I met with Kristine to discuss the various camera angles and shots that we would like to capture. During this meeting, we had an idea to develop a sort of script that would allow us to frame our application and its features in a way that felt natural to viewers (rather than a video that just lists features for the sake of listing features). Essentially, Kristine and I would work as an actress and actor - she would act as a general rowing machine user in the gym, and I would act like someone who uses GoRo. Kristine would observe me using GoRo, and would ask various questions about how it works. Through this, we could effectively expose all our application features, while also making it flow quite smoothly.
We set up the camera and rowing equipment in the High-Performance Unit (HPU) at DkIT Sport. This took longer than expected, and it was already 10.45 am before we were ready to shoot (leaving us with only 45 minutes to record all of the shots for our demo!). We spent the next 45 minutes recording and re-recording various shots. There were a number of issues that arose from this (primarily, Kristine and I have no acting experience!). Additionally, we had no way to place the phone above the Concept2's Performance Monitor, meaning that we would have to place our phones on the steel bar between our feet. As a result of this, we had no way of effectively recording a video that showed both a person rowing and their phone screen (the rowing machine's chain and arm would block the screen's view as the rower rows). This was a planning oversight. Next, we discovered that the multiplayer game system was in an unusable state, meaning that we could not record multiplayer gameplay. Finally, we realised that we didn't have enough time to do a long stint of rowing, meaning that we could not get an effective shot of our reaction after completing a row. These issues upset our recording schedule, and meant that we were unable to record all of the shots that we needed. Regardless, we had recorded some content, and Andrew assured me that he would be able to make it work with the shots that we managed to record.
I spent some time working with Andrew to screen record the clips that we were not able to capture at DkIT Sport (essentially, I would use capture software to record my screen as I interacted with the game on my PC). My primary goal was to fill in the blanks where necessary to suit Andrew's edit. There were a number of issues that I needed to navigate when recording the video for the clip. Primary was consistency - the screen records that I capture needed to match up with the videos that Andrew captured at DkIT Sport. The most difficult aspect of this was updating the stats graph to closely match the graph in Andrew's video (thankfully, the stats in Andrew's video do not update, meaning that I would only have to do this once). I recorded some footage and passed it over to Andrew. Andrew reviewed my footage and requested a number of changes to ensure my content stayed consistent with his (such as updating the aspect ratio, editing room numbers in the multiplayer menu, switching camera positions, and ensuring that the timings of my button presses matched up Andrew's audio). This over and back of requesting and recording happened throughout the weekend, but eventually we got to a point at which we were both happy (Andrew was very accomodating throughout!). There are some still very evident consistency issues within the final promo video, but it was taking over two hours for each change that was requested (as we needed to chat about the change, record the change, upload/download the change, integrate the change, render the video, and upload/download the video for each change requested). We decided that the minor remaining inconsistencies were acceptable. Andrew and I had agreed on a price of 75EUR beforehand for the entire recording and editing process, and I paid him once the process was complete. The final edit can be viewed here!
Prepare Gameplay Content
The video which Andrew created was quite impressive, and it serves as a great promo video for our game. However, we weren't sure about how suitable it would be for the expo - our acting felt quite stiff and forced, and we never actually demo how the interaction between rowing machine and gameplay works. As such, I decided to spend more time recording extra content in various locations to help us demo our app. I didn't own any suitable recording equipment, so I asked my family for friends for help, and was able to source an extra Android phone (for recording multiplayer gameplay), a Nikon camera (for capturing footage of my using our game), and a tripod (for positioning the camera while I rowed). I also used some scrap material (brown tape, double-sided sticky tape, and a cardboard box) to develop a temporary phone holder that would allow me to position my mobile phone above the Performance Monitor while recording gameplay. I then reached out to Jonny from Zoo Fitness, and asked if I could come in over the weekend to record some gameplay. I travelled to Zoo Fitness on Sunday, and spent 1 hour and 30 minutes recording content for our game. To capture content, I used both the camera and tripod setup (to capture me using the machine), and the phone's built-in screen recorded (to capture gameplay). I needed to screen record some content for our multiplayer race, but I didn't have anyone there with me to use the other rowing machine. So, I decided to place their two rowing machines side-by-side so that I could row each machine with one hand to simulate the race - exhausting stuff! Overall, I was quite happy with the content that I recorded - an example output of this footage can be seen here.
However, an issue with the recording relating to how fluidly it worked with our presentation meant that I needed to re-record more gameplay while using a rowing machine. Unfortunately, this was only discovered the night before we were due to present our project. A late-night spin to Louth Physio in Dundalk allowed me to capture more suitable gameplay that would fit for our presentation (at least all of this recording was keeping me fit!).
I then worked with Kristine to record multiplayer race gameplay via our PC's (we decided to not use the screen-record footage that I captured in Zoo Fitness, because the footage was quite choppy at times). We both joined a game, set up a race, and rowed around the track. I captured the footage on my PC, and sent it over to Kristine for editing. We decided to splice in old footage of us rowing on the rowing machines in Zoo Fitness, to further demonstrate how our multiplayer gameplay works (unfortunately, we were unable to record updated footage due to various logistical constraints). While Kristine worked on the editing of the multiplayer gameplay demo (which turned out brilliantly!), I worked on editing the rowing demo, the features demo, and the trial demo.
Reflection
There were a number of recording and editing issues that arose throughout this week. These issues added stress to the already present pressure of our upcoming presentation and exam, which were both taking place on the same day. However, this was probably the best sprint of our entire project - we spent a lot of time together as a team, and for the first time, we were able to just play our game, without worrying about further development or enhancements. I think we were all quite impressed with what we have built, and we really enjoyed looking at all of our gameplay footage (particularly Kristine's multiplayer demo). It was an incredibly high-tempo end to our project, but I guess it was a fitting end to what was a massive achievement. Reflecting on our project now, it's hard to believe that two of our developers never even got a chance to test our game! What we managed to achieve is truly amazing.