Yes, those apps are either made with a professional compiler like Android Studio using Java or a compiler made specifically for building high speed games. These compilers are usually multi-threaded compilers. App Inventor 2 is single threaded. Despite all its nice features and ease to developing apps, App Inventor's graphic rending is very slow. This is most likely your problem.
Said another way, App Inventor 2 graphics are slow compared to game engine compilers. Attempting to program a fast action graphic intensive game is almost hopeless.
Importantly, we do not leverage technologies like OpenGL. The Canvas and its associated components are rendered 100% using the default graphics pipeline for the particular build of Android + hardware you're using. It might be possible for some builds of Android that it would leverage OpenGL under the hood but we can't provide any insight into that. By default they will be rendered using the software pipeline.
A round ‘sprite’ that can be placed on a Canvas, where it can react to touches and drags, interact with other sprites (ImageSprites and other Ball s) and the edge of the Canvas , and move according to its property values.
For example, to have a Ball move 4 pixels toward the top of a Canvas every 500 milliseconds (half second), you would set the Speed property to 4 [pixels], the Interval property to 500 [milliseconds], the Heading property to 90 [degrees], and the Enabled property to true . These and its other properties can be changed at any time.
The difference between a Ball and an ImageSprite is that the latter can get its appearance from an image file, while a Ball ’s appearance can only be changed by varying its PaintColor and Radius properties.
Canvas.Height and Width are in pixels, and are available after the Canvas appears on the screen, so those should help you fit to your devices.
I assume different sprites have different lists of images to represent different phases of their jumping. You could keep a reference copy of that in a global dictionary, keyed by sprite, kept constant.
You could also keep a dictionary of jump instructions for the sprites, initially loaded with empty lists for each sprite, because no one is jumping yet.
When you want to start a sprite jumping, load that sprite's value in the jump instructions with a copy of the reference image list for that sprite, and start a repeating jumper clock.
The repeating jumper clock checks each key in the jump instructions for a nonempty jump image list. If a nonempty list is found, load the sprite's image from the front (item 1) of that list, and remove item 1 from that list, then put it back in the dictionary for the next cycle.