Ruskul's Forum Posts

  • Ah gotcha, I was doing alot of js blocks to test out script interfaces, so I wasn't in vs atm. I suppose I could write it there and then just paste it to blocks so I don't make dumb mistakes like this.

    I am of the belief that, overall, you can make whatever 2d game you need with whatever system the game needs, whether stacking buffs, equips, anything. Maybe not one-click out of the box, but, idunno, I don't touch JS and yet to hit any dead ends with some wacky ambitious ideas. I'd be curious to know what isn't possible in events or sdk-safe JS that can't be done for making any 2D system. Or is it to eek out more performance by using these undocumented js functions?

    Jase00 - ugh, I wrote a novel. TLDR at the bottom, but I'm not sure it makes sense with the context. Nobody should feel the need to read this, but I'll post it anyway. The reason I tended towards using internal api is to increase performance where I could, or avoid repetition. More on that later

    I think you are already familiar with some of my complaints, and I'll clarify as best I can. If you have a game with a fairly rigid organization, known ahead of runtime and before devtime, then producing it in c3 is fairly straight forward. Producing a game like Mario is a breeze. Making a game like pokemon, only slightly more difficult.

    The stacking buffs example is actually a good one. If you know in advance the types of buffs that can exist, and the number of stats they affect, then making a system for that is easy. When things become difficult is when you don't know how many types of effects will exist before hand(or the number is high), nor the objects that can be affected. Some a problem is best solved by a completely abstract system to accommodate designers future ambitions.

    Take mario as an example: We have a character that can walk/run, duck slide, wall jump, and a few other things. Now lets give mario an inventory for items that can alter his stats, and even add completely new abilities. Without deep oop, or ecs architecture, the easiest way to solve this problem is to embed conditional branches in mario's behavior logic that checks for items containing abilities. Not too bad... so far... especially if the number is low.

    But now extend that to all characters and allow any character to have any ability. Now every character needs conditional branches. Now keep adding abilities into the game. With new abilities might come the need for new stats, etc... and the base class for characters is getting hefty.

    The issue at this point isn't that you can't make the game, Its that you've created a situation where adding a new behavior to a single character requires a deep understanding of all characters and all abilities in the game so far, typically requires modyifying the code base in multiple places, and if you want to remove an ability, you have to remember all the places it has code for it. This becomes unmaintainable. A single developer likely can do this if well organized- but only to a point; and as part of a team - this is nearly impossible if you have two people working on the same system.

    The second issue is that every character, from a performance standpoint, no matter how simple, is as complicated as the most complicated character, as every character needs to have a conditional tree for every ability it could posses and the stats to go with it. Every object is bloated with data it doesn't need and this can hurt performance. We haven't even gotten to input support, ai support, or flexible and dynamic stats, etc... which also has to balloon to fit the conditionals. If you take a game like vampire survivors, and require bullets to also have abilities or dynamic effectors, you now have a code structure that kills performance so completely you have to do something about it.

    So the need to change can be 1 or both. Poor performance, or difficultly adding, maintaining, or removing code.

    Enter ECS or OOP. The problem outlined above is a fairly classic problem and there are multiple ways to deal with. My favorite is ECS, but i'll cover both and why they aren't great in c3.

    In either system, the goal is to create a framework that efficiently facilitates adding functionality to objects arbitrarily at runtime, and reduces the complexity of maintaining and creating new functionality in this environment. Good for the game, good for the developer.

    But construct makes this incredibly difficult, as it can't natively do OOP or ECS.

    As will be pointed out, you can code javascript. Construct has a great event editor and that is a major selling point. It is fast to iterate with and I like it. That is why I use it. But coding in c3 is more verbose and difficult to learn than pretty much any other engine I have used. I started in c2, so I should be biased to it, but I am not. Creating addons is painfully slow compared to creating editor integrated scripts in unity, and simply coding bare functionality in c3 takes more writing to achieve the same results in unity. That isn't to say some people may still prefer c3 in this regard, but it is a factual claim to say it takes more typing to get the same thing done in c3 than in a program like unity. Other game engines are much better organized at a architectural api level, so the power and flexibility is also typically higher than in construct. Compare the unity collision api to constructs and you'll get a simple overview of just how tiny construct is in comparison.

    OOP in c3:

    Families currently allow only one layer of abstraction in construct, (since you can't nest families in families), so to create a better OOP system in events, you have to get creative. You can simulate deeper structures by adding objects into multiple families, and then use a uid picking framework to select multiple families from a single object. The organization of these families and which objects go in them has to be documented and remembered by the dev. You then can assume (as the dev) that any objectA in some family will also be in another family that it "inherits from". If you want that base functionality, you then pick that family by the object uid you currently have picked. But this forces every object to now require complex picking boiler plate and for each loops (which reduces performance). Worse, you also have to repeat the boiler code events for every level of depth you wish to add for an object, and for every structure you create, meaning the exercise of abstraction isn't itself abstracted - this also creates significant event sheet performance overhead. If you also want inherited custom actions, you now have to repeat the creation of those actions for each family in a tree, and link them together. Every object added will require this boiler plate. The deeper the structure being simulated, the more events per object that have to be run and the more boiler plate you need to set it up, increasing dev time per object added. So even if things were running okay, once you add this system to ease development, your performance may likely go down. It will depend on how many abilities you have in game, vs the average number per object, contrasted to the depth required to simulate OOP. The processing power per empty event is not trivial when duplicating structures like this with nested loops. I have found this method to be cumbersome to set up and don't recommend it. It creates less complexity to add and modify abilities or effects, but is itself difficult to maintain at any scale and tedious to expand.

    If construct allowed families to inherit from families, this would be a non-issue. Another solution would be containers, but containers don't work with families. So you have a weird dynamic where you have to pick your poison.

    ECS

    :

    Setting up ECS is easier imo, but similar to simulated OOP, it requires repeating boiler plate for every system you need to handle ECS. In a nutshell, you have to use some OOP style systems like described above, but you use it only for attaching the ecs behavior to objects. The depth of the oop is shallow, but it still requires boiler plate per added object type. Basically you create a family for "systems", and family for "components". The raw requirements for these families is that the system handles component addition and removal, and routes functionality to the components. You can connect this via UIDs or create a parent/child graph and use that. In a platformer example, a system could be a character handler, and a component would be the ability or behavior. The handler performs the abstract task of routing input and state to the active behavior. Some people use state machine type logic where the components define transitions to other behaviors, but I prefer the transitions to be handled without a component specifying the components it transitions to.

    At the end of the day, for ECS, you will have to use a Foreach loop for every system, and then loop through the components, running various custom actions like "Check" "Enter" "exit" "update" etc... The overhead of the small scale OOP to pick related families, dictionary lists of components, etc... and then dynamic routing of state/input through components, means that the performance overhead of each system scales with its complexity and its possible to achieve better performance than the simple/basic branching conditionals if the project is complicated. Also, adding functionality is as easy as the system is designed for, and allows the functionality to be self contained. apart from the fact that you still have to boiler plate every object.

    In other words, if I want mario to be able to do a ground pound one day, I clone the object type "BehaviorComponentsTemplate", name it "Behavior_groundPound", copy the template eventsheet for the boiler plate (replacing appropriate code), and then manually add every action needing overridden, and then add in specific functionality. The actual functionality is faster to write than setting up the boiler plate. Then add an include in the correct event sheet and go.

    The payoff? All the functionality for ground pound is now a component I could add to any character, without having to add conditionals, and all contained in one place instead of being in multiple locations.

    The downside: This system requires a lot of nested loops and picking so you do take a performance hit over a manually crafted rigid system. Any character that has rigid abilities should still be manually coded, meaning you now potentially have duplicated code all over or need a simple light weight router system. Again, this is alot of work to set up, and requires ongoing boiler plate management; Meaning if you change the system, you may have to change every single bit of boiler plate through the entire project.

    BOOM. That is BAD, BAD, BAD.

    TLDR

    So basically, c3 forces bad types of programing in order to avoid bad types of programing while solving complex/advanced problems, and the solutions themselves are difficult to maintain or scale. You avoid one issue, but create a different issue.

    The takeaway? Construct isn't the right solution for a sufficiently complex project with high dynamic object counts. It literally becomes a case of, this isn't possible, and better off in a different engine.

    For my own project, I really wanted to finish a game with construct, so I went from a bullet hell style game with high numbers to a game with fewer but much more impactful threats. It actually turned out to be good for game feel, as it reduces noise and makes choices more meaningful. Each enemy is an intimate threat, instead of one of a 1000.

  • Okay, Cool, cool.

    ... Uh, Does type script work within the c3 environment?

    I thought typescript only works outside of c3?

  • Cool, Thanks.

    And I would add that while I "can" do many things correctly when it comes to my hacks, but constructscorrect way is via very terrible coding practices, duplicating code, and other bad practices.

    The advice to not do something is actually advice to move to a different engine; An admittance that c3 is not the right tool for the job.

    Had a private api existed in 2014, I personally never would have bought construct 2. I never would have bothered with construct at all for that matter, as several rex plugins gave me the ability to jump in and start doing stuff that c2 wouldn't let you, and recently, I certainly wouldn't have made the decision to port my existing project into c3. I'll also point out that being able to download and tweak existing vanilla behaviors was also the only reason I started using the sdk in the first place. Without that, I would have surely skipped. I think many others would agree; The addon devs made construct viable for alot more projects than you can imagine, and the result was more paying users.

    Construct is great in a few areas, and very lacking in many others. Addon devs picked up that slack, and this isn't to underrate the work you have done Ashley .

    I don't have the data to make such claims credibly, but I don't think nearly as many people would have used/adopted or stuck with construct if add-ons had been limited the way you wish they were back in 2014.

    This whole issue makes staying with construct less advantageous than I perceived it to be 6 months ago. This CAN change with a solid API, but looking at the rate of development in the last 10 years, I'm not holding my breath, and I think that's fair.

  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads

    Ah... well... that makes sense then, Ashley

    And on point #2, I agree as well mostly, which is fundamentally why I took your advice last spring a cleaned everything up. I personally used internal calls because it was convenient - and more performant in some cases, but I also stopped prematurely optimizing everything and try to not worry about it.

    Here in a few months, I might actually be to a point where I can fundamentally prove whether or not the performance concerns I had are really concerns at all.

    As an aside, and just to further plug my agenda, Nested families or shared behaviors across families would solve so many problems (any way to prevent the cumbersome alternative of nested foreach loops and picking companion objects by uid.) Extending behaviors would also be a huge gain for scalable design and good coding practice. Custom actions are a start on this, but being unable to nest families or share behaviors creates a situation where you still can only go one layer deep into abstraction, which basically precludes any deeper/complex custom systems being constructed via events.

  • Wholly barbecued barnacles. You have no idea how long I stared at that even after you pointed it out.

    Thanks.

    As to the second point, is there a way to prevent users from accessing functions we want to keep private?

    I have typically structured my addons the way it is recommended, with properties and methods in the main class and all ACES simply calling getters/setters/and functions, but I always had some functions that shouldnʻt be accessed out of order or context and I donʻt really need them exposed to the scripting side in c3 editor.

    ex:

    //Some complicated task
    DoSomething() {
    	_doPrepThingA();
    	if(_checkThingB()) {
    		_doSomething();
    		_resolveThingA();
    	}	
    }
    

    Where DoSomething wraps up all the things that need to happen everytime a user would want to do the thing. But, _doSomething does that thing without the additional checks and is used internally for performance reasons, but shouldnt be available to the user.

  • Okay, do you know if there was any plan to allow such a thing? Or is that problematic?

    Currently I have been moving more and more functionality into scripts or addons as that allows me to avoid event sheet forloops, but I still preffer the visual nature of event sheets over scripting.

  • Hello,

    I am having trouble accessing behaviors from js in construct:

    For example. I am using the sdk2 sample behavior.

    const o = runtime.objects.Sprite.getFirstInstance();
    const b = o.behaviors.MyCustomBehavior;
    o._setMyProperty(5);

    I get the error that _setMyProperty is not a function. In the v1 of the sample behavior, the script interface maps to _setMyProperty.

    The manual says to simply put what we want to access via script in main class.

    Which, I am trying to understand. Wouldn't this mean we have no ability to prevent users from accessing a behaviors runtime methods or properties, or will #private protect them. I wanted to test this, but atm, I can't access anything from script on v2.

  • Basically the title.

    I want to be able to add common aces to a project in order to get around the limitations of behaviors and families not being able to be nested. Since I have a number of families that need access to raytracing, and some of those families have the same objects in them, alot of objects end up having 3-5 los behaviors on them and a similar number of custom movement behaviors.

    Nested families or ECS would solve this issue, but apart from that, being able to add common functionality to all objects is also a way to deal.

    Without duplicating code, my current workaround (event sheet ecs) is cumbersome and result in object count bloat, necessary and abundant "foreach" loops with picking byuid, and event sheet overhead increase, resulting in poor performance.

    > I already know of at least one easy way to hack around the sdk2 limitations and expand the api to include whatever internal calls I want anyway - its just "slightly" more bothersome than just installing a plugin - until someone releases tool to automate it. At which point, poor little Timmy IS going to use that hack.

    >

    It's probably best not to do this, this could incite more stricter changes to the API's which would not help anyone, and just create a worse ecosystem for construct, and create a worse relationship between addon devs and the folks making the engine.

    I think there is plenty of time, to migrate and test, and request api changes. before v1 gets sunset. I worry statement like these will just antagonize the dev's instead of bringing positive change.

    I agree. I have no intentions of doing this. I already cleaned up most of my behaviors to avoid internal api use, and have stayed clean since. Most reasons for using internal calls arenʻt as applicable to me now as in previous years, due to better api around collisions, and the addition of particular features.

    I mention it because the major reason for sunsetting sdk1 will be irrelevant if such hacks are adopted, which I believe they will be unless every need of users can be addressed in sdk2. Due to the size of the construct team, I find that incredibly unlikely. Even getting feature additions, like vector math or expanded collision support natively takes forever if at all ever. (using a behavior to resolve collisions, or raycast has severe disadvantages over common ACES, for example, given how you canʻt nest families and such forces poor practices when it comes to architectural design.

    Ashley - Sorry, I donʻt seem to be able to find the rule in the link provided.

    Either way, you are right, and I apologize. I meant that statement as hyperbole, and not literally - which I agree is in bad faith to the conversation at hand. I meant that despite listening to us, you chose a path that I disagree, nor do I understand the reasoning from my perspective.

    You have obviously spent a great deal of time decidedly NOT ignoring us, and myself, and I am grateful for that. So sorry for being boorish these last few days.

    Thank you for the time you have spent dealing with this.

  • Awesome, that is very helpful. When I am really screwing around , I manage to crash the editor several times an hour. Lol. This is usually my fault entirely, but when its not, it also probably is. This should speed up the number of times I can crash in a single sitting :D

    I was going to also bring up what piranha305 mentioned as that is currently an issue at the moment for me, and frustration there led to my last few posts. It seemed from other forum posts there was no intention of porting over the event sheet classes.

    -----

    Honestly though, It still seems like letting sdk1 slowly vanish organically by virtue of blasting warning signs not to use sdk1 would more than solve the problem, we could have cake and we could eat it too. Anyone using sdk1 gets no support. Warn them in the editor on load, etc... problem more than solved. We all move on to sdk2 and live happily ever after.

    I just don't the current move. You say this way solves the issue of people coming to you crying when their project breaks, but all the crying we are doing here is completely ignored. Its like poisoning the beehive to prevent hornets from attacking them, who cares about any of the bees. The fact is, there will always still be hornets tomorrow, so even the bees of tomorrow won't be safe.

    I already know of at least one easy way to hack around the sdk2 limitations and expand the api to include whatever internal calls I want anyway - its just "slightly" more bothersome than just installing a plugin - until someone releases tool to automate it. At which point, poor little Timmy IS going to use that hack.

    Then once again, we will have a bunch of plugins that will all break just as soon as each update happens and we will be right here again.

  • Well, right. I get that, but I am saying that other engines also don't force you into a proprietary model, which the marketing language implies. It is a comparative statement that is misleading. It should just highlight constructs strengths, not rely on making a comparison that isn't relevant.

    Gamemaker is the worst of the lot, and in most ways construct has an objective advantage over it. But even it doesn't require gsml at this point.