By Yexiang Chang (yc2523) & Mengde Wu (mw934)
December 2018
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.
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[1] 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.
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[2].
Following the tutorial, we first use our Mobaxterm with ssh:
ssh pi@(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:
sudo su
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:
xauth list
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!
1. Improve the quality of video tranfer
2. Improve the visual effects
3. Add more monsters and skills
4. Make battle mechanism more various
5. Make a storyline for this game
mw934@cornell.edu
Developed the game exploring interface, servo
programs and communication system
yc2523@cornell.edu
Developed the game battle interface, PiCamera
and openCV programs and communication system
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | #!/usr/bin/env python # Team member: name: Mengde Wu netId: mw934 # name: Yexiang Chang netId: yc2523 import numpy as np from random import uniform import pygame from pygame.locals import * import sys, termios, tty, os import time import RPi.GPIO as GPIO import cv2 import subprocess # setup the callback routine, the system will run the callback function # if it detects a falling edge in one of these GPIOs GPIO.setmode(GPIO.BCM) GPIO.setup(26, GPIO.OUT) GPIO.setup(5, GPIO.OUT) s1 = GPIO.PWM(26, 46.5) s2 = GPIO.PWM(5, 46.5) s1.start(0) s2.start(0) # num:1 left servo # 2 right servo # dir:1 clockwise # -1 counterclockwise # 0 stop def set_direction(num,dir): if num==1: if dir==1: dc=7.8 elif dir==0: dc=0 elif dir==-1: dc=6.0 s1.ChangeDutyCycle(dc) elif num==2: if dir==1: dc=7.8 elif dir==0: dc=0 elif dir==-1: dc=6.0 s2.ChangeDutyCycle(dc) class Monster: def __init__(self,Skill,mid,pic,lv,name,hp,atk,defen,money,exp): self.Skill=Skill self.mid = mid self.pic = pic self.lv = lv self.name = name self.hp = hp self.atk = atk self.defen = defen self.money = money self.exp = exp class Skill: def __init__(self,name,dmg,pic,cost): self.name = name self.dmg = dmg self.pic = pic self.cost = cost class Player: def __init__(self,Skill,hp,mp,lv,exp,atk,death): self.Skill = Skill self.hp = hp self.mp = mp self.lv = lv self.exp = exp self.atk = atk self.death = death def map_generator(): map = np.zeros(49).reshape(7,7) for i in range(5): for j in range(5): map[i+1,j+1] = int(uniform(1.0,21.0)) if map[i+1,j+1] >= 7: map[i+1,j+1] = 1 map[3,3] = 1 return map def check_facing(pos,map): if pos[2] == 1: facing = np.array([int(map[pos[0]-1,pos[1]+1]),\ int(map[pos[0],pos[1]+1]),\ int(map[pos[0]+1,pos[1]+1])]) elif pos[2] == 2: facing = np.array([int(map[pos[0]-1,pos[1]-1]),\ int(map[pos[0]-1,pos[1]]),\ int(map[pos[0]-1,pos[1]+1])]) elif pos[2] == 3: facing = np.array([int(map[pos[0]+1,pos[1]-1]),\ int(map[pos[0],pos[1]-1]),\ int(map[pos[0]-1,pos[1]-1])]) elif pos[2] == 4: facing = np.array([int(map[pos[0]+1,pos[1]+1]),\ int(map[pos[0]+1,pos[1]]),\ int(map[pos[0]+1,pos[1]-1])]) return facing # check if all the monsters in the map are killed def check_map(map): if np.max(map) == 1: return 1 return 0 # initialize monsters rush = Skill(name='rush',dmg=1,pic='',cost=1) bat = Monster(rush,mid=1,pic='bat.png',lv=1,name='Bat',\ hp=4,atk=1,defen=1,money=1,exp=5) centaur = Monster(rush,mid=2,pic='centaur.png',lv=1,name='Centaur',\ hp=8,atk=5,defen=5,money=10,exp=10) angryboy = Monster(rush,mid=3,pic='angryboy.png',lv=1,name='Angryboy',\ hp=16,atk=10,defen=8,money=100,exp=15) pumpkin = Monster(rush,mid=4,pic='pumpkin.png',lv=1,name='Pumpkin',\ hp=32,atk=15,defen=15,money=500,exp=30) dragon = Monster(rush,mid=5,pic='dragon.png',lv=1,name='Dragon',\ hp=64,atk=20,defen=30,money=1000,exp=50) # initialize player player = Player(rush,hp=100,mp=100,lv=1,exp=0,atk=8,death=0) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 | #!/usr/bin/env python # Team member: name: Mengde Wu netId: mw934 # name: Yexiang Chang netId: yc2523 import sys import pygame from pygame.locals import * import time import cv2 import os import numpy as np from header import * ######################################## Initialization ######################################### # initialize pygame pygame.init() pygame.display.set_caption('PI AR GAME') # set color black = 0, 0, 0 green = 124, 252, 0 red = 255, 0, 0 blue = 0, 255, 255 yellow = 255, 215, 0 white = 255, 255, 255 # set the size of screen to 640*480 size = width, height = 640, 480 # initialize the screen with parameter size screen = pygame.display.set_mode(size) # load all images map_image = pygame.transform.scale(pygame.image.load("map.jpg")\ ,(120,120)) map_rect = map_image.get_rect(center=[width-80,80]) pi_image = pygame.transform.scale(pygame.image.load("pi.png")\ ,(90,120)) pi_rect = pi_image.get_rect(center=[150,240]) ar_image = pygame.transform.scale(pygame.image.load("ar.png")\ ,(90,120)) ar_rect = ar_image.get_rect(center=[260,240]) game_image = pygame.transform.scale(pygame.image.load("game.png")\ ,(250,120)) game_rect = game_image.get_rect(center=[450,240]) hero_image = pygame.transform.scale(pygame.image.load("hero.png")\ ,(100,100)) hero_rect = hero_image.get_rect(center=[70,70]) sword_image = pygame.transform.scale(pygame.image.load("sword.png")\ ,(128,128)) spring_image = pygame.transform.scale(pygame.image.load("spring.png")\ ,(128,118)) atk_image = pygame.transform.scale(pygame.image.load('attack.png')\ ,(100,100)) skill_image = pygame.transform.scale(pygame.image.load('skill.png')\ ,(200,200)) skill_rect = skill_image.get_rect(center=[320,240]) dead_image = pygame.transform.scale(pygame.image.load('dead.png')\ ,(300,300)) dead_rect = dead_image.get_rect(center=[320,240]) heal_image = pygame.transform.scale(pygame.image.load('heal.png')\ ,(100,100)) skillbutton_image = pygame.transform.scale(pygame.image.load('skillbutton.png')\ ,(100,30)) forward_image = pygame.transform.scale(pygame.image.load('forward.png')\ ,(100,100)) forward_rect = forward_image.get_rect(center=[320,80]) backward_rect = forward_image.get_rect(center=[320,480-80]) left_image = pygame.transform.scale(pygame.image.load('right.png')\ ,(100,100)) left_rect = left_image.get_rect(center=[70,480-70]) right_rect = left_image.get_rect(center=[640-70,480-70]) close_image = pygame.transform.scale(pygame.image.load('close.png')\ ,(20,20)) close_rect = close_image.get_rect(center=[640-10,10]) # set font my_font = pygame.font.Font(None, 20) # initialize camera (using opencv, videocapture) camera = cv2.VideoCapture(0) ############################# Drawing(virtual buttons; UI; Background) ##################################### display_mode = True # draw the virtual buttons def draw_movebutton(): screen.blit(forward_image,forward_rect) screen.blit(pygame.transform.flip(forward_image, False, True),backward_rect) screen.blit(left_image,left_rect) screen.blit(pygame.transform.flip(left_image, True, False),right_rect) # draw the ui of this game def draw_ui(): global pos, map_image,map_rect global map global name # show player lv and name screen.blit(map_image, map_rect) screen.blit(hero_image, hero_rect) ply_info = 'lv: '+str(player.lv)+' '+name ply_button = {ply_info:(55,125),\ 'HP: ':(35,145),\ 'MP: ':(35,165),\ 'EXP:':(35,185),\ 'Death: '+str(player.death):(45,205),\ 'Atk: '+str(player.atk):(40,225)} # show player hp pygame.draw.rect(screen,red,(20,\ 140,\ 100, 10),0) pygame.draw.rect(screen,green,(20,\ 140,\ player.hp, 10),0) # show player mp pygame.draw.rect(screen,red,(20,\ 160,\ 100, 10),0) pygame.draw.rect(screen,blue,(20,\ 160,\ player.mp, 10),0) # show player exp pygame.draw.rect(screen,red,(20,\ 180,\ 100, 10),0) pygame.draw.rect(screen,yellow,(20,\ 180,\ player.exp, 10),0) for my_text, text_pos in ply_button.items(): text_surface = my_font.render(my_text, True, white) rect = text_surface.get_rect(center=text_pos) screen.blit(text_surface, rect) if display_mode: for i in range(5): for j in range(5): if map[i+1,j+1] > 1 and map[i+1,j+1] <= 6: pygame.draw.circle(screen, red, [560+(j+0-3)*24+24,\ 80+(i+2-3)*24-24], 5) if map[i+1,j+1] == 7: pygame.draw.circle(screen, blue, [560+(j+0-3)*24+24,\ 80+(i+2-3)*24-24], 5) if map[i+1,j+1] == 8: pygame.draw.circle(screen, green, [560+(j+0-3)*24+24,\ 80+(i+2-3)*24-24], 5) arrow_image = pygame.transform.scale(pygame.image.load("arrow.png")\ ,(20,20)) arrow_rotate = pygame.transform.rotate(arrow_image, 90*(pos[2]-1)) arrow_rect = arrow_rotate.get_rect(center=[560+(pos[1]-3)*24,80+(pos[0]-3)*24]) screen.blit(arrow_rotate, arrow_rect) else: arrow_image = pygame.transform.scale(pygame.image.load("arrow.png")\ ,(20,20)) arrow_rotate = pygame.transform.rotate(arrow_image, 90*(2-1)) arrow_rect = arrow_rotate.get_rect(center=[560,80]) if pos[2] == 2: for i in range(5): for j in range(5): if map[i+1,j+1] > 1 and map[i+1,j+1] <= 6: pygame.draw.circle(screen, red, [560+(j+0-3)*24+24,\ 80+(i+2-3)*24-24], 5) if map[i+1,j+1] == 7: pygame.draw.circle(screen, blue, [560+(j+0-3)*24+24,\ 80+(i+2-3)*24-24], 5) if map[i+1,j+1] == 8: pygame.draw.circle(screen, green, [560+(j+0-3)*24+24,\ 80+(i+2-3)*24-24], 5) elif pos[2] == 4: for i in range(5): for j in range(5): if map[i+1,j+1] > 1 and map[i+1,j+1] <= 6: pygame.draw.circle(screen, red, [560+(4-j+0-3)*24+24,\ 80+(4-i+2-3)*24-24], 5) if map[i+1,j+1] == 7: pygame.draw.circle(screen, blue, [560+(4-j+0-3)*24+24,\ 80+(4-i+2-3)*24-24], 5) if map[i+1,j+1] == 8: pygame.draw.circle(screen, green, [560+(4-j+0-3)*24+24,\ 80+(4-i+2-3)*24-24], 5) elif pos[2] == 1: for i in range(5): for j in range(5): if map[i+1,j+1] > 1 and map[i+1,j+1] <= 6: pygame.draw.circle(screen, red, [560+(i+0-3)*24+24,\ 80+(4-j+2-3)*24-24], 5) if map[i+1,j+1] == 7: pygame.draw.circle(screen, blue, [560+(i+0-3)*24+24,\ 80+(4-j+2-3)*24-24], 5) if map[i+1,j+1] == 8: pygame.draw.circle(screen, green, [560+(i+0-3)*24+24,\ 80+(4-j+2-3)*24-24], 5) elif pos[2] == 3: for i in range(5): for j in range(5): if map[i+1,j+1] > 1 and map[i+1,j+1] <= 6: pygame.draw.circle(screen, red, [560+(4-i+0-3)*24+24,\ 80+(j+2-3)*24-24], 5) if map[i+1,j+1] == 7: pygame.draw.circle(screen, blue, [560+(4-i+0-3)*24+24,\ 80+(j+2-3)*24-24], 5) if map[i+1,j+1] == 8: pygame.draw.circle(screen, green, [560+(4-i+0-3)*24+24,\ 80+(j+2-3)*24-24], 5) screen.blit(arrow_rotate, arrow_rect) screen.blit(close_image,close_rect) # draw the background of the game def background(): global screen,camera ret, frame = camera.read() frame = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB) frame = np.rot90(frame) frame = np.fliplr(frame) frame = pygame.surfarray.make_surface(frame) # show background screen.blit(frame,(0,0)) draw_ui() # draw the monster (show what you are facing) def generate_monster(num,Center): if num == 2: monster = pygame.image.load(bat.pic) mon_rect = monster.get_rect(center=Center) screen.blit(monster,mon_rect) elif num == 3: monster = pygame.image.load(centaur.pic) mon_rect = monster.get_rect(center=Center) screen.blit(monster,mon_rect) elif num == 4: monster = pygame.image.load(angryboy.pic) mon_rect = monster.get_rect(center=Center) screen.blit(monster,mon_rect) elif num == 5: monster = pygame.image.load(pumpkin.pic) mon_rect = monster.get_rect(center=Center) screen.blit(monster,mon_rect) elif num == 6: monster = pygame.image.load(dragon.pic) mon_rect = monster.get_rect(center=Center) screen.blit(monster,mon_rect) elif num == 7: mon_rect = sword_image.get_rect(center=Center) screen.blit(sword_image,mon_rect) elif num == 8: mon_rect = spring_image.get_rect(center=Center) screen.blit(spring_image,mon_rect) ###################################### BATTLING ######################################### # the main function when you encounter a monster def encountering_monster(Monster): global flag pygame.mixer.music.load('encounter.ogg') pygame.mixer.music.play(-1) full_hp = Monster.hp mon_image = pygame.transform.scale(pygame.image.load(Monster.pic)\ ,(160,160)) mon_rect = mon_image.get_rect(center=[320,240]) speed = [1,1] start = time.time() while Monster.hp > 0 and flag: # monster hit player once per sec if time.time() - start > 1: player.hp -= Monster.atk start = time.time() if player.hp <= 0: player.hp = 100 player.death += 1 break background() atk_rect = atk_image.get_rect(center=[int(uniform(hero_rect.left,\ hero_rect.right)),\ int(uniform(hero_rect.top,\ hero_rect.bottom))]) screen.blit(atk_image, atk_rect) else: background() skillbutton_rect = skillbutton_image.get_rect(center=[560,440]) screen.blit(skillbutton_image, skillbutton_rect) skillbutton_rect = skillbutton_image.get_rect(center=[560,400]) screen.blit(skillbutton_image, skillbutton_rect) skillbutton_rect = skillbutton_image.get_rect(center=[560,360]) screen.blit(skillbutton_image, skillbutton_rect) skill_button = { '(1)Attack: 0 MP':(560,360),\ '(2)Light: 10 MP':(560,400),\ '(3)Heal: 30 MP':(560,440)} for my_text, text_pos in skill_button.items(): text_surface = my_font.render(my_text, True, white) rect = text_surface.get_rect(center=text_pos) screen.blit(text_surface, rect) mon_rect = mon_rect.move(speed) mon_info = 'lv: '+str(Monster.lv)+' '+Monster.name info_button = {mon_info:((mon_rect.right+mon_rect.left)/2,\ mon_rect.top-20)} if mon_rect.left < 220 or mon_rect.right > width-220: speed[0] = -speed[0] if mon_rect.top < 130 or mon_rect.bottom > height-130: speed[1] = -speed[1] # add the rectangle containing the monster to this screen screen.blit(mon_image, mon_rect) # show monster's hp with rect pygame.draw.rect(screen,green,((mon_rect.right+mon_rect.left)/2-Monster.hp/2,\ mon_rect.top-10,\ Monster.hp, 10),0) # add the info of monster to this screen for my_text, text_pos in info_button.items(): text_surface = my_font.render(my_text, True, white) rect = text_surface.get_rect(center=text_pos) screen.blit(text_surface, rect) attack = check_attack(mon_rect) #if check_attack() == 1: #Monster.hp = 0 if attack == 1: Monster.hp -= player.atk atk_rect = atk_image.get_rect(center=[int(uniform(mon_rect.left,\ mon_rect.right)),\ int(uniform(mon_rect.top,\ mon_rect.bottom))]) screen.blit(atk_image, atk_rect) #print("Monster's remaining HP: "+str(Monster.hp)) elif attack == 2: #print("Lighting") if player.mp >= 10: Monster.hp -= player.atk * 1.5 player.mp -=10 screen.blit(skill_image, skill_rect) elif attack == 3: #print("Heal") if player.mp >= 30: player.mp -= 30 player.hp += 50 if player.hp > 100: player.hp = 100 heal_rect = heal_image.get_rect(center=[int(uniform(hero_rect.left,\ hero_rect.right)),\ int(uniform(hero_rect.top,\ hero_rect.bottom))]) screen.blit(heal_image, heal_rect) pygame.display.flip() if Monster.hp <= 0: Monster.hp = full_hp Monster.lv += 1 Monster.hp += (Monster.mid-1)*4 player.exp += Monster.exp if player.exp >= 100: player.lv += 1 player.hp = 100 player.mp = 100 player.atk += 2 player.exp -= 100 else: Monster.hp = full_hp screen.blit(dead_image, dead_rect) pygame.mixer.music.stop() # using pygame event to check if you are attacking def check_attack(rect): global flag,display_mode for event in pygame.event.get(): if(event.type is MOUSEBUTTONDOWN): pos = pygame.mouse.get_pos() elif(event.type is MOUSEBUTTONUP): pos = pygame.mouse.get_pos() x,y = pos if y > 360-15 and y < 360+15: if x > 560-50 and x < 560+50: return 1 if y > 400-15 and y < 400+15: if x > 560-50 and x < 560+50: return 2 if y > 440-15 and y < 440+15: if x > 560-50 and x < 560+50: return 3 if y > rect.top and y < rect.bottom: if x > rect.left and x < rect.right: return 1 if y > 0 and y < 20: if x > 620 and x < 640: flag = False if y > map_rect.top and y < map_rect.bottom: if x > map_rect.left and x < map_rect.right: display_mode = not display_mode elif event.type == pygame.KEYDOWN: if event.key == pygame.K_1: return 1 elif event.key == pygame.K_2: return 2 elif event.key == pygame.K_3: return 3 return 0 #################################### Moving ######################################### # using pygame event to check your commands for moving def check_direction(): global flag,display_mode for event in pygame.event.get(): if(event.type is MOUSEBUTTONDOWN): pos = pygame.mouse.get_pos() elif(event.type is MOUSEBUTTONUP): pos = pygame.mouse.get_pos() x,y = pos if y > forward_rect.top and y < forward_rect.bottom: if x > forward_rect.left and x < forward_rect.right: Forward() if y > backward_rect.top and y < backward_rect.bottom: if x > backward_rect.left and x < backward_rect.right: Backward() if y > right_rect.top and y < right_rect.bottom: if x > right_rect.left and x < right_rect.right: Right() if y > left_rect.top and y < left_rect.bottom: if x > left_rect.left and x < left_rect.right: Left() if y > 0 and y < 20: if x > 620 and x < 640: flag = False if y > map_rect.top and y < map_rect.bottom: if x > map_rect.left and x < map_rect.right: display_mode = not display_mode elif event.type == pygame.KEYDOWN: if event.key == pygame.K_w: Forward() elif event.key == pygame.K_s: Backward() elif event.key == pygame.K_a: Left() elif event.key == pygame.K_d: Right() elif event.key == pygame.K_q: sLeft() elif event.key == pygame.K_e: sRight() def Forward(): global pos,move,move_start if pos[2] == 1: for i in range(5): for j in range(4): map[i+1][j+1] = map[i+1][j+2] map[i+1][5] = 1 rand = int(uniform(1.0,9.0)) if rand <= 8: map[int(uniform(1.0,6.0))][5] = rand elif pos[2] == 2: for i in range(5): for j in range(4): map[5-j][i+1] = map[4-j][i+1] map[1][i+1] = 1 rand = int(uniform(1.0,9.0)) if rand <= 8: map[1][int(uniform(1.0,6.0))] = rand elif pos[2] == 3: for i in range(5): for j in range(4): map[i+1][5-j] = map[i+1][4-j] map[i+1][1] = 1 rand = int(uniform(1.0,9.0)) if rand <= 8: map[int(uniform(1.0,6.0))][1] = rand elif pos[2] == 4: for i in range(5): for j in range(4): map[j+1][i+1] = map[j+2][i+1] map[5][i+1] = 1 rand = int(uniform(1.0,9.0)) if rand <= 8: map[5][int(uniform(1.0,6.0))] = rand set_direction(1,-1) set_direction(2,1) move = 1 move_start = time.time() def Backward(): global pos,move,move_start if pos[2] == 1: for i in range(5): for j in range(4): map[i+1][5-j] = map[i+1][4-j] map[i+1][1] = 1 rand = int(uniform(1.0,9.0)) if rand <= 8: map[int(uniform(1.0,6.0))][1] = rand elif pos[2] == 2: for i in range(5): for j in range(4): map[j+1][i+1] = map[j+2][i+1] map[5][i+1] = 1 rand = int(uniform(1.0,9.0)) if rand <= 8: map[5][int(uniform(1.0,6.0))] = rand elif pos[2] == 3: for i in range(5): for j in range(4): map[i+1][j+1] = map[i+1][j+2] map[i+1][5] = 1 rand = int(uniform(1.0,9.0)) if rand <= 8: map[int(uniform(1.0,6.0))][5] = rand elif pos[2] == 4: for i in range(5): for j in range(4): map[5-j][i+1] = map[4-j][i+1] map[1][i+1] = 1 rand = int(uniform(1.0,9.0)) if rand <= 8: map[1][int(uniform(1.0,6.0))] = rand set_direction(1,1) set_direction(2,-1) move = 1 move_start = time.time() def Right(): global pos,move,move_start if pos[2] == 1: pos[2] = 4 else: pos[2] -= 1 set_direction(1,1) set_direction(2,1) move = 2 move_start = time.time() def Left(): global pos,move,move_start if pos[2] == 4: pos[2] = 1 else: pos[2] += 1 set_direction(2,-1) set_direction(1,-1) move = 2 move_start = time.time() # make a small right turn def sRight(): global move,move_start set_direction(1,1) set_direction(2,1) time.sleep(0.1) set_direction(1,0) set_direction(2,0) # make a small left turn def sLeft(): global move,move_start set_direction(2,-1) set_direction(1,-1) time.sleep(0.1) set_direction(1,0) set_direction(2,0) ############################## generate title page ################################ def check_click(): for event in pygame.event.get(): if(event.type is MOUSEBUTTONDOWN): pos = pygame.mouse.get_pos() elif(event.type is MOUSEBUTTONUP): pos = pygame.mouse.get_pos() x,y = pos return 1 return 0 pygame.mixer.music.load('Netherplace.ogg') pygame.mixer.music.play(-1) mon_list = np.array([1,2,3,4,5,1,2]) mon_pos = np.array([[0,480-25],[-120,480-25],[-240,480-25],[-360,480-25],\ [-480,480-25],[-600,480-25],[-720,480-25]]) mon_spd = np.array([[1,-uniform(1.0,15.0)],[1,-uniform(1.0,15.0)],\ [1,-uniform(1.0,15.0)],[1,-uniform(1.0,15.0)],\ [1,-uniform(1.0,15.0)],[1,-uniform(1.0,15.0)],\ [1,-uniform(1.0,15.0)]]) while True: # fill the screen with black screen.fill(black) screen.blit(pi_image,pi_rect) screen.blit(ar_image,ar_rect) screen.blit(game_image,game_rect) if check_click(): break for i in range(len(mon_list)): if mon_list[i] == 1: mon_img = pygame.image.load(bat.pic) elif mon_list[i] == 2: mon_img = pygame.image.load(centaur.pic) elif mon_list[i] == 3: mon_img = pygame.image.load(angryboy.pic) elif mon_list[i] == 4: mon_img = pygame.image.load(pumpkin.pic) elif mon_list[i] == 5: mon_img = pygame.image.load(dragon.pic) #mon_img = pygame.image.load(mon_list[i].pic) mon_rect = mon_img.get_rect(center=(mon_pos[i][0],mon_pos[i][1])) # make monster jump if mon_rect.bottom > 480: mon_spd[i][1] = -uniform(1.0,15.0) mon_rect = mon_rect.move(mon_spd[i]) mon_spd[i][1] += 0.5 screen.blit(mon_img,mon_rect) mon_pos[i][0] = mon_rect.center[0] mon_pos[i][1] = mon_rect.center[1] if mon_pos[0][0]-25 > 640: for i in range(len(mon_list)-1): mon_list[i] = mon_list[i+1] mon_pos[i] = mon_pos[i+1] mon_spd[i] = mon_spd[i+1] ran_mon = int(uniform(1.0,6.0)) if ran_mon == 1: mon_list[len(mon_list)-1] = 1 elif ran_mon == 2: mon_list[len(mon_list)-1] = 2 elif ran_mon == 3: mon_list[len(mon_list)-1] = 3 elif ran_mon == 4: mon_list[len(mon_list)-1] = 4 elif ran_mon == 5: mon_list[len(mon_list)-1] = 5 mon_pos[len(mon_list)-1][0] = mon_pos[len(mon_list)-2][0]-120 mon_pos[len(mon_list)-1][1] = 480-25 mon_spd[len(mon_list)-1][0] = 1 mon_spd[len(mon_list)-1][1] = -uniform(1.0,15.0) # display the whole screen pygame.display.flip() time.sleep(0.03) ############################## input name page ##################################### NM_Font = pygame.font.Font(None,50) done = False name = '' input_box = pygame.Rect(240,300,160,50) Button_Enter = 'ENTER' Enter_Font = pygame.font.Font(None,40) Enter_surface = Enter_Font.render(Button_Enter,True,white) Enter_box = pygame.Rect(500,430,100,40) Text1 = 'Hi Player:' Text2 = 'Write down your name and start a great adventure.' Text_Font = pygame.font.Font(None,30) Text1_surface = Text_Font.render(Text1,True,white) Text2_surface = Text_Font.render(Text2,True,white) while not done: for event in pygame.event.get(): if event.type == pygame.KEYDOWN: if event.key == pygame.K_BACKSPACE: name = name[:-1] elif event.key == pygame.K_RETURN: done = True else: if len(name) <= 7: name += event.unicode elif(event.type is MOUSEBUTTONDOWN): pos = pygame.mouse.get_pos() elif(event.type is MOUSEBUTTONUP): pos = pygame.mouse.get_pos() x,y = pos if x > 500 and x < 600: if y > 430 and y < 470: done = True # fill the screen with black screen.fill(black) screen.blit(Text1_surface,(50,50)) screen.blit(Text2_surface,(80,80)) name_surface = NM_Font.render(name,True,white) screen.blit(name_surface,(245,305)) pygame.draw.rect(screen,white,input_box,2) screen.blit(Enter_surface,(505,435)) pygame.draw.rect(screen,white,Enter_box,2) pygame.display.flip() time.sleep(0.01) pygame.mixer.music.stop() ################################ main game #################################### # Save the position and orientation of player # ori: # 1 -- facing right # 2 -- facing up # 3 -- facing left # 4 -- facing down pos = np.array([3,3,1]) # initialize map map = map_generator() move = 0 move_start = 0 # flag is the global value controling the running of pygame # it's default value is True flag = True # use flag to control the running, if flag value is False, end the loop while flag: facing = check_facing(pos,map) # generate the background background() if move == 0: if map[pos[0],pos[1]] == 1: # generate monsters generate_monster(facing[0],[100,240]) generate_monster(facing[1],[320,200]) generate_monster(facing[2],[540,240]) elif map[pos[0],pos[1]] == 2: encountering_monster(bat) map[pos[0],pos[1]] = 1 elif map[pos[0],pos[1]] == 3: encountering_monster(centaur) map[pos[0],pos[1]] = 1 elif map[pos[0],pos[1]] == 4: encountering_monster(angryboy) map[pos[0],pos[1]] = 1 elif map[pos[0],pos[1]] == 5: encountering_monster(pumpkin) map[pos[0],pos[1]] = 1 elif map[pos[0],pos[1]] == 6: encountering_monster(dragon) map[pos[0],pos[1]] = 1 elif map[pos[0],pos[1]] == 7: player.atk += 10 map[pos[0],pos[1]] = 1 elif map[pos[0],pos[1]] == 8: player.hp = 100 player.mp = 100 map[pos[0],pos[1]] = 1 draw_movebutton() check_direction() elif move == 1: if time.time()-move_start > 1: set_direction(1,0) set_direction(2,0) move = 0 elif move == 2: if time.time()-move_start > 0.5: set_direction(1,0) set_direction(2,0) move = 0 # display the whole screen pygame.display.flip() time.sleep(0.01) ################################## END PAGE ################################## pygame.mixer.music.load('Netherplace.ogg') pygame.mixer.music.play(-1) Text1 = 'Thank You For Playing!' Text2 = 'Made By:' Text3 = 'Mengde Wu' Text4 = 'Yexiang Chang' Text_Font = pygame.font.Font(None,30) Text1_surface = Text_Font.render(Text1,True,white) Text2_surface = Text_Font.render(Text2,True,white) Text3_surface = Text_Font.render(Text3,True,white) Text4_surface = Text_Font.render(Text4,True,white) mon_list = np.array([1,2,3,4,5,1,2]) mon_pos = np.array([[0,480-25],[-120,480-25],[-240,480-25],[-360,480-25],\ [-480,480-25],[-600,480-25],[-720,480-25]]) mon_spd = np.array([[1,-uniform(1.0,15.0)],[1,-uniform(1.0,15.0)],\ [1,-uniform(1.0,15.0)],[1,-uniform(1.0,15.0)],\ [1,-uniform(1.0,15.0)],[1,-uniform(1.0,15.0)],\ [1,-uniform(1.0,15.0)]]) while True: # fill the screen with black screen.fill(black) screen.blit(Text1_surface,(80,60)) screen.blit(Text2_surface,(400,80)) screen.blit(Text3_surface,(410,100)) screen.blit(Text4_surface,(410,120)) if check_click(): break for i in range(len(mon_list)): if mon_list[i] == 1: mon_img = pygame.image.load(bat.pic) elif mon_list[i] == 2: mon_img = pygame.image.load(centaur.pic) elif mon_list[i] == 3: mon_img = pygame.image.load(angryboy.pic) elif mon_list[i] == 4: mon_img = pygame.image.load(pumpkin.pic) elif mon_list[i] == 5: mon_img = pygame.image.load(dragon.pic) #mon_img = pygame.image.load(mon_list[i].pic) mon_rect = mon_img.get_rect(center=(mon_pos[i][0],mon_pos[i][1])) # make monster jump if mon_rect.bottom > 480: mon_spd[i][1] = -uniform(1.0,15.0) mon_rect = mon_rect.move(mon_spd[i]) mon_spd[i][1] += 0.5 screen.blit(mon_img,mon_rect) mon_pos[i][0] = mon_rect.center[0] mon_pos[i][1] = mon_rect.center[1] if mon_pos[0][0]-25 > 640: for i in range(len(mon_list)-1): mon_list[i] = mon_list[i+1] mon_pos[i] = mon_pos[i+1] mon_spd[i] = mon_spd[i+1] ran_mon = int(uniform(1.0,6.0)) if ran_mon == 1: mon_list[len(mon_list)-1] = 1 elif ran_mon == 2: mon_list[len(mon_list)-1] = 2 elif ran_mon == 3: mon_list[len(mon_list)-1] = 3 elif ran_mon == 4: mon_list[len(mon_list)-1] = 4 elif ran_mon == 5: mon_list[len(mon_list)-1] = 5 mon_pos[len(mon_list)-1][0] = mon_pos[len(mon_list)-2][0]-120 mon_pos[len(mon_list)-1][1] = 480-25 mon_spd[len(mon_list)-1][0] = 1 mon_spd[len(mon_list)-1][1] = -uniform(1.0,15.0) # display the whole screen pygame.display.flip() time.sleep(0.03) pygame.mixer.music.stop() ############################ Clean Up ################################## s1.stop() s2.stop() GPIO.cleanup() pygame.quit() cv2.destroyAllWindows() |