Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411283 Posts in 69325 Topics- by 58380 Members - Latest Member: bob1029

March 29, 2024, 05:34:10 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityDevLogsMenagerie ✧ A Magical Virtual Pet Garden
Pages: 1 [2]
Print
Author Topic: Menagerie ✧ A Magical Virtual Pet Garden  (Read 5124 times)
whistlerat
Level 1
*



View Profile
« Reply #20 on: August 06, 2018, 06:14:29 AM »

Yessss I was so happy to see a new post here! Your UI work and pixel art is already looking so great, I LOVE your monster-designing style. Really hitting the spot for me. It's super interesting to read how you're tackling the whole emotes/motivation/drives/all that good stuff, and it seems like you're on an excellent track. Those emote bubbles are everything. The idea of a monster having a bad experience and then wibble but then going either Cry or  Cool is such a great idea and I can't wait to see more Kiss
Logged

sand-bird
Level 0
**



View Profile
« Reply #21 on: August 06, 2018, 04:36:57 PM »

whistlerat, you are such an inspiration to me, seriously. Thank you so much ❤️

This is another design post, on some of the foundations of Menagerie's behavior system. It, uh, came out a bit longer than I anticipated. Giggle

---

Recently I've been reading the fantastic Wobbledogs devlog, and it really struck me how opposite my process is from ActualDog's. I'm an obsessive planner, which has been to my detriment more often than not. Partially, this is lack of experience, and I expect it'll get better with time. But I think it's also a natural tendency -- something I'll have to work on.

Menagerie's behavior system is so complex that it will both take forever to plan thoroughly and be highly dependent on iterative development anyway. And that makes the prospect of actually implementing it totally paralyzing to me. As a result, I've spent a ton of time goofing around with way less critical stuff like data loading and worldbuilding. It's high time to get back on track!

So I've been working on breaking behavior down to the simplest possible functionality. To start with: when a monster is dropped into a totally empty void of existential dread, what does it do?

Normally, monsters will check their surroundings for Entities (that is, Objects, Items, and Monsters) that they can interact with, generate a list of possible Actions they can perform with each one, then evaluate all those Actions based on a utility calculation. (Or at least, that's the idea.)

This is basically a freakish hybrid of a general utility-based system, where each Action has its own predefined utility formula based on the agent's current state, and a Sims-esque needs-based system, where Entities advertise the effects they might have on an agent's state and then the agent chooses the most lucrative result. (There's an important dichotomy here: present versus future. I think we will need aspects of both.) On top of that, Actions themselves should work like little behavior trees -- most of them are composed of other, simpler Actions, and complex ones like "play tag with <target>" might have a lot of logic inside.

(Then there's interrupt handling, which is super duper important for realistic creatures. I've got a plan for this, but I'm sure there will be a bunch of obnoxious cases I haven't thought of, so we'll have to see if it will actually hold up.)

Anyway, back to the void. There are always a few actions we can take that don't depend on other Entities. For now I've identified three: SLEEP, WANDER, and IDLE. (There is potentially a fourth, REST, though I might be able to finagle it as a subset of IDLE. Similarly, we should be able to WANDER at a run instead of a walk, but that also applies to the general case of movement.)

So, we're a monster in an empty void. How do we decide whether to SLEEP, WANDER, or IDLE?

...

(Here's where I fill another 7 pages of notes trying to answer that basic question.)

The important thing is that even in an empty void, a monster's personality should noticeably affect the way it behaves. In other words, the choice between SLEEP, WANDER, and IDLE must be nontrivially influenced by our traits.

I floated a bunch of stuff, like a BOREDOM drive and/or a CURIOSITY trait that affects how quickly the pet gets bored, and based on that how likely it is to WANDER around looking for stuff to do versus IDLE vacantly. But I think this might be overkill, at least for this situation. After all, in an empty void, just about any intelligent creature should trend toward maximum boredom.

What it comes down to, I think, is really just ENERGY. Some monsters (read: pets) will prefer to move about often, and others will prefer to sit there and stare out the window, or sleep for 20 hours a day. There really isn't much else you can do in a void.

I mentioned that traits affect drives. The EXTRAVERSION trait is a good example: in my notes, I wrote that it should affect the pet's desire to socialize and also the "amount" of socializing necessary to meet that desire. So you can have a pet that's oversocialized, which will make it unhappy, and this will happen more often for introverted pets.

Esseintally, here are two different kinds of effects on the SOCIAL drive: one, an ideal threshold, more than which is "too much" -- I call this the equilibrium. And two, the rate at which the drive changes. A drive is a meter that can be anywhere between full and empty; the more extraverted the monster, the faster its SOCIAL meter will decay, and the more socializing it will take to fill it again.

Most drives decay naturally with time -- monsters get hungrier, lonelier, and less happy. ENERGY is a little more complicated, since its rate and direction of "decay" depend on what the monster is doing. Its ENERGY should decrease slowly while it's walking and quickly while it's running; it should increase slowly while it's idling (or resting), and quickly while it's sleeping.

I had already planned for the "decay" rate of ENERGY to be modified by the monster's VIGOR trait, on top of the effect of whatever it's doing. High VIGOR makes a monster lose ENERGY slower and regain it faster. (Realistically, the fullness of a pet's BELLY should affect the decay rate too -- and all of these multipliers need to play nice together... Waaagh!)

But I didn't have anything affecting equilibrium of ENERGY -- it didn't occur to me, because I hadn't really thought about equilibrium that way yet. So I added PEP, a trait that determines how active your monster is, by affecting its ENERGY equilibrium. And suddenly a whole range of personality opens up, based around PEP and VIGOR:

A monster with low PEP will have a high ENERGY equilibrium, which means it's happiest when it has lots of energy to spare. If its VIGOR is high, it'll be somewhat active, since it can gain back whatever ENERGY it spends relatively quickly. But the lower its VIGOR, the harder it is to maintain that high equilibrium, and the lazier it will be.

A monster with high PEP needs to maintain a low level of ENERGY to be happy, so it will regularly be motivated to exercise. If its VIGOR and PEP are both high, it'll constantly be active -- it won't even need much sleep. If its VIGOR is low, it won't have to exercise often, but it will still have to do so consistently.

So why use two separate traits, you ask, when one is enough for SOCIAL?

Because even though all traits are supposed to be malleable, some should be more so than others. The human equivalent of VIGOR would be physical health, while the equivalent of PEP would be a preference for activity. One is way easier to change than the other, in the game as in real life -- VIGOR is naturally increased by doing cardio expending ENERGY. (This also means that a high PEP monster should naturally trend toward high VIGOR over time, which I think is pretty neat.)

"Vigor" is also more quantifiably useful than "pep", and that matters too. As a gameplay incentive for all the discipline stuff you can do, Menagerie has a framework for evaluating monsters: attributes, which are visible indicators of related groups of traits. VIGOR in particular contributes strongly to the attribute of VITALITY, along with the traits STRENGTH (improved through deadlifting certain activities) and SIZE (improved through good feeding). In fact, some traits mostly exist for the sake of attributes.

Attributes have their own relevance, of course, but that's a different topic...

(Edit: Oh, and by "visible" I mean, like, there's a section for attributes in the "monster details" menu, where you can check its birthday and preferences, edit its name, stuff like that. Not that its sprite grows huge muscles if its VITALITY is really high or something, although that would be rad.)
« Last Edit: August 06, 2018, 04:51:24 PM by sand-bird » Logged

sand-bird
Level 0
**



View Profile
« Reply #22 on: August 08, 2018, 08:08:25 PM »

time for a MATH POST I guess!

So, at this point, I'm pretty satisfied with my design for drives and traits. I think it's ready for implementation! There's just one problem left to solve: choosing values for these properties.

On the inside, of course, all this stuff is just numbers. The question is, what numbers best represent full (or empty, or X% full) ENERGY? What numbers best represent maximum, minimum, and "average" PEP?

As I see it, there are two possible paths: natural numbers, or real numbers.

A natural number range would be something like 0 to 100. This would restrict the property to discrete values -- a hundred of them, or however many numbers there are in the range. (I could use 0 to 1000, for example, if I needed more granularity.) This is the de facto choice for stats in most games, probably thanks to tabletop rules -- you know, you have 14 charisma, or your sword does 192 damage, or whatever. It's definitely more intuitive in that sense, which is something to consider for moddability. On the other hand, if you have a bunch of different ranges for different stuff, it could get messy and confusing.

A real number range would consist of all the values between (for example) 0 and 1. It isn't limited to a specific number of steps, so you can have as much or as little granularity as you want -- a monster's current ENERGY could be 0.54, which is equivalent to 54 out of 100, or it could be 0.54614327. I'm a little mistrustful of floating-point math, so I was hesitant to choose this route. But after thinking about it, I think it's the way to go.

Here's an example:

A game tick is equivalent to a twelfth of an in-game hour, or five in-game minutes, which means there are 288 ticks in a 24-hour day. (In real time, a tick takes about half a second.) I think this is a reasonable frequency for drive updates.

Say we're using a natural number range for ENERGY. Consider decay rate per tick: we can decay by any multiple of 1, or we can approximate fractions like "2/5 per tick" by subtracting 2 every 5 ticks. This would require a counter, so we know when 5 ticks are up. For balance, it makes sense to think of decay rates as "the number of ticks N that it will it take to drain X amount of this drive" -- in per-tick terms, that's X/N.

2/5 is a nice, friendly fraction. But since our range of possible VIGOR values needs to have a continuous effect on ENERGY's decay rate -- that is, the N ticks it will take to drain -- we're going to end up with a lot of Ns that don't play well with X, resulting in ugly fractions with large numbers on the top and bottom. These ugly fractions lead to really big updates at really big intervals. (I had some math written up to prove this out, but it was super boring, so I decided to spare you guys.) It would probably be workable with some rounding shenanigans, but I don't think it's worth the mess.


...Honestly, I wasn't sold on the real number range until just now. Writing this really helped! Smiley

All that remains is deciding which real number ranges to pick. 0 to 1 translates intuitively into percentages, which is nice. 0 to 2 works especially well for multipliers that have to play nice with each other, since the median is 1, which of course changes nothing when you multiply with it. And -1 to 1 is a nice representation of traits where  anything less than average actually signifies the "opposite" of the trait, like EXTRAVERSION and KINDNESS. I may end up using all these, or I might just pick one for consistency, since they can easily be transformed into each other anyway.
« Last Edit: August 08, 2018, 08:32:36 PM by sand-bird » Logged

sand-bird
Level 0
**



View Profile
« Reply #23 on: August 09, 2018, 11:00:46 PM »

After struggling with the drives hud for longer than I would've liked, I finally managed to get a look I'm somewhat happy with. So I thought I'd celebrate by sharing some WIP mockups! These are basically devoid of polish, but as the kids say...


I finished the postgame this week. Wizard

By "drives hud", I mean the part of the garden's HUD that comes up when you have a monster selected. This was my first attempt:



That bit of extra space on the right of each bar isn't just laziness. Originally, I wanted to have a little face next to the bar as an extra bit of feedback. I thought this would be a good way to communicate that a drive is "overfilled": it'd have a little overwhelmed expression. I scrapped this when I realized there just isn't enough room inside that panel.

Here's what I have now:



The vertical one is MOOD. Everything's a little bit smaller, and after actually filling some of the bars, I changed the shape of the segments to be vaguely reminiscent of the game's four-pointed star motif. The SOCIAL drive (bottom one) is supposed to be overfilled, so I made it red and added the little !! thing instead -- hopefully that'll be clear enough. Ingame, I also want to add a pulsing red outline to the bar itself.

The game's internal resolution is flexible up to (currently) 480x360 pixels maximum, or 320x196 minimum (before scaling, of course). These mockups show the minimum resolution, which is what I typically use for UI to make sure it all fits. My first few attempts at the drive bars took up way too much space, but I think it's in a good place now.

By the way, that background is not mine -- it's from here. This is what I mean by "devoid of polish" -- the style doesn't even match! But I've been using it as a placeholder since the very beginning, so I'm kinda fond of it at this point.

Moving on, here are some old menu screens:



The first menu page I drew, featuring the old Pufig sprite and palette! The plan is for those portraits to show whatever the monster is actually doing. I'm still happy with this layout, and it should work fine with the new assets.



The old calendar and requests screen. MAN these colors were ugly.



The new calendar! I managed to recycle that star pattern, but if you look close at it you can see it's not actually uniform. I still need to fix that before I can import and reuse it. Also, I realized I should probably do more design work on requests before trying to implement a UI for them. It'll probably be a tight squeeze to get it all on one page, but I'm okay with that.

The thing in the middle there is the month icon. You can also see it in the other bit of HUD in the garden, which shows the time, date, and some notification-type stuff. The month icons are done done, and I'm very happy with them:



(Tempest is actually the one with the wave in it; I just used the word because it's the longest month name. I'm sorry my mockups suck so bad.)



A not-so-old save list, featuring new panel assets for stuff that isn't part of the main menu. This interface is actually in the game and working except for the pagination (I'll get to it eventually). I also decided it would be cool if you could name your garden, so I added a field for that and moved the other stuff around to make room. It's more compact and nicer now.

Finally, the very first mockup:



This was made in Godot, before I had any idea how the engine's UI containers worked and also before I had any assets whatsoever. It's supposed to be the monster info screen, which is a modal that you pop up by selecting a monster and hitting "INFO" on its context menu. Speaking of which...



The "interaction" menu, also Made In Godot(tm), before I created the basic buttons. (After this one, I learned my lesson and started drawing mockups in GIMP.) All context menus are four-directional like this; if you're using a joypad or keyboard, you can pick actions really quickly just by pressing a direction. (This setting can also be disabled, so you have to press the direction and then hit the confirm button.)

I haven't really talked about UX, but hopefully the pictures convey a little bit of my goals here. Everything "clickable" must be an obviously-pressable button, for touch input. It also has to be selectable and intuitive to navigate to from other selectable things, for joypad and keyboard-only input -- of course, the selectors get hidden in touch input mode, just as some touch-specific stuff like "X" buttons should get hidden in other modes. This is one reason why everything paginates and nothing scrolls. (The other reason is that touch-friendly scrollbars take up way too much real estate. I actually tried it, but it just didn't work.) And that's the reason the menu is a book.

This was a fun post to share! I hope you enjoyed it too. Smiley
Logged

flex$
Level 2
**



View Profile
« Reply #24 on: August 11, 2018, 12:18:14 AM »

love these mockups and the look you're going for, keep pressing those unique fantasy elements and working hard Wizard
Logged

sand-bird
Level 0
**



View Profile
« Reply #25 on: August 12, 2018, 07:44:01 PM »

love these mockups and the look you're going for, keep pressing those unique fantasy elements and working hard Wizard

Hey, thanks! Always nice to hear. I'm definitely planning some unique-ish fantasy lore stuff (I know, I know, scope and all, but one can dream). Hopefully I'll get to talk about it someday Smiley


I've got a juicy ol' BIG POST TO POST, but since it's currently midnight on a Sunday and all, I'm gonna save it for a reasonable hour tomorrow literally three weeks later oops to see how that works out.

It's about attributes! and traits! How exciting! See you then!!
« Last Edit: September 04, 2018, 06:24:30 PM by sand-bird » Logged

sand-bird
Level 0
**



View Profile
« Reply #26 on: September 01, 2018, 07:00:10 PM »

Uh, heck, it's definitely not tomorrow anymore, is it. Well, better late than never, I always say! So without further ado:

ON ATTRIBUTES

There are six. I already talked a bit about VITALITY (VIT), which is governed by SIZE, STRENGTH, and VIGOR; the other five are...

---

INTELLIGENCE (INT) is governed by the traits IQ and LEARNING (and possibly CURIOSITY).

IQ is a particularly important trait, but also a very sticky one, meaning it's hard to change significantly. This isn't so much for realism as for practicality: IQ affects many different aspects of behavior, so it ends up having a disproportionate result on the personality of the monster. Of course, time and testing will be the judge, but I have a hunch that the more character-defining traits are best kept relatively static, to make the monsters' personalities seem more consistent.

For a while I couldn't come up with any other traits to govern INTELLIGENCE, and IQ is too static to work by itself. LEARNING is my answer to that -- it's a strictly accumulative trait, meaning it starts at 0 when a monster is born. (Most traits are generated based on a standard deviation from a universal mean and/or the parents' traits, depending on how heritable the trait is supposed to be. IQ is one example of a highly heritable trait.) LEARNING is pretty purely a "measurement" trait, though I'm not totally clear on what exactly it should be measuring just yet. I expect we'll figure out in time, though!

Highly accumulative "measurement" traits like LEARNING exist primarily to help their attribute improve over time. They're not as interesting as the more behavior-focused traits, but they do serve an important purpose.

---

CONSTITUTION (CON) is probably my favorite attribute, at least conceptually. It's governed by COMPOSURE (which I've talked about), PATIENCE, and (possibly) WILLPOWER.

PATIENCE is all about how the monster's other drives affect its MOOD. Monsters will try to keep their drives at equilibrium, but sometimes they can't -- like when there's no food available, or someone keeps bothering them when they're trying to sleep. Naturally, this is an unpleasant experience, and the monster will start taking mood hits once its "patience" runs out.

There are two main reasons it works this way, rather than, say, adding a multiplier to MOOD's decay rate depending on the other drives. First, it allows you to develop the monster's PATIENCE trait, and its CON attribute, by letting its needs go unmet until it almost -- but not quite! -- loses patience with you. (This is analogous to teaching it to trust that you'll always come through for it, which I think is a really powerful bit of interaction.) Second, by delaying the consequence of neglect (the mood hit) until after plenty of feedback has been given in the form of various "patience" emotes, the system should feel more fair in general, and be more accommodating to younger players. Sure, it might make tending to monsters' drives too "easy", but that kind of meter-management isn't a very compelling form of challenge anyway.

In my notes I defined WILLPOWER as "willingness to continue at a difficult or unpleasant task." Originally, I had this idea that monsters would be able to study various skills that would allow them to be sent out on certain requests, and this is what WILLPOWER was aimed at. But I couldn't come up with a way to make skill-learning interesting, so I scrapped it, and WILLPOWER was left without much of a purpose. I do like the concept, though, so hopefully I'll find a reason to keep it.

---

CHARM (CHA) is governed by CONFIDENCE, BEAUTY, and POISE.

CONFIDENCE is to EXTRAVERSION as VIGOR is to PEP: it represents the monster's "social fitness", and can be cultivated through practice, while (as every pop psych mag on the internet will tell you) EXTRAVERSION is basically an innate preference. Aside from providing a nice balance between sticky and accumulative aspects of sociability, the dichotomy allows for a monster with very high EXTRAVERSION and very low CONFIDENCE -- essentially an avoidant personality.

(By the way, EXTRAVERSION is a good example of a standard-deviation trait with very low heritability, in contrast to IQ.)

My reference for BEAUTY is the evaluation criteria for show dogs: essentially, how similar their proportions and features are to the "ideal" for the breed. Most of that is genetic, so I guess BEAUTY should be highly hereditary. But there's also stuff like grooming (do I actually want to implement grooming??), and, uh, good diets? I dunno. I feel that it's a pretty essential component of an attribute called CHARM, which in turn is a pretty essential aspect of how people evaluate animals, so I don't want to leave it out even if I'm not totally sure what to do with it.

On the plus side, out of all the traits, BEAUTY is the most relevant application for the other monster-enhancement systems -- potions, spells, special Objects that improve traits somehow, that kind of thing. So I'm okay with it being sort of an outlier.

POISE controls the effect of a monster's MOOD on its interactions with other monsters, and vice versa. I haven't fleshed out social actions as much as I'd like, so it's still pretty vague, but I think there's a solid foundation there. A high POISE monster will be less likely to take out its bad mood on others, and won't let negative interactions affect its MOOD as much. High POISE will also temper the effects of a couple other traits I'll describe shortly.

(POISE is highly accumulative, meaning it starts low and is meant to increase over the monster's lifetime, but it doesn't necessarily start all the way from 0. I like the idea of a somewhat hereditary starting point for POISE, as an analogy for "good breeding".)

---

AMIABILITY (AMI) -- it was the best name I could come up with, sorry -- is governed by a whopping four traits: HUMILITY, KINDNESS, EMPATHY, and AGGRESSION.

HUMILITY is a cool one. This was "ARROGANCE" until literally a second ago, but I'm really trying to stick with "positive" trait names, where generally more of it is better (sorry, introverts). Monsters are born as mostly-blank slates, but they quickly develop preferences for everything, including other monsters -- and HUMILITY (or lack thereof) is all about negative preferences for other monsters. There will be some obvious displays of arrogance, like a "sneering" emote, which you can discipline to develop your monster's HUMILITY.

There's a lot of other fun stuff you can do with this -- a monster with low HUMILITY and high IQ, for example, will be prone to dislike any monsters less intelligent than itself.

KINDNESS is pretty self-explanatory, I think. The higher a monster's KINDNESS, the more it enjoys making other monsters happy, and vice-versa. If some traits are able to have negative values, with a baseline of 0, this would be a good contender -- 0 would be total neutrality to others' MOODs, while negative KINDNESS would create a monster that prefers to see suffering.

EMPATHY affects how good the monster is at predicting the results of its actions on other monsters, and increases the effect that other monsters' MOODs have on its own. The thing is, this goes both ways -- that's really why EMPATHY is a separate trait from KINDNESS. In theory, you could make a sociopathic monster with high EMPATHY and rock-bottom KINDNESS -- its EMPATHY would make it great at torturing other monsters, and it'd enjoy it more, too!

I tried, but I just couldn't come up with a "positive" name for AGGRESSION. The concept for it was "makes the monster more likely to pick fights" -- not super interesting on its own, but its primary goal is to encourage tradeoffs between attributes. Fighting, or sparring, is a good way to develop STRENGTH and VIGOR; on the other hand, AGGRESSION contributes negatively to the AMI attribute, as you might expect. Sometimes, excellence in one attribute should come at the cost of another. I think fighting is also a pretty important type of interaction, in general -- it's a big part of animal behavior, after all.

---

Last but not least, SPIRIT (SPR) is governed by HAPPINESS, LOYALTY, and ACTUALIZATION.

SPR is special: it's a measure of how well you've raised your monster, which is arguably the most important feedback of all. Accordingly, 2/3 of the traits that govern SPR do nothing but govern SPR, though achieving a high SPR has some pretty nice perks.

HAPPINESS is the measure of your monster's MOOD over its lifetime. Once per day, at a random time (so it can't be gamed), the monster's current MOOD is added to its HAPPINESS total. I say added, since SPR should be purely accumulative, and this makes sense for HAPPINESS as a measure of a "long and happy life". That might be overkill, though, we'll see.

LOYALTY measures how much your monster respects, loves, and trusts you. This is arguably the most special of all the traits, since it has huge effects on the whole mechanic of discipline -- the higher the LOYALTY, the more effective your discipline is. Likewise, disciplining your monster affects its LOYALTY, as do lots of other things. It's also the only trait you can measure directly: it'll show up as Harvest Moon-style hearts on the "monster details" panel (at least, that's the plan).

Finally, ACTUALIZATION measures how much the monster's attributes have improved over its life. It's the ultimate accumulative trait! There's really not much else to say about it, but for a simple thing it should hopefully have a pretty big impact.

---

There are a few more traits that don't govern any attributes: EXTRAVERSION is one, APPETITE (which governs the decay rate of the BELLY drive) is another, and PEP probably belongs here as well. The last two are INDEPENDENCE, which affects how quickly the monster forms bonds with other monsters (a high preference for them, in system terms) and the player (that is, LOYALTY) -- and OPENNESS, which is lifted straight out of the Big 5 personality model. These are all very sticky, non-accumulative, character-defining traits; they wouldn't have contributed much to an attribute, anyway, so they ended up on the outside instead.
Logged

sand-bird
Level 0
**



View Profile
« Reply #27 on: September 04, 2018, 06:08:11 PM »

Gonna take a break from design posts to write a quick-ish "recent developments" update. (I also hope do this kind of thing more often; seems like it'd be pretty useful.)

So I've been working on (gasp!!) actually implementing behavior, inching my way toward something playable. Here's an ugly little gif that shows off some of it:



Roughly in order of appearance:

  • Save list updates: it was working before, but now it paginates and the saves are properly shown in most-recent order.

  • Behind the scenes, a whole bunch of UI-stack stuff (reminder to talk about this eventually - it's nothing groundbreaking, but I think it'd still make a neat post). It's pretty flexible and most if not all of the bugs are gone, so I'm almost happy with it, although I'm sure it'll still see plenty of improvements down the line.

  • Proper monster deserialization! That lil' dude is fully loaded from the save file. (Serialization is working too, including autosaving at the end of each in-game day, but there's currently a bug where it saves the time wrong. Whoops.)

  • Scrolling - this isn't new but I've never shown it off before, so I might as well mention it. The gif shows the two currently-implemented scroll types, edge scroll (like an RTS) and drag scroll (meant for touch input, but it works with mouse too). Still to-do are joystick scroll and key scroll, which shouldn't be too difficult.

  • Awful screen tearing?? Yeah, I dunno. I think it's the capture, please ignore Sad

  • Cursor behavior: I had implemented this a long time ago but needed to bring it up to date with other changes to get it working again. With every form of input besides touch, you control a cursor within the garden (of course, with touch, your finger is the cursor). When the cursor gets close to an interactible entity, it snaps to it, with the exact distance depending on the input mode (eg, more snap for joypad or key-only, since it's less precise than mouse input).*

  • Drives - they exist and they update properly over time! Currently, BELLY and ENERGY are just about fully functional. Delta ENERGY is calculated first, according to a positive or negative modifier specified in the monster's current Action (still placeholder for now), along with VIGOR; it's then used in the calculation for BELLY's decay rate, along with APPETITE. SOCIAL and MOOD are still to-do for now.

  • Pausing! Time only runs while you're in the garden and you don't have any menus open. The Godot engine has a handy pause functionality built in - it pauses every single node by default, including globals, though they can still be set to run individually. This is kind of a pain since we really only want to pause Time, the garden node, and its children, but - since a paused node does nothing whatsoever (including physics processing and all that) - I figure it's still the best option.

  • Inventory stuff! The gif is a terrible example, since that save has no items in it (oops), but inventory items and objects are now properly loaded from the player.save file, which stores only their id, quantity, and state information if they have any. Icons and descriptions and whatnot - static content - are then fetched from Data, assuming data for that id is actually found. If not, the game treats the item/object like it doesn't exist, which makes it possible to disable a mod that you possess items from without breaking anything.

  • Finally, data-driven monster animations! I dreaded this for a long time, but it was actually surprisingly painless. It's visible in the gif, technically - Pufig's idle animation is actually running, it's just... only one frame long right now. I'd like to make a whole post about this, I think. Hopefully soon!!

* While the cursor is snapped on an entity (I call this state "focused"), the status HUD is visible, and the cursor and camera will follow the entity around if it moves. If you press the ACTION BUTTON (left-click, or some button, or some key, depending on the input mode), the entity becomes "selected", and the interaction menu pops up. (In touch mode, tapping an entity once focuses it, and tapping it again selects it and brings up the interaction menu.) This paragraph is relegated to a footnote 'cause none of it except the HUD is actually implemented yet.

---

As of now, 100% of the groundwork necessary to implement ACTUAL BEHAVIORS (!!) is done. This means that the next thing I have to do is implement the actual behaviors. Architecture-wise, it is by far the most complicated part of the whole project, so I've been kind of freaking out about it for, like, basically this whole time.

So, uh, wish me luck, I guess! Coffee
Logged

sand-bird
Level 0
**



View Profile
« Reply #28 on: November 13, 2018, 11:20:29 PM »

Some of you might remember the post I wrote about data conditions some time ago. This is still easily my favorite part of the work I've done so far; I love thinking about that kind of stuff, so it's been a ton of fun to design and implement. Maybe too much fun -- whenever I spend too long on it, I tend to get caught up in pipe dreams about creative kids discovering the joy of programming for the first time through modding my silly little game. Realistically, of course, no matter how much documentation I manage to write or how much care I take designing the syntax, it'll probably still be inaccessible to most people. Bums me out sometimes, man.

But I'm still going to talk about it here, because this is my party and I'll ALIENATE EVERYONE IF I WANT TO Cool

So, conditions depend on data, and most data in Menagerie is in the form of collections. For example, the garden scene maintains collections of all the monsters, items, and objects inside it. Naturally, the player's inventory is also a collection. And all the data definitions that get loaded into memory when the game is started are stored in a collection as well.

Just about all of these collections are dictionaries -- also known as hashes, objects, structs, et cetera, but Godot uses "dictionary" so that's what I'll use -- where the keys are all the IDs of whatever the collection is about, and the values are the things themselves.

Conditions depend on data, and most data is in collections, so conditions need a way of looking at stuff that's in a collection. The first and simplest way is your standard get operation: you have a thing-what-has-keys, and you have a key, and it finds the key in your thing-what-has-keys and yanks out whatever value is associated with that key. It looks like this (remember, we're dealing with JSON files here):

Code:
{"get": [
  "some_collection",
  "my_id"
]}

But wait, that's nasty verbose. If you need to do a whole bunch of these it's gonna get ugly real quick. How about, like, a shorthand?

Code:
"some_collection.my_id"

Much better!

Now we can chain gets without wrecking our eyeballs. This is particularly useful when we're looking up data definitions, since the data object where that stuff lives is basically a mess of nested dictionaries. Menagerie's condition handler parses the string for what I call seperators (a portmanteau of "separator" and "operator" that will never not look like a shameful spelling error), and expands them to the longer form above. So the seperator form of our get operator is the humble period (.), while the expanded form is (*deep breath*) a dictionary with one key, "get", whose value is a two-element array where the first element is a string expression that evaluates to the collection or object containing the desired property and the second element is a string expression that evaluates to the identifier for that property.

(Yeah, get also works to fetch properties out of objects, as in Godot nodes created from GDScript classes. For our current purpose, a game object is basically just a dictionary of its properties, anyway.)

The astute reader will have noticed that I said seperators, plural. Actually, there are only two (for now), but the second one is a doozy. It's called map, and I'll tell you way more about it than you ever wanted to know... in the next post. Wink
Logged

sand-bird
Level 0
**



View Profile
« Reply #29 on: November 21, 2018, 07:40:19 PM »

To develop the syntax for conditions, particularly for shorthands like get's period form, I devised a bunch of use cases -- things conditions ought to be able to do -- and then implemented the operators necessary for those to work. As I came up with more and more complex use cases, I had to add more complex operators to make them work: first the basic comparison operators (to check if one value is equal to another, or larger or smaller), then get, then map, and then others I'll hopefully get to talk about later.

I ran into a very common situation in these use cases: when you have a collection of dictionaries or game objects, all the same, and you want to get a certain property out of each one.

For example, let's say we are designing a new monster. Monsters can evolve into other monsters when they're old enough, but only if certain requirements are met. I wrote about one such evolutionary requirement in the first post about conditions -- when Pufig is ready to evolve, it will be able to evolve into Pufine if its patience trait is higher than its aggressiveness trait, or Pufrit if the opposite is true. Any condition inside a monster's data definition has access to that monster's properties using the @ sigil (or property sigil), so the condition for Pufine looks like this:

Code:
{">": ["@patience", "@aggressiveness"]}

So far so good. But what about something a little more complicated? We want to create a monster Pikablu that can only evolve into another monster, Raiblu, if it's the favorite monster of another monster in the garden -- and we should be able to do that!

I'll walk through two variations of this concept: in one, a Pikablu can evolve into a Raiblu if any other monster in the garden considers the Pikablu its favorite. In the other, a Pikablu can only evolve into a Raiblu if its own favorite monster considers the Pikablu its favorite in return.

Can you guess which of these makes for the simpler condition? Actually, it's the second! I'll start with that one -- let's call it the "reciprocal favorite" example -- and ramp up to the first, and we will discover why this is so.

Advanced Condition Example 1: "Reciprocal Favorite"

Let's assume that the Monster class has a handy favorite_monster property, which we can trust to contain the unique ID of whichever monster ours likes best (the one it has the highest preference for, in game terms). Like any other local property, we can access it with the @ sigil:

Code:
"@favorite_monster"

(Thanks to Godot's setget functionality, "convenience" properties like this one -- where the data already exists within the game object (buried in @preferences, in this case) -- are easy to add, and don't require any messy duplication of that data. Hand Thumbs Up Right)

We can access the garden within a condition -- and, by extension, get its collection of the monsters currently inside it -- using the global sigil, $, like so:

Code:
"$garden.monsters"

This gives us a dictionary where each key is a monster's unique ID, and each value is a reference to that monster's actual game object, from which we can access its properties.

In this case, we're only interested in one particular element from the collection: our Pikablu's favorite monster, whose unique ID we can obtain from @favorite_monster. And since the collection $garden.monsters has unique IDs for its keys, we can chain gets with @favorite_monster to get our Pikablu's favorite monster's game object...

Code:
"$garden.monsters.@favorite_monster"

...from which we can then get its favorite_monster property...

Code:
"$garden.monsters.@favorite_monster.favorite_monster"

...which will give us the unique ID of some monster -- maybe our monster, maybe not. All we have to do now is compare it with our monster's own ID, which we can simply access using another @ sigil. Here's the complete condition:

Code:
{"==": [
  "@id",
  "$garden.monsters.@favorite_monster.favorite_monster"
]}

Advanced Condition Example 2: "General Favorite"

As we just saw, the "reciprocal favorite" use case can be done using only chained gets. Not so with the "general favorite" case, where a Pikablu can evolve into Raiblu if any other monster in the garden considers the Pikablu its favorite. Since we can't pinpoint a particular monster to look at, we'll need to be able to check the favorite_monster property of every monster in the garden.

Of course, the monsters in your garden (and their preferences) are variables -- they are constantly changing, and there's no way to define a condition that can get each of their IDs from $garden.monsters like we did in the last example. This, finally, is where map comes in. As of now, map takes a collection and a key, and it gets that key from every element in the collection.

The result is a new collection made up of all the values map found. So when we use map to get the favorite_monster out of every monster in the garden, like this...

Code:
{"map": [
  "$garden.monsters",
  "favorite_monster"
]}

...the result is a collection of unique IDs, each of which is the ID of some monster's favorite monster.

As with get, we can use a seperator -- in map's case, the colon (:) -- to simplify things a bit:

Code:
"$garden.monsters:favorite_monster"

And now, we can simply use the in operator to check if our Pikablu's unique ID is somewhere inside that collection. Again, here's the full condition:

Code:
{"in": [
  "@id",
  "$garden.monsters:favorite_monster"
]}

This stuff is already in the game, and it works perfectly. But there's a problem with the map I just described, as I discovered recently through a new use case -- not a hypothetical "let's create a monster" scenario this time, but an actual bit of configuration for the game's main menu. I'll talk about that in the next post, which should be the last in this series.
Logged

TonyManfredonia
Level 6
*



View Profile WWW
« Reply #30 on: November 26, 2018, 04:19:40 AM »

Just wanted to swing by to say that anything inspired by the Chao Garden is A+ in my book.

Looks great! Thanks for all of the details regarding attributes, etc., as well.
Logged

Composer | Orchestrator
Website
Twitter

Soundtracks include:
Kharon's Crypt
Call of Saregnar
Pages: 1 [2]
Print
Jump to:  

Theme orange-lt created by panic