Skip to content

Commit 120653a

Browse files
committed
add obstacle-race
1 parent d0713f2 commit 120653a

File tree

4 files changed

+284
-8
lines changed

4 files changed

+284
-8
lines changed

10-anahori/main.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class BaseEnemy:
123123
dx: int
124124
dy: int
125125
direction: int
126-
is_alive: bool
126+
is_die: bool
127127

128128
def update(self):
129129
pass
@@ -143,15 +143,15 @@ def __init__(self, img, x, y):
143143
self.dx = 0
144144
self.dy = 0
145145
self.direction = 1
146-
self.is_alive = True
146+
self.is_die = False
147147

148148
def reset(self):
149149
self.x = self.initial_x
150150
self.y = self.initial_y
151151
self.dx = 0
152152
self.dy = 0
153153
self.direction = 1
154-
self.is_alive = True
154+
self.is_die = False
155155

156156
def is_other_enemy(self, x: int, y: int) -> bool:
157157
for enemy in enemies:
@@ -165,8 +165,8 @@ def is_in_wall(self) -> bool:
165165
return is_in_wall(self.x, self.y)
166166

167167
def update(self):
168-
if self.is_alive and self.is_in_wall():
169-
self.is_alive = False
168+
if not self.is_die and self.is_in_wall():
169+
self.is_die = True
170170
global score
171171
score += 100
172172
return
@@ -186,7 +186,7 @@ def update(self):
186186
self.x, self.y = push_back(self.x, self.y, self.dx, self.dy, False)
187187

188188
def draw(self):
189-
if not self.is_alive:
189+
if self.is_die:
190190
return
191191

192192
u = pyxel.frame_count // 4 % 2 * 8 + 16
@@ -455,14 +455,14 @@ def update(self):
455455
return
456456

457457
for enemy in enemies:
458-
if not enemy.is_alive:
458+
if enemy.is_die:
459459
continue
460460
if abs(player.x - enemy.x) < 6 and abs(player.y - enemy.y) < 6:
461461
player.die()
462462
return
463463
enemy.update()
464464
if enemy.x < scroll_x - 8 or enemy.x > scroll_x + 160 or enemy.y > 160:
465-
enemy.is_alive = False
465+
enemy.is_die = True
466466

467467
def render(self):
468468
g = self.img

11-obstacle-race/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# 障害物レース
2+
3+
- ジャンプは着地時のみ可能
4+
- 障害物に当たったらゲームオーバー
5+
6+
## アプリ起動
7+
8+
```shell
9+
uv run main.py
10+
```
11+
12+
## 操作
13+
14+
- 走る: 右キー
15+
- ジャンプ: スペースキー
16+
- 終了: ESC
1.95 KB
Binary file not shown.

11-obstacle-race/main.py

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# title: Pyxel app 11-obstacle-race
2+
# author: Takayuki Shimizukawa
3+
# desc: 障害物レース
4+
# site: https://github.com/shimizukawa/pyxel-app
5+
# license: MIT
6+
# version: 1.0
7+
#
8+
# /// script
9+
# requires-python = ">=3.11"
10+
# dependencies = [
11+
# "pyxel",
12+
# ]
13+
# ///
14+
15+
import time
16+
import random
17+
import pyxel
18+
19+
TRANSPARENT_COLOR = 2
20+
TILE_FLOOR = (1, 0)
21+
WALL_TILE_X = 4
22+
23+
_height = 0
24+
player = None
25+
is_loose = False
26+
# show_bb = False
27+
# is_pback = False
28+
is_gameover = False
29+
30+
31+
def get_tile(tile_x, tile_y):
32+
return pyxel.tilemaps[0].pget(tile_x, tile_y)
33+
34+
35+
def is_colliding(x, y, is_falling, use_loose=False):
36+
x1 = pyxel.floor(x) // 8
37+
y1 = pyxel.floor(y) // 8
38+
x2 = (pyxel.ceil(x) + 7) // 8
39+
y2 = (pyxel.ceil(y) + 7) // 8
40+
if use_loose:
41+
x1 = (pyxel.floor(x) + 4) // 8
42+
x2 = (pyxel.ceil(x) + 3) // 8
43+
44+
for yi in range(y1, y2 + 1):
45+
for xi in range(x1, x2 + 1):
46+
if get_tile(xi, yi)[0] >= WALL_TILE_X:
47+
return True
48+
if use_loose:
49+
return False
50+
51+
if is_falling and y % 8 == 1:
52+
for xi in range(x1, x2 + 1):
53+
if get_tile(xi, y1 + 1) == TILE_FLOOR:
54+
return True
55+
return False
56+
57+
58+
def push_back(x, y, dx, dy):
59+
for _ in range(pyxel.ceil(abs(dy))):
60+
step = max(-1, min(1, dy))
61+
if dy > 0 and is_colliding(x, y + step, dy > 0):
62+
break
63+
elif dy < 0 and is_colliding(x, y + step, dy > 0, use_loose=is_loose):
64+
break
65+
y += step
66+
dy -= step
67+
for _ in range(pyxel.ceil(abs(dx))):
68+
step = max(-1, min(1, dx))
69+
if is_colliding(x + step, y, dy > 0):
70+
break
71+
x += step
72+
dx -= step
73+
return x, y
74+
75+
76+
class Player:
77+
def __init__(self, x, y, img):
78+
self.img = img
79+
self.X = x
80+
self.x = x
81+
self.y = y
82+
self.dx = 0
83+
self.dy = 0
84+
self.direction = 1
85+
self.is_falling = False
86+
self.frame_count = 0
87+
88+
def __repr__(self):
89+
return f"Player(x={self.x}, y={self.y}, dx={self.dx}, dy={self.dy})"
90+
91+
def update(self):
92+
self.frame_count = pyxel.frame_count
93+
last_y = self.y
94+
if pyxel.btn(pyxel.KEY_LEFT):
95+
self.dx = -1 * (2 if pyxel.btn(pyxel.KEY_SHIFT) else 1)
96+
self.direction = -1
97+
if pyxel.btn(pyxel.KEY_RIGHT):
98+
self.dx = 2 * (2 if pyxel.btn(pyxel.KEY_SHIFT) else 1)
99+
self.direction = 1
100+
self.dy = min(self.dy + 1, 10)
101+
if pyxel.btnp(pyxel.KEY_SPACE):
102+
if self.dy == 10 and not self.is_falling: # 落下3で落ちていない状態
103+
self.dy = -6
104+
self.x, self.y = push_back(self.x, self.y, self.dx, self.dy)
105+
106+
# 頭をぶつけたら上昇を止める
107+
if self.dy <= 0 and self.y == last_y:
108+
self.dy = 0
109+
110+
# looseモードでの、ブロックハマりからの押し戻し処理
111+
# if is_pback and is_colliding(self.x, self.y, False):
112+
# shift_x = round(self.x / 8) * 8 - self.x # 近い方のタイルにずらす
113+
# shift_x = max(-1, min(1, shift_x)) # ずらす量を-1, 0, 1に制限
114+
# # 全てのdxについて、スクロール内で、かつ、ぶつかっていないdxがあるか
115+
# for dx in (-4, 4):
116+
# if is_colliding(self.x + dx, self.y, False):
117+
# self.x += shift_x
118+
# break
119+
# else:
120+
# # ハマっているので右方向にずらす
121+
# self.x += 1
122+
123+
if self.y < 0:
124+
self.y = 0
125+
self.dx = int(self.dx * 0.8)
126+
self.is_falling = self.y > last_y
127+
128+
if self.y >= _height:
129+
game_over()
130+
131+
def draw(self):
132+
u = (2 if self.is_falling else self.frame_count // 3 % 2) * 8
133+
w = 8 if self.direction > 0 else -8
134+
self.img.blt(self.X, self.y, 0, u, 24, w, 8, TRANSPARENT_COLOR)
135+
# if show_bb:
136+
# if is_loose:
137+
# self.img.trib(
138+
# self.x + 4, self.y, self.x, self.y + 7, self.x + 7, self.y + 7, 14
139+
# )
140+
# else:
141+
# self.img.rectb(self.x, self.y, 8, 8, 10)
142+
143+
144+
class Obstacle:
145+
def __init__(self, x, y, img):
146+
self.img = img
147+
self.x = x
148+
self.y = y
149+
self.direction = 1
150+
self.frame_count = 0
151+
152+
def __repr__(self):
153+
return f"Obstacle(x={self.x}, y={self.y})"
154+
155+
def update(self):
156+
self.frame_count = pyxel.frame_count
157+
158+
def draw(self):
159+
u = (self.frame_count // 3 % 2) * 8
160+
w = 8 if self.direction > 0 else -8
161+
self.img.blt(self.x - player.x, self.y, 0, u, 32, w, 8, TRANSPARENT_COLOR)
162+
163+
164+
class App:
165+
def __init__(self, width, height):
166+
self.width = width
167+
self.height = height
168+
global _height
169+
_height = height
170+
self.img = pyxel.Image(width, height)
171+
pyxel.load("assets/11-obstacle-race.pyxres")
172+
173+
# Change enemy spawn tiles invisible
174+
pyxel.images[0].rect(0, 8, 24, 8, TRANSPARENT_COLOR)
175+
176+
global player
177+
player = Player(8, 30, self.img)
178+
179+
x = 96
180+
self.obstacles = [
181+
Obstacle(x, 72, self.img)
182+
]
183+
for i in range(100):
184+
x += random.randint(3, 15) * 8
185+
self.obstacles.append(Obstacle(x, 72, self.img))
186+
187+
def update(self):
188+
# global is_loose, show_bb, is_pback
189+
# if pyxel.btnp(pyxel.KEY_1):
190+
# show_bb = not show_bb
191+
# elif pyxel.btnp(pyxel.KEY_2):
192+
# is_loose = not is_loose
193+
# elif pyxel.btnp(pyxel.KEY_3):
194+
# is_pback = not is_pback
195+
if pyxel.btnp(pyxel.KEY_4):
196+
game_over()
197+
player.update()
198+
for obs in self.obstacles:
199+
obs.update()
200+
# 障害物当たり判定
201+
x, y = player.x + 8, player.y
202+
for obs in self.obstacles:
203+
# もしobsとplayerが4ピクセル以上重なっていたら衝突
204+
if abs(obs.x - x) <= 4 and abs(obs.y - y) <= 4:
205+
game_over()
206+
207+
208+
def render(self):
209+
g = self.img
210+
g.cls(0)
211+
212+
# Draw level
213+
g.bltm(0, 0, 0, player.x % 8, 0, 128, 128, TRANSPARENT_COLOR)
214+
g.text(1, 1, f"DISTANCE: {player.x/10:4.1f} m", 7)
215+
# g.text(1, 1, "1:BBox", 7 if show_bb else 5)
216+
# g.text(32, 1, "2:Loose", 7 if is_loose else 5)
217+
# g.text(68, 1, "3:PBack", 7 if is_pback else 5)
218+
g.text(104, 1, "4:RST", 5)
219+
220+
# Draw characters
221+
for obs in self.obstacles:
222+
obs.draw()
223+
player.draw()
224+
return g
225+
226+
227+
def game_over():
228+
time.sleep(5)
229+
player.x = 0
230+
player.y = 30
231+
player.dx = 0
232+
player.dy = 0
233+
global is_gameover
234+
is_gameover = True
235+
236+
237+
class ParentApp:
238+
def __init__(self):
239+
pyxel.init(128, 96, title="obstacle race")
240+
self.child = App(width=128, height=96)
241+
pyxel.run(self.update, self.draw)
242+
243+
def update(self):
244+
self.child.update()
245+
246+
def draw(self):
247+
g = self.child.render()
248+
pyxel.blt(
249+
(pyxel.width - g.width) // 2,
250+
(pyxel.height - g.height) // 2,
251+
g,
252+
0,
253+
0,
254+
g.width,
255+
g.height,
256+
)
257+
258+
259+
if __name__ == "__main__":
260+
ParentApp()

0 commit comments

Comments
 (0)