Thursday, November 28, 2024

Devlog 11 - Systems!

Well, I've rebuilt my systems, I tried to design a gentle onboarding process, and I have given myself the day tomorrow to add some fun stuff into a build that has been almost entirely focused on being non-frustrating and robust, and then it's testing time again. Let's go through what I have tried to address!

Feedback 

After my first round of wider testing I highlighted three comments that really spoke to me about where I could improve the game, so here's how I've tried to address them:

"A lot of vectors to "do" things and I fear it's confusing." Firstly, the number of vectors have been reduced. Some of the things that were being handled with two separate vectors are now handled with a single one, and some of the things that the player had to do manually are now being handled automatically by the game. Secondly, I have built a small section of game that is designed to introduce the player to the various vectors one at a time, so that they are not immediately confronted with a whole slew of confusing options. I suspect this may feel a little too pandering, that's okay, it's hard for me to tell from my position and so I can only design it the best I can and wait for feedback.

"I wouldn't want the game to feel like it has a systemic core underneath it, but in fact the solutions be very linear." I have made a point of introducing the player to the idea that there are multiple ways of solving puzzles within this small onboarding section. When the player solves a small obstacle with one method, the game checks which method they used and another character points out the other method that could have worked. This is a small thing, but I am hoping to convey to players that this game will have mechanical non-linearity. There may be "consequences"* to your actions - maybe you will use a resource that you would have not used otherwise with a different solution - but I want players to understand that they have options. Such is the entire point of building a systemic foundation.

"[ability] felt really good because it had absolutely no gameplay system connected to it. I did it just to feel good." Some of the stuff in this build (and some of the things I hope I have time to add tomorrow) will absolutely be "just for fun" stuff. I want players to feel satisfied by overcoming challenges, but I also want them to have some joy in just poking at this world and seeing what happens outside of those challenges. This is the thing that led me to enjoying adventure games as a kid, the satisfaction of solving puzzles didn't kick in for me until much later.

Coding

So obviously I have been doing lots of this the past few weeks, and I keep talking about "systems" and so I want to show roughly what I mean here with a simple example:

Rarely have I regretted anything as much as the space between words in the property "Max Energy", a sin I pay for every day and keep meaning to change.
Here's an example of one of the player's abilities, which is "Flux" - giving and taking power from devices in the world. It's the most straightforward of your abilities, offers the least amount of fun, creative design, but when used in combination with your other abilities it offers some interesting gameplay options. In the process of using this ability on a device I have to do a number of checks to see which party has how much power, how much power each party can hold, and trigger a variety of different animations and responses based on the outcomes of these checks. I also know that every time I use this ability the player will have to walk up to the device and touch it, and different devices will require that the player walks to different locations, faces different directions, and plays different animations. Some of these animations will require the player to reach below the bottom of their feet, meaning we have to account for an offset (I have done enough animation work with AGS to believe that I won't need an X offset, but I can add this easily enough if it does become necessary).

As you can see from the code above, I've automated this whole process via a series of extender function calls and custom properties. I have to make sure that I populate each new hotspot with information that will drive these functions, but this is very quick and simple to accomplish. Then, when I want to add this ability to the hotspot I simply use:


With this single function call all of my animations, calculations and fiddly little things are handled for me, keeping my room scripts nice and neat and saving me from having to remember coordinates and sprite numbers/animation loop numbers, and meaning that I only have to write each function once. It's been a little intense getting these various systems built after my years of being away from coding, but it's coming together nicely. We'll see if this is smooth enough in the upcoming test!

Design

Most of my design lately has been focused on being gentle and informative, to help guide players into the experience, but I have still been thinking about systems based design a fair bit in the background, and playing a wide variety of different games to see how designers think about challenges outside of the very discrete world of point & click adventure games. It's really interesting to break games down into their core systems and see the various factors involved, and sometimes see the challenges that present themselves. Here's an interesting problem I noticed this week, playing "Inventioneers", a very well made clone of The Incredible Machine for iPad. Here's a viable solution to a level, in which your goal is to put the snowman's head onto his body:


It's typical fare for the genre, the balloon lifts the head, the timer wakes up the blower chap who blows the hammer which swings around and knocks the balloon into the scissors which cuts the rope, etc etc etc. However, one of the obstacles is a wall of ice, which you can handily melt by putting a flamethrower chap in place and dropping a bauble on his head to make him shoot fire. Anything in front of him with the "ice" property will melt, the snowman's head can pass through. But, in action:


See how the snowman's head happily rolls directly into the blast of the flame? It doesn't melt. That's because the snowman's head is a "goal" object, not an "ice" object. And so the logic of the game breaks just slightly because an object that I know is ice is not acting like ice. Of course, I can understand why the developers of this lovely children's game didn't want to draw a horrid animation of a lovely snowman's head melting down to a withered carrot and charred top hat, but this illustrates the sort of thing that I will have to be careful to avoid in my own project if I don't want players thinking "No, I can't send the snowman's head through there, it'll melt" and them getting stuck without trying it as a solution. Much to think on!

Summary

It's been a busy week, and I wish I'd given myself another day for adding fun stuff and polish, but tomorrow will have to be enough and there's no point polishing something that absolutely needs testing before anything else. I'm not confident that I've solved all of the problems with my first build, but I do think that I've taken steps to address the majority of the non cosmetic concerns (the fonts are still awful, there are still many missing animations and things), and I will start making plans for what's next once I have gotten some more feedback from folks and have a better idea if I have made progress or not! Thanks for following along in the journey! :)

PS: If you want to critique my code here, feel free! I'm very much an amateur, looking to improve my abilities.

*I am approaching consequences here quite differently to how I usually see it done in adventure games. I do not plan to scold players for their decisions, to change the ending based on their choices, or to present choices as 'better' or 'worse'. Instead I am interested in allowing players to solve problems in multiple ways. This is inspired by my love of RPGs and the like which allow a fairly wide variety of options to solve problems like "get into the building". I am even planning to have things designed so that if you spent a resource (say an inventory item) in solving a puzzle one way, but then think of a different solution that either doesn't spend that resource or spends a different resource, to allow the player to go back and undo their solution and do things a different way. Think of games like Eye of the Beholder, where you could hold a pressure plate down with a sword if you had nothing else, but if you then find a rock through the door you just opened, you can go back, pick up your sword, and put the rock down in its place. Or put the rock down as well as the sword. Or maybe your party is all casters & thieves and nobody uses swords, but rock throwing is great for you in combat. Options! :)

8 comments:

Snarky said...

Not coding critique, but a tip: You're using the pattern SetProperty("PropertyName", GetProperty("PropertyName")+x) a lot, and it is quite verbose, with a risk of making a typo in one of the property names.

If it were me, I would write a "ChangeProperty" helper function (for each type: Character, Hotspot, etc.), along the lines of:

void ChangeProperty(this Character*, String propName, int change)
{
this.SetProperty(propName, this.GetProperty(propName) + change);
}

And then you'll be able to write things like player.ChangeProperty("Energy", -1); It only saves you a few keystrokes each time, but it adds up, and makes the code more readable.

Snarky said...

... And I'd also put something like

int thisEnergy = this.GetProperty("Energy");

once at the top of the function, so I could simply use that variable rather than look it up every time. (And the same thing for any other properties checked repeatedly.)

Ben304 said...

Great suggestions, thank you!

Anonymous said...

If this is C#, you can create properties with get and set functions that can alias to your Get/SetProperty functions

Example syntax:
public int Energy
{
get { return this.GetProperty("Energy"); }
set { this.SetProperty("Energy", value); }
}

Even though this means a ton more boilerplate code (one per property you want to use) you can make the actual logic much easier to read:
this.Energy = this.Energy - 1;
player.Energy = player.Energy + 1;

Snarky said...

This is written in AGS script, used by Adventure Game Studio, a free engine and framework for creating point-and-click adventure games.

What you describe is possible in AGS4 (still in alpha) using "extender properties," but not in AGS3 (the current release version) for predefined standard classes like Character and Hotspot.

Tlarhices said...

Hi, long time lurker but first time commenter.
Really like your work and enjoyed your series on the composition of classic game backgrounds.

But code review is my domain. I have never used AGS, so I will stay quite generic.

First, I would recommend leaving more space.
Put some space between different steps, it will be easier to feel the flow and read.
For instance, your function can be separated in 4 different steps:
1. Setup
2. Walk to hotspot
3. Energy transfer
4. Reset
So add some blank lines between those and it will be easier to read.

Then, if AGS allows you to create functions easily, I would recommend creating helper functions.
For instance this.hasEnergy() and this.isEnergyFull() would make the conditions much clearer.

Avoid duplicated code. For instance the while loop for givepower are duplicated at lines 112 and 136.
This brings the risk of tweaking/fixing one while forgetting the other. Make it a function that is called twice to avoid the issue.

At last, I would recommend always using curly brackets for if/else/while/... blocks. While it might feel a bit heavier at first, it makes the different blocks more explicit and avoids putting a statement outside of a condition when doing a last minute quick fix.

Keep up the good work.

Ben304 said...

Great suggestion, thank you! Snarky's mention of this being possible in AGS4 makes me think that this is definitely an avenue I want to investigate. I definitely do not mind a bit of boilerplate code if it means making things simpler later, and I love learning different methods of handling things!

Ben304 said...

This is such fantastic feedback, thank you!

Until very recently I absolutely always used curly braces for if/else/while blocks. However with this project I was starting to feel like my functions were getting extremely long, and that it was taking me a long time to navigate through my scripts because of it. But I do find this more compact method harder to parse quickly, and I've been debating whether it's superior or not. Really nice to hear your insights here.

Your helper functions definitely make sense. I already have quite a few of these, and they're easy enough to do in AGS, but I hadn't considered making functions for the purposes of error avoidance. Largely I have only been doing it as a means of saving myself time, and so for instances where a thing is only done 2-3 times, I hadn't considered that keeping it as a function call would be worthwhile. But your point is very well made, thank you!

I also like your point about sectioning up the function. I absolutely do this in functions where I'm doing math, and I'll split it up into each piece of the calculation, similar to your suggestion here. However I hadn't thought much about doing this for conditional logic.

Great stuff all around, thank you for the support and the suggestions!