microsoft / pxt-arcade

Arcade game editor based on Microsoft MakeCode
https://arcade.makecode.com
MIT License
477 stars 207 forks source link

Dynamic variable names? #2443

Closed chino closed 3 years ago

chino commented 3 years ago

Is your feature request related to a problem? Please describe.

I was trying to reuse code and wanted to move a group of blocks into something like a CreateEnemy() function so I could call it multiple times.

However the set ... to block only seems to support variables that are predefined by hand.

Describe the solution you'd like

It would be great if set ... to would support passing a variable name to it?

The set ... to variable field could for instance accept a text or variable argument instead of being a fixed drop down selector?

That way a text block, variable name, function argument name, etc. could be dragged right onto it.

There seems to be many other cases that use drop downs as well (ex: play sound ...) where there could be dynamic arguments I'd imagine.

Describe alternatives you've considered

Perhaps have an enemies "array" and push sprites onto it. This would possibly be fine for large swaths of enemies.

However that doesn't really give you a way to "name" the entity. Obviously in the named case you could create a one off variable but that defeats the purpose of trying to reuse code by using a function?

The function could "return" a sprite allowing the caller to pass it like, set ... to CreateEnemy() but it feels a bit hacky/limiting?

Maybe if there was "maps" that would let you add named entries but that doesn't seem to exist as a block?

Additional context

Perhaps this is all misguided and there is better ways to achieve this! Looking forward to any help.

Apologies if this isn't the right medium for this. I have a few other ideas which I'd like to possibly suggest. If there is a better place for this please let me know.

jwunderl commented 3 years ago

This does sound a lot like an array and/or function that returns the enemy would be more helpful here; from how you're describing it, the generated names would likely either end up being either:

image

As an implementation based note, I think making the set {variable} to ... take a string would disconnect it from the generated javascript quite a bit / make that mapping harder to follow, as currently that corresponds to just setting a variable, where allowing for computed names would require there to be some sort of global object that we'd be indexing to with the name

Also, suggestions are always welcome here! If you want to discuss things in general, the forum (forum.makecode.com) is also available, but if you have an idea with a suggestion in mind already filing a bug / feature request is great~

chino commented 3 years ago

Thanks for answering!

That's a very helpful tip there about the array list.

I agree that it feels like in most cases you would just end up with an array (hence indexed names) but in many cases we're just trying to "DRY" up code by creating functions? Does that seem like reasonable use case?

A function returning a sprite (requiring the caller to assign it to a variable) would be very limiting because many of the operations you want to perform "require" the sprite/variable name to perform actions onto! For instance move ... with buttons, place ... on ..., camera follow sprite ..., etc?


There is also all the "callbacks" like, on ... of kind ... hits wall at ... which would be nice if I could set those as normal connectable blocks inside of a function for specific instances. That would allow me to create a function that could define an entity and all of it's callbacks in one shot? Instead of needing completely disconnected blocks that functions have no way to define? I imagine the only way to have collision callbacks for one off entities would be overloading a bunch of "kinds" for one off entities and/or having a ton of if/else blocks saying, if sprite is target-sprite ... which is a lot of spaghetti code?

I realize that's a massive change to even support such an idea so it's a long shot I suppose but just throwing it out here.


In regards to the variable implementation.

I'm not sure how the internal code generation works but I picture it could simply template the code to generate let {var} = ...?

Reading into this a bit more I just realized that since this is using TypeScript it's not as easy as using classic window handle as a global scope where you could trivially do things like window[x]=y with ease. Although I'm not sure if TypeScript offers any metaprogramming that would allow you to do something like let global=this; ... global[foo]=bar and so forth (I'm not very familiar with TyepScript).

Also isn't it already keeping it's own internal record of objects to be able to offer things like array of sprites of kind? I guess it's the sprites object (from sprites.create()) that's internally keeping something like a class variable to maintain an internal list of all sprites created.

Would be interesting to start looking at some of these internal details to see how it's all wired together :]

jwunderl commented 3 years ago

Using functions to avoid repeating sections of code / keeping things DRY is a totally valid use case, yes -- it's typically one of the earliest use cases for functions that is taught, as it gives an easy scenario for students to work out -- for example, this is the first assignment the intro to java course I was a TA of for several years uses to introduce methods: https://courses.cs.washington.edu/courses/cse142/20sp/handouts/03.pdf -- the assignment is based off identifying sections that repeat structurally or behaviorally.

The second portion (referencing the sprite within the function) is the portion that I was referring to when I mentioned that local variables would be helpful, but we can do the same by keeping one variable as a temporary reference; e.g. something like

image

As long as nothing yields (e.g. a pause) this will run sequentially without issue, and is something I personally do quite often.

We used to have events that were attached to specific sprites at the very beginning of arcade, but they dropped off in favor of the kind based overlaps and eventually got removed; having them tied to specific sprites ended up typically being harder to follow, and resulted in code being too smooshed together / we typically focus on top level events in general in makecode. Kinds are intended to represent groups / types of sprites, so the intention would typically be to create different kinds for sprites that should have different behaviors; if you want to split up behaviors on a more granular level (e.g. make all the enemies behave in the same way, but make certain ones worth more points), I'd say the arcade-sprite-data extension is probably the easiest way to handle that -- by attaching values or identifiers to the sprites to narrow down the behaviors:

image

with that, instead of saying something like if sprite == enemy214 do abc, you'd do something like if sprite has score stored on them, add it when the sprite is destroyed orif sprite has a name attached that is "Horatio", do a special attack`

Re: implementation, it's worth noting we're not exactly typescript -- we're a subset / variation of it to allow us to support browsers and hardware with the same user code (more details at https://makecode.com/language). For example, we don't have a window at all; it's just not relevant to the targets we support / doesn't apply. Implementing something like this would be very similar - declaring a global object at the top and indexing into as in globalVarHolder["myComputedName"], but that isn't reminiscent of how typescript / javascript / etc is typically taught and is definitely harder to parse (especially for a new learner). (there's also concerns identifying types of variables / etc, but that's all secondary)

The issue isn't really whether we can generate the code to make it work, it's how we can make the code work in such a way that a new user can follow along and see roughly how things convert from blocks to the generated typescript / have code that wouldn't usually surprise you too much.

For sprites.allOfKind, that's not doing anything super special; the game's physics / scene need to refer to all of the sprites to apply movements / render them each frame, so they keep track of them internally; e.g. array of sprites of kind just returns the sprites stored here: https://github.com/microsoft/pxt-common-packages/blob/c876ad7cce27ef17e7f4c9fe640e42ae87fa4768/libs/game/scene.ts#L81. The more interesting part about how everything is wired together is how we generate code that runs the same on hardware (merged with built c++) and the browser (with actual typescript connecting between the user code and the simulator). For example, you can open the built js and assembly that actually runs in the simulator under explorer->built->binary.[js|asm]. That stuff is not typically useful to read on it's own though :)

built js

chino commented 3 years ago

Sorry, I only read #2302 after replying and the temp variable approach does make sense. Though they're worried about possible thread safety of such an approach it seems?

The arcade-sprite-data extension would also be a great help because it allows you to attach any other details (images, status bar, etc) you would like directly to the sprite which you can then pass around as one nice sprite pointer.

As a side note is there any plans to support a map/hash type of block?


I understand your point on the historical evolution. I guess so far my issue has been most of the event blocks we've created become a big spider web of if is-in-platform-mode ... else if in drive-mode ... and so on ... it ends up like a big cobb web of branches for everything. My son frequently forgets to update different places or loses track of what's where.

As another side note one idea I had that would be great is if we could search (ctrl +f) through the block pane to search for instances of text similar to how you would search around the js view for where a variable name is used or a functions is called and so forth.

I ended up with similar spaghetti related to button press events. I ended up just having the button presses set a variable like is-button-x-pressed and then checking for that in other places.

I also ended up with one massive on-game-update routine that checks a bunch of state flags to change the movement/world properties (like input controls and gravity). This allowed me to have different game modes because otherwise I have no way to override/change existing event blocks that were already defined. This was mainly because my kid wanted to have different scenes act differently (top down view, side platformer, riding bike/ship, etc) all in one game which made me get inventive. I did tell him he had to stick to smaller games because it quickly becomes too much :]


That language link is the one I landed on as well!

I agree that something like global[x]=y would only be something rarely used and if the user caused the compiler to generate that then they would perhaps learn something from that!?

This is basically what I was imagining it was doing: https://github.com/microsoft/pxt-common-packages/blob/master/libs/game/sprite.ts#L402

On the topic of physics I don't believe sprite rotations is possible?

That simulator / code generation output is really interesting!

I've dug around in that area a bit trying to figure out why/how/if I could expose A6 pin to read the battery status: https://github.com/adafruit/uf2-samdx1/issues/134


I just want to say that we have really enjoyed makecode so far. It's really amazing what a child can create all on their own. It's extremely empowering and really appreciate all the work.

Would be great if we could add more tutorials (we could probably help) to baby step children through it and/or perhaps teach more complicated techniques like we hit above.

Random thought, how persistent are the share links? Do they expire after some time?

jwunderl commented 3 years ago

local scoping in theory would resolve any thread safety / etc in this sort of situation; the tempSprite variable approach above requires you to avoid doing anything that yields (or else another call to create enemy sprite could potentially change the sprite that is being referred to)

re: making a map in blocks, this extension is pretty much that: https://github.com/microsoft/arcade-block-objects

A ctrl+f equivalent for blocks would be great, yes; I definitely remember talking about that sort of thing, just takes time / designing / prioritization to implement

re: game update, you can actually have multiple of those; so you could split up the on game updates to contain just one states events (e.g. have the entire on game update's content be wrapped in an if for the particular scene you want to be in).

Making games that switch between a lot of views is a bit tedious at the moment as you've noted - we have a way of handling that nicely in typescript, by pushing and popping scenes (which keep events tracked separately), but we haven't come up with a good way of handling that in a single blocks project yet. For the first game jam, we took in a lot of blocks games and combined them into one with a small extension: https://forum.makecode.com/t/makecode-arcade-game-jam-and-video-series/530 might be interesting for your kid, as they could work on each game separately and then eventually combine them into one as a javascript project later on (with a bit more experienced help)?

Glad to hear you've been enjoying it, and thanks a bunch for all the feedback you've given already!

More tutorials is always good, yes; it just ends up taking time to create them and make something that is interesting and useful. A while back I made some minimal tutorials that try to keep things to just a few steps (the game design concepts ones), but the issue tends to be that the result isn't particularly interesting without a bit more work / have to combine several tutorials to get something you could call fun:

image

Share links typically last indefinitely (at this point) unless it gets taken down due to inappropriate content / etc.

chino commented 3 years ago

Thanks!

Maybe these questions I've had could be some simple quick tutorials that would unlock answers for others?

chino commented 3 years ago

I've been looking for a game jam as well to join. Any possibility in having another one!?

jwunderl commented 3 years ago

They'd be a bit advanced compared to most tutorials, but it's a good idea -- I'll try and find some time to make some :)

And we've actually done several now, and just finished another one https://forum.makecode.com/t/announcement-makecode-arcade-traffic-jam/3357/66 but you can always add your game as a comment on that thread! We've been doing a number of these, and I think we want to keep up the pace / do them regularly, so I would suggest keeping an eye on the forum / twitter for when the next one comes -- I doubt it will be too long!

for reference, https://arcade.makecode.com/gamejam will always link to the latest one, here's two of the other ones we've had:

https://arcade.makecode.com/gamejam/traffic

https://arcade.makecode.com/gamejam/garden

chino commented 3 years ago

This looks great! Did I read somewhere there was a discord?

jwunderl commented 3 years ago

There used to be / technically might still be, but it's mostly been left in favor for the forum (it's been a lot easier to keep track of topics / questions / previous responses with discourse) ~

chino commented 3 years ago

Hm... for those of us who might not be constantly watching the forums (or ever use twitter?) then perhaps a little mailing list type of feature specially for game jam announcements might be a good idea!? We would really like to participate in this :] I'll try to signup and keep and eye on it!

jwunderl commented 3 years ago

We don't currently have one specifically for game jams, but it's a good idea / something to think about!

Adafruit has one for us makecode in general, that does include game jams; http://makecode.adafruitdaily.com/ and sign up is at https://www.adafruitdaily.com/

abchatra commented 3 years ago

Question answered here. If not let us know.