Death-Mask Release!

Today I release Death-Mask game, based on Flame Steel Engine, Flame Steel Engine Game Toolkit and other cool technologies.

https://demensdeum.com/games/deathMask/

In this game you will play a role of Revil-Razorback, guy who wants to find mysterious artifact called the Death-Mask. This artifacts gives an eternal life to it’s owner. You will walk through endless techno-maze, and die many many times, before you find want you want.

There will be 3D models updates, minor gameplay changes in the distant future.

Death Mask – Cyber Fantasy Adventure in Endless Techno-Maze. Prepare to die!

0

Observer Pattern

The Observer pattern refers to behavioral patterns of design.
The pattern allows you to send an object state change to subscribers using a common interface.
Suppose we are developing a messenger for programmers, we have a chat screen in the application. When you receive a message with the text “problem” and “error” or “something is wrong”, you need to paint the error list screen and the settings screen in red.
Next, I will describe 2 options for solving the problem, the first is simple but extremely difficult to support, and the second is much more stable in support, but it requires skills! during initial implementation.

Common Bus

All implementations of the pattern contain sending messages when data changes, subscribing to messages, and further processing in methods. The shared bus option contains a single object (a singleton is usually used) that provides message dispatch to recipients.
Ease of implementation is as follows:

  1. The object sends an abstract message to the shared bus
  2. Another object subscribed to the shared bus catches the message and decides to process it or not.

One of the implementation options available from Apple (NSNotificationCenter subsystem), message header matching is added to the name of the method that is called by the recipient upon delivery.
The biggest drawback of this approach is that with a further change in the message, you will first need to remember and then manually edit all the places where it is processed, sent. There is a case of quick initial implementation, further long, complex support, requiring a knowledge base for correct operation.

Multicast Delegate

In this implementation, we will make the final class of the multicast delegate, as well as in the case with the common bus, objects can be subscribed to receive “messages” or “events”, however, the tasks of parsing and filtering messages are not assigned to the shoulders of objects. Instead, subscriber classes should implement the delegate’s multicast methods by which it notifies them.
This is realized by using the interfaces / protocols of the delegate, when changing the general interface, the application will stop compiling, at this point it will be necessary to change all the places of processing this message, without having to keep a separate knowledge base for remembering these places. Compiler is your friend.
In this approach, the team’s productivity is increased, since there is no need to write, store documentation, there is no need for a new developer to try to understand how a message is processed, its arguments, instead it works with a convenient and intuitive interface, so the documentation paradigm is implemented through code.
The multicast delegate itself is based on the delegate pattern, which I will write about in the next note.

References

https://refactoring.guru/design-patterns/observer

0

Proxy Pattern

Proxy pattern refers to structural design patterns.
The pattern describes the technique of working with the class through the class layer – proxy. The proxy allows you to change the functionality of the original class, with the ability to preserve the original behavior, while maintaining the original class interface.
Imagine the situation – in 2015, one of the countries of Western Europe decides to record all requests to the websites of users of the country, in order to improve statistics and an in-depth understanding of the political view of citizens.
Imagine a pseudo-code of naive gateway implementation used by citizens to access the Internet:


class InternetRouter {

    private let internet: Internet

    init(internet: Internet) {
        self.internet = internet
    }

    func handle(request: Request, from client: Client) -> Data {
        return self.internet.handle(request)
    }

}

In the above code, we create an Internet router class, with a pointer to an object that provides Internet access. When a client requests a site, we will return a response from the Internet.

Using the Proxy pattern and the singleton antipattern, add the logging functionality of the client name and URL:


class InternetRouterProxy {

    private let internetRouter: InternetRouter

    init(internet: Internet) {
        self.internetRouter = InternetRouter(internet: internet)
    }

    func handle(request: Request, from client: Client) -> Data {

        Logger.shared.log(“Client name: \(client.name), requested URL: \(request.URL)”)

        return self.internetRouter.handle(request: request, from: client)
    }

}

Due to saving the original InternetRouter interface in the InternetRouterProxy proxy class, it is enough to replace the initialization class from InternerRouter to its proxy, no more changes to the code base are required.

References

https://refactoring.guru/design-patterns/proxy

0

Death-Mask Wild Beta

Game Death-Mask goes into the status of a public beta (wild beta)
Reworked the main menu screen of the game, added a view of the blue zone of the techno-labyrinth, with pleasant music in the background.

Next, I plan to rework the gameplay controller, add smooth movement like in old shooters, high-quality 3D models of boxes, weapons, enemies, the ability to move to other levels of the techno-maze not only through portals (elevators, doors, fall through holes in the floor, holes in the walls), add a little variety to the environment of the generated maze. I will also work on the game balance.
Skeletal animation will be added at the polishing stage before release.
You can check public beta here:
http://demensdeum.com/games/deathMask/wildBeta/

0

Prototype Pattern

The pattern “Prototype” refers to the group of creational patterns.
Suppose we are developing applications for dating Tender, according to the business model, we have a paid opportunity to make copies of our own profile by changing the name automatically, and the order of photos in places. This is done in order for the user to be able to conduct several profiles at once with a different set of friends in the application.
By clicking on the button to create a copy of the profile, we need to implement a profile copy, auto name generation and re-sort the photos.
Pseudocode:


fun didPressOnCopyProfileButton() {
    let profileCopy = new Profile()
    profileCopy.name = generateRandomName()
    profileCopy.age = profile.age
    profileCopy.photos = profile.photos.randomize()
    storage.save(profileCopy)
}

Now imagine that other team members made a copy-paste copy code or invented it from scratch, and after that a new field was added – likes. This field stores the number of profile likes, now you need to update * all * of the place where copying takes place manually, adding a new field. It is very long and difficult to maintain the code, as in testing.
To solve this problem, a prototype design pattern was invented. Let’s create a common copying protocol, with the copy () method which returns a copy of the object with the required fields. After changing the entity fields, you will need to update only one copy() method, instead of manually searching and updating all places containing the copy code.

References

https://refactoring.guru/design-patterns/prototype

0

State machine and State pattern

In this article I will describe the use of the state machine, I will show a simple implementation, an implementation using the State pattern. It is worth mentioning that the use of the State pattern is undesirable in the presence of fewer than three states, since this usually leads to an excessive complication of the readability of the code, accompanying support problems – everything should be in moderation.

MEAACT PHOTO / STUART PRICE.

Flags Ruler

Suppose we are developing a video player screen for a civil aircraft media system, the player must be able to download the video stream, play it, allow the user to stop the download process, rewind and perform other ordinary operations for the player.
Suppose the player has cached the next chunk of the video stream, checked that there were enough chunks to play, began to play the fragment to the user and at the same time continues to download the next one.
At this point, the user rewinds to the middle of the video, so now you need to stop playing the current track, and start loading from a new position. However, there are situations in which this can not be done – the user can not control the playback of the video stream, while he is shown a video about safety in the air. Get the isSafetyVideoPlaying flag to check this situation.
Also, the system should be able to pause the current video and broadcast an alert from the captain of the vessel and crew through the player. Let’s get another flag isAnnouncementPlaying. Plus, there was a requirement not to pause the playback while displaying help on working with the player, one more flag isHelpPresenting.

Pseudocode:


class MediaPlayer {

    public var isHelpPresenting = false
    public var isCaching = false
    public var isMediaPlaying: Bool = false
    public var isAnnouncementPlaying = false
    public var isSafetyVideoPlaying = false

    public var currentMedia: Media = null

    fun play(media: Media) {

        if isMediaPlaying == false, isAnnouncementPlaying == false, isSafetyVideoPlaying == false {

            if isCaching == false {
                if isHelpPresenting == false {
                    media.playAfterHelpClosed()
                }
                else {
                    media.playAfterCaching()
                }
            }
    }

    fun pause() {
        if isAnnouncementPlaying == false, isSafetyVideoPlaying == false {
            currentMedia.pause()
        }
    }
}

The above example is poorly readable, such code is difficult to maintain due to the large variability (entropy) This example is based on my experience with the code base * of many * projects where the state machine was not used.
Each flag should “manage” the interface elements in a special way; the application’s business logic, the developer, adding the next flag, should be able to juggle them, checking and re-checking everything several times with all possible options.
Substituting into the formula “2 ^ number of flags” you can get 2 ^ 6 = 64 variants of application behavior for a total of 6 flags, all these combinations of flags will need to be checked and maintained manually.
From the developer’s side, adding a new functionality with this system looks like this:
– You need to add the ability to show the browser page of the airline, while it should be minimized as with movies, if the crew members announce something.
– Ok, I will. (Oh, damn, you’ll have to add one more flag, and double-check all the places where the flags intersect, that’s how much you need to change!)

Also a weak point of the flag system is making changes to the behavior of the application. It is very difficult to imagine how to quickly / flexibly change the behavior based on the checkboxes, if, after changing only one flag, you have to double-check everything. This approach to development leads to a lot of problems, loss of time and money.

Enter The Machine

If you take a good look at the flags, you can understand that in fact we are trying to handle specific processes taking place in the real world. We list them: the usual mode, the display of video security, broadcast messages from the captain or crew. For each process, a set of rules is known that change the behavior of the application.
According to the rules of the state machine, we will list all the processes as states in enum, add such a concept as state to the player code, implement the behavior based on the state, removing the combinations on the checkboxes. Thus, we reduce the options for testing to exactly the number of states.

Pseudocode:


enum MediaPlayerState {
	mediaPlaying,
	mediaCaching,
	crewSpeaking,
	safetyVideoPlaying,
	presentingHelp
}

class MediaPlayer {
	fun play(media: Media) {
		media.play()
	}

	func pause() {
		media.pause()
	}
}

class MediaPlayerStateMachine {
	public state: MediaPlayerState
	public mediaPlayer: MediaPlayer
	public currentMedia: Media

	//.. init (mediaPlayer) etc

	public fun set(state: MediaPlayerState) {
		switch state {
			case mediaPlaying:
				mediaPlayer.play(currentMedia)
			case mediaCaching, crewSpeaking,
			safetyVideoPlaying, presentingHelp:
				mediaPlayer.pause()
		}
	}
}

The huge difference between the flag system and the state machine lies in the logic state switching funnel in the set (state: ..) method, it allows you to translate the human understanding of the state into the program code, without having to play logic games to convert the flags to states with further support of the code .

State pattern

Next, I will show the difference between the naive implementation of the state machine and the state pattern. Imagine that you need to add 10 steits, as a result, the state machine class will grow to the size of a godobject, it will be difficult and expensive to maintain. Of course, this implementation is better than the flag, (if the flag system is shot first by the developer, and if not, after seeing 2 ^ 10 = 1024 variations, QA hangs, but if both of them * do not notice the complexity of the task, then the user who has the application just refuses to work with a certain combination of flags)
With a large number of states, it is necessary to use the State pattern.
We will issue a set of rules in the protocol of the State:


protocol State {
    func playMedia(media: Media, context: MediaPlayerContext)
    func shouldCacheMedia(context: MediaPlayerContext)
    func crewSpeaking(context: MediaPlayerContext)
    func safetyVideoPlaying(context:MediaPlayerContext)
    func presentHelp(context: MediaPlayerContext)
}

We will carry out the implementation of the set of rules in separate states, for example, the code of one state:


class CrewSpeakingState: State {
	func playMedia(context: MediaPlayerContext) {
		showWarning(“Can’ t play media - listen to announce!”)
	}

	func mediaCaching(context: MediaPlayerContext) {
		showActivityIndicator()
	}

	func crewSpeaking(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func safetyVideoPlaying(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func presentHelp(context: MediaPlayerContext) {
		showWarning(“Can’ t present help - listen to announce!”)
	}
}

Next, create a context with which each state will work, integrate the state machine:


final class MediaPlayerContext {
	private
	var state: State

	public fun set(state: State) {
		self.state = state
	}

	public fun play(media: Media) {
		state.play(media: media, context: this)
	}

	…
	Other events
}

Application components work with the context through public methods, the state objects themselves decide from which state to which to make the transition using the state machine inside the context.
Thus, we have implemented the God Object decomposition, and it will be much easier to maintain a changing state by tracking the changes in the protocol by the compiler, reducing the complexity of understanding the states due to reducing the number of lines of code and focusing on solving a specific state task. Also, you can now share teamwork, giving the implementation of a particular state to team members, without worrying about the need to “resolve” conflicts, which happens when working with one large class of state machines.

References

https://refactoring.guru/design-patterns/state

0

Skeletal Animation (part 1 – shader)

In this article, I will describe my understanding of skeletal animation, which is used in all modern 3D engines to animate characters, game environments, etc.
I will start the description from the most simple part – the vertex shader, because the whole way of the calculations, no matter how complicated it was, ends with the transfer of the prepared data to render in the vertex shader.

Skeletal animation after calculating the CPU enters the vertex shader.
I recall the vertex formula without skeletal animation:
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vertex;
For those who do not understand how this formula originated, you can read my article describing the principle of working with matrices to display 3D content in the context of OpenGL.
For the rest – the formula for the implementation of skeletal animation:
” vec4 animatedVertex = bone0matrix * vertex * bone0weight +”
“bone1matrix * vertex * bone1weight +”
“bone2matrix * vertex * bone2weight +”
“bone3matrix * vertex * bone3weight;\n”
” gl_Position = projectionMatrix * viewMatrix * modelMatrix * animatedVertex;\n”

Ie the final matrix of bone transformation is multiplied by the vertex and the weight of this matrix relative to the vertex. Each vertex can be animated with 4 bones, the force of the impact is regulated by the bone weight parameter, the sum of the effects should be equal to one.
What to do if the vertex is affected by less than 4 bones? It is necessary to divide the weight between them, the effect of the rest to be equal to zero.
Mathematically multiplying a weight by a matrix is ​​called “Multiplying a matrix by a scalar”. Multiplication by a scalar allows you to sum up the effects of the matrices on the final vertex.

The bone transformation matrixes themselves are passed in an array. Moreover, the array contains the matrix for the entire model as a whole, and not for each mesh separately.

But for each vertex the following information is transmitted separately:
– Matrix index that affects vertex
– The weight of the matrix that affects the vertex
More than one bone is transmitted, usually 4 bones per vertex are used.
Also the sum of the weights of the 4 bones should always be equal to one.
Next, consider how it looks in the shader.
Array of matrices:
“uniform mat4 bonesMatrices[kMaxBones];”

Information on the effect of 4 bones on each vertex:
“attribute vec2 bone0info;”
“attribute vec2 bone1info;”
“attribute vec2 bone2info;”
“attribute vec2 bone3info;”

vec2 – in the X coordinate, store the bone index (and translate to int in the shader), in the Y coordinate, the weight of the impact of the bone on the vertex. Why do you have to transfer this data in a two-dimensional vector? Because GLSL does not support the transfer of readable C structures with correct fields to the shader.

Below is an example of obtaining the necessary information from a vector, for further substitution in the formula animatedVertex:

“int bone0Index = int(bone0info.x);”
“float bone0weight = bone0info.y;”
“mat4 bone0matrix = bonesMatrices[bone0Index];”

“int bone1Index = int(bone1info.x);”
“float bone1weight = bone1info.y;”
“mat4 bone1matrix = bonesMatrices[bone1Index];”

“int bone2Index = int(bone2info.x);”
“float bone2weight = bone2info.y;”
“mat4 bone2matrix = bonesMatrices[bone2Index];”

“int bone3Index = int(bone3info.x);”
“float bone3weight = bone3info.y;”
“mat4 bone3matrix = bonesMatrices[bone3Index];”

Now the vertex structure that fills up on the CPU should look like this:
x, y, z, u, v, bone0index, bone0weight, bone1index, bone1weight, bone2index, bone2weight, bone3index, bone3weight

The structure of the vertex buffer is filled once at the time of loading the model, but the transformation matrices are transferred from the CPU to the shader at each rendering frame.

In the remaining parts, I will describe the principle of calculating animation on the CPU, before passing to the vertex shader, I will describe the tree of bones, the passage through the hierarchy of animation-model-node-mesh, matrix interpolation.

References

http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html

Source Code

https://gitlab.com/demensdeum/skeletal-animation

0

Template Method Pattern

The template method refers to behavioral design patterns. The pattern describes how to replace parts of class logic on demand, leaving the common part unchanged for posterity.

Cuban Cars

Suppose we are developing a bank client, consider the task of developing an authorization module – the user should be able to log in to the application using abstract login data.
The authorization module must be cross-platform, support different technologies of authorization and storage of encrypted data of different platforms. To implement the module, we choose the Kotlin cross-platform language using the abstract class (protocol) of the authorization module, we will write the implementation for the MyPhone phone:


class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    abstract fun authorize(authorizationData: AuthorizationData)
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
    override fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

Now for each phone / platform we will have to duplicate the code to send authorization to the server, there is a violation of the principle of DRY. The above example is very simple, there will be even more duplication in more complex classes. To eliminate code duplication, use the Template method pattern.
We will carry out the common parts of the module in immutable methods, we transfer the functionality of the transfer of encrypted data to specific classes of platforms:


class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    
    fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

References

https://refactoring.guru/design-patterns/template-method

Source code

https://gitlab.com/demensdeum/patterns/

0