Tuesday 21 May 2013

Runes: Songs of the Gods - Version 0.9

Version 9 – Making new sexy runes, fixing the more-than-three selection bug (partially)

In this build I decided to destroy one of the two bugs that were really starting to get on my nerves. The fix was not perfect and thanks to my girlfriend testing the build I found out it was still broken after this build. It’s amazing how you can overlook things when you are swamped with details.

I take a look at the following in this build:
  • Remodel my rune assets with blender and reimport them (fixes the cubed runes collision bugs)
  • I work on debugging code, which slowly drives me insane

I took some deep breaths and opened up Blender again. I can never remember the shortcuts in that program so I spend more time fiddling with controls than actualy modelling. I managed to replace my cube models with a nicer looking rune model. I remade the UVmap and made sure the textures were right.

These new runes would fix the old bug where cubed runes would fall over, fly off the board or clip into each other.

I also added a new aura effect and made it look more frosty then shiny to add to the windy “up in the air” theme. After this I spent the rest of my time on this build debugging and trying to fix my rune selection code.

First fixing the array desync bug

This is how it was supposed to go. The goal was to be able to select at most 3 runes. Once the 3 runes were selected, they were locked in place as the compare code ran. The problem lied in what happens after. The way runes were being reset was after x amount of seconds for each rune individually. For example:

Rune 1 selected -> Rune 2 selected -> Rune 3 selected

Compare runs

Rune 1 deselected -> Rune 2 deselected -> Rune 3 deselected

This would be fine if the array mirrored the rune selection functionality but it didn’t at all. What happened in the array was much simpler. The array would be reset back to an empty state after a comparison was run.

The first bug that I worked out here was caused by the above design. After a comparison was run, and the array was reset the runes would start deselecting themselves one at a time. This would mean that even though two runes were still in a selected state, due to the array being empty the player would be able to select three more runes to be active. This would mean that the two runes would be added to the comparison’s selection effects even though they would not be in the array.

This made it possible for the player to select more than three runes. If the player continued on this track, due to the array being out of sync the player could eventually select all runes on the board and have them enter a selected state.

All Runes selected bug







To fix this issue I came up with the following possible solution. Either have the array mirror the runes or have the runes mirror the array. The easier option in terms of my code was to mirror the runes functionality.

I played around in my code for quite a few hours over the next few days to work out how to accomplish this. The final solution I managed was to change how the Runes reset themselves from the select state back into the unselected state.

How the select state was set:

The select / deselect state was called as part of a co-routine OnMouseDown() function. If the player clicked on a rune, the OnMouseDown() method would start the selected state. This would include freezing the rune in the place so it didn’t change, making the rune vibrate and activating the rune aura effects. It would then wait for a certain amount of seconds then deactivate again.

As each rune has their own states and own OnMouseDown co-routines, they were not working in sync. They were simply carrying out their select / deselect code after their respective timers were done. To get them to work in sync I realised I had to have them communicate with each other in some way.

How I solved the problem:

Once I realised this I started thinking about how the best way would be to go about making the runes communicate with each other. I knew I had all the pieces there, I just had to put them together. I looked to my runesaver code as that was the object I used to store all the runes that were selected.  I read through my runesaver code and came to the conclusion that the best way to get this communication done was to use the array. Since this was the one thing all three runes had in common. It was the one place they all met up accessing and sending data.




Now that I had a basic idea figured out I thought about what needed to be communicated. The runes could be selected separately whenever but had to deselect at the same time. The runes had to be deselected simultaneously – that was the communication required here. And they had to deselect as the array was reset.

I now knew that I had to use the selection functionality in the runes. I wanted them to reset their select state after three runes were selected and matched. Meaning that if the rune was clicked and there were three runes stored and the array. The array checked to see if they were a match and the runes would have to call the select / deselect functionality located in the OnMouseDown() co-routine. However the OnMouseDown() co-routine only works … well… on mouse down. This meant I had to restructure some of my rune code.

I had to check the array or listen to when the array was full. I knew then that the array was full meaning three runes were selected. The way I went about “listening” to the array was by making use of the Update() function.

The Update() function, which is a standard function in the Unity3D engine, fires off every frame. This means it is constantly (pretty much) running whatever code in inside it. I figured this was the perfect place to add a check to see if the array was full. I added the code to check if a rune was clicked and that the array[3] entry was not null. If this was the case I wanted to fire off the OnMouseDown()s code to deselect the runes. I copied out the deselect code and put it into a new function called DeactivateRunes(). I then called this function in the Update():

if(Isclicked == true && runesaver.getRuneThreeValue() != null){
transform.Translate(0,Random.Range(-2 * Time.deltaTime, 2 * Time.deltaTime),0); //wobble
    MakeParticle();
    PlayActiveSound();
    DeactivateRunes();
}

This worked but not amazingly well. Actually it worked to well. It was doing exactly as instructed. Once the array was full these functions all fired off multiple times in quick succession until the array was reset. Update was firing off the functions every frame. This was ok for the other functions as I put in Boolean toggles to prevent them from running amok but DeactivateRunes() did not have these. This resulted in the ActivateAura(); being called hundreds of times and this resulted in the selected runes having their auras being activated/deactivated over and over once per frame.

This looked like a really cool electricity effect but it wasn’t by intent. I quickly added in some Boolean toggles to ensure that DeactivateRunes() was only run once per check.

void DeactivateRunes(){
Freeze = false;
    Isclicked = false; //Added 18/04/2013
    IsStored = false;
    if(quicktoggle == true){
        ActivateAura();
        quicktoggle = false;
        runesaver.RemoveFromArray();
    }
}
This resulted in the runes being instantly deselected once the array was full. This made my heart leap, as they were in sync! But I didn’t think about leaving enough time for the graphic effects and sound effects to play through!

I forgot that I made OnMouseDown() an IEnumerator with a wait yield. I knew I needed to have the same wait yield functionality for my DeactivateRunes() but couldn’t figure out how to combine the IEnumerator with the engines built in Update(). Simply adding one in resulted in the DeactivateRunes() never being called as the Update would redo the wait command every frame. I read up on so many different IEnumerators solutions and facts. I even converted the example CoUpdate() function example on the Unity3D site from javascript into C# and just couldn’t get it to work for me the way they said it should.  It compiled fine but I was missing something.

I’d like to say I solved this problem quickly but I stumbled about in my code for the rest of the day before frustratingly calling it quits.

The next day I shut myself off from the human world and spent a few more hours trying to solve the problem in very complicated ways. Often getting my brain stuck in a logic loop. The main problem I was running into was trying to control the flow of my code in Update() and thinking of the different parts running independently on their own timers (of which there were two; the OnMouseDown() and the DeactivateRunes()) along with the Update() which ran per frame.

 I got fed up again and went out to grab a coffee; on my walk back I solved the problem and implemented it in under 5 minutes:

void Update(){
        if(Isclicked == true && runesaver.getRuneThreeValue() != null){
            transform.Translate(0,Random.Range(-2 * Time.deltaTime, 2 * Time.deltaTime),0);
            MakeParticle();
            PlayActiveSound();
            StartCoroutine(DeactivateRunes());
       }
}

IEnumerator DeactivateRunes(){
        yield return new WaitForSeconds(4F);
        Freeze = false;
        Isclicked = false; //Added 18/04/2013
        IsStored = false;
        if(quicktoggle == true){
            ActivateAura();
            quicktoggle = false;
            runesaver. RemoveFromArray ();
        }
    }

The solution was so simple I had to laugh at myself. Rather than call void DeactivateRunes() in Update() and trying to make Update() wait before continuing I could instead just keep Update() as it is. Instead I use it to initialise the co-routine, which uses its own timer. That way Update() can be on its merry way processing frames while my DeactivateRunes() does its thing!

This did cause another bug though, if less than three runes were selected then they would remain that way and not be deselected. Since the OnMouseDown() was responsible for also deselecting the runes, which I now removed into DeactivateRunes().
   
OnMouseDown() -> Selected -> waitXSeconds -> Deselect

I tried fixing this by keeping OnMouseDown() as an IEnumerator and simply calling the DeactivateRunes() after waiting x amount of seconds in OnMouseDown(). This was silly because DeactivateRunes() was an IEnumerator itself which meant I had to wait for both OnMouseDown() and DeactivateRunes() to yield.

I fixed this by adding deactivate code into the OnMouseDown(). A pretty hacky solution but one that got things working finally.

if(runesaver.getRuneThreeValue() == null){
Freeze = false;
    Isclicked = false; //Added 18/04/2013
    IsStored = false;
    if(quicktoggle == true){
        ActivateAura();
        quicktoggle = false;
        //runesaver.RemoveFromArray(); //Resets Array - name is misleading
        runesaver.RemoveRune(RuneID);
    }
}

So now I had runes that worked in sync with each other. Plus they would have their individual deselect states should they need to be reset after x amount of seconds. Everything seemed fixed. I made a build and tested it. Huzzah! It worked now! The bug was fixed! Or so I thought.

Another Desync issue

Dealing with the three different timers and getting everything to work at the right place at the right time kept me so busy I forgot one important fact. The reset array functionality, stupidly named RemoveFromArray(), worked as follows:

public void RemoveFromArray(){
for(int i = 0; i < 3; i++){
        RuneSet[i] = null;
        count = 0; //**NEW on 09/04/2013
        isMatch = false; //Added on 18/04/2013
        CheckArrayToggle = false;
    }
}

As you can see, it didn’t Remove any one thing from the array it reset the whole damn thing. So calling it RemoveFromArray only lead to me misunderstanding what it was doing. Lesson learnt there.

The bug here was pretty similar to the first one. Three runes would deselect themselves now as required. When one or two runes were selected they would deselect after the OnMouseDown() co-routines yield wait was done. The problem was that when one or two runes were selected they would reset one at a time and cause the same problem as before. The array would go out of sync by resetting all the runes in the array even though there may have been one still selected.





So the array resetting itself worked fine with 3 runes but not with 1 or 2 of them. To fix this I had to find a way to keep track of all three rune values and reset them one by one. If a preceding rune was reset and a player clicked on a new rune, the preceding rune slot in the array had to be filled. Not the next rune slot in the array.


To do this code wise I change the way I add values to the array. The important part here is the RemCount. Each rune gets their RemCount and uses it to store where it is in the Array.

    public void AddToArray(string x){
       
        if(RuneSet[0] == null){
            RuneSet[0] = x;
            RemCount = 0;
            count = 1;
        }
        else{
            if(RuneSet[1] == null){
                RuneSet[1] = x;
                RemCount = 1;
                count = 2;
            }
            else{
                if(RuneSet[2] == null){
                    RuneSet[2] = x;
                    RemCount = 2;
                    count = 3;
                }
            }
        }

Once the rune expires (meaning if there were less than three runes active) the rune will reset and remove itself from the Array via a set method:

runesaver.RemoveRune(RuneID);

RuneID here is derived from RemCount. Below the method removing the value from the array:

    public void RemoveRune(int y){
        RuneSet[y] = null;
    }

This now worked awesomely! I was super chuffed then I had my runes keeping track of themselves! Woo!

To Summarise:
  • Fixed the array desync bug, which finally gets done
  • Make new runes to fix the collision bugs and make things look better


No comments:

Post a Comment