Hi, Demon Crush fans! Elias here to drop another engineering blog post (previously, I wrote about How a Calculator Enables Gameplay).
Today, I’ll write about how I compute game intensity to enhance Demon Crush’s game feel.
Game feel is difficult to define, and even more difficult to compute.
Why?
Because game feel is as much a computer science endeavor as it is an artistic undertaking. That said, I leveraged computer science concepts to enhance the game feel of Demon Crush by building an input-response fuzzy controls system. Specifically, I leveraged player input to scientifically compute game intensity, which Demon Crush uses to generate an “artistic” response.
At a basic level, input is how the player controls what happens in the game and response is how Demon Crush interprets and reacts to input. For example, the most basic interaction between input and response is Kenzo’s fluid movement when the player presses a specific set of inputs with their game controller.
Figure 1: Kenzo throws a kusarigama in response to player input.
If there is ever going to be an official definition for game feel, it will most probably lean on the basic control systems we design with input-response in mind. And when building the logic that drives the controls system responsible for implementing game feel, we often start by defining input predicates using first order logic.
Predicate Logic
First, a quick definition of predicate logic – technically, a predicate is a functional procedure that returns a truth value, and we typically write them as the following.
Predicate(P) ≜ FunctionalProcedure(P) ∧ Codomain(P) = bool
Where you can have n-ary predicates and n-ary operations, which is very useful when designing input-response control systems that deliver game feel. In the case of Kenzo’s kusarigama input-response control system, we can assume that it is a unary predicate, which is a predicate that takes one parameter (in this case, the “B” button input on the controller) to execute an operation, such that the definition looks like the following.
UnaryPredicate(P) ≜ FunctionalProcedure(P) ∧ UnaryFunction(P)
And the pseudocode for Kenzo’s kusarigama unary operation could look something like the following.
int32 IsBButtonPressed(int32 key)
{
if(compareChar(‘B’, key.toChar())) return 1; else return 0;
}
Now, let’s say you want the kusarigama to respond to a combination of inputs such that a binary predicate enables Kenzo to execute a binary operation, consisting of throwing the kusarigama before pulling an enemy combatant towards Kenzo, like in the following figure.
Figure 2: Kenzo uses the kusarigama to pull the enemy towards him to throw a strike.
In this case, the player inputs the “B” button press and holds the button for a specific amount of time, which could look like the following kusarigama binary operation.
std::function<int (int32)> kusarigamaCombo() {return [ ](int32 key, int32 time) { return key * time; };}
The great thing is that one can express a predicate in n-ary ways, which is what makes them so valuable when designing a control system that implements n-ary operations. And because combat is central to Demon Crush’s game feel, our Director (Richard, who also wrote a combat design article on Buffering and Canceling) spent a lot of engineering time to implement predicates for various operations – just take a moment to look at our technique scrolls to see how many incredible combinations you can execute when chaining attacks against an enemy force!
While developing the combat system, Richard reached out to me and asked if we could design a system that computes game intensity input so that we could use that intensity value to enhance the core game feel of Demon Crush – combat.
Fuzzy Logic
One of the use cases for computing game intensity is to dip the audio when the game is super intense, which enables players to focus on the combat that’s taking place on screen. Also, when we dip audio during intense moments of gameplay, we simulate what warriors go through in combat.
Because game feel is so difficult to define and because it’s subjective and full of nuance, I had to think of a different way to represent the inputs to generate a single intensity value that enables the game to produce a response that’s appropriate to the context of the game.
While predicate logic has its strengths when using classical set theory, such that Xa(x) = 1 if x ∈ A ∨ 0 if x ∉ A where A ⊆ X. And indeed, if you have to define a system where you have a series of discrete input-response controls, predicate logic is great at helping you define your controls, as with the combat input-response system that was built for Demon Crush. However, if you have a series of continuous input-response controls that need to be mapped to an intensity system, fuzzy logic presents a great framework to help define that control system because a fuzzy set A is a defined mapping where A:X → [0,1] where A(x) is the membership degree of x to the fuzzy set.
What does this all mean?
Basically, it means that for the most part, predicate logic effectively models linguistic certainty whereas fuzzy logic effectively models linguistic uncertainty. For example, if you look at the earlier example with Kenzo’s kusarigama, we are able to build n-ary predicates to model classical sets of the input-response controls we need to implement for the game’s combat control system. We can visualize predicate logic with the following picture.
Figure 3: Predicate logic of a player pressing the “B” button over time.
However, when it comes to defining what makes an intense moment, there’s a lot more linguistic uncertainty because there are differing degrees of intensity. For example, if we observe the example of the “B” button being pressed over time to deliver some kind of combo damage, we can hypothesize that intensity increases as the frequency of button presses increases over time. But how does someone define high intensity versus medium intensity or low intensity?
We can look at it visually with the following figure.
Figure 4: We can define how much the kusarigama combo damage contributes to intensity.
As shown in Figure 4, the mathematical framework of fuzzy logic helps us define a combo damage which equates to an intensity feeling that is linguistically low or medium or high (or somewhere in between) because one of the helpful properties of fuzzy logic is that every classical set is also a fuzzy set, and we can define A ⊆ X as its characteristic function μa(x) = 1 if x ∈ A ∨ 0 otherwise. This means that much like predicate logic and classical set theory, you get basic connectives in fuzzy logic and fuzzy set theory, such as complement, union, intersection, and inclusion – and of these connectives, I used intersections and unions a lot when building the intensity control system.
As a reminder, we denote intersection as C = A ∧ B where we let A, B ∈ θ(x), which is defined as C(x) = min{A(x),B(x)} = A(x) ∧ B(x), ∀x ∈ X.
And we denote union as C = A ∨ B where we let A, B ∈ θ(x), which is defined as C(x) = max{A(x),B(x)} = A(x) ∨ B(x), ∀x ∈ X.
Intensity System
To see this in action, here’s a recording of one of the first sets of data that was used as input into the intensity system.
Figure 5: This was an early test of ComboDamage over TimeRemaining.
And in those early days, we were mostly using low, medium, and high qualifiers to define the amount of intensity the player would feel because we only defined a couple of gameplay data inputs and a single intensity output. This is a screenshot of some old gameplay input data that fed the first crisp output of the intensity system, which we labeled KID_Intensity (KID stands for Kenzo_Intensity_Data).
Figure 6: These linguistic values were derived from thousands of hours of playtests.
And as the gameplay inputs got more and more complex, we moved away from triangular linguistic values to trapezoidal linguistic values that eventually gave us more granular data that fed into the intensity system, which is shown in the following footage.
Figure 7: We recorded hundreds of videos showing the KID_* values and intensity output.
We ultimately built the system to integrate lots of gameplay data to compute the intensity of the movement and combat in our game, which helps determine the overall intensity that the player feels in the input-response loop that is core to the experience of Demon Crush, which you can see in this recently recorded footage.
Figure 8: The intensity system responds to player input in real-time.
If you listen closely, you can hear the game play more intense music as you smash through enemies. To do this, we hook intensity data to craft how the game responds to player input.
Figure 9: Our Sound Designer (Chris Sabin) plays a test level to observe intensity in real-time.
In addition to the current use of modulating the audio in-game, we plan on continuing to extend the use of the intensity system, so keep that intensity up and see (or hear) how Demon Crush responds to your player input!
0 Comments