Bewegte Bilder auf PicoBoy Color


technische Daten wie beim RPi Pico
Microcontroller RP2040 - 1,69 Zoll Farbdisplay mit 240 x 280 Pixeln

Projekte und Anleitung in CircuitPython 9.x.x




Jetzt wird das Ganze spielbar. Die Geschichte lautet: PicoMax erhält für das Einsammeln von Geldkisten, Pilzen oder Fragezeichen Punkte. Aber Vorsicht! Manchmal begegnet er auch der Hinterlassenschaft eines Hundes oder wird sogar von ihm gebissen. Dann werden ihm Punkte abgezogen oder er verliert ein Leben.

Zunächst gibt es die positiven Sachen: Geldkisten, Pilze und Fragezeichen mit einer Gutschrift von jeweils 15 Punkten. Die erscheinen im oberen und unteren Bereich immer an unterschiedlichen Stellen und müssen durch einen Sprung nach oben berührt werden. Wenn PicoMax von links nach rechts läuft, erreicht er die Brücke und fällt in den 'Keller'. Dort ändert sich die Laufrichtung von rechts nach links. Am Ende öffnet sich die 'Fahrstuhltür' und PicoMax gelangt wieder nach oben. Durch einen Druck auf den Joystickbutton kann PicoMax jederzeit angehalten werden.
Der Quellcode dafür ist im unteren Kasten enthalten. Kopieren Sie ihn zunächst ins Thonny und testen ihn. Anschließend werde ich einige Erläuterungen geben. Wenn Ihnen das Ganze aber noch etwas zu komplex ist, oder Sie noch nicht so weit sind, müssen Sie nicht verzagen. Es ist Weihnachten und da gibt es auch kleine Geschenke. Ihres haben Sie schon mit dem Weihnachtsgadget heruntergeladen. Alles was hier beschrieben ist, befindet sich schon auf Ihrem PicoBoy-Color. Wenn Sie die Datei 'code.py' z.B. vom Dateiexplorer aus in 'xmas_start.py' umbenennen und dafür die Datei 'picomax_start.py' in 'code.py', dann startet das Spiel beim Einschalten des PicoBoy-Color. Das sollte Sie aber nicht davon abhalten, die Anleitung trotzdem bis zum Ende zu verfolgen.

  
  
1   # SPDX-FileCopyrightText : 2024 Detlef Gebhardt, written for CircuitPython 9.2.0
2   # SPDX-FileCopyrightText : Copyright (c) 2024 Detlef Gebhardt
3   # SPDX-Filename          : PicoMax's Abenteuer - Spiel auf PicoBoy-Color
4   # SPDX-License-Identifier: https://dgebhardt.de
5   import time
6   import gc
7   import board
8   import busio
9   import displayio
10  import terminalio
11  import digitalio
12  from adafruit_display_text import label
13  from adafruit_st7789 import ST7789
14  import keypad
15  import adafruit_imageload
16  import random
17  import microcontroller
18
19  # 1,69 Zoll 240x280 Pixel ST7789 Display
20  # SPI,  in Landscape format
21  dc=board.GP8
22  reset=board.GP9
23  cs=board.GP10
24  sck=board.GP18
25  mosi=board.GP19
26  bl=board.GP26
27  # Release any resources currently in use for the displays
28  displayio.release_displays()
29  spi = busio.SPI(sck, mosi)
30  display_bus = displayio.FourWire(spi, command=dc, chip_select=cs, reset=reset)
31  display = ST7789(display_bus, rotation=90, width=280, height=240, backlight_pin=bl, rowstart=20, colstart=0)
32  display.brightness = 1
33  main = displayio.Group()
34
35  K_UP = 0x01     # GP3  dez. 1
36  K_LEFT = 0x02   # GP2  dez. 2
37  K_RIGHT = 0x04  # GP4  dez. 4
38  K_DOWN = 0x08   # GP1  dez. 8
39  K_A = 0x10      # GP27 dez. 16
40  K_B = 0x20      # GP28 dez. 32
41  K_C = 0x40      # GP0  dez. 64
42
43  class _Buttons:
44      def __init__(self):
45          self.keys = keypad.Keys((board.GP1, board.GP4, board.GP2, board.GP3, board.GP28, board.GP27, board.GP0),
46                                  value_when_pressed=False, interval=0.05)
47          self.last_state = 0
48          self.event = keypad.Event(0, False)
49          self.last_z_press = None
50
51      def get_pressed(self):
52          buttons = self.last_state
53          events = self.keys.events
54          while events:
55              if events.get_into(self.event):
56                  bit = 1 << self.event.key_number
57                  if self.event.pressed:
58                      buttons |= bit
59                      self.last_state |= bit
60                  else:
61                      self.last_state &= ~bit
62          return buttons
63  buttons = _Buttons()
64
65  # Load the backgr sprite sheet (bitmap)
66  sprite_sheet, palette = adafruit_imageload.load("/images/floor.bmp", bitmap=displayio.Bitmap, palette=displayio.Palette)
67  backgr_group = displayio.TileGrid(sprite_sheet, pixel_shader=palette, width=7, height=6, tile_width=40, tile_height=40, default_tile=7)
68
69  # Load the sprite sheet for PicoMax (bitmap)
70  sprite_sheet, palette = adafruit_imageload.load("/images/picomax.bmp", bitmap=displayio.Bitmap, palette=displayio.Palette)
71  palette.make_transparent(1)
72  good1 = displayio.TileGrid(sprite_sheet, pixel_shader=palette, width=1, height=1, tile_width=32, tile_height=32, default_tile=0)
73  good1_group = displayio.Group(scale=1)
74  good1_group.append(good1)
75  good2 = displayio.TileGrid(sprite_sheet, pixel_shader=palette, width=1, height=1, tile_width=32, tile_height=32, default_tile=0)
76  good2_group = displayio.Group(scale=1)
77  good2_group.append(good2)
78
79  picomax = displayio.TileGrid(sprite_sheet, pixel_shader=palette, width=1, height=1, tile_width=32, tile_height=32, default_tile=0)
80  picomax_group = displayio.Group(scale=1)
81  picomax_group.append(picomax)
82  ## Draw the label for the score
83  updating_score = label.Label(font=terminalio.FONT, text = "     Los geht's",scale=2, color=0x0000ff, line_spacing=1)
84  updating_score.anchor_point = (0, 0)
85  updating_score.anchored_position = (40, 5)
86
87  backgr = [15, 16, 13, 13, 13, 13, 13,
88            18, 19, 20, 14, 20, 14, 20,
89            7, 7, 7, 7, 7, 7, 7,
90            7, 0, 5, 5, 5, 8, 6,
91            7, 0, 7, 7, 7, 7, 2,
92            12, 0, 11, 11, 8, 10, 11]
93
94  def floor():
95      # show the 42 sprites
96      for x in range(0, 7):
97          for y in range(0, 6):
98              backgr_group[x, y] = backgr[y*7 +x]
99
100 def testing_things(points):
101     if abs(picomax.x - good1.x) < 5 and abs(picomax.y - good1.y) < 15 and not good1[0] == 0:
102        good1[0] = 0
103         points[0] += 15
104         updating_score.text = ("Punkte:" + str(points[0]) + " Leben:" + str(points[1]))
105     if abs(picomax.x - good2.x) < 5 and abs(picomax.y - good2.y) < 15 and not good2[0] == 0:
106         good2[0] = 0
107         points[0] += 15
108         updating_score.text = ("Punkte:" + str(points[0]) + " Leben:" + str(points[1]))
109
110
111 def set_sprites():
112     backgr_group[5, 4] = 7
113     good1.x = random.randint(90,220)
114     good1.y = random.randint(45,70)
115     good1[0] = random.randint(12,15)
116     good2.x = random.randint(90,200)
117     good2.y = random.randint(125,140)
118     good2[0] = random.randint(12,15)
119
120
121 main.append(backgr_group)
122 main.append(good1_group)
123 main.append(good2_group)
124
125 main.append(picomax_group)
126 main.append(updating_score)
127 display.root_group = main
128 floor()
129 set_sprites()
130 picomax.x = 90
131 picomax.y = 90
132 picomax[0] = 3
133 points = [0, 5]
134 right = False
135 left = False
136 up = True
137 down = True
138 before = picomax.y
139 debounce = False
140 i = 0
141 step = time.monotonic()
142 jump = time.monotonic()
143
144 while True:
145     cur_btn_vals = buttons.get_pressed()
146     testing_things(points)
147
148     ####
149     # picomax bleibt stehen
150     ####
151     if cur_btn_vals == 0x40 and not debounce:
152         if right == True:
153             picomax[0] = 3
154         if left == True:
155             picomax[0] = 7
156         right = False
157         left = False
158     ####
159     # picomax läuft nach rechts
160     ####
161     if cur_btn_vals == 0x04:
162         right = True
163         left = False
164         m = 1
165     ####
166     # picomax läuft nach links
167     ####
168     if cur_btn_vals == 0x02:
169         right = False
170         left = True
171         m = 5
172     ####
173     # picomax springt nach oben
174     ####
175     if cur_btn_vals == 0x01:
176         if not debounce:
177             debounce = True
178             before = picomax.y
179             picomax.y -= 33
180             jump = time.monotonic()
181
182     if (time.monotonic() - jump > 0.3):
183         picomax.y = before
184     ### wenn picomax nach rechts laeuft
185     if right == True:
186         if time.monotonic() - step > 0.05:
187             picomax[0] = m
188             m += 1
189             picomax.x += 4
190             if m == 4:
191                 m = 1
192
193             step = time.monotonic()
194
195     ### wenn picomax nach links laeuft
196     if left == True:
197         if time.monotonic() - step > 0.05:
198             picomax[0] = m
199             m += 1
200             picomax.x -= 4
201             if m == 8:
202                 m = 5
203
204             step = time.monotonic()
205
206     ##### pruefen ob picomax rechts oder links angekommen ist
207     #
208     # von der Bruecke in den Keller
209     if picomax.x >= 240:
210         backgr_group[6, 3] = 7
211         backgr_group[1, 2] = 7
212         picomax.y = 130
213         before = picomax.y
214         right = False
215         left = True
216         m = 5
217     # zweite Stufe im Keller
218     if picomax.x < 215 and picomax.x > 210 and left:
219         backgr_group[6, 3] = 6
220         backgr_group[1, 4] = 3
221         picomax.y = 170
222         before = picomax.y
223
224     # links mit dem Fahrstuhl nach oben
225     if picomax.x <= 45:
226         picomax[0] = 0
227         backgr_group[1, 2] = 3
228         backgr_group[1, 4] = 0
229         time.sleep(0.2)
230         picomax.y = 90
231         before = picomax.y
232         picomax[0] = 1
233         set_sprites()
234         right = True
235         left = False
236         i = 0
237         m = 1
238     # wenn keine Taste gedrückt ist- debounce zurücksetzen
239     if cur_btn_vals == 0:
240         debounce = False
241     gc.collect()
242     #print(gc.mem_free())
  

Import der Bibliotheken
Neben den schon vorhandenen Bibliotheken werden die für den Text, die Tastenabfrage und Zufallszahlen importiert. Zusätzlich 'microcontroller' - wird später zum Neustart bei Spielende gebraucht.

Display initialisieren
Zeilen 19 bis 33 wie gehabt.

Tasten des PicoBoy-Color
Dazu wird die 'Keypad'-Bibliothek genutzt. So kann abgefragt werden, ob eine Taste gedrückt wurde und wenn ja, welche. In den Zeilen 35 bis 62 wird deshalb eine Klasse 'Buttons' definiert und in Zeile 63 an das Programm übergeben. Außerdem wird eine Variable 'debounce' (Zeile 139) definiert, mit der die Tasteneingaben entprellt werden. Solange 'debounce = True', wird keine weitere Eingabe erfasst. Am Programmende (Zeilen 238 bis 240) wird 'debounce' zurückgesetzt. Man spart sich so die Unterbrechungen des Programmablaufs wie z.B. 'time.sleep()' u.ä.

weitere Sprites laden
Bisher haben wir die Sprites für den Hintergrund, die Figuren und 'PicoMax' geladen. Jetzt kommen die Sprites 'good1' und 'good2', welche die Geldkiste, den Pilz oder das Fragezeichen darstellen, sowie das Label zur Anzeige des Punktestandes dazu. Das umfasst die Zeilen 65 bis 85.

Liste für das Hintergrundbild
In den Zeilen 87 bis 92 wird die Liste mit den Nummern der Hintergrundsprites definiert. In den Zeilen 94 bis 98 wird der Hintergrund in der Funktion 'floor()' dargestellt.

Funktionen 'testing_things() und 'set_sprites()'
In jeder Runde von PicoMax werden mit Hilfe der Funktion 'set_sprites()' die good's gesetzt (und später auch poop und dog). In der Funktion 'testing_things()' wird festgestellt, ob PicoMax den good's (und später auch poop und dog) nahe genug gekommen ist, um Punkte zu bekommen bzw. abgezogen zu bekommen.

Variable und Programmvorbereitung
Die Zeilen 121 bis 142 stellen noch benötigte Variable bereit bzw. bereiten den Progammstart vor. Die Funktionen 'floor()' und set_sprites() werden aufgerufen, PicoMax plaziert, eine Punkteliste 'points' mit 0 Punkten und 5 Leben sowie Laufrichtungen festgelegt. Mit Hilfe der Variable 'step' und 'jump' werden die Laufgeschwindigkeit bzw. Sprungdauer bestimmt.

Hauptprogramm in der 'while'-Schleife
Ab Zeile 144 beginnt die Endlosschleife. Dort wird bei jedem Durchlauf geprüft, ob eine Taste gedrückt wurde und eine 'Kollision' stattfand. Alle anderen Aktionen sind durch entsprechende Kommentare ausführlich beschrieben.

Eine letzte Bemerkung zu Zeile 241 u.242
Der Befehl 'gc.collect()' sammelt bei jedem Schleifendurchlauf den nicht mehr benötigten Arbeitsspeicher und gibt ihn frei. Bei einer solchen, für den PicoBoy speicherextensiven Anwendung, ist das keine Kleinigkeit. Mit dem Ausdrucken des noch vorhandenen Arbeitsspeichers in Zeile 142 kann man in der Entwicklungsphase kontrollieren, wie viel Speicher noch verfügbar ist. Danach richtet sich z.B., ob noch weitere Spites installiert werden können.

Mit der nächsten Box erhalten Sie den kompletten Quellcode. Darin sind jetzt noch die Charaktere 'Hundekode' und 'Hund' enthalten. Wenn PicoMax es nicht schafft über 'poop' zu springen, werden 10 Punkte abgezogen, während er bei Kollision mit 'dog' jeweils ein Leben verliert.
Achtung: Nachdem 'dog' übersprungen wurde erscheint er im 'Fahrstuhl'. Nach dem Zufallsprinzip lässt er sich manchmal verjagen, so dass PicoMax den Fahrstuhl nach oben nehmen kann. Ansonsten muss er sehr schnell umkehren, denn 'dog' verfolgt ihn kurz und PicoMax muss die bereitstehende Leiter auf der rechten Seite nach oben nehmen.

  
  
# SPDX-FileCopyrightText : 2024 Detlef Gebhardt, written for CircuitPython 9.2.0
# SPDX-FileCopyrightText : Copyright (c) 2024 Detlef Gebhardt
# SPDX-Filename          : PicoMax's Abenteuer - Spiel auf PicoBoy-Color
# SPDX-License-Identifier: https://dgebhardt.de


Klicken Sie auf 'Code kopieren' und fügen
ihn ins Thonny ein. Speichen Sie unter dem
Namen 'code.py' und starten das Spiel.

PS: Die Bibliotheken und Bilddateien aus den
vorhergehenden Seiten werden als vorhanden
vorausgesetzt.
  

Erweitern Sie die Anwendung gerne und nutzen, was der Arbeitsspeicher noch hergibt.

Viel Spass und Erfolg beim Ausprobieren.

Ein erholsames Weihnachtsfest
and don't bite the dog or step into dog poop.