Games in Umbraco
Heads Up!
This article is several years old now, and much has happened since then, so please keep that in mind while reading it.
Those of you lucky enough to witness the quivering mess that was me on the main stage at Codegarden 2018, will be somewhat aware of what I've been up to in my spare time in the last year. I've been building a text-based MMORPG using Umbraco's CMS and services as an engine. I didn't give very much away while I was on stage as I was ill-prepared and nervous. So here we are today, to go over in a little more detail as to how I've been accomplishing this, without even having extended upon Umbraco's functionality.
When you think of the content hierarchy in the back office, you'd typically think of navigational parent/child relationships. For example: You'd have "Home > About Us" and each node would be a whole web-page. Where this typical structure still applies for some of the pages for my game, that's not always the case. I still have "Home" at the top, along with the first level of pages. But if you go into the "Game" child hierarchy, then how I'm using the content structure changes.
What you can see here is everything that is manageable within the game. Each of these content nodes is a component of the game. Everything on the Game page works by getting partials asynchronously dependent on user interaction, using Umbraco's own APIs and services. For example, if the user was to try and buy items, the partial would load into view and look under the "Equipment" node for all the available equipment types. Then the children for each of those equipment types are the equipment items that you can buy. The partial just loops through it all to display everything on page for you to choose from.
Please bear in mind that I haven't styled anything properly as of yet (still searching for a designer).
Here's a snippet of that partial:
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@using Game.Models;
@using Game.Helpers;
@{
IEnumerable<IPublishedContent> types = Umbraco.TypedContent(1227).Children().Where(x => x.Id != 7560);
IPublishedContent InitialItemList = types.FirstOrDefault();
BuyItemsModel buyType = new BuyItemsModel();
buyType.type = InitialItemList.Id.ToString();
IPublishedContent member = Umbraco.TypedMember(Members.GetCurrentMemberId());
int inventoryCount = CommonFunctions.CheckInventoryCount();
}
<div id="buy-items">
<div id="inventory-count">Capacity: @inventoryCount/@member.GetPropertyValue("inventoryCapacity")</div>
<div id="item-types">
<label for="item-type-select">Buy:</label>
<select id="item-type-select">
@{
foreach (IPublishedContent type in types)
{
<option value="@type.Id" @if (type.Id == InitialItemList.Id) { @("selected")}>@type.Name</option>
}
}
</select>
</div>
<div id="item-list">
@Html.Partial("BuyItemsList", buyType)
</div>
<div><a href="" class="back-to-main">Go Back.</a></div>
</div>
Simple enough right? Instead of using the hierarchy for whole pages, I'm using it to define parts of a single page. This same concept goes for all other components within the page too. Depending on your character's location, then different lists of monsters to attack would show. A character on the "Midgard" Plane will see a monster list for that plane, whereas someone in "Helheim" would see another list. All generated from these partials using the data in the above method. Your location is stored against your "Member" in the backoffice, and it just checks your plane and gets the correct list when rendering the partial.
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@{
IPublishedContent member = Umbraco.TypedMember(Members.GetCurrentMemberId());
IPublishedContent plane = member.GetPropertyValue<IPublishedContent>("plane");
IPublishedContent lastmonster = (member.HasValue("lastMonster") ? member.GetPropertyValue<IPublishedContent>("lastMonster") : null);
}
<div id="attack">
<form id="attack-form">
<label for="monster">Attack: </label>
<select id="monster">
<option disabled>--- Monsters ---</option>
@foreach (IPublishedContent monster in plane.Children(x => x.IsVisible())) {
<option value="@monster.Id"@if (lastmonster == monster) { @(" selected")}>@monster.Name</option>
}
</select>
<button id="attack-submit">Attack</button>
<button id="auto-attack-submit">Auto</button>
</form>
</div>
As you can see, it just gets the plane content node that's attached to the member and displays a list accordingly.
I'm doing it in this way, because there's many different uses for the content node. Like above, I attach a plane to the member according to their actions within the game. Then I can easily get that information to display information in other partials accordingly.
Next, I have the JavaScript (with jQuery because we all still love that, right?). Taking the above partial as an example, submitting the form for an attack. We take the monster's node id and true/false on if you click auto attack or manual attack (auto attack will automatically play out an attack every 5 seconds for a predetermined amount of times). When submitting an attack, an Ajax event is formed to Umbraco's surface controller, inside of which talks to an API controller to update the member with their new experience, gold etc... Then the surface controller returns a result of a partial to display the results of said attack. Throw it on the page and done.
$(document).ready(function () {
$('body').on('click', '#attack-submit', function (e) {
e.preventDefault();
performAttack($('#monster').val(), false);
});
});
function performAttack(monster, auto) {
clearTimers();
$.ajax({
method: 'GET',
url: '/umbraco/surface/Attack/Attack/',
data: { monsterid: monster, auto: auto}
})
.done(function (response) {
$('#action-wrap').html(response);
$('.auto-timer').each(function () {
var timeRemaining = $(this).data('timer');
startTimer(timeRemaining, $(this));
});
getPartial('Stats', '#stats-wrap');
getPartial('Skills', '#skills-wrap');
});
}
The method for attacking is rather large. There's checks in place to prevent creating intervals and looping attacks with JS, random chances to return a security test rather than performing the attack (fill out captcha within time limit), there's chances for different variants of the monster you're attacking, chances to spawn Beasts (boss monsters), gaining new skills, allsorts really! And I'm not going to show it to you. I don't want people to know the formulas used throughout to calculate damages, gains, losses and everything else that makes for fun of trying to figure it all out when trying to maximize the potential of your character. Besides, how else am I supposed to keep you on the edge of your seat whilst I drip feed you information?
Now, I do understand that this method of using content nodes might not be required for most instances of use, but it's slightly out of the box compared to expected usage. Maybe it'll come in handy to you one day, or maybe it wont. I'm just trying to get you to think differently.
Stay tuned. #BuildingGamesInUmbraco
P.S. I should have an alpha/beta version available by the end of the year, perhaps even by the time you read this! ;)
Dave Pearcey
Dave is on Twitter as @dave_pearcey