Monday, 4 May 2020

New website and blog

jdxdev.com/blog/

Moving to a new website and blog to allow for better customisability in the future.

The first post is about deterministic prototyping in Unity using the new data orientated technology stack.

Deterministic Prototyping in Unity DOTs



Saturday, 26 January 2019

Deterministic Unity RTS Dev

Deterministic Unity RTS Dev
I started creating a deterministic RTS in Unity in November 2017. I thought it would be fun & interesting project to recreate a ClashRoyale style game in a sci-fi setting with some additional features.

I knew from the start I wanted the game to be based around great multiplayer. I saw this as what I enjoy most in these sort of games both for the gameplay and the technical challenges involved in the dev process.

Here's a progress vlog for the first six months.



I was very impressed with how easy it was to get going with Unity. I’ve been developing games for over a decade but that doesn’t always mean it’s seamless to learn a new engine.

Getting started - determinism & multiplayer
I picked Photon TrueSync to help me get started with multiplayer. It’s been extremely useful even if I’ve not really used it in the intended way.

TrueSync is designed around a lock-step deterministic model where only the inputs are sent over the network. This was very appealing, however I didn’t want to be too hamstrung by their API. What I needed was something simpler where I could just use their tech purely for lockstep and networking and keep my simulation code separate.

I eventually ended up having a single TrueSyncBehaviour called TSPlayerInput. This allowed me to keep my own sim code fairly clean and not dependent on TrueSync.

public override void OnSyncedInput ()
{
    //
    // Write player inputs

    m_writeInputs.Clear ();
    m_simulationController.PollInput (ref m_writeInputs);

    if (m_writeInputs.m_spawns.Count > 0)
    {
        WriteCommands (m_writeInputs.m_spawns, SpawnCommandId);
    }

    if (m_writeInputs.m_emotes.Count > 0)
    {
        WriteCommands (m_writeInputs.m_emotes, EmoteCommandId);
    }

    TrueSyncInput.SetInt (CheckSumId, JDX.Checksum.Instance ().GetChecksumValue ());

}

public override void OnSyncedUpdate ()
{
    //
    // Read inputs for each player and update the sim if it's the last player

    int inputPlayerId = owner.Id;


    // Each player has it's own input lists separate to other players so it is necessary

    // to process each players inputs in OnSyncedUpdate.
    {
        ReadCommands (ref m_readInputs.m_spawns, SpawnCommandId);
        ReadCommands (ref m_readInputs.m_emotes, EmoteCommandId);
     
        m_simulationController.ReadInputs (inputPlayerId, m_readInputs);

        m_readInputs.Clear ();


        int playerFrameChecksum = TrueSyncInput.GetInt (CheckSumId);

        m_simulationController.SetPlayerChecksum (inputPlayerId, playerFrameChecksum);
    }
 

    // OnSyncedUpdate is called for every player on each device. We only want the simulation

    // to be run a single time on each device though. So only for the last player after ProcessInputs()
    // has been called for each player do we tick the sim controller.
    {
        int lastPlayerIndex = TrueSyncManager.Players.Count - 1;
        int lastPlayerId = TrueSyncManager.Players [lastPlayerIndex].Id;
        if ((inputPlayerId == lastPlayerId) || (inputPlayerId == OfflinePlayerId))
        {
            m_simulationController.FixedUpdateNetwork ();
        }
    }
}

Wednesday, 12 August 2015

OpenGL Multithreading Basics

Hacks are bad

I recently wanted to put some effects in to my latest project that required render targets, including a text glowing effect. On other occasions where I had needed render-to-texture I had been able to pre-cache it without much issue on the main thread but to create the glow effect in this way would have lead to a hack in my code and a bad taste in my mouth. I wanted the effect to be created and cached on one of my worker threads, ideally. I started looking into what would be required to allow multiple threads performing graphical operations asynchronously. It would also be beneficial and less restrictive, from a high level coding point of view, in the future to have this functionality in-place for other effects and graphical operations.

Pthreads to the rescue

The current Graphics module was not thread safe, mostly because OpenGL is not thread safe. I started writing a very simple and basic mutex locking system that wrapped pthreads around a more elegant and minimal API.

I littered my graphics code with critical section entry/exit points and began the process of working through all the deadlocks from where I had consecutive lock calls. This wasn't that difficult but it started to become less obvious which lock calls were calling the deadlock gradually. To make this go quicker I added some assert functionality to check that a lock call didn't precede a previous lock call without an unlock first. I used pthread_key values to store a variable to say if the thread was locked. Following the assert on 'already locked' the rest of the errors were solved within minutes.

OpenGL thread context management

Next was to fix the random OpenGL crashes and improper rendering I was seeing due to improper OpenGL context management. An OpenGL context can only be used on a single thread at a time. Graphics critical sections must lock the graphics mutex and then set the OpenGL context and then unset the context (by setting it to 0 on iOS) before unlocking the mutex.

If you do not manage the OpenGL context in this way crashes and odd random rendering issues will start to happen. My text rendering was not working until I wrapped calls to it with critical sections.

The OpenGL context was set and unset through critical sections and everything seemed to work but I was slightly unsatisfied with the sheer number of critical section entries and exits in my code. To somewhat clean and simplify the code and also cut down on the number of mutex operations, I altered functions to simply assert that I was in the graphics critical section. I moved the mutex lock/unlock to higher level code. I minimised the code a little further by using instances of a class where constructor and destructor made calls to enter and exit critical sections, if required.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
void JDXCriticalSection::Enter (JDXKeyedMutex &keyedMutex)
{
    ASSERT_MESSAGE (!GetEntered (keyedMutex), "Cannot enter critical section on the same thread having not not exited first.");
    
    int* thisThreadVal = (int*) pthread_getspecific (keyedMutex.m_threadKey);
    
    if (nullptr == thisThreadVal)
    {
        thisThreadVal = new int (1);
    }
    else
    {
        ASSERT ((*thisThreadVal) == 2);
    }
    
    *thisThreadVal = 1;
    pthread_setspecific (keyedMutex.m_threadKey, thisThreadVal);
    
    pthread_mutex_lock (&keyedMutex.m_mutex);
}


void JDXCriticalSection::Exit (JDXKeyedMutex &keyedMutex)
{
    ASSERT_MESSAGE (GetEntered (keyedMutex), "Cannot exit critical section that has not being entered on the same thread");
    
    int* thisThreadVal = (int*) pthread_getspecific (keyedMutex.m_threadKey);
    
    ASSERT ((*thisThreadVal) == 1);
    
    *thisThreadVal = 2;
    pthread_setspecific (keyedMutex.m_threadKey, thisThreadVal);
    
    pthread_mutex_unlock (&keyedMutex.m_mutex);
}


bool JDXCriticalSection::GetEntered (JDXKeyedMutex &keyedMutex)
{
    int* thisThreadVal = (int*) pthread_getspecific (keyedMutex.m_threadKey);
    
    bool threadValueNotSet = (thisThreadVal == nullptr);
    if (threadValueNotSet)
    {
        return false;
    }
    
    bool threadValueSetEntered = ((*thisThreadVal) == 1);
    if (threadValueSetEntered)
    {
        return true;
    }
    
    return false;
}

Sunday, 10 March 2013

Annihilator coming 21.03.13

Annihilator is a top-down action shooter for iOS devices and will be available from 21st March on the Appstore as a free download. Check it out!



Saturday, 2 March 2013

Mobile Game Shop Screen Design

With my latest game soon to be released it seems a good time to start the post-mortem on what went right and what went wrong. In this post I'm going to talk about some of my UI design and show the stages it went through from bad to polished and nice.

I found the design, layout and art style for the UI to be sometimes one of the most frustrating whilst also one of the most joyful parts of the development process. On mobile platforms the UI is so important. The appeal and look of it as well as the flow and control are vital to keeping users engaged. Frustrating the user in the initial menus is likely to stop them interacting with your app fairly quickly and can quite easily lead to a straight deletion from their device.

Below are some of stages the shop/profile screen went through in development. You can see the huge progress in layout, appearance, neatness, symmetry, consistency and my art UI skill development in general.

The first version of the profile screen with an old very simple green and black UI style, similar to in Annihilation Arena. The screen is pretty poor and unintuitive at this stage. The items you select are set on the left in the slider. The categories for these items are on the far right (attack, defence, special). The description and image of the item on the tank were to be displayed in the middle. Unordered and messy, it needed to be improved dramatically.



This design shows retina graphics with shine added to the buttons. I was having issues with sliders at this point so had opted for arrow buttons to move the set of displayed shop items up and down (this was a bad idea and eventually I switched to a slider).
The items are now in a larger block, there is no category selection and the buy/equip buttons are underneath the description of the current item and image of the ship with the new item.



The equip button is now removed and a tick is used to indicate which item button was last clicked to equip it. The buy button is in a box with the item information contained in to make it a bit clearer. The buttons texture and style are coming closer to the final design. The ship is visible to display the current items. The main problem at this stage is it's difficult to tell what the items are by the icons alone. The image items need to be more than clear, they need to make players want to click on them and find out more.

A major overhaul happened here. I wanted attractive and obvious item images so I blew them up with prettier pictures to look at. Of course now the images are way too big! Only three could fit on the screen at once. I was forced to switch back to the slider again to account for the larger item images. To buy items now you just clicked on their image and then a popup would prompt you. There is no room to display the ship.

More major changes. The players tank is now the main focus, showing off the currently selected item in full 3D. The items have been reduced in size but kept large enough to look nice and clear still. They are kept on a slider which obviously suits the iPhone very well. A less obvious change is the back and next buttons frame shape changing to point in a direction.





The final design is a fully integrated shop/profile screen (previous designs had these areas seperated). Item category is selected at the bottom between weapons/armour and specials. The camera moves around and views the ship from different angles dependant on the item category. Different information is displayed depending on the current category. In this shot we are viewing armour upgrade items and so we can see the currently equipped armours durability and other stats. Items that aren't yet purchased have their prices displayed inside their container and glow golden.

Friday, 21 September 2012

iPhone 5 GPU information from OpenGL


I hooked up my iPhone 5 to Xcode and printed out some renderer from the new device information.

Renderer: PowerVR SGX 543

Vendor: Imagination Technologies

Version: OpenGL ES-CM 1.1 IMGSGX543-73.16

Extensions:
GL_OES_blend_equation_separate
GL_OES_blend_func_separate
GL_OES_blend_subtract
GL_OES_compressed_paletted_texture
GL_OES_depth24
GL_OES_draw_texture
GL_OES_element_index_uint
GL_OES_fbo_render_mipmap
GL_OES_framebuffer_object
GL_OES_mapbuffer
GL_OES_matrix_palette
GL_OES_packed_depth_stencil
GL_OES_point_size_array
GL_OES_point_sprite
GL_OES_read_format
GL_OES_rgb8_rgba8
GL_OES_stencil_wrap
GL_OES_stencil8
GL_OES_texture_mirrored_repeat
GL_OES_vertex_array_object
GL_EXT_blend_minmax
GL_EXT_debug_label
GL_EXT_debug_marker
GL_EXT_discard_framebuffer
GL_EXT_map_buffer_range
GL_EXT_read_format_bgra
GL_EXT_texture_filter_anisotropic GL_EXT_texture_lod_bias
GL_EXT_texture_storage
GL_APPLE_copy_texture_levels
GL_APPLE_framebuffer_multisample
GL_APPLE_texture_2D_limited_npot
GL_APPLE_texture_format_BGRA8888
GL_APPLE_texture_max_level
GL_IMG_read_format
GL_IMG_texture_compression_pvrtc