Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Red Mike
Jul 11, 2011

Beelzebub posted:

I found this thread two months ago and decided to give Python a shot. So far this is what I've managed to accomplish with the Pyglet and Rabbyt modules.

http://youtu.be/F_R-WEK_Xfo
http://youtu.be/dXUTT9J_r7o

I plan on working in about 6 to 10 levels/zones with three acts per level. The bloody art sucks up most of the time. But I'm pretty dedicated to seeing this through.


Oh, hey. Fancy meeting you here. Looking forward to new videos of it.

I have a question for people who are familiar with OpenGL. Is there a way, library or drivers, to wrap native OpenGL calls such that you work and have a context in memory? I'm looking, basically, for a way to create a context and draw scenes on my server, but any attempt at OpenGL generally gives me errors, and I'm not sure how to go about doing it any other way. I understand the new OpenGL specs have a command for creating a context without a window, but the example code I've got doesn't work.

Adbot
ADBOT LOVES YOU

Red Mike
Jul 11, 2011
Oh brilliant, thanks. I'll see if I can manage to work it so that I can pass the context to Pyglet. Sadly, I'll probably need a Python wrapper for the thing, but I'll see what I can do.

Red Mike
Jul 11, 2011

Suran37 posted:

I quick question about roguelikes and Java.

I am interested in making a roguelike in java, but I can't decide between SDL or a curses implementation. Does anybody have any recommendations or experience?

SDL will definitely keep your project open to expansion. You can change it to a graphics-based one at any time with minimal code changing, while the curses implementation limits you to text.

First advice that comes to mind is 'Don't bite off more than you can chew.'. Roguelikes are a surprising amount of work, even short or coffeebreak ones.

If you happen to know Python/C/C++ or would want to learn, there's a great library that will take care of some of the dirty work and let you focus more on game logic.

Red Mike
Jul 11, 2011

Jaur posted:

I'm using PyGame to make a small platformer, it uses 32x32 tiles to build stages and stuff. I'm wondering what would be the best method for making the program able to scroll across a level that encompasses a larger area than the window. (Basically, I want to be able to have a camera focus on the player as she moves around the level). I have read a few things, but I'm having a hard time putting all of it together in a way I can actually translate to code. How would I go about creating some kind of camera that follows the main avatar sprite along a world bigger than the window?

This might not help at all, but it's worth a shot.

Red Mike
Jul 11, 2011

Top Bunk Wanker posted:

Well that cleared up all the issues

Knew that'd come back and bite me in the rear end. Remade the tutorial and illustrations, hopefully a bit clearer. Here you go.

Red Mike
Jul 11, 2011

westborn posted:

He clearly does know how the seed for the terrain generation works. He was asking how the spawnpoint is determined afterwards, as Minecraft has/had the tendency to not always spawn you at the same point on the same seed.

No, he clearly doesn't. Here:

quote:

If it's random, then it wouldn't be the same every time the same seed is used.

It's pseudo-random, and it uses the same seed, so it always spawns you at the same point on the same seed.

It just uses the generator as it does when it creates the terrain. This uses x numbers from the pseudo-random series. The next 3, x+1, x+2, and x+3 are used to generate the first location it tries to put the player on. These are always the same if you use the same seed, because of how pseudo-random generators work. He clearly does not understand this, or he wouldn't be asking why they're the same every time.

And yes, you misread and thought he was arguing that it doesn't spawn at the same point for the same seed, next time read a little further.

To answer you, it uses the first 3 numbers it gets after generating the world, then, if they aren't valid, increments each one by 1 until it gets a position that works. Considering the same seed will give you the same sequence of 3 numbers after world generation every time, obviously you'll get the same position for the same seed.

Red Mike
Jul 11, 2011
Actually, the biggest confusion came from the first post, the following part clearly showing lack of understanding of pseudo-random number generators, whatever way I look at the phrasing.

HaB posted:

Does anyone know the method by which Minecraft generates the initial player spawn based on the seed? All I can find from googling is a general "picks a location within a few hundred blocks of (0, 0) and tries to spawn, if it fails, it increments until it finds a valid location"

And that's all well and good except it's not random. If I start a world with a particular seed, open it up, check my spawn coords, then delete the world and re-create using the same seed, I get the same spawn point.

Unless you suddenly moved from that question to another, what you said then has no relevance to what you ended up with as a solution now.

So it doesn't just pull 3 numbers from the generator and plug them into the coordinate system, it pulls one number from the length of the list of biomes, then keeps iterating and incrementing until it reaches a grass block. This doesn't change the fact that if you did understand how a pseudo-random generator worked at the time, the fact that it wasn't different for the same seed shouldn't have surprised you at all.

Even moreso, it should have been expected, and the question would have become 'I don't understand how it's choosing the first random position from which it starts incrementing.'

Back to the actual topic at hand, the last two should be desert and tundra, if that helps.


e: Christ almighty, I'm being pedantic and unhelpful. I'll stop this, my apologies.

Red Mike fucked around with this message at 23:31 on Feb 18, 2012

Red Mike
Jul 11, 2011
Is XNA going 360 or Windows Phone only soon? The main site definitely makes it seem that way. In which case, what's a good alternative for 3D game development in C#?

Red Mike
Jul 11, 2011

poemdexter posted:

I haven't heard anything about it. I do know that the 360 and winphone are their main selling points for XNA to the point that when you download the VS bundle with XNA, it's called the windows phone dev kit or something. Aside from that, I haven't had any trouble with PC development or seen anyone raising flags about them stopping the PC side of development.

So I'm supposed to be downloading the windows phone dev kit then, since I can't really find any proper info on windows development on the site. Thanks.

Red Mike
Jul 11, 2011

Hubis posted:

Counterpoint: A straight-up clone is the best place to start if you want to do variations on a genre.

Countercounterpoint: That best place to start can be thought of as 'learning experience'. Once you're adding variations, it's no longer a clone.

Red Mike
Jul 11, 2011

The King of Swag posted:

• How do you come up with new projects? As I previously mentioned, my way of going about it is to come up with game ideas and then try to develop gameplay concepts that the game will be based around, but I feel like there might be much better ways to go about this.

Other ways, sure. Better ways, debatable. My own way is to come up with a simple game mechanic to start off. This need not be the only mechanic, or the main purpose of the game, just something to start everything else off of. For a Mario-like game, this'd be jumping in a gravity-influenced environment on 2D sidescroller view, for me. Note that it's a complex mechanic, but it's simple in the sense that in practice, it's literally just a boost to your dy, with a continual smaller drain on your dy, in my chosen case. Basically, I choose something I can wrap my mind around "in one go". Then, I start picking out other game mechanics that complement this. Anything that is roughly related and possible, I jot down. Then, when prototyping starts, I start checking off what isn't fun. Fun is the factor I'm testing the most when coming up with gameplay ideas.


quote:

• What do you do about assets? How do you gauge how much is too much and how many assets you can reasonably develop for your project?

To start off, programmer art. Once the base mechanics are more or less set in stone, and the superfluous mechanics are at least roughly settled, I try and figure out how much work needs to be done. If it feels like it's way too much, I do workarounds. This sidescroller would need a lot of frames of animation to permit targetting at any angle?! Only add a few angles and have it snap between the two. Or maybe animate different body parts separately. Many workarounds. If assets become "too much", you take a step back and decide whether you're going to force your way through, or take a workaround. For a recent Ludum Dare 48 hour game, I realised I wasn't going to have the time or skills to do a complex landscape/character and not have it look horrible. So, I decided to go with monochrome, two-colours-per-level stylized art, instead of realistic. It looks worse than it would have looked if I had all the time I needed and did realistic art, but it looks far better than leaving it as unfinished realistic art or programmer assets.

quote:

• I actually had a few more questions, but now that I get to it I'm drawing a blank, so please feel free to include or mention anything you feel is important or wish you had known when you started out.

Two things I've started recently to keep in mind regarding game development, especially on a deadline. Workarounds, and rewriting ideas. If an idea proves too hard to implement, or an asset doesn't seem to be coming out right at all, either work around it: implement it in a different way, choose a different art style/target for the asset; or worse case: cut the idea altogether (and take a step back and examine how the other mechanics will work afterwards), drop the asset altogether (and make adjustments as necessary to make the game still work).

Red Mike
Jul 11, 2011

Jewel posted:

Hi! I did exactly this, to the dot, but I moved to C++ with SFML. It's wwway better than pygame and I have no idea how I used pygame for everything before. C++ is encroaching on "favorite language" now too, somehow.

Seconding this. I'm still mid-transition, and don't feel confident enough with C++ to actually tackle a larger project, but it feels like I'm slowly moving completely off of Python for game development.

Relating to this, I've had two attempts so far to combine Python and C++. Basically, a C++ engine, with scripts available for each object, and a console object interpreter to allow running of arbitrary code. Failed to even get it to work the first time, and only managed to get the interpreter to work the second time, and that barely. Does anyone have any good references on this, that aren't in Python's official docs?

Red Mike
Jul 11, 2011

Jewel posted:

I realllllly wanted to try doing this, because I adore how Garry's Mod and Cortex Command does it for lua. I'll probably end up using lua in the end anyway but I just really love being able to make every object in the game via scripting which leaves it hugely open to modding and easily changing stuff. Also reloading objects while ingame.

If my third attempt fails, I'm most likely going to go with Lua as well. Most guides I found to getting Lua going are straight-forward, as opposed to the Python ones.

Relating to Python, a suggestion I keep receiving is to use Boost.Python, however going through the documentation briefly, it seems to be for allowing C++ function calls from Python, rather than the other way around.

Red Mike
Jul 11, 2011

The Gripper posted:

You'll want to look at the docs for embedding instead of extending (applies in general, not just to Boost.Python)

:doh:

There it is, thanks. Going to give this another shot now.

Red Mike
Jul 11, 2011

Pollyanna posted:

I'm looking to do something new for my next app, and I want to try learning web game development. Is there a recommended beginner framework, or a well-liked tutorial website?

I quite like Pixi.JS for rendering, or Phaser for a full game framework (built on Pixi.JS for rendering).

Red Mike
Jul 11, 2011

xzzy posted:

This seems like a decision Unity is gonna regret.

https://arstechnica.com/gaming/2019/01/unity-engine-tos-change-makes-cloud-based-spatialos-games-illegal/

It's obvious why they're doing it, they want to get in on that sweet cloud money, but this seems a bit heavy handed.

From what I can tell, this is being blown way out of proportion. It sounds like someone generalised their terms too much and what they were trying to target is what SpatialOS is offering:

SpatialOS host Unity processes themselves in the cloud, running their own licenses.
Those Unity processes take third-party code from SpatialOS clients.
That third-party code was written by non-SpatialOS game developers potentially using free licenses of Unity, but benefit from SpatialOS's license.

The alternative to that is that they offer some manner of SDK, and the non-SpatialOS game developers build a Unity application of their own using that SDK, then host it themselves in the cloud/wherever.

The problem is that the actual hosting in the cloud/wherever is currently also being blocked, but it just sounds like an accident in wording it, not intentional money-grabbing.

Red Mike
Jul 11, 2011

OneEightHundred posted:

Benefit how though? The runtime is the same for all tiers, and the developers hosting their stuff on SpatialOS still need the same licenses they would if they were hosting it themselves.

They don't need the same license, because they're effectively sending SpatialOS code that the SpatialOS guys run on their own Unity instances. Basically, you wouldn't be allowed to use the free version of Unity (or a Pro trial) to write up a codebase that you then build on someone else's Pro version. The same applies if people write a framework that runs third party code as a platform. They've now clarified this.

The blog post makes it clear that it's what I said. You can't have a platform offering fully hosted Unity runtimes that run third party code from other developers without talking to Unity. This was the case before and is now clarified in the ToS. No-one hosting their own gameserver is affected, because that's you hosting your own code in a Unity runtime, not you taking code from other people (not covered by your license) and then hosting it under your own license.

It's just community overreaction as usual.

Red Mike
Jul 11, 2011
Instantiating objects (especially something like that) is generally slow and you want to avoid it during regular play. Pre-load them during expected slow times (loading screen, initial game setup) and deal with the possibility of wasted loading if they're not actually needed.

If you're trying to keep your game data-driven and not make your scenes embed the data and duplicate the infrastructure, that doesn't exclude having multiple scenes. Besides the fact that you can additively load scenes (load everything in a new scene on top of what you currently have), you can also have objects that survive scene changes (DontDestroyOnLoad), ScriptableObjects in AssetDatabase, etc. Those are what you should be using for your infrastructure anyway. Or you put your infrastructure in a core scene, and additively load/keep objects alive when you transit to any 'level' scenes (but that means scene changes no longer remove existing objects the way you'd expect).

One of the reasons multiple scenes would maybe help is because you can load them asynchronously.

Whether or not that would matter when it comes to this problem is unclear, because the real problem is that you're instantiating at runtime a terrain object with 34MB of data. Preloading and/or chunking and/or baking the Terrain into non-Terrain object would solve more issues in this case. Or, add a loading bar and call it a day until you have a reason to think this is a big problem.

Red Mike
Jul 11, 2011

CitizenKeen posted:

Quick, beginner question: ideal way to store lots of static data for a card game?

I'm putting together a card game (browser based, partly as a proof-of-concept for Blazor UI), and I'm curious about the best way to store the card data. At work we store everything in SQL, but that feels... unnecessary here. I'm the only one making the cards, so having them as their own class files seems to make sense. But then I think about updating 100 .cs files when I decide to change some game math and I barf a little.

I'm thinking... let's say 100+ distinct cards, many with simple effects, a few with more complicated effects. Enough that I want to code game logic into the cards themselves, I think. A deck of cards would have multiple instances of a card, but where to store the abstract data of the card?

Separate your data from your logic (and state), and you should find a good pattern pretty quickly.

Data is things like 'Card X has effect Y instantly and then effect Z once discarded'. You could call these "entities".
Logic is things like 'Effect Y causes this number to go up' and 'Effect Z causes a replacement card to appear'. You could call these "systems" or "components".
State is things like 'Card X was played 2 turns ago and will be discarded next turn'.

It depends on specifics of your game mechanics, but a good pattern could be:

Card.cs has a class that represents a card in abstract. It contains a list/dictionary of Effect subclasses, maybe split by activation trigger, etc. You don't subclass this, you just make a new instance of it with the right values.
DeployedCard.cs has a class that represents a card in play/hand/etc. It contains a reference to a Card instance, and any state you need to keep track of.
Effect.cs is an abstract base class that represents one of these effects, but has no knowledge of when the effect would trigger (for flexibility). It just has an OnActivate abstract method or similar. You do subclass this, but there is MINIMAL shared code in the base class and nothing like game logic.
ReplaceCardEffect.cs is a subclass of Effect where OnActivate will (through some manager class/whatever) cause the card it's on to be replaced with another. This is where the game logic actions live, i.e. where you'd likely need to modify things to balance the game at a micro scale, such as making a buff more powerful or making an effect not happen in certain scenarios.
System.cs is a class that instantiates Card/DeployedCard as needed (by creating Effect subclass instances as needed too), and keeps track of what needs to happen when (phases, etc). This is where the game logic orchestration lives, i.e. where you'd likely need to modify things to balance the game at a macro scale, such as making effects happen at the start of the turn in a different order or making competing effects resolve in different ways.

It doesn't solve the issue of "where to store the data" fully, because then you need to make System.cs load that "static data" from somewhere. At that point for a prototype/local project, just make a big static class and fill it with dictionaries/lists/etc, or load from file (JSON, etc).

However the important thing is that you should never end up with "updating 100 .cs files" unless you're fundamentally changing some core feature.

Red Mike
Jul 11, 2011

CitizenKeen posted:

Excellent advice, thank you. I wanted to highlight this and follow up:

Can you please speak to why you recommend Option 2?

I wouldn't recommend either option because the problem is earlier in the chain.

What I would recommend is this:

A Card class, which has (not declares that it has, it actually contains) a dictionary of string to Attribute and a list of Abilities. Attributes could be of type int or classes, etc, depends. By default, both of these lists are empty. I'll discuss subclasses of this below.
An Ability class, which mandates that an ability has a 'DoAbility' method (it doesn't actually contain this method, it's abstract). It does contain a reference to its containing Card, and maybe a reference to a target Card; set during construction.
Ability class is subclassed once for each type of ability where there's different logic to run, not when there's the same logic but different data/values.
For example, an ability called BoostAttribute could define: TargetAttributeName string, Amount int. This Ability subclass could be used both for a Boost Defense ability (TargetAttributeName = 'Defense', Amount x), an Increase Cost ability (TargetAttributeName = 'Cost', Amount x), or anything else like that.
If you then wanted an ability that does that but only when a different attribute is less than a condition, you'd make a BoostAttributeIfCondition ability, that defines: ConditionAttributeName string, ConditionValue int, TargetAttributeName string, Amount int. This Ability subclass could be used for buffing an attribute when another attribute matches a condition, or you could use it for a Relentless Defense ability (ConditionAttributeName = 'Health', ConditionValue = 1, TargetAttributeName = 'Health', Amount 3).

There are no subclasses of Card because the logic of Card never changes. Structurally, all cards are the same, therefore one class is enough. Data-wise, cards are different because their initialisation is different. Initialisation is not defined in the class (it can be, but it's an anti-pattern that makes it hard to maintain the code) but in the code that actually creates the class.
Taking your example in Option 1: what's to stop a bit of code from creating Cool Card and setting values to Simple Card values? Well you can make the fields read-only, or make them only settable in private, sure. This means that the constructor of Cool Card is the GetCoolCard() method from Option 2, and the constructor of Simple Card is the GetSimpleCard() method from Option 2. So the two options are equivalent.

On the other hand there are subclasses (or implementations of the interface) of Ability because the logic of Ability changes. But importantly, the name "Ability" is misleading because you don't want to tie it to in-game "abilities", because then you will have duplicate logic all over the place (or try to abuse inheritance to share code in some way). That's why I suggested Effect. So a card's in-game ability could be "if the target's health is less than 5, then this card gains 10 temporary attack power during the attack; if the target dies then the attack power becomes permanent" which would be a combination of some sort of BoostAttributeIfCondition effect and a BoostAttributeIfKillEnemy effect. And these effects (separately, together, etc) could show up in other cards that are completely unrelated (with or without the same parameters on the effects).


As for your concern about how the instantiation of the Card/Ability/etc classes or subclasses should work, the important thing is that it overall doesn't matter (you can do it via a method, via deserialisation, via another manager class calling one or another, etc). What's important is that you understand that the act of instantiating it (or choosing to instantiate or not) may be logic, but the values that are used during instantiation are data. Therefore subclassing to set different values is subclassing on data. It's entirely possible to do, but it leads to extra complexity and you don't get any benefit from it.

e: or read the post below that summarised it a lot better

Red Mike fucked around with this message at 16:36 on Nov 16, 2021

Red Mike
Jul 11, 2011

CitizenKeen posted:

1. I'm making an asynchronous multiplayer game (think Dominion against friends, played very asynchronously). I'm trying to be performant loading game data - it won't happen just at the start of the game (like a client based game), but I think every time a player loads up the site to see what their opponent did. Which is why I was thinking of instantiating should be done in code, not from JSON/Markup. I'm also thinking from SQL, but storing that seems a pain. I don't want to make my server read 100 JSON files every time a player loads a page.

Even if you're trying to be performant, instantiating "in code" vs. loading from JSON is equivalent. All deserialising from JSON would do is instantiate the classes you asked for.

Beyond that, what other people also said: there's no reason why a player loading up to see what their opponent did would cause the server to reload the data. Even in the worst case of wanting each game to potentially have different design data (e.g. you start a game on an old version so it remains on the old version with the old cards until you're done), that just means you want to save a snapshot of the design data at game start and always reference that snapshot.

As a rough guide, this is what I'd imagine the sequence of events to be:

1. Player loads up the site to see what their opponent has done.
2. The client code (in the browser) may at this point retrieve the card design data/etc from cache if the last check was done locally
3. If the cache is old, or the last check was done on a different machine/etc, then the client code will request the design data (for the current game, or just in general)
4. The server will send the design data (or more likely a URL that links to a CDN that stores the design data) and the client will parse it and then save it to the local cache
5. The client code now requests the state data (i.e. what cards are where, what was the last action, etc).
6. The server will send that info across (which is just stored in whatever database you're using).
7. When the player chooses an action, the client code sends a request to the server telling it the action the player chose.
8. The server now needs to run game logic, therefore will retrieve the appropriate design data for the current game (most likely from a cache that lasts a few days; if not in cache, then from a CDN).
9. The server runs the game logic as needed, saves the data to database, then tells the client OK.

In the above, only #8 involves running game logic, and therefore it's the only place the server would really need the game design data; the rest is handled by the client (if at all).


CitizenKeen posted:

2. So I've got some game data (a card, for example). I have the abstract data (in code, or in markup). I have an instance of the card. Then, I need to put that instance of the card into a database (so that game state can be saved). How do I best reference the game data in the database? A CardId GUID that's hard-coded into the code/markup? Hard-coded IDs are foreign to me.

An ID is not "hard-coded" if its purpose is to link game design data (i.e. static data) to a dynamic set of data (i.e. state data). If you're making a game and you need to build a set of unique entities (e.g. to map an image to each on the client-side for displaying them), then giving each an ID and then having a mapping is the only way you can do that. If the fact that it's a GUID bothers you (because it's so abstract), just replace it with a hard-coded string you write yourself and just make sure they're unique when you modify the design data (otherwise you'll have some odd bugs). No real reason to have cards be saved as "8d1a2170-4a7e-43be-ad48-12608a21ef77" in a prototype when you can have them be saved as "relentless-endurance" or "green-mana-1"; GUIDs work better long-term (especially once you have tools and automation) but that's not initially important.

There are other considerations at scale/when working with a live game, like having a version number so that you can update your design data while allowing games to continue running on older data, etc; but those aren't worth planning for at this stage it doesn't sound like.

Red Mike
Jul 11, 2011

Raenir Salazar posted:

- I wonder if I should have some means of automatically detecting if a content goes all the way across the north/south of the map and either rejecting this or randomly prune either the north, south, or both edges to provide a sea route around? Or just to tell Columbus/Megallan gently caress you and let this situation occur.

In a past project I've seen that was similar, their "realistic" solution was to basically make the map that's being generated only fill the space that's non-polar. So basically have the polar areas (a rectangle or polygon at the top/bottom of the final image) pre-generated in a simplified way that makes sure it's traversable, and the non-polar areas (a rectangle or polygon for the rest of the final image) generate using whatever normal method you use. At the end, you merge the two.

The benefit on that project was that it allowed the polar areas to be constrained to some gameplay mechanics (don't accidentally generate endgame things that can be traversed with non-endgame abilities, which the rest of the map is allowed to do because it's not as unbalanced as the polar area endgame things), while not having to change the generation of the non-polar areas (because all they changed was they made the top/bottom 5% of the image be generated ahead of time). Rejecting/randomly changing the non-polar generation would have been difficult because it would lead to more unbalanced outcomes.

The downside is that if your generation of the non-polar areas is hard to merge with the polar areas, then you spend more time trying to figure out a nice 'merge'.

Red Mike
Jul 11, 2011

TooMuchAbstraction posted:

especially that cause objects to want to change their registration status for events

First lesson in using event buses in anger: your system's subscription is a lifetime thing. If you want to ignore events for a time/until something happens, add it above the event bus layer.

It doesn't completely mitigate but definitely helps against the big problem of live/via-logs debugging/diagnosing issues with event buses: the effect an event has is dependent on the state of the event bus (i.e. who is subscribed at this point).

My only problem with boilerplate in the way these event buses tend to be set up in Unity is that you end up having to make it its own MonoBehaviour on each object because there's no multiple-inheritance and object creation isn't usefully manageable. In non-Unity, it's easy to set up your objects so that they're something like:

code:
public class MyProjectile : IListenToEvent<ProjectileDestroy>, IListenToEvent<MobCollision>, ISendEvent<ProjectileDestroy>
{

    //implementation of IListenToEvent<ProjectileDestroy>
    public void HandleEvent(ProjectileDestroy ev)
    {
        //...
    }

    //implementation of IListenToEvent<MobCollision>
    public void HandleEvent(MobCollision ev)
    {
        //...
    }

    //ISendEvent<ProjectileDestroy> offers a default implementation of SendEvent(ProjectileDestroy ev) that can be called internally
}
Then Subscribe/Publish calls can all be handled generically by one big manager system, and the code very clearly lets you track who listens to what and who sends what. The only thing missing from the code is "when does this object exist and not exist" which is equivalent to "when is Subscribe called"; that's the last bit of "state" that can make weird issues hard to track down.

Red Mike
Jul 11, 2011

leper khan posted:

You can make an abstract NotificationBehaviour<T> which is both MonoBehaviour and IListenToEvent<T>

Or just put the interfaces in the specific class decs. Not understanding your issue. Unity isn't really special in a meaningful way here. You can absolutely do things the way you're describing.

The interfaces are there to expose the logic to the class, or act as ways to identify the class (via reflection most commonly) in order to call the Subscribe/Publish as needed. On their own they won't do anything.

You either have something external to the class handle the Subscribe/Publish logic (usually some sort of lifetime-manager, a factory/scope class, or a general event bus manager that knows about all objects) in reaction to being told to create/drop that object - this isn't something you can do reliably in Unity because MonoBehaviours are not plain C# classes. Constructing/destroying a MonoBehaviour isn't trivial and has many effects beyond just instantiating a class. Doing it this way leads you back to worse boilerplate because suddenly creating a GameObject/adding a component is no longer trivial to do.

Whereas in a non-Unity setup, you've most likely got a factory class/injection-based way of getting instances that are created as needed, and you have a way to hook in code to run on setup/dispose for those instances.

Or like you said, you make your MonoBehaviours be instead a different abstract base class that can do this. Which works, as long as you only need that one base class functionality, which won't be the case forever. Next you want to add analytics or special shared logic functionality to the abstract base class. So you won't have a NotificationBehaviour<T>, you'll have a MyMonoBehaviour<TEventToListenTo, TAnalyticsEventToSend> but also MyMonoBehaviour<TAnalyticsEventToSend> for when you need the other base functionality but not listening to event bus events. Or even ignoring the other functionality, you have to define NotificationBehaviour<TEvent1, TEvent2, TEvent3>, but also NotificationBehaviour<TEvent1, TEvent2>, ..., and what about defining the methods for sending the events (you can just keep those generic and send BaseEventClass...but now you've lost code-tracking of who sends what).

Whereas in a non-Unity setup, you don't have to worry about this because you can compose things instead of having to inherit them (but MonoBehaviours don't make it easy to do this; the composed parts also want to be MonoBehaviours which means you get to play the Awake/Start/Update dance to figure out if you're done initialising, and sometimes get to wait for the next frame for it to exist/disappear).

So then you end up with just having interfaces on your methods, and you need some central shared object that can run reflection/whatever on all your objects to subscribe/dispose on-demand, and you've written a lot of code that is functionally (for debugging/diagnosis/performance/amount of boilerplate) the same as what TooMuchAbstraction posted, just written slightly differently.

TL;DR: MonoBehaviours aren't as simple as classes, "instantiation"/"destruction" for them is harder to work with, finding "all objects with X interface" is non-trivial and hard to work with unless you're doing this from within another MonoBehaviour, and the forced base class on them means you're also limited in how you can set up shared base logic.
What was posted is about as little boilerplate as you can get away with in Unity, it's just a question of where you put it.

Red Mike
Jul 11, 2011

CitizenKeen posted:

I assume signals are a trap here? What kind of structure am I looking for? (Using Godot/C# if that matters, but I'm just looking for theory.)

What KillHour posted is great and probably the right approach to start with.

The core of the problem is that an event bus is not just "unordered" itself, but the events on it are meant to be stateless. This means that, even in my example earlier, you have the potential for example for two ProjectileDisappeared events to happen one after the other about the same projectile. Or a ProjectileHitMob event to happen after the ProjectileDisappeared for the same projectile. Yes, you could e.g. start including a timestamp/turn ID in the events, and have the listeners make sure they process things in the right order. You could also listen for a particular event, process it, and then send out a reaction event to "continue the processing". But you're working against the system.

What you want for "rules events" is something "ordered", but also that holds some sort of state (that is either shared or global). So for example if two events happened this turn: DrawCardToHand, DiscardAllCardsFromHandBecauseOfAbility; you need those two to happen in order (because the card must exist in order to be discarded). But also, the 'listeners' to the events must always receive both of them so that they end up seeing the same outcome at the end; it's not the same as triggering a visual effect vs not triggering. Usually there is also a time component (an 'end turn' at which point all events are processed).

Basically, an event bus is useful for cases where you want systems to notify other systems about something having happened, in a way that lets each system manage its own internal state/consistency/logic without understanding the other systems.

Whereas what you want is an event queue/action queue/change stack/etc, which is useful for cases where you want systems to act and react to changes being done in some state that they share with other systems, in a way that lets each of these systems handle parts of the state. It's not just notification because the system's reaction is basically to also "send events"/change state.

And because these systems all share state, you probably don't want to go overboard in the way you would with event buses and make loosely coupled distributed systems, because it gets unmanageable and hard to split. You don't want e.g. AttackCardDrawManager that listens to CardDrawn and handles only attack cards. You want a general CardDrawManager that handles probably all CardDrawn events (regardless of where it comes from). And you probably really want it to be a CardHandsManager that does more than just drawn cards, but also discarding cards, because that changes a shared bit of state (the available hand).

My rule of thumb for this is to look at the state they're actually sharing, and make managers that handle one discrete unit of state (and nothing else touches that state except to request things to happen).
For example: having an AttackDamageManager that handles all events that deal attack damage (i.e. that change the LifePoints/whatever state) is fine. It doesn't specifically care if the damage is coming from an attack card, or from an effect on drawing a card, or from an effect on discarding a card.
But you probably can't have a DiscardManager that handles all events that happen on discard. Because what it'll do in response could be any number of things (changing LifePoints, drawing more cards, discarding more cards, etc).
100% keep the number down and keep them simple though because if you choose the wrong way to abstract it, it'll look fine but cause more work.


e: What you posted in the meantime is fine, but I'm pointing out that keeping it as a signal basically forces each system to store internal state (these are the cards as I understand them, these are the events I've seen, etc) or to try to handle things statelessly which is hard to do reliably.
The way to keep it simple is to have a CardDrawManager/CardManager that stores the cards it thinks are in play/what state they're in/etc, and it acts on that internal state. The signals are only there to change that internal state. But the manager itself decides when and what methods to call on the cards; it might react to a CardDrawn signal by "change internal state; now check if there is any change that I need to react to by calling methods on the cards". This way, your signals are simple and stateless (a card has been drawn, add it to the state); the complex logic (which effects are higher in priority, what card effect triggers which cards, etc) isn't logically tied to a signal.

Red Mike fucked around with this message at 17:21 on Mar 15, 2023

Red Mike
Jul 11, 2011

CitizenKeen posted:

At first I was thinking I would have the Deck signal that the player wanted to draw a card, and then the CardManager would receive that signal and queue up the DrawCard action and then queue up any reactions to the DrawCard action, but then I started thinking about what happens if I want a card to say "When you would draw a card, instead..." kind of shenanigans.

But I'm probably getting way ahead of myself. I think if I have a clean CardManager that handles all card state, and an ActionManager that actually processes all actions in the game, I'm probably a lot further along than I am. I want to design with the future in mind, but YAGNI/perfect is the enemy of the good, etc.

Yeah, that's the right way to approach it (signals or not): have some clean managers that talk to each other, and split them only when you can see that the logic inside them clusters into distinct chunks.

In this specific case, you're ending up with an XManager (that internally will know about all X's) that will need to talk to an YManager (that internally will know about all Y's), etc. A small number of managers that mostly talk to non-managers. Therefore, maybe you'll want signals to handle the CardManager talking to individual Cards (but most likely not), but it doesn't really make sense to have your CardManager use signals to talk to the ActionManager (they're pretty coupled anyway).

Red Mike
Jul 11, 2011

TooMuchAbstraction posted:

I've been thinking about a problem lately, and it occurs to me that there may be a well-studied solution for a variation on the problem. The problem is "how do I make high-level Metroidvania maps that adhere to certain constraints", with "high-level" meaning that I don't care about the contents of individual rooms, just how they connect to each other and how big they are. I think this could generalize to "how do I make a planar graph that adheres to certain constraints", where the constraints are things like "there is a length-5 path between these two nodes", "there are two paths between these two nodes", "the path from this node to that node must pass through this third node", etc. Does anyone know of any such algorithms, or any resources I could read up on to get a better understanding of the domain?

I've made several stabs at this problem in the past, and had a moderate degree of success, but all of my approaches boiled down to "throw a bunch of stuff at the wall and then try to make sense of it." I'd like something a bit more formal.

Outside of very specific problems, I'm fairly sure there's no closed-form solution generally available for this kind of thing. It's much easier to set up a generator that can produce garbage as well as the desired outcome, and you prune/retry any failure until you meet all constraints. Because as soon as you have two separate constraints, any closed-form solution (or tailored generator that guarantees the result) will be so complex that it's very hard to tweak behaviour.

I've done some research into this in the past but there's no real unified 'domain' that you can dig into directly because the problem is so broad. You're better off focusing on particular use-cases (generating graphs for terrain generation in games, or related to analysing a story, etc).

Red Mike
Jul 11, 2011

roomforthetuna posted:

I think you might be describing something like what I did for my mahjong solitaire board generator to ensure that every setup is solvable. If you start with the constraints, build *only* the bit of the map that matches the constraints, and then build out the rest of the map from there, you guarantee that the final map matches the constraints and minimize the "getting stuck" problem. Doing it this way may mean you have to program the constraints both forwards and backwards though.

This is one approach to simplifying generation like this, you turn your exact constraints into minimal constraints and you ensure only the minimal version first, then restart if not fulfilled.

So the given examples:

"There is a length-5 path between these two nodes." becomes "before actual generation, run a sub-generator that makes a length-5 path between these two nodes in a different way than the rest of the map".
"There are two paths between these two nodes" becomes "run a sub-generator to make a path between these two nodes twice before the actual generation (and make them not overlap/etc)".
"The path from this node to that node must pass through this third node" becomes "run a sub-generator for a path from A to B, then again from B to C before the actual generation".

Once you have your initial constraints filled, you just generate the rest of the map normally, and then apply inverse constraints you might have because of the initial generation ("there are two paths between these two nodes" => "trim any new/the worst paths between those two nodes", etc).

Basically, it's doable/"easy" to make a generator that works for a small number of constraints (ideally just one), and it's doable/"easy" to combine generators like this to make a more complex result that is overall unconstrained (because the combined generated object might not fit all constraints any more). This is all because checking a small number of constraints is easy to write "closed-form" stuff for.
But it's "hard" to make a bigger generator (whether by combining generators and then cleaning the result/restarting until it fits or just making one big generator) that does respect many constraints. Because checking a large number of constraints is hard to write "closed-form" stuff for.

Red Mike
Jul 11, 2011

Passive Aggreeable posted:

I'm finding myself wanting to do *something* so I am on the cusp of getting my feet wet on this idea:

Would anyone be interested in an interpreter for unity/c# that creates 'code' (basically linq expression trees) that could be built upon with new, user-defined keywords and such? There already are c# intepreters that use compilers such as Rosalyn or the .Net compiler. This would of course include a 'console' that interacts with the interpreter. I'm still thinking about use cases. Would this be fast enough to act as a LUA alternative for most people? But the idea is that you would ultimately create source code from the expressions though. Any ideas for what people might want to do?

I've gone deep enough into this side of things to tell you that madness lies that way, with only one of the major points being Unity itself having odd support for C# itself (and platforms like WebGL relying on IL2CPP compilation which is very easy to break in weird ways). The other points being "you're basically trying to make a compiler/transpiler while your hands are tied and you have to work blindfolded with boxing gloves on" because you're basically trying to implement an interpreter on top of parts of an existing language, and to expose something that's intuitive to the user (and to understand what would be intuitive to the user when you're used to working with the language itself).

If you want to do it as a fun little project, have at it, but be prepared for constant confusion and roadblocks that have no apparent workaround, as well as for finding a solution that seems to work then discovering a week later that it completely breaks something else that's important. It's fun to bang your head on a wall like that for a while (hello from the well that is "surely it's possible to turn IL into LLVM IR properly"), but be aware that that's what it will likely be.

e: And everything from the above post too on a technical level; and all those problems look eminently solvable, and none of those solutions work together in a way that is useful.

Red Mike
Jul 11, 2011

OneEightHundred posted:

You can output to C++ instead of LLVM IR but the real problem is debugging it.

You can in the sense of the same thing IL2CPP (or wasm workloads in dotnet) does, which is just take specific IL constructs and turn them into the same specific C++ constructs (that you can then compile to LLVM IR and therefore get multi-platform easily). But that's literally relying on you to write C++-like C# (or like Unity's IL2CPP does, tie all your C# scripts to specific existing C++ structures so that generally your code will be used that way anyway).

You want to use any of the useful IL constructs that most idiomatic C# uses? Tough, at most someone might have written very specific checks for particular types of structures and converts them to actual usable C++ so that one particular feature works. And they've only done that for the absolutely most important parts that everyone uses.

Debugging absolutely is the first thing you'd also need to work properly though, for sure. I've done a few WASM-based projects in dotnet now (with the experimental WASM workloads) and debugging is pretty much impossible.

e: Also that contra/covariance thing is hilarious but also such an edge case that I can believe they haven't actually tackled it. Especially since the spec is arguably more confusing than the behaviour.

Red Mike
Jul 11, 2011

leper khan posted:

you dont need to mark explicit numbers in a flags enum [except for on None]

Yes, you do, otherwise you'll get the wrong behaviour when using them as flags.

code:
public enum TestEnum
{
	A,
	B,
	C,
	D
}

[Flags]
public enum TestFlagsEnum
{
	A,
	B,
	C,
	D
}

[Flags]
public enum ProperFlagsEnum
{
	A = 0b0000001,
	B = 0b0000010,
	C = 0b0000100,
	D = 0b0001000
}

foreach (var val in Enum.GetValues<TestEnum>())
{
	Console.WriteLine($"TestEnum.{val:G} {val:D}");	
}

foreach (var val in Enum.GetValues<TestFlagsEnum>())
{
	Console.WriteLine($"TestFlagsEnum.{val:G} {val:D}");	
}

var isBAndC = TestFlagsEnum.B | TestFlagsEnum.C;
var isD = TestFlagsEnum.D;
Console.WriteLine($"These two should not be equal: {isBAndC} != {isD}");

foreach (var val in Enum.GetValues<ProperFlagsEnum>())
{
	Console.WriteLine($"ProperFlagsEnum.{val:G} {val:D}");	
}
		
var isBAndC2 = ProperFlagsEnum.B | ProperFlagsEnum.C;
var isD2 = ProperFlagsEnum.D;
Console.WriteLine($"These two should not be equal: {isBAndC2} != {isD2}");
code:
TestEnum.A 0
TestEnum.B 1
TestEnum.C 2
TestEnum.D 3
TestFlagsEnum.A 0
TestFlagsEnum.B 1
TestFlagsEnum.C 2
TestFlagsEnum.D 3
These two should not be equal: D != D
ProperFlagsEnum.A 1
ProperFlagsEnum.B 2
ProperFlagsEnum.C 4
ProperFlagsEnum.D 8
These two should not be equal: B, C != D
e: also in general it saves so much sanity to just always specify explicit values for enums, because you avoid running into issues where another programmer/new dev/etc adds a value to the middle of your enum and suddenly all your previously serialised values change meaning (or your client can't understand the server any more and vice versa).

Red Mike fucked around with this message at 21:13 on Feb 18, 2024

Red Mike
Jul 11, 2011
Because all [Flags] does is change the ToString method on the enum (or rather the ToString knows to check for it), and enable a couple code inspection warnings in a compatible IDE. You could use normal enums and have them act the same as a flags one, but you'd end up with confusing ToString behaviour when you combine flags (unless the combined value maps to an existing name).

It gets more fun when you realise that you can have multiple names that point to the same value (see HttpStatusCode that has Moved = 301 and MovedPermanently = 301), and that evaluating equality between them gets confusing quickly.

There's a reason I said it just saves so much sanity to keep enums simple, explicit, and expressive, and not rely on any built-in handling.

Red Mike
Jul 11, 2011

Red Mike posted:

Of course I wrote that but then I realised there's a simple workaround to avoid it entirely, using the C# built-in NativeLibrary.Load/GetExport to pretend to be calling dlopen/dlsym. I got a basic MonoGame sample running now, with only these changes needed in the MonoGame repo: load "libSDL" without anything else, don't set "Sdl.GL.Attribute.ContextFlags" for debug (it breaks context creation), fix MonoGame's glEnable/glDisable thinking they should return int (they're void return delegates), enable GLES define in MonoGame (and fix the couple places that don't listen to the define), don't call glPolygonMode which isn't implemented in WebGL (and I assume there's others I haven't run into). Commits with the changes here.

The OpenGL ES support is the big one that might need some weird changes to the code, but the others are all things that can live in the existing DesktopGL backend. That said, I have a feeling MonoGame maintainers will be reluctant to look at this.

MonoGame branch of the sample here.

e: Especially because the reason NativeLibrary works for this is because I'm basically hitting the SDL binding I'm including in the project, so they can't escape having to do that for WASM builds anyway.

Cross-posting myself from the .NET thread, since I feel like gamedev people might be particularly interested in this.

Managed to get SDL, and then MonoGame, compiling for WebAssembly directly via a .NET8 build (not Blazor, not IL2CPP, etc). No weird custom runtimes, no having to unpick arcane symbols, no figuring out the magic emscripten commands to do things.

That said, the MonoGame code will definitely need more changes to prevent usage of legacy GL/attributes/etc but in principle this approach works, and means any project using DesktopGL should translate pretty much 1-to-1 with only internal library changes. It also needs the MonoGame maintainers to decide on/agree with the approach, as well as particulars, and ideally one of them to take this forward.
And the specifics of my WASM sample will likely change with .NET 9 and onwards as this bit of the runtime keeps changing.

Red Mike
Jul 11, 2011

Sick burn, but FYI there is an actual point to this work. Any dev who has a MonoGame/FNA/etc project (not a Unity one) can publish directly to web without needing to change their game code, only with an updated library and a config change in their .NET project. Before this, the only real option was to find a fork that implements this in a much more difficult way and change your game code a bunch to allow it to work.

Red Mike
Jul 11, 2011

Hughlander posted:

That's not a burn. I'd love for you to post there in great details for how you did what you did. I think that it's pretty drat awesome and would love to know more.

I came across pretty serious but I was laughing, because it does feel like one of those "overcomplicated" projects even if it has a goal. I'll try and post in that thread with more info, although at this point the amount of info I'd have to dump to get to the actual meat of the problems is huge. Appreciate the kind words from everyone though!

The MonoGame discussion in their Discord didn't really go anywhere, and I'd need maintainers to take it up and agree on the right approach (because moving to OpenGL ES instead of what they currently use involves a big performance hit/feature loss, and it would mean switching the GLES define to be a runtime check which has implications), but no-one's really been that interested yet. Someone did take it and set up a repo to publish the sample to web: Github link, Hosted sample.


In the meantime I found FNA-XNA and realised they've been trying to get WASM going for a while, but ran into the same issues as I did initially and decided to work through it using Uno.Wasm which is a can of worms; it's been defunct for a while now. I jumped in and went through their docs and process, ran into a major issue because FNA requires SDL 2.26 (which doesn't normally compile well with Emscripten 3.1.34 that dotnet is locked to), but I found a good workaround within Emscripten to make it build! So with that, FNA doesn't need any code changes and WASM builds work. I also set up a Github CI to build the dependencies for Emscripten, so you don't need to build C libraries yourself.

FNA sample branch here.
FNAlibs release page here for Emscripten.

Effectively if you take your C# FNA project (or a sample one), take the FNAlibs from there instead of the normal place, and import them in a new wasmbrowser template that has the basic changes from my sample repo's FNA branch (csproj, main.js, the new .js file) that calls your Game class, it should work as-is with no real changes. I'll probably try to set up a basic guide or C# template that does this. e: I'll also probably try to get a CI action to deploy my sample to Github pages, which should technically work.

There's probably things to figure out re: file/asset loading, features that don't exist in web (some types of GL blending, some transparency things, multiple contexts); but in principle the library and your game code need no changes, only some platform-specific additions at most. I also have no idea what performance is like, but probably pretty low due to lack of threads among other things.

Red Mike
Jul 11, 2011

Raenir Salazar posted:

To be clear, that's not what's actually happening here as being able to store a delegate as a variable, or at least pass a delegate to a function in Blueprint is a useful thing to know.

If multiple people are basically replying with "that doesn't sound like something I've run into before"/"what are you actually trying to do because this doesn't sound like the right approach", then it is not a useful thing to know; it is something that seems like a useful thing to know ..if you're trying to apply knowledge/approaches from a different engine/framework/language to one where it doesn't apply.

It's really hard to get out of the "I know what I'm trying to do, I don't understand why people don't see why it's the right approach/an actual problem to solve" hole until you step back all the way to the actual problem you're solving (which in this case sounds like a combination of "I'm trying to keep X and Y in blueprint when it should ideally be in C++" and "I want my blueprint to look 'clean' like I'd want my C++ to be clean") and not the adjacent problems you're stuck on ("how do I get references to an event to be saved like in a variable" and "how do I avoid duplication/lines crossing/blueprint code becoming a spaghetti mess"). And yes even when internally it sounds like you've gone to the actual problem, it doesn't look like you have if people keep reacting with the same "but what are you actually trying to achieve" questions.

The back and forth literally reads like every time I've helped someone moving from Unity to Unreal and had to repeatedly play the XY problem game as soon as they hit one of the big differences, like the most common being UI and how in Unreal you set up 'pull' code vs. Unity you push data from code instead. Yes technically you could write your Unreal UI code to work like Unity's UI code, and it will always look ugly as hell and ridiculously over-complicated, because you're fighting the actual system you're working in every step of the way. Neither approach is "the right one", it's not changing your normal approach to match the system ("how do I save a reference to the event" => "how do I tell the label to change its text"), it's realising that you have to learn a completely new approach ("how do people keep their blueprint code clean when they need to add complex logic like X" => "how do people get labels with dynamic text").

e: ^ all that being said, anecdotally yes Unreal does seem to cause this way more...primarily with the blueprint code split and the lack of people sharing actual advanced practical usage; I've seen so many working examples of how people using Unity solved actual problems they ran into while working with it, whereas with Unreal it tends to mostly be guides/documentation that solve standard problems. Unity's slowly going the same way though with newer versions/features, so yeah.

Red Mike fucked around with this message at 21:41 on Apr 12, 2024

Red Mike
Jul 11, 2011

Chillmatic posted:

I have a weirdly personal vendetta against this line of thinking, mostly because in all honesty I love Blueprint. The last big studio I was at switched to UE for their current project, and I was the "Blueprint Person" that engineering would ask questions to when they didn't want to dig around in source. It's one of my favorite scripting languages, and it drives me crazy when people think it has to look like poo poo or be impossible to reason about.

In eight years I don't think it's ever failed, when someone's blueprints look like this, they inevitably complain that cpp would make this all so much "Cleaner" and "More elegant", and when I look at their typed code it's the exact same kind of nonsense but this time in text form and even more impossible to understand.

Basically I think Blueprint bothers people because it doesn't give bad programming practices any place to hide; it makes spaghetti actually look like spaghetti. It is perfectly possible to make an entire game using it, and the only valid criticism I've seen about the format is that version control and merging is indeed a nightmare. That's a good enough reason on its own to minimize their use if that's an issue on larger teams.

But messy blueprints are an unforced error on the part of the user, not an inevitable end result.

I pretty much agree, I just think there is a point at which generally blueprint spaghetti is much harder to read through than C++ spaghetti solely because of how the C++ spaghetti has enough tooling/IDE stuff around it to keep it sensible. And I don't think you can avoid "spaghetti" in this sense completely in a real project, some code just is a complicated mess and keeping it "clean" just means you have to do even more work the next time you need to change it. So I tend to prefer to keep the spaghetti parts wherever they're easier to read.



Raenir Salazar posted:

I am currently stuck using Blueprints in this way for work reasons; I've already said I would be using a C++ instead if I could; but I also don't see it as approaching Blueprint as I would C++, I think I am approaching them in very different ways and the only ways that could come across as similar is pulling from my experiences regarding concepts and ideas that are definitely language/platform/system agnostic and clearly would be adapted differently to accomplish the idea behind it to suit the system/language/engine in question.

This is exactly what I'm raising: you're still assuming that these concepts are agnostic and therefore apply to every language/platform/system, and yet you're describing things that people haven't generally needed to do in this system (because in this system, there's a better/different pattern/approach that means those concepts aren't necessary). If you stop trying to pull from those experience, look at what you're really trying to achieve and ask how someone experienced in the system would achieve that, you'll likely get a different answer.

A simpler example of what I mean: the idea of a plain CS linked list is language/platform/system agnostic, but if you kept asking how to create one properly in Javascript then you'd keep getting reactions like "what are you trying to do?" and "huh I've never really had to do this myself".
If you instead asked how you'd store elements in a way you can iterate over them in Javascript, you'd be told to use the built-in arrays/objects and that's why people didn't really need to solve the problems you're having.

Red Mike
Jul 11, 2011

Raenir Salazar posted:

The point you're raising is that you've decided I meant something else from what I meant? I don't see how it makes sense for you to decide I somehow only meant the concepts you can trivially point to in order to support your point about me. I don't think you know which concepts I was referring to, would you agree that there are some concepts in the universal set of CS concepts that would be widely applicable to some game engines and programming languages in gamedev? While of course, accounting that these concepts might be applied differently, which I've already said. Or are you saying that there is no single concept that would apply to both Unreal and Unity3D? Not a single one?

e to add: It kinda just sounds to me like you maybe have certain notions about what makes a good programmer and that maybe I don't fit your particular mental mold of what that looks like, but I also don't feel comfortable with the fact that I feel like I'm being forced to defend myself for things I didn't say or do, and is coming across as very uncomfortable and personal and if you want to have a discussion about different approaches I'd like you to drop this being about me.

Sorry, I must have come across really aggressive but I definitely didn't mean it that way! I don't think any of this relates in any way to what makes a good programmer or anything like that, I think this sort of situation is one anyone can run into and generally I think more experienced programmers actually have more trouble with, because basically the deeper/more well-used your toolbox the less likely you are to think that a thing that looks like a screw/nail/etc doesn't actually need a screwdriver/hammer/etc.

It's entirely possible I'm misreading what you're describing and you are in fact looking for something commonly done in Unreal, in which case maybe someone can actually help with it. But from my past experience it sounds a lot like the situation I'm describing, so I was only trying to help. When you replied with an explanation and it sounded to me even more like the situation I'm describing, I tried clarifying what I meant more in case it helped. If that still doesn't help, then yeah maybe I'm wrong.

Either way sorry I definitely didn't mean it in any personal way or an attacking way, I was just trying to explain a situation I've seen/run into before and the problem/solution from my perspective.

Red Mike
Jul 11, 2011

Rocko Bonaparte posted:

Are you comparing Unreal event-drive GUI programming to Unity's immediate mode GUI stuff? This seems really important and I want to make sure I understand it.

Also, whatever other contrasts you have on the feel of Unity. These kind of "vibes" are actually pretty useful to know especially for some poor fool like me coming over from Unity.

Basically yes, although it's not strictly speaking immediate mode, it's just a push approach instead of event driven, i.e. you set values/options in the UI objects directly. It's not like imgui where you're having to basically re-specify the UI every frame in order to keep it drawn (and therefore have to manage state/etc yourself), you just get a bunch of UI objects that you store references to and then set properties in those objects to make it change.

I don't think either approach is better or worse for small scale projects, but Unity's approach breaks down pretty badly for large scale projects that want to have a consistent UI framework, unless you build something that 99 times out of 100 is a pull architecture built on top of what's there. But if people used it at smaller scales or only for prototypes, then they won't have done this and expect the push architecture to be 'standard'; hell AFAIK there still isn't any established major UI library/framework on top of Unity UI that does this (probably because it's so hard to genericise something like this properly for game UI).

leper khan posted:

most large scale projects I've worked in unity have some sort of mvvm through a context object to decouple ui development from janitoring data. so it nets out the same.

^ Basically this, you end up setting something up where you end up with something much closer to Unreal's approach (but not necessarily the exact same, and you usually add a bunch of stuff that Unreal doesn't provide out of the box anyway).

To be clear: the thing I'm highlighting with the example is when someone used to pushing data to UI goes to Unreal, their default approach seems to be the opposite: try to get the Unreal approach to work in a push style.. which sounds like it should be pretty simple (just make something to hold the desired state of the UI, make the UI read from it, and have inputs on that state container that you can 'push' updates to). Unfortunately, they tend to run into two issues in the same order:

"How do I make my state container push the info to the UI" - until they remember this is the point of the thing and you have to make the UI read from it instead; usually they get fooled by the fact that the C++ classes for the UI do in fact allow some amount of access to apparently 'push' data.
"OK I have my state container working and the UI pulling from it (probably tied in via blueprint), how do I make the inputs generic so that I don't have to write boilerplate for each new UI component/value I want to support" - usually after a couple attempts to do this they start realising that there's something wrong with the approach because all they're doing seems to be writing boilerplate/glue code.

jizzy sillage posted:

I don't enjoy UI development at all so I tried to avoid it and jumped ship to Unreal long before I learned Unity best practice, but I'm guessing in Unity the object will set the value in the UI, so the object is pushing to the UI?

Yeah more or less; you create your UI objects (ideally at design-time) and get references to them. You then have various properties you can set/methods you can call to affect the UI, and you'd normally be calling them from whatever your update/render logic is doing.

There are a couple other approaches to UI in Unity really, but they're not as well-supported/used still AFAIK. Or at least one of the official ones does tend to be used/supported but for specific kinds of UI like game world UI or web views. All I really know about the newer developments is what I've heard or seen in projects but I haven't had to use them.

Red Mike fucked around with this message at 09:04 on Apr 15, 2024

Adbot
ADBOT LOVES YOU

Red Mike
Jul 11, 2011

leper khan posted:

counterpoint, managed languages are bad because you have the same or similar problems while lacking some of the tools to deal with them and people who dont understand you need to manage memory carefully anyway

i'd take C where people around me know they need to care vs C# where people around me pretend they dont any day

I'm not making any judgment on either side of this because honestly I've gone both directions in the arguments at various times and I think there's room for both (and would be more room if the interop between the two weren't godawful), but I find it funny that C# is the example for the managed language because it's the one language where I keep running into people that deliberately remove all the benefit of the automatically managed memory "for performance" (usually well before any optimisation is actually justified in any way).

I don't think this happens usually in Unity/client code at least, but on the backend side I keep running into the same things all drat time: people setting up shared buffers/pools and access with unsafe blocks "to avoid GC" (while doing it wrong and making it not just slower but also worse for GC), people deciding that the standard serialisation/logging/etc libraries are inefficient and rolling their own (that doesn't work properly, has weird bugs, and is worse for performance and GC), people deciding that instead of creating instances of classes to pass stuff around services within the same program they'll instead use byte array buffers that they use unsafe/pointers to access into the buffer (literally a worse approach than even the worst written C, particularly because the language and tooling isn't set up to support this).

By comparison, in Python the closest thing I've seen has been people setting up C libraries and doing interop to achieve this, and that's at least giving you a clean cut where managed ends and native begins. In C# you can do some horrible chimeras that randomly switch between managed and native without you noticing.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply