The Sunday Blog
Scalable design and gaming – Part 1
Today, we will dive into scalable design. This means that we want to develop for our beloved Nextion HMIs without yet knowing which screen size and resolution we’ll finally use. So, we want to look how we can do a HMI design which, within the actual limits of firmware and memory, adapts itself to the screen.
To make things interesting, we’ll take a use case which promises some fun: We’ll develop a Pong game for the Nextion.
First, some history
The Pong game (popular in the second half of the 1970ies) was one of the first what we could call console games, today. Basically, it simulates a simple Tennis game, one player on each side, equipped with a potentiometer to move the tennis racket up and down on their respective screen border in order to bounce the ball back. If you missed it, your adversary got one point. A round was ended when the first player reached 10 points or whatever the limit was.
The very first Pong consoles weren’t even yet built around microprocessors, but around Standard TTL logic ICs, oscillators, counters, monoflops, all what was needed to generate the different luminance pulses for the white spots on the black screen, in accordance with the horizontal and vertical sync pulses. A simple resistor and diode adder “assembled” everything to an analog video signal which was then sent through an UHF modulator. The output signal of the latter was then fed into the antenna input of the TV, separate screens as we know them today didn’t yet exist or were simply not affordable.
Second, some strategic thoughts
The way an analog TV showed the image is totally different from how an image is displayed on a LCD display. The main component of such an old TV was the cathode ray screen where a single electron beam was produced in the back area, then accelerated until it would hit a luminescent pixel on the inner side of the front glass. To fill the full screen, this electron beam had to be deflected by strong electromagnetic fields, in a way that it would “scan” the whole screen line for line, from top to bottom. At the same time, the intensity of the beam was permanently modulated which allowed to “draw” lighter and darker areas and details of a picture. All this was done with incredible speed, 25 times a second, the beam would draw 625 lines in Europe or 30 times a second 525 lines in the U.S. (in reality, it was still a little more complicated with interleaving to reduce flicker, but we’ll let it at that for the moment).
Since this principle was intended to show moving pictures with a frame rate of 25 or 30 FPS, you (as a game developer) hadn’t to worry about fluid movements. Each 1/25 or 1/30 second, you’d simply draw the moving object at a different location of its calculated trajectory, because the screen would not longer show it at its previous position.
On our Nextion HMIs, things are totally different. If you draw a spot somewhere, using GUI commands, for example, it will sit there until you erase it by drawing it again in the background color. Thus each moving object has to be undrawn before it can be drawn again in a different location. Beyond this relatively simple constraint, we see that if all that should happen in front of some background content, we would need to restore this background content by refreshing the whole screen or most parts of it which costs precious time and processor cycles. To ease our life and to make everything happen in a way that we can see a fluid movement, we just decide to use no background for our Pong playing area. In ever case, the vertical center line can be omitted, it’s only eye candy and has no impact on the game.
So, there remains the question of the score display. I decided to put small vertical progress bars at the extreme right and left borders of the screen. Invisible at startup, the colored bar will grow each time until it fills the screen height at a score of 10.
The rackets can then be simulated by two vertical sliders, one left, one right, glued next to the progress bars.
So, we’ll have only 4 visible screen components to place in the editor, to make them full height, and to give them a fixed width, since the x,y,h, and w attributes can’t be modified at runtime. Experimenting with the smaller Nextion T (Standard series) displays, I found that a width of 5px for each progress bar and a width of 35px for each slider give nice results as a starting point. This can then individually be modified. It is just important to retain how many pixels are “eaten up” for the controls on each side of the screen, and we declare a variable for this in the program.s tab: int ctl_width=40. This would be the only constant which we’d need to modify when we change the screen layout. Promised.
All other aspects of our screen and of the gaming can then be held in variables which can be calculated in the page’s postInitialize event, when we can read the page height and width to calculate everything proportionally. For example, the visible height of the rackets can be set at runtime, using the .wid attribute. The ball’s radius would also need to change, depending on the screen size, as does the movement vector.
Did you just read “vector”? You did. But don’t fear, it’s just 2 other variables, setting the number of pixels in x and in y direction which the ball will move from one timer tick to the next. The advantage of this approach is that if the ball bounces, either at the upper and lower borders or at one of the rackets, you’ll just have to revert the sign of the y or the x component to make the ball bounce back, following the physical principle that the angle of incidence equals the angle of reflection.
But enough theory, that was already a rather lengthy introduction and we’ll put everything into practice, next Sunday. Stay tuned, here already a little teaser: