11"""
2- Sprite Multi-Region Hit Boxes
2+ Sprite Multi-Region Hit Boxes with Collision Channels
33
4- Demonstrates sprites with multiple hit box regions. The player sprite
5- has a "body" region and a "shield" region extending to one side.
6- Coins that touch any region of the player are collected.
4+ Demonstrates sprites with multiple hit box regions and per-region
5+ collision channels. The player sprite has a "body" region on channel 1
6+ and a "shield" region on channel 2.
7+
8+ Gold coins are on channel 1 and can only be collected by the body.
9+ Blue gems are on channel 2 and can only be collected by the shield.
10+ Coins pass through the shield, and gems pass through the body.
711
812Hit box outlines are drawn for visual debugging. Use A/D to rotate
913the player and see both regions rotate together.
1620import random
1721import math
1822import arcade
19- from arcade .hitbox import HitBox
23+ from arcade .hitbox import HitBox , channels
2024
2125WINDOW_WIDTH = 1280
2226WINDOW_HEIGHT = 720
2327WINDOW_TITLE = "Multi-Region Hit Box Example"
2428
2529PLAYER_SPEED = 5.0
26- COIN_SPEED = 2.0
27- COIN_COUNT = 20
30+ ITEM_SPEED = 2.0
31+ COIN_COUNT = 15
32+ GEM_COUNT = 10
2833
2934
3035class GameView (arcade .View ):
@@ -34,7 +39,9 @@ def __init__(self):
3439 self .player_sprite = None
3540 self .player_list = None
3641 self .coin_list = None
37- self .score = 0
42+ self .gem_list = None
43+ self .coin_score = 0
44+ self .gem_score = 0
3845 self .score_display = None
3946 self .background_color = arcade .csscolor .DARK_SLATE_GRAY
4047
@@ -46,9 +53,11 @@ def __init__(self):
4653 def setup (self ):
4754 self .player_list = arcade .SpriteList ()
4855 self .coin_list = arcade .SpriteList ()
49- self .score = 0
56+ self .gem_list = arcade .SpriteList ()
57+ self .coin_score = 0
58+ self .gem_score = 0
5059 self .score_display = arcade .Text (
51- text = "Score : 0" ,
60+ text = "Coins: 0 | Gems : 0" ,
5261 x = 10 , y = WINDOW_HEIGHT - 30 ,
5362 color = arcade .color .WHITE , font_size = 16 ,
5463 )
@@ -58,57 +67,79 @@ def setup(self):
5867 self .player_sprite = arcade .Sprite (img , scale = 0.5 )
5968 self .player_sprite .position = WINDOW_WIDTH / 2 , WINDOW_HEIGHT / 2
6069
61- # Replace the default hitbox with a multi-region hitbox.
62- # "body" is a box around the torso, "shield" extends to the right.
63- # Collision detection automatically checks all regions.
70+ # Multi-region hitbox with collision channels:
71+ # "body" on channel 1 — collides with coins (also on channel 1)
72+ # "shield" on channel 2 — collides with gems (also on channel 2)
6473 self .player_sprite .hit_box = HitBox (
6574 {
6675 "body" : [(- 15 , - 48 ), (- 15 , 40 ), (15 , 40 ), (15 , - 48 )],
67- "shield" : [(15 , - 30 ), (15 , 30 ), (45 , 30 ), (45 , - 30 )],
76+ "shield" : {
77+ "points" : [(35 , - 30 ), (35 , 30 ), (65 , 30 ), (65 , - 30 )],
78+ "channel" : channels (2 ),
79+ "mask" : channels (2 ),
80+ },
6881 },
6982 position = self .player_sprite .position ,
7083 scale = self .player_sprite .scale ,
7184 angle = self .player_sprite .angle ,
7285 )
7386
7487 self .player_list .append (self .player_sprite )
75- self ._spawn_coins (COIN_COUNT )
88+ self ._spawn_items (self .coin_list , COIN_COUNT , is_gem = False )
89+ self ._spawn_items (self .gem_list , GEM_COUNT , is_gem = True )
7690
77- def _spawn_coins (self , count ):
91+ def _spawn_items (self , sprite_list , count , is_gem ):
7892 for _ in range (count ):
79- coin = arcade .Sprite (":resources:images/items/coinGold.png" , scale = 0.4 )
93+ if is_gem :
94+ item = arcade .Sprite (
95+ ":resources:images/items/gemBlue.png" , scale = 0.4 ,
96+ )
97+ # Gems are on channel 2 — only the shield can catch them
98+ item .hit_box = HitBox (
99+ item .hit_box .points ,
100+ position = item .position ,
101+ scale = item .scale ,
102+ channel = channels (2 ),
103+ mask = channels (2 ),
104+ )
105+ else :
106+ item = arcade .Sprite (
107+ ":resources:images/items/coinGold.png" , scale = 0.4 ,
108+ )
109+ # Coins keep the default channel 1 — only the body can catch them
80110
81111 # Spawn along a random edge
82112 side = random .randint (0 , 3 )
83113 if side == 0 :
84- coin .center_x = random .randrange (WINDOW_WIDTH )
85- coin .center_y = WINDOW_HEIGHT + 20
114+ item .center_x = random .randrange (WINDOW_WIDTH )
115+ item .center_y = WINDOW_HEIGHT + 20
86116 elif side == 1 :
87- coin .center_x = random .randrange (WINDOW_WIDTH )
88- coin .center_y = - 20
117+ item .center_x = random .randrange (WINDOW_WIDTH )
118+ item .center_y = - 20
89119 elif side == 2 :
90- coin .center_x = - 20
91- coin .center_y = random .randrange (WINDOW_HEIGHT )
120+ item .center_x = - 20
121+ item .center_y = random .randrange (WINDOW_HEIGHT )
92122 else :
93- coin .center_x = WINDOW_WIDTH + 20
94- coin .center_y = random .randrange (WINDOW_HEIGHT )
123+ item .center_x = WINDOW_WIDTH + 20
124+ item .center_y = random .randrange (WINDOW_HEIGHT )
95125
96126 # Aim toward the center with some randomness
97127 target_x = WINDOW_WIDTH / 2 + random .randint (- 200 , 200 )
98128 target_y = WINDOW_HEIGHT / 2 + random .randint (- 200 , 200 )
99- dx = target_x - coin .center_x
100- dy = target_y - coin .center_y
129+ dx = target_x - item .center_x
130+ dy = target_y - item .center_y
101131 dist = math .hypot (dx , dy )
102132 if dist > 0 :
103- coin .change_x = (dx / dist ) * COIN_SPEED
104- coin .change_y = (dy / dist ) * COIN_SPEED
133+ item .change_x = (dx / dist ) * ITEM_SPEED
134+ item .change_y = (dy / dist ) * ITEM_SPEED
105135
106- self . coin_list . append (coin )
136+ sprite_list . append (item )
107137
108138 def on_draw (self ):
109139 self .clear ()
110140
111141 self .coin_list .draw ()
142+ self .gem_list .draw ()
112143 self .player_list .draw ()
113144
114145 # Debug: draw each hitbox region in a different color
@@ -119,10 +150,12 @@ def on_draw(self):
119150 arcade .draw_line_strip (tuple (pts ) + (pts [0 ],), color = color , line_width = 2 )
120151
121152 self .coin_list .draw_hit_boxes (color = arcade .color .YELLOW , line_thickness = 1 )
153+ self .gem_list .draw_hit_boxes (color = arcade .color .BLUE , line_thickness = 1 )
122154 self .score_display .draw ()
123155
124156 arcade .draw_text (
125- "Red = Body | Cyan = Shield | Arrow keys to move | A/D to rotate" ,
157+ "Red body (ch1) = coins | Cyan shield (ch2) = gems | "
158+ "Arrows to move | A/D to rotate" ,
126159 WINDOW_WIDTH / 2 , 20 ,
127160 arcade .color .WHITE , font_size = 12 , anchor_x = "center" ,
128161 )
@@ -165,27 +198,46 @@ def on_update(self, delta_time):
165198 if keys [arcade .key .D ]:
166199 self .player_sprite .angle += 3.0
167200
168- # Move coins
201+ # Move items
169202 self .coin_list .update ()
203+ self .gem_list .update ()
170204
171- # Standard collision check — automatically tests all hitbox regions
172- hit_list = arcade .check_for_collision_with_list (
205+ # Coins only collide with the body (channel 1)
206+ coin_hits = arcade .check_for_collision_with_list (
173207 self .player_sprite , self .coin_list
174208 )
175- if hit_list :
176- for coin in hit_list :
177- coin .remove_from_sprite_lists ()
178- self .score += 1
179- self .score_display .text = f"Score: { self .score } "
209+ for coin in coin_hits :
210+ coin .remove_from_sprite_lists ()
211+ self .coin_score += 1
212+
213+ # Gems only collide with the shield (channel 2)
214+ gem_hits = arcade .check_for_collision_with_list (
215+ self .player_sprite , self .gem_list
216+ )
217+ for gem in gem_hits :
218+ gem .remove_from_sprite_lists ()
219+ self .gem_score += 1
220+
221+ if coin_hits or gem_hits :
222+ self .score_display .text = (
223+ f"Coins: { self .coin_score } | Gems: { self .gem_score } "
224+ )
180225
181- # Replace coins that left the screen
226+ # Replace items that left the screen
182227 margin = 100
183- for coin in list (self .coin_list ):
184- if (coin .center_x < - margin or coin .center_x > WINDOW_WIDTH + margin
185- or coin .center_y < - margin or coin .center_y > WINDOW_HEIGHT + margin ):
186- coin .remove_from_sprite_lists ()
228+ for item in list (self .coin_list ):
229+ if (item .center_x < - margin or item .center_x > WINDOW_WIDTH + margin
230+ or item .center_y < - margin or item .center_y > WINDOW_HEIGHT + margin ):
231+ item .remove_from_sprite_lists ()
232+ for item in list (self .gem_list ):
233+ if (item .center_x < - margin or item .center_x > WINDOW_WIDTH + margin
234+ or item .center_y < - margin or item .center_y > WINDOW_HEIGHT + margin ):
235+ item .remove_from_sprite_lists ()
236+
187237 if len (self .coin_list ) < COIN_COUNT :
188- self ._spawn_coins (COIN_COUNT - len (self .coin_list ))
238+ self ._spawn_items (self .coin_list , COIN_COUNT - len (self .coin_list ), is_gem = False )
239+ if len (self .gem_list ) < GEM_COUNT :
240+ self ._spawn_items (self .gem_list , GEM_COUNT - len (self .gem_list ), is_gem = True )
189241
190242
191243def main ():
0 commit comments