Welcome to our third installment discussing how we built a dungeon crawler in Salesforce. If you haven't had a chance to try the game yet, we'd recommend you mosey on over there and give it a go first. The context will help. It's free to play and we'd love any and all feedback. Find it here: https://www.cloudpacific.tech/blockzero
The Start Menu
When we designed the game we didn't contemplate the start menu at all. We were wrapped up in designing the actual game play and how the player would progress, not character creation. Lesson learned. There were no massive setbacks but understanding the start-to-finish experience of the player was key and we started in the middle. For these reasons, our start menu could be a lot better. For one thing, the Start Menu is really plain. It's just a standard white background, nothing snazzy. We slapped a couple buttons on there, a graphic and called it done. It was rushed and if we had more time, we likely would restyle the entire Start Menu experience. We have scolded ourselves thoroughly here, and rest assured we will roll out an update to fix it in the future. That said, lets break down the page.
The menu page is actually a standard Community template page. The page is sub-sectioned into two columns, one column that takes up 2/3 of the page and a second column, on the right, takes up 1/3 of the page. On the left-hand side we have one custom component and on the right-hand side we have another custom component. We'll primarily be discussing the left-hand side component called CrawlerLaunch. It was built to do the following:
Allow players to create a new, customized, character.
Allow players to input their Private Key to retrieve their character.
Call the Dungeon component (the game).
We'll review all of these one-by-one.
Creating a New Character
When a player clicks 'New Game' we pop open a modal with 5 input fields. Anyone that has programmed a lightning component knows input fields very well. It has the exact same look and feel of a standard lightning form. Everything you see here uses standard lightning and/or Aura framework tags.
We allow the player to create the name of their character, which we store as an attribute. We also allow the player to assign a number of points to their character attributes.
SIDE NOTE: Try not to get confused Non-Dungeon Crawler Gamers. Attributes can be aura:attributes as well as the characteristics of a tag. Dungeon crawler characters also have characteristics (like strength, wisdom, dexterity, etc.) that are referred to as attributes. We apologize for the confusion, but it is what it is.
We set up the attributes as aura attributes as well, but of type picklist. This allowed us to make these clickable and to set number values to each of the picklist options. In the code it looks like this:
Below the attribute assignments, we have a value that dynamically changes called 'Attribute Points Left to Assign'. Essentially, as a player picks attribute values, it updates with a new recalculated value.
We wanted a character to start with a limited number of attribute points, so they could build up their character over time. The Start Menu limits those attributes to 8. If a player were to exceed that amount, the 'Attribute Points Left to Assign' number actually goes negative. This was intentional. We did consider disallowing a negative value here, but ultimately we believed it would be frustrating to players. We instead validate it when the player tries to create their character.
This is how the 'Attribute Points Left to Assign' value gets updated:
When a player selects a value from the picklist, it triggers the checkAttributes function onClick. The function just pulls the current values from the character attribute picklists, converts those values to integers, and then subtracts them all from 8 (the allowable number of starting attributes). Finally, we set the value of the attributesAvailable aura attribute (the 'Attribute Points Left to Assign' value).
Before we actually create a character, we want to ensure that the attributes that were selected fall within the limitations. So, if we were to all max out all of the character attributes to 5, we would have an 'Attribute Points Left to Assign' value of -20, well beyond what is allowable.
The code for the validation checks works as follows: When a player clicks the Create Character button, it calls the createCharacterController function, which does a number of checks before calling the createCharacterHelper function. Here is the code for the checks:
The code grabs the attributesAvailable aura attribute we have been calculating every time there was a change to the character attribute picklist values. We check to see what the value of that variable is. If is less that 0, then the player chose too many attributes, and we let them know. If the attributesAvailable is not equal to 0, the player did not assign all of their character attributes. Finally, we check to see if the nameInput (the input for character name) is blank. If it is, we require the player to enter a name.
If the input fields all meet required client-side validation requirements, we call the createCharacterHelper function which calls the database.
We set the apex controller method, createCharacter, and gather all of the character inputs, then we pass them to the server. The server responds by passing back the character. We then set the character attribute, the characterCells attribute, and a boolean attribute CharacterCreateSuccessful letting us know that the records were created successfully.
Okay, before we go further, we don't actually use the character or the characterCells attribute (currently). We just care that we have a Private Key (what we will later pass into the actual game) as well as an indicator that the player was created in the database or exists in the database. These other attributes were left over from an early iteration of the CrawlerLaunch component and we have kept them due to some other ideas we have. We didn't want you to be confused.
Now to what the apex is doing server-side...
In apex, we use the @AuraEnabled method to accept the character name and all of the attributes from the lightning component. The first thing we want to do is double-check the character attributes to make sure they were not hacked. If they were hacked, we reset the character attributes to 1 and change the character's name to 'Sad Hacker'. We can't have hackers jacking up our Score Board!
Next, we create the character, using our custom object crawler_character__c. We set the Name and the character attribute fields.
Next we want to create a random integer for the character record's Private key. For this we use the crypto class. We get the random integer and assign it to the Private_Key__c field. We also run a check on the random number to ensure it is unique across other characters. It's highly unlikely we'd have an overlap (less than one in a billion), but you never know. The crypto class is super powerful. It can do a lot. If you want to explore it more check it out here: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_restful_crypto.htm
Lastly, we commit the character to the database.
We have one last thing to do before returning the character record to the client. We need to create all of the cells for the character. If you've been reading all of our posts on the game development, you know that we have a Custom Metadata Type called cell and a Custom Object called cell. In summary, the MDT is used for defining the cells (spaces) in a map which are do not change. The Custom Object is use for defining how a particular character has interacted with a cell.
We loop through the total number of cells required for the game (100) creating each of them, assigning them to a list, and committing them all to the database.
Finally, we query the character in the database, along with all of the character cells and pass that data back to the client. Again, this could be greatly simplified by just passing back the character's Private Key to let the client know the character creation was successful.
The character is created and now we want to start our game.
Back in the CrawlerLaunch component, we successfully get the character record returned, and now we have access to the Start Game button, which calls the gotoGameController, which in turn calls the gotoGame function in the helper. This is the final step.
The gotoGame function gets the PrivateKey and passes it to a page within the community which has the Dungeon component sitting on it. The page redirects the user to the game, leaving the Start Menu, and the Dungeon component accepts a string variable (PrivateKey). From here the Dungeon component has everything it needs to get the character, set up the map, and let the player start the game!
Loading a Character
Loading is a great deal more simple.
We need to accept a Private Key in the Private Key input field and go search for that character in the database. If we successfully find the character, we pass back the character record with the Private Key, and now have everything we need to run the same gotoGame function like we did when creating a new character. Let's step through it in more detail.
We have a Load Game button as well as a Private Key text input field. The player enters in their Private Key and clicks the Load Game button which calls the loadGameController function.
The function gets the privateKeyInput input and checks that a value was entered. If there is no value, an alert() is triggered. Otherwise, we open the Load Game modal and call the loadGameHelper function.
The loadGameHelper gets the privateKeyInput field input and passes it to the getCharacter apex controller method. Just like when creating a new character, the server returns the character record and we set the character, characterCells and CharacterGetSuccessful attributes. Let's take a look at the apex.
In the getCharacter method, we get the character level first. We want to know this value, because we only want the character cells for the current level of the character. We don't want all character cells passing back at once.
Then, we get the character using a SOQL query which returns all of the fields from the character, all of the child cell records, and we filter the cell records by the character level. We also filter the character by the Private Key we passed into the method. Finally, we return the character record to the lightning component and set our attributes.
At this point, the process of loading the game is the same as creating a new character. We click the Resume Game button which calls the gotoGameController function which calls the gotoGame helper function that passes the Private Key of the character to the Dungeon lightning component and redirects the player to the /dungeon community page.
The two paths from Start Menu to game play now scale as levels are added. Not bad!
Thanks for joining us! Here is what we'll discuss next week:
Enemy Attacks (first)
Dynamic Hit Probabilities
Collecting Enemy Loot
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!