Many makers are fond of Nextion displays, because they simplify the process of creating graphical touch interfaces for embedded systems, and are less expensive than other HMI (Human-Machine Interface) screens. They were not designed for games, so I decided I had to write one as a challenge!
Nextion displays, made by ITEAD studio, have a built-in pre-programmed microcontroller which handles the resistive touch input, the graphical output, and two-way UART communications. The basic interface is designed using the Nextion IDE on a PC and uploaded to the screen module; then, a supervising embedded system (e.g. an Arduino) can talk to the module through the UART, activate the different graphical components and respond to the users’ input.
The display also has its own “logic”, a simple and very limited scripting language. With it, the display module can do a little processing on its own and take some load off the master system. At first glance this language may look a bit like C, but that’s just an illusion. For instance, there’s no semicolon, no pointers, arrays or structures, and the condition of an “if” statement can’t even contain any numerical or logical operators. All this is not the result of bad design necessarily – it’s simply tailored for one specific purpose, which is definitely not video games. So I just had to try and write one, you see… 🙂
For lack of time (or certainty about the feasibility of the process) I decided to start with something simple: a one-player Pong, AKA Brickless Arkanoid. It’s easy to program, and doesn’t require much from the platform in terms of resources.
The first part, creating a “ball” (a small square actually) that bounces around on the screen, was easy. The scripting language doesn’t support variable declarations, but there are three predefined global variables (sys0, sys1 and sys2), and the IDE allows you to add “invisible” variable objects. I used a “fill” command to draw the ball and erase it (i.e. draw it in the same color as the background), and a couple of “if”s to check whether it hit any side of the display and change is direction accordingly. Now it was time to get the player into the loop.
That, apparently, isn’t so easy. The software loops in Nextion are blocking, meaning that while they are running, the screen does not check or record user input. Of course, a game requires some kind of iteration, but also fast response to user commands. The alternative to loops is to use a Timer, which “ticks” and activates some custom code at short, regular intervals. However, the shortest “tick” supported by the Nextion is 50ms, which is quite long actually – only 20 times per second. This is a slow framerate even for Pong,
Imagine, for example, that the ball moved one pixel with every tick of the timer. On a 400×240 pixel display such as the Nextion module I used, This means 12 whole seconds to cross the screen along its shorter axis! To remedy this I used 4-pixel steps; not blazing fast, but closer to a proper game. Larger steps may cause the ball movement to look “jumpy” and disjointed.
Perhaps the ball can’t move faster*, but the game can still be challenging if the paddle itself moves slower. I added two controls to the display, one for moving the paddle left and the other for moving it right. While one of them is pressed, the paddle crawls at 1 pixel per tick in the corresponding direction. Unlike regular Pong, the point of my game is therefore not quick reflexes, but estimating and predicting in advance where the ball is going and where it will be full seconds from now, when it reaches the bottom of the screen.
* Since writing this, I figures a clever way to have faster timer ticks despite the Nextions’ 50ms limitation. But that’s for another post…
The Nextion scripting language is primitive, but with a little creativity it’s possible to perform some interesting and useful tricks. For instance, it’s impossible to define proper functions, but you can initiate a call – from your own code – to a “click” event of visual components, even if they are not on the current display. Click events can have associated code of their own, so calling them is similar to calling a function. That’s how I implemented the resetting of the ball position, which is done at the beginning of the game and after a miss. Without this trick, I probably would have needed to write the same code twice in different places.
I added a basic score point counter – the number of consecutive hits since the last miss, and the highest score achieved since the device was turned on. The game plays nicely and is even fun, at least for a couple of minutes.
Following this success, I started thinking about other, more advanced directions, that the creators of Nextion probably never even dreamed about. I also found there were a few standalone Nextion games in the Nextion forums (for example, a tic-tac-toe for two human players), although they are not of the dynamic kind like my Pong. Currently I’m looking into faster timing and lookup tables (again, in a language without arrays or pointers!). Hopefully, this will lead to more exciting programs, so stay tuned 🙂