Sunday, September 29, 2019

Back to the programming

It's finally time for a new blog post! So far this has been one blog a month... hopefully I can pick up the pace in the future.

Basically, I took a three week "break" from this project immediately after the last post. Not a good idea. I'm not completely new to the danger of "taking a break" from a project only to never return. Many of projects have gone that way, but this blog has really been a great motivator. So last week I decided it was time to get back into it, and to get something done. I spent two nights stressing over what the next step I should take and realised it would have to be the dialog system. So come Monday, I opened the Unity project and...

Began procrastinating.

But, I had made a start. I fiddled with some settings and scripts in-between watching Simpsons videos on Youtube. In about 3 hours of time I probably got 20 minutes of work done on the game, but it was STILL PROGRESS! I finally set up a local git repo for the main files in my project - which brought some much needed peace of mind and confidence when I started refactoring massive sections of my janky but functional scripts - and collected some more references. I did some other assorted house cleaning tasks as well, updating my version of visual studio and organising my reference images.

And then, the rest of the week, I just plowed through my scriptwork. I committed code every day of the week and made some solid and serious progress. I don't have much in the way of images to show for it, because it was script work after all. But the main thing I did was a basic dialog system and restructure of my NPC classes:


I now have a functional UI system too. You can walk up to an NPC and they'll say something to you on the screen. After a time the message will fade away. From a gameplay point of view I'm not sure about this system yet. It's not obvious where the text is actually coming from or who it's relating to from it's position. I might instead have to place the text about the NPC that is speaking. This will also allow me to have multiple NPCs "talking" at the same time, without player interaction.
When the player enters "conversation" with an NPC (where they will need to decide on a response and the game pauses) I will use a UI perhaps closer to this experimental one, where it takes up more of the screen and is obviously the main mode of interaction.

Either way, I had some interesting challenges and decisions to make during this development "sprint". For one, I wanted the text to fade out after some time, and as part of this to set a global "in_conversation" state to be false once the text was gone. It turns out Unity has a function for this: CrossFadeAlpha. You give the function a time and some alpha variables and hey presto it fades your graphic away! However; there is no callback or return for this function. It doesn't return a task, it doesn't even set a flag. It just "goes". I'm not sure at all why this function was written in this way, but it makes it completely impossible to work out when the fade is actually finished.

More revealing, while trying to solve this I found many online forum threads with other programmers trying to solve this problem. The solution I kept seeing come up was to check the "alpha" state of the graphic being faded every frame in the object's update function and react once it reached 0. This is just a terrible idea. Checking a random attribute every frame is a shocking anti pattern. To see it repeated so much online has me very worried about any of the advice or examples I'm seeing online.

In the end, I wrote my own IEnumerator function that fades a given graphic out given a time to do it in. This allowed me to have actual control over when the fade was done. However as part of writing this I had to rely pretty heavily on online examples and tutorials. IEnumerators and the yield keyword are very new and confusing to me. I ended up writing the dialog function like so

private IEnumerator RenderDialog (string message, int duration) {
    dialogText.text = message;
    yield return new WaitForSeconds(duration);
    yield return StartCoroutine(FadeOut(dialogText, 2));
    dialogText.text = "";
    dialogDisplayed = false;
}

Which freaked me out due to the two return statements. I'm still not yet used to yields, IEnumerators, and Coroutines, but it's fascinating learning such different programming styles (coming from Javascript's Async promises).


Finally I once again restructured my NPC class structure. Again, coming back into a highly structured language has been interesting. I'm re-learning all my old Java concepts in a new context and it's been a lot of fun.
This array is an array of "Interactables". An Interactable is any class that implements my "Interactble" interface, which at this point just means it has a "onInteract" function for the controller to run if the object has been activated. I have a class now GenericNPC that impliments this interface for NPCs to use. After some experimentation I ended up making this class Abstract, as a sort of midway between an interface and an class. It is a generic class for instances of "real" NPCs to extend, which contains basic NPC logic - such as the implementation of the "onInteract" function that fulfills the Interactable interface. Whew. This whole structure looks like this right now:

At the moment I have a huge global static state singleton that holds the gamestate. I've done this in the hopes it'll make saving/loading incredibly simple, and to ease global controller scope. As part of this, there is a global 2D array that is the size of the map, which contains every interactable object on the world at the slot in the array that corresponds to the slot in the map where it exists. So if I have a door at 8,12 on the map, the door object will be in the global interactables array at 8,12. This makes checking where things are pretty easy.




In this image you can see what really drove this structure right now: the need for a singular place where I define what an NPC should do (print dialog when interacted with), and the need to have instances of NPCS that actually contain the unique properties of each NPC (unique dialog). 

Obviously this system isn't done yet. For one, I'm probably going to need to make further layers of abstraction and inheritance in order to have multiple types of generic NPCs. For example the different factions will have their own generic NPC scripts that behave differently, and there may be different types of NPCs in general. 



That's probably enough on all that for now. I have some final thoughts though before I close, about the work I've done this week. I expected going into this project that I would enjoy switching between the art, music, story, and gamedesign rapidly and repeatedly, changing hat and working in all the areas maybe at the same time. Not so. I've found so far I've much preferred working only on the art for a week or so, and then only working on the programming another week. It seems to be much easier for me to just focus on a singular area for a reasonably extended time. I didn't find myself at all interested in working on the sprites or anything else this week while programming. 
This has me wondering if maybe I should try a sprint-style development method where I deliberately only work on a single aspect in cycles, perhaps only programming for a time and then switching to only doing art for a week, and so on. It might be a very productive and motivating work style. 

Finally, I did some more thinking about music (again). I'm starting to swing back around to making my own music again, but very minimalist and sparse. I'm considering producing music consisting completely of sampled sounds of the 60s/70s. I've found it very hard previously to edit audio to make it sound like it was from that era. It seems that the best way to get that authentic period sound would be to use actual recordings from then. Creating my own warped and disjointed sounds from original samples would be a very neat way to capture the era while also creating something new and tailored to this project. I'll keep thinking about it.

No comments:

Post a Comment