We had a little extra time at Cloud Pacific during quarantine, so we decided to do something fun, different, and innovative. We built a dungeon crawler in Salesforce.
We won't make you scroll to the bottom. If you want to jump right into the game without any understanding of how or why we built a dungeon crawler using Salesforce, go to the link below. It's free. Please enjoy, and we'd love your feedback!
Let's first start off with a disclaimer: We are not breaking new ground here--not in gaming, not in the dungeon crawler genre, and not in programming. We didn't create a game that would keep you entertained for hours or tell a story that would be riveting. The game is a demo, showing what is possible on the platform, and how it can be used to build projects outside of the commercial space. We also are not game developers. We didn't create much in way of custom graphics or animations, and we aren't privy to any game development standards on how something like this would typically be built. We also understand there are likely platforms out there that would be much better suited for building such a game. We understand that and agree. We are just a handful of developers, with no game development backgrounds, that jumped right into it. There are likely much easier ways of building a game like this one. So please, be gentle, Internet.
That all being said, we do believe we have done something that hasn't been done before by creating a real game using nothing but Salesforce. This isn't tic-tac-toe.
For those of you that do not know what a dungeon crawler is... A dungeon crawler is a type of role-playing game (RPG) where heroes move through an environment (typically a dungeon), fight enemies, discover items, and complete quests.
Because the work we did isn't small (~2,500 lines of code), and because we want to provide a comprehensive review of the decisions we made (for better or worse), we are going to break it into several posts over several weeks.
The game needed to keep the players interest, so we wanted to make it somewhat involved. We wanted items that could be discovered, player stats that would make meaningful differences in the abilities of each character, and we didn't want it to be easy. But we were also doing this in our spare time, and couldn't do everything. This meant our grandiose ideas of what we wanted had to be scaled down to what we could feasibly achieve in a short period.
To make the hard decisions, we knew we needed a project deadline and a budget of hours. Ah hem... we went over that budget. Doesn't every game go over budget? Anyway, we decided on 4 weeks to architect, build (including sounds & graphics), test, debug, beta test, and deploy; and we decided it should take us no longer than 100 hours.
Hard decisions were made.
The original attribute types for our Character were changed from 7 to just 4. We dropped Dexterity, Perception and Luck.
We originally planned on creating 12 levels for a full game. What were we thinking!? We switched that to a single level demo, with a plan to build out an additional 2 levels in the future.
Dynamic maps were something that was a must when we started planning how the map would work. This was likely the hardest part of development, and we played around with this a lot. Ultimately, we went with a 10x10 grid that was fully dynamic and changed after a player interacted with it. More on this later.
We wanted the game playable on desktop and mobile, but the UI was a huge obstacle, especially for a dungeon crawler, and we abandoned the mobile design to focus on making the desktop design the best it could be with such limited time.
We originally planned a fairly lengthy story with up to 3 different story-line branches. That turned into a simple story with no story-line branches.
Much of game development is actually the development of game assets (things in the game). We decided there was just too much good free stuff out there that it made more sense to leverage the wonderful internet than create the assets ourselves. This saved us an enormous amount of time. Obviously, these had to be free, appropriately licensed and consistent, so we needed to find the right places to look. We spent a lot of time scouring the internet and found some AWESOME resources out there. Here are the highlights:
Enemy Icons: We used https://game-icons.net/. It is a repository of free-to-use icons specifically for games. Many of these are straight-up for the dungeon crawler genre. All we had to do was download the files and add them as static resources.
Location Images: We used two websites for these images. First we went to https://www.pickpik.com/ and found actual photos that represented the locations on the map. Then we went to https://www.befunky.com/ and used their filters to 'cartoonify' the images. Using the same filters across all images gave it a consistent and hand-crafted look (latte anyone?). Befunky requires a subscription fee, but the tools were just too good to pass up. We paid the $6.99/mo and dropped the monthly subscription a week later after we were done with it.
Sounds: We wanted unique sounds for everything--for every weapon, every enemy death, looting, moving, etc. Couldn't do it all, though. In the end we settled on three custom sounds (Basic Attack, Energy Attack and Enemy Death). But there are a lot of free sounds out there! We used https://opengameart.org/. It's free and has more than just sounds.
Music: We used https://freemusicarchive.org/. Tens of thousands of free songs to download and use as you wish. We went with instrumental works by Chad Crouch. Great stuff for old school games! The main music was pulled from here: https://freemusicarchive.org/music/Chad_Crouch/Arps.
One thing we learned during this process, was that storytellers have their work cut out for them. Coordinating a story with a development process is one of the hardest parts of creating a game. A story takes a lot, and I mean A LOT, of time to develop, and it doesn't guarantee the story with be interesting. Props to all the storytellers out there, especially those working for game studios. We tip our hat and share a beer in your honor.
We developed a very basic story to give the player some purpose to achieving the goal of the game--essentially, getting to one particular location on the grid map. That's it. The story, therefore, needed to be one of discovery with some basic conflict. We decided on a dystopian future, where automation has taken over, killed the economy and given rise to artificial super intelligence. Oh, and Bitcoin is the only currency because, hey, we're developers and stuff.
We added information along the journey that the player could find, including graffiti telling you where to go, an artificial sense for danger, and regions within the map with a story of their own.
In the end, this was basic, and yet, still a challenge.
We used nothing but Salesforce, but we leveraged quite a bit of Salesforce to make this happen. Here is the abridged component list:
Aura Component: Start Menu
Aura Component: Dungeon
Apex Test Class
Custom Metadata Type: Level
Custom Metadata Type: Cell
Custom Metadata Type: Bad Guy
Custom Metadata Type: Item
Custom Object: Character
Custom Object: Character Cell
Custom Object: Score
<lots of Custom Fields...>
<lots of Static Resources...>
I can hear some of you yelling 'Death to Aura!' I get it. We get it. We, frankly, are more comfortable with Aura. It's been the way to build on Lightning for some time. And while we agree LWC is preferred, for such a big project and such little time, we went with what we knew best. Feel free to voice your displeasure though. We know it's coming. We'll likely refactor it in the future.
From the start we knew we wanted to create something that could easily be customized by others. We wanted other creators to be able to take what we built and create new maps, new enemies, new items, new locations, completely unique story-lines, unlimited levels, and we wanted them to be able to do it all without having to write a single line of code. We also knew that the build would be mostly custom and would use little to no declarative automation tools. We did end up using some declarative tools. More on that later...
To build such a game, we knew that much of the data model would require Custom Metadata Types as much of the game would be unchanging. For instance, we knew that Cells (each space on the grid map) would always need to be present and static for the game. I'll explain more about the grid map later, but the basics of the map are a 10x10 grid. Each grid is a Level and each grid location is a Cell. Since each level had 100 cells related to it, we created a Metadata Relationship between them. There is never less than 100 cells and never more than 100 cells.
There can only be one Item per Cell and one Bad Guy per Cell. Here too, we created Metadata Relationships. There are some workarounds where one 'Bad Guy' could be a record called, say, 'Two Bandits' and then make the strength of 'Two Bandits' double that of another Bad Guy record called 'Bandit'. But I digress...
We toyed with the idea of making Item and Bad Guy custom objects, and we even built some of this out to test the idea, but we struggled with it because we didn't need an Item or Bad Guy to change. For instance, we didn't want the Energy Cannon (and Item) to get damaged and then be less powerful. Though, if we did want something like that, a custom object would have likely been the best option. We also didn't want Items to be randomly generated at any point during the game. So these were always the same. Since these Items and Bad Guys would be the same across all games and would be unchanging, we settled on Custom Metadata Types for both of them. Ultimately, this was done for simplicity.
Data Model: Custom Metadata Types
What did need to change was the character. Looking back we should have named this object Player, so lesson learned there. We also needed to track which cells on the grid map had been cleared by the character.
One of the unnecessary complexities of the code, as you'll later see, is that both Cells (custom metadata type) and Character Cells (custom object) were being used in the game play Lightning component to define and trigger game interactions. Looking back we could have greatly simplified the code by just creating the Character Cells off of the Cells when a new Character record was created and only used Character Cells in the game play Lightning component. That way we would have retained the standard map data for all players, while allowing cells to change when a player interacted with them. For instance, you want to only find a secret item once. So if you go back to the same location it shouldn't be there again.
We built the Score custom object as well which is completely standalone and is meant to hold a player name and their final score. That's it. It doesn't come into play until a player beats the game.
Data Model: Custom Objects
Security wasn't that difficult for this project because there was nothing truly sensitive we needed to secure in the database. For this reason, all of the custom objects have sharing settings of Read/Write for both Internal and External users. The Guest Profile for the blockZero Community also has access to all of the blockZero objects.
We would have preferred to offer a login page, where a player could click on a character and jump back into their game, but we knew we'd have to pay Salesforce's $5/user/month price for community users. So we needed to use a fully public community and needed an alternative way to retrieve a character as no login would be available.
Our alternative became the Private Key. If you hold or trade cryptocurrency, you'll know that the 'key' to your cryptocurrency is called a private key. We used this same concept for game retrieval, which matched up with the story we were trying to tell in the game. The way it works:
When a player creates a new character it creates a Character record in Salesforce. The Character record is created via Apex code.
The Apex code uses the crypto class to create a random integer, which is then assigned to a field (Private_Key__c) on the Character object.
When a player wants to retrieve their previous Character, they input the Private Key for that Character. The server is called and the Private Key is passed in. An Apex class gets the Character record with the matching Private Key.
There is one caveat: If someone gets their hands on your Private Key, then they have access to your Character. But this is the way cryptocurrency works, so we thought 'what the hell.' And again, we saved ourselves $5/user/month...
Over the next several weeks, we'll post about each development challenge we faced and how we went about tackling it. I'm laying out each topic below, but they may shift a little as we start writing each part to this series. Everything is interconnected, and so many decisions were influenced by other development tasks. Below are the topics I'll cover at length. Until then, happy coding!
The Grid Map
Buttons vs Keys
Shops, Taverns, Secret Locations (Items)
Attributes & Their Advantages
Strength (Attack Adders)
Endurance (Health Adders)
Intelligence (Learning Adders)
Charisma (Cheaper Prices)
Saving & Loading a Game
Enemy Attacks (first)
Dynamic Hit Probabilities
Collecting Enemy Items
User Interface & Mobile
A Basic Inventory System
To get started with Cloud Pacific, go here to complete a simple form. A Cloud Pacific Account Manager will collaborate with you to clarify your needs and goals, and customize a service package and roadmap that will help your business grow and thrive on Salesforce. Reach out to us here!