We developed an AR game based on RPi in our project. In this game, player can control a robot through WiFi to explore the environment around it. There would be some monsters and other items for player to find. Player can fight with monsters to gain experience or can be defeated by a monster. Enough experience can level up player and monsters can grow stronger, too.
- Designed the whole game, mainly about exploring and battle
- Display game interface on real scene to achieve effects
- Read input from keyboard to RPi through WiFi
- Transfer images from RPi to monitor through WiFi
- Enhance the visual effects of game
In our game the background should be the real environment picture, so we need use camera to take video. First we installed PiCamera on RPi as our camera. Then we can put game interface upon the video to augment reality, so players can play with real scene as background.
Initially, we used code provided by PiCamera documentation to get video from PiCamera, and the way this code did it is it asks camera to take photos every certain seconds and store it as a jpg file. To make it a video, we use PyGame to load this jpg file every certain seconds so people can see a video. But this method is slow and the frames per second is too low to allow players react to attacks from monsters.
In order to improve the frames per second, we tried use NumPy to store the images from camera and transfer them to PyGame for further operation so we don’t need to store a jpg file in disk which is time expensive. NumPy makes the video much smoother, but still not good enough. Finally, we installed openCV on our Pi and use libraries provided by openCV to read NumPy arrays from PiCamera, and it works very well as monsters can show on the screen in a very smooth way.
In the figure above, the background is video catched by PiCamera, and upon that there is a small blue monster bouncing around to helps us test if the frames per second is good enough.
Communication Between RPi and Host
Players will control robot to explore the surrounding environment instead of hold a RPi and hang around, so we don’t want to display our game on PiTFT or use buttons on it. A wireless controller (host) is necessary. In our project, we use a laptop as the host.
At first we tried use a laptop to control RPi through WiFi and VNCserver. In order to use VNC we installed tightvncserver on RPi. Through VNC Viewer installed on a device, we can connect to RPi through WiFi and start the game. It works on a laptop or an iPad. But here is the problem again, the frames per second is too slow to allow players play this game normally. We thought if we could improve the internet speed the display can be better, and then we tried using laptop itself as a WiFi hotspot and let RPi connect to it directly. Hotspot makes video smoother but still not good enough.
Actually, we tried using ssh to connect laptop and RPi at the very first time, but we found that we can’t run PyGame through ssh so we turned to other solution. But after all those attempts failed to meet our expectation, we finally found X11 forwarding which allows ssh to run PyGame.
X11 forwarding is a special case of remote tunneling and we can easily display the window of pygame on our laptop with X11 forwarding. Since X11 server has already installed on Mobaxterm, we don’t need to install other tools on our laptop. But there are other things that need to be set on our RPi.
We need to run our main.py code with sudo to start the game because we need to run openCV in our program, but it has something conflicting with X11 forwarding. The normal X-forwarding can only work without sudo. So at first we couldn’t run our game with sudo, but we finally solved this problem with a tutorial.
Following the tutorial, we first use our Mobaxterm with ssh:
ssh [email protected](our ip)
Then we need to enter a command:
xauth list $DISPLAY
This command will returns:
server/unix:10 MIT-MAGIC-COOKIE-1 blablablablabla
Note that this will change every time we connect our RPi with ssh, so we have do these things every time. Then we need to change a file as root:
xauth add server/unix:10 MIT-MAGIC-COOKIE-1 blablablablabla
After doing this, we actually have already set up the X11 server successfully. You can check if you have done with command:
At last, we can change the user to pi and run your PyGame code with sudo. Solving this makes our pygame window run successfully on our laptop
As it is mentioned before, players will control a robot to play this game. The skeleton of robot is shown in the figure below.
On this skeleton we installed 2 wheels and 2 servos to control wheels, a battery pack to provide energy for servos as our basic robot.
We used Parallax Continuous Rotation Servo in our project. To control the servo, we basically will need to use RPi to generate PWM signals and send them to servos. The connection of RPi servos is shown in the figure below.
The signal shown in the figure below will stop the servo, and if we change the pulse width to 1.3ms, the servo will rotate in clockwise at its fastest speed, or if we change the pulse width to 1.7ms, the servo will rotate in counterclockwise at its fastest speed.
The figure shown below is the robot we finally have. Beside wheels and servos, we installed RPi, a breadboard, PiCamera, a mobile charger to support RPi and a speaker above to play music.
Basically the game consists of two parts: exploring and battle.
In the exploring part, players will control the robot to explore the surrounding environment. Like shown in the figure below, player can either use keyboard or click arrows on the screen to move the robot. The status including health points, mage points, experience, count of death and attack power of player is shown on the left side and a small map is shown on the right side, the red spots on the map means there is a monster in that block.
The map is a 5 x 5 grid, which we use a 5 x 5 matrix in NumPy to implement it. We initialize this matrix by filling it with random numbers range from 1 to 20. When a element in this matrix is between 2-6, in the block this element represent would be a monster, and the kind of monster is correspond to the number, for instance, if the element is 6, there should be a dragon in this block.
And when the player is moving in this map, the arrow which represent the location player at now will stay in the center of this map, while the map will move relatively. During moving, the farest column or row in the direction of moving will generate a set of new elements. The way we generate new elements is we generate 2 numbers, one of them ranges from 1-9 and another ranges from 1-5. The first number indicates what will be in a block, and the second indicates which block this thing will be in among the 5 blocks of the new column/row. And when the number is 7 or 8 there would be a sword or a fountain respectfully. Sword can increase the attack of player and fountain can recover player from wounds, and both of them will be marked as blue spots on the map. This mechanism of generating new columns/rows of map can reduce the space to store a large map and increase the speed of reading a map. What’s more, it allows players explore the environment without space constraints.
When the player is close to a monster and face to them, monsters will show in the player’s vision like the figure shown below.
If the monster is at the middle, that means this monster is in the block in front of you, and if the monster is at the left/right side, that means this monster is in the block in forward left/forward right.
When the player walk in a block with monster, then the game would change to battle phase. In the battle phase, the monster will look larger and will jump in the screen. Monsters will attack players automatically, and player will lose certain health points according to the monsters’ attack. Player can either use normal attack or skill to attack monsters. Push number “1” can use normal attack, push “2” can use skill which has more damage than normal attack but will cost mage points, and push “3” can use heal skill which can heal player but cost some mage points.
If player kill a monster, it will give you experience as award and the amount of experience depends on how strong the monster is, a stronger monster usually will award player more experience. And when a monster is killed, the next monster of the same kind will have a higher level and is stronger than before. When the experience bar is full, player will level up and attack power of player will increase. If player is killed, the death counts will plus one, and player respawn in the block he/she is.
When playing this game, the speaker will play different background music under different conditions, and when you fight with monsters, there would be both sound effects and visual effects.
We successfully designed a game on RPi and display the interface of game upon a real scene catched by PiCamera. Realized wireless control and display with a laptop through WiFi and enable player to control the robot to explore. In general, the game fully meet our expectation. Besides, we also developed some interesting additional stuffs like sword and fountain, and greatly enhanced the interface of our game which make this game more fun!
In this project, we not only develop a game but also learned a lot from the process of developing it. And of course, we came across many problems while doing it.
In the beginning, we used a function named getch in a while loop to get input from keyboard, which means this program will keep polling input from keyboard. And this cause us have to use two Python programs, one to run getch in the background and one to run the game. Besides, in order to exchange information between these two Python programs, we need another txt file to store the temporary information including map matrix and input from keyboards and others. We tried using FIFO to solve this problem at first, but we want to read information from another file multiple times but only write once, so it is hard to implement it with FIFO. Because we will control RPi wirelessly, the wait-for-edge in GPIO doesn’t work either, and we struggling with two Python programs for a long time. In the last week, we find a simple way to solve this problem which is use event in PyGame to read input from keyboards, there are certain event type of mouse and keyboard, and because PyGame is the core part of our code, so it can perfectly fit in our main program. By using this method, we combined those two programs as one.
Like we can’t use polling to get input, we can’t use time.sleep() to keep program running since it will block other codes from running. And the solution to that is using time.time() to get the start time and keep using time.time() to subtract the start time so to check the time.
Communication between RPi and laptop is complex when we need to transfer video. In the process of developing this part, we tried VNC server and VNC server with hotspot, but both of them didn’t meet our expectation because they are too slow. We finally used X11 forwarding. It works much smoother than before but it is only acceptable because the protocol it used is designed in 70s and out of time, it could be faster to transfer video through WiFi, but we didn’t find a better solution than X11 forwarding.
Working under openCV environment is annoying sometimes, because we have to enter a bunch of commands in order to work on openCV, and it can’t be input in a bash script, we didn’t find much about how to make openCV start automatically.
At the end of this project, we made an animation on the title and ending window, which is all of our monsters jump in the screen and jump out gradually. It is more complex than we expect, basically we made a matrix to store speed of each monster, the horizontal speed for every monster is a constant and they are equal, while the vertical speed will suddenly increase a random number when the monster contact with the bottom of screen, and it will gradually decrease when the monster is jumping up. All this setting made monsters jump like there is gravity, and it looks fun!
Source: ECE5725 RPi Based AR Game