rpg app demo
Since I was a kid, I have always played Fantasy RPGs (Role-Playing video-games). The first generation of Pokémon was my first RPG, however, more specifically related to Medieval Fantasy RPG, my passion started with a game called Runescape when I was around 10 years old and Mu Online: Continent of Legend later on with my friends. These are games based in a medieval era, where battles are fought using swords, bows and magic, in a open map to explore that also includes fantastic creatures such as elves, orcs and dragons.
The objective of these types of games is to do quests, fight monsters, earn experience to level up, gather items and craft more powerful weapons.
My passion didn't end in playing these games but evolved to start creating them myself. My first attempt was The World of Mu Evo, based in Mu Online, developed in Scratch when I was 18,. Then after learning Java at university I started working in a simplified game, The World of Mu, using that language.
Even though I have a strong passion for game development, I have always kept it as a hobby, working on my projects whenever I had time after class or work. Closer to finishing my undergrad, I learned how to develop Android applications and since then I have been focusing my gaming prototypes for such platform, finding ways of solving problems (such as game animation, AR, etc.) by myself without having attended any game development module or course.
My most recent project, this RPG App Demo (name TBC), is a new game I'm working on, similar to the Java version The World of Mu but developed for Android and entirely using Kotlin as the programming language. My objective was not to only work in a game but also apply the latest technologies used for Android development in order to increase my experience in them.
This is a RPG game that allows the user to fight monsters, gather items and level up to become more powerful to destroy the ultimate boss. More specifically, the user can:
- Create a character from one of 3 types: Warrior, Wizard or Archer.
- Select a monster from a region to attack (during the attack, between 1 and 5 monsters can appear).
- Status infliction to characters and monsters during battle, such as poison, freeze and stun.
- Earn experience, game currency and items after defeating the monsters.
- Random generation of items levels (more attack/defense power) and attributes (extra stat increases such as extra health and more experience earned when a monster is defeated, which are applied when the item is wielded).
- Random appearance of the Boss monster for that region which drops unique (special) items.
- Increase stats for Strength, Agility, Energy and Vitality (stat points are given every time the character levels up).
- Wield items such as weapons, armours, rings and pendants to increase attack and defense power.
- Use items from bag such as potions to restore health or mana points.
- Use potions during battle to restore health or mana points.
- Drop items that does not longer want.
- Buy and sell items to a merchant (NPC - Non-Player Character).
- Restore health and mana points using help from a NPC.
- Offline (local) storage of their progress.
This is an ongoing project and hasn't been published yet.
- Implemented in Kotlin
- Coroutines + LiveData
- Retrofit 2
- Dagger 2
The project follows the MVVM architecture implementing the Repository pattern to handle the communications between Database, Network and UI. There are 3 modules: app, rpgui and rpgcore.
Module holding all the UI related implementation: Application, Activities, Views, potential Services (not implemented here), etc.
Module holding all the UI related implementation that can be reused in other developments, in particular, Views.
Module holding all the app's 'backend' implementation: Database, Network calls.
Let's animate it!
This is a native android game implementation. Not Unity or GameMaker or Unreal - just pure Android using Kotlin.
I'm not sure if there are any Gaming libraries for Android out there, but I wanted a challenge and do everything myself (or most of it at least).
And one of those challenges means animation. Animate the characters and monsters, and their attacks instead of keeping everything in a static image. The approach I took was to work with Sprite Sheets. Sprite Sheets is a single image (e.g. png) holding each of the frames used for a sequence that we want to iterate. The idea is to get hold of one frame in that sequence, display it on the screen for a few milliseconds, and then change to the next frame repeating this execution endlessly. That way, we simulate a movement.
I created a custom View that does all the logic to create this sequential frame interchange by overriding the draw() method and working on the Canvas, displaying a 'window' of the Sprite Sheet that holds a single frame and moving that window to create the movement illusion. This way, I simply need to load this view in my Layout and pass the bitmap of the Sprite Sheet I want to animate.
I spent several hours working in the animation view logic, and then several days just putting the sprites together: I got a few sprites from Gravity for testing purposes and rearranged the frames in a way that are useful for my app (process that took me the 3 Lord of the Rings and 1 The Hobbit movies playing in the background while working on this edition - just so you know how long it took..).
It's all about UI
I'm not good in coming up with nice user interfaces, I must confess. When I don't have an amazing designer holding by back with gorgeous designs and water-mouth animations, I try to my best to make something that's good enough.
Overall, I'm quite happy with my Rewards dialog, which displays to the user the amount of experience earned after defeating all the monsters, a list of items dropped by them and the character's XP (experience) bar that is loaded with an animation.
When the XP bar is filled, a "Level Up" animation is also displayed, followed by the bar continuing to grow.
"Listening" to the character's health and magic
The game itself, even though visually simple, is on its core (and code) a bit complex. One of it most complex functionalities is to keep up with the data updates happening at different places at the same times and unrelated to each other.
An example is the character's display data about life (HP - hit points) and magic (MP - mana points). They are displayed at all times at the bottom of the screen on the region view and over the character's animation during battle. Why is this particularly complex? Character's HP and MP can be affected by different factors such as a monster attack, an NPC action or a status infliction (poison, for example).
We could have had some sort of code that executes every time any of these actions happen in order to update the HP and MP bars views. However, this can end up in a messy code being called everywhere and even repeating itself in several places (which goes against good practices and code quality).
Character's HP and MP are always stored and updated in the database at all times. Whenever a monster attacks the character or a poison status is inflicted, the subtraction of these values are done at a database level. This means (and following Android's view) that the database is the ultimate source of truth. Hence, in order to always have the character's latest HP and MP values, we simply need to 'listen' to the character's data stored in the database. And there's nothing better than using Room in combination with LiveData.
LiveData is very powerful for a reactive programming, and in a situation like this one, it simply suits perfectly: the Activity holding the views that display the HP and MP simply need to Observe a LiveData object holding the character stored in the database. This way, whenever the HP and MP are modified, the view will automatically get notified with this modification, regardless where this modification took place originally.
Have I gotten poisoned?
One of the biggest challenges I faced, which ended up in a rally of a few days worth of code, was the implementation of what I called 'Statuses'. Statuses are symptoms that are inflicted over a character or monster. These can be inflicted only once or can last for a period of time inflicting some value into the target's stats, and affecting the target in a positive or negative way. Some examples:
- A poison reduces their life by 5 points every second for a period of 5 seconds.
- A healer increases their life by 50 points one time when it is inflicted.
- A booster can increase the experience earned by 50% for a period of 10 minutes.
So the challenge was to write the code that applies the status (a character is now poisoned), inflicts the damage every x amount of time (a character now loses 5 HP due to the poison) and removes the status when the duration is over (after 5 seconds the character is no longer poisoned).
To solve this problem I worked with a sort of Service that's available all the time while the application is running. This service, called the StatusManager, is constantly listening to the statuses inflicted to any target in the system. It's role is to get all the statuses from database (and any future additions or deletions of such statuses), executing them (either once or schedule executions for the status duration) and removing them when they are over.
To do this, again I rely on LiveData to observe all the statuses that belong in the system (regardless if they are affecting a character or monster). When a new status is received, it's immediately executed. That means, it inflicts the damage or increase the stat right away (on a database level).
Then I check if the status is a single-time one or if it has a duration. In case it's the latter, I use Handlers to schedule the next time it should be executed. Else if it's the former or it has expired (time_of_execution_infliction + duration > Time.Now), I don't execute them any longer.
Moreover, I need to keep track of all the statuses that have already been executed and scheduled so that when my observer sends me all the statuses again, I don't re execute them.
Show me that I got poisoned!
After implementing the infliction of a status, I also needed to display this information to the user through the UI. The infliction of a status affects the target's stats (HP, MP, etc.) but those stats don't know how they were affected (the update from 250 to 245 HP indicate if it was due to a direct hit (monster attack) or due to the result of a poison status.
The way to solve the problem was to allow anyone interested in these effects to register to the StatusManager. This way, whenever the manager executes a status infliction, it will notify everyone registered to these events. This is a Listener that will receive an Event data object holding the values that they might be interested in: type of status inflicted (e.g. Poison) and the value inflicted (5). This way, the view that registers to these events can simply display a small floating message with "5" together with a skull image in a green colour when the target has been poisoned; or a "+133" with a potion image in blue to represent a healing status.