萌趣赛博电子宠物,养一只属于你的屏幕小怪兽
哈喽各位创客小伙伴!最近参加社区造点好玩的活动,拿行空板做了一款桌面电子小宠物。之前版本存在耳朵裁切、进度条溢出等小bug,我反复调试修复完成,现在分享完整项目给大家。
这只小怪兽拥有完整的生命行为:会闲逛溜达、发呆放空、眨眼、呼呼大睡,睡觉还会冒“Z”符号;屏幕随机刷新金色小零食,小家伙看到食物会兴奋,吃到零食还会弹跳庆祝,吃够食物就能升级;支持手指直接拖拽把玩,移动的时候身后还会拖着淡淡的渐变尾迹;定时弹出趣味对话气泡,UI界面带等级进度条,底部实时显示宠物当前状态。所有界面做了边界保护,耳朵、文字、对话框不会被屏幕边缘切掉,直接复制代码即可跑通。
------

-----
硬件准备
行空板 ×1
软件环境
建议使用Mind+
代码分步解析
为方便大家学习理解,拆分成多个代码片段,每段负责不同功能模块。
1. 库导入与屏幕基础参数设置
# -*- coding: utf-8 -*-
import time
import random
import math
from unihiker import GUI
SCREEN_W = 240
SCREEN_H = 320
SAFE_MARGIN = 16
TOP_UI_HEIGHT = 22
FPS = 33
FRAME_DELAY = 1.0 / FPS
BG_COLOR = "#ffffff"
MAX_BUBBLE_WIDTH = 120
gui = GUI()
2. 触摸交互相关变量与回调函数
touch_x = 0
touch_y = 0
pressed = False
last_touch_time = 0
TOUCH_TIMEOUT = 0.08
def mouse_move(x, y):
global touch_x, touch_y, pressed, last_touch_time
touch_x = x
touch_y = y
pressed = True
last_touch_time = time.time()
gui.on_mouse_move(mouse_move)
3. 宠物、等级、食物、动画参数初始化
x = 120
y = 160
r = 14
MIN_R = 8
MAX_R = 28
r_gain_per_food = 0.4
speed = 0.8
vx = speed * random.choice([-1, 1])
vy = speed * random.choice([-1, 1])
dragging = False
last_pet_action = ""
max_level = 10
level = 1
food_this_level = 0
food_need_per_level = 10
pet_action = "walk"
action_timer = 0
action_duration = 0
wait1_frames = int(FPS * 0.5)
jump_total_frames = int(FPS * 0.6)
wait2_frames = int(FPS * 1.0)
eat_timer = 0
jump_origin_y = 0
walk_timer = 0
walk_interval = 40
food_r = 5
food_x = random.randint(SAFE_MARGIN, SCREEN_W - SAFE_MARGIN)
food_y = random.randint(TOP_UI_HEIGHT + 10, SCREEN_H - SAFE_MARGIN)
food_detect_range = 50
trail = []
max_trail = 10
4. 对话气泡文本列表、行为切换工具函数
greeting_list = [
" Hi!",
" Hello",
" Happy today?",
" Play with me",
" Want snack",
" Keep going",
" Hehe",
" I see you",
" Let's move",
" Take a rest"
]
greeting_interval = FPS * 11
greeting_show_time = FPS * 3
greeting_timer = 0
current_greeting = ""
def get_inner_angle():
margin = 12
if x < margin:
low = -math.pi / 2
high = math.pi / 2
elif x > SCREEN_W - margin:
low = math.pi / 2
high = math.pi * 1.5
elif y < margin + TOP_UI_HEIGHT:
low = 0
high = math.pi
elif y > SCREEN_H - margin:
low = math.pi
high = math.pi * 2
else:
low = 0
high = math.pi * 2
return random.uniform(low, high)
def pick_new_action():
global pet_action, action_duration
rand_val = random.random()
if rand_val < 0.55:
pet_action = "walk"
action_duration = random.randint(80, 180)
elif rand_val < 0.80:
pet_action = "daze"
action_duration = random.randint(40, 90)
elif rand_val < 0.92:
pet_action = "blink"
action_duration = random.randint(8, 20)
else:
pet_action = "sleep"
action_duration = random.randint(100, 200)
5. 初始化绘制全部UI控件、宠物、食物、气泡、拖尾
bar_top = 4
bar_h = 6
bar_left = 50
bar_right = SCREEN_W - SAFE_MARGIN
bar_w = bar_right - bar_left
bar_center_x = bar_left + bar_w / 2
bar_center_y = bar_top + bar_h / 2
progress_bg = gui.fill_round_rect(x=bar_center_x, y=bar_center_y, w=bar_w, h=bar_h, r=3, color="#a0a0a0")
progress_fg = gui.fill_round_rect(x=bar_center_x, y=bar_center_y, w=bar_w, h=bar_h, r=3, color="#ffc81e")
level_text = gui.draw_text(text=f"Lv{level}/{max_level}", x=4, y=2, font_size=14, color="#000000")
food_obj = gui.fill_circle(x=food_x, y=food_y, r=food_r, color="#ffd700")
pet_body = gui.fill_circle(x=x, y=y, r=int(r), color="#82dcb4")
ear_left_circle = gui.fill_circle(x=x - r * 0.45, y=y - r + r * 0.35, r=int(r * 0.55), color="#82dcb4")
ear_right_circle = gui.fill_circle(x=x + r * 0.45, y=y - r + r * 0.35, r=int(r * 0.55), color="#82dcb4")
ear_rect_w = r * 0.55 * 2
ear_rect_h = r * 0.55
ear_left_rect = gui.fill_rect(
x=(x - r * 0.45) - ear_rect_w / 2,
y=(y - r + r * 0.35),
w=ear_rect_w,
h=ear_rect_h,
color="#82dcb4"
)
ear_right_rect = gui.fill_rect(
x=(x + r * 0.45) - ear_rect_w / 2,
y=(y - r + r * 0.35),
w=ear_rect_w,
h=ear_rect_h,
color="#82dcb4"
)
ear_left_inner = gui.fill_circle(x=x - r * 0.45, y=(y - r + r * 0.35) + (r*0.55)*0.15, r=int((r*0.55)*0.42), color="#ffb4c8")
ear_right_inner = gui.fill_circle(x=x + r * 0.45, y=(y - r + r * 0.35) + (r*0.55)*0.42, r=int((r*0.55)*0.42), color="#ffb4c8")
eye_left = gui.fill_circle(x=x - max(3, r / 3), y=y - r * 0.25, r=1.5, color="#000000")
eye_right = gui.fill_circle(x=x + max(3, r / 3), y=y - r * 0.25, r=1.5, color="#000000")
eye_left_dot = gui.fill_circle(x=x - max(3, r / 3)+0.5, y=y - r * 0.25 -0.5, r=0.5, color="#ffffff")
eye_right_dot = gui.fill_circle(x=x + max(3, r / 3)+0.5, y=y - r * 0.25 -0.5, r=0.5, color="#ffffff")
blush_left = gui.fill_circle(x=x - r*0.6, y=y + r*0.1, r=int(r*0.22), color="#fa9ab2")
blush_right = gui.fill_circle(x=x + r*0.6, y=y + r*0.1, r=int(r*0.22), color="#fa9ab2")
mouth_center = gui.fill_circle(x=x, y=y + r * 0.22, r=0.8, color="#000000")
sleep_z_text = gui.draw_text(text="", x=x + r + 2, y=y - r, font_size=12, color="#000000")
bubble_bg = gui.fill_round_rect(x=120, y=200, w=0, h=0, r=4, color="#505050")
bubble_text = gui.draw_text(text="", x=80, y=200, font_size=12, color="#ffffff")
state_text = gui.draw_text(text="Walking", x=5, y=SCREEN_H - 18, font_size=12, color="#000000")
trail_objs = []
for _ in range(max_trail):
c = gui.fill_circle(x=0, y=0, r=0, color="#82dcb4")
trail_objs.append(c)
6. 宠物图形更新函数,切换不同表情状态
def update_pet():
global last_pet_action
eye_offset = max(3, r / 3)
eye_y = y - r * 0.25
mouth_y = y + r * 0.22
blush_r = r * 0.22
ear_r = r * 0.55
ear_d = r * 0.35
ear_cy = y - r + ear_d
ear_rect_w = ear_r * 2
ear_rect_h = ear_r
earL_rect_x = (x - r * 0.45) - ear_rect_w / 2
earL_rect_y = ear_cy
earR_rect_x = (x + r * 0.45) - ear_rect_w / 2
earR_rect_y = ear_cy
pet_body.config(x=x, y=y, r=int(r))
ear_left_circle.config(x=x - r * 0.45, y=ear_cy, r=int(ear_r))
ear_right_circle.config(x=x + r * 0.45, y=ear_cy, r=int(ear_r))
ear_left_rect.config(x=earL_rect_x, y=earL_rect_y, w=ear_rect_w, h=ear_rect_h)
ear_right_rect.config(x=earR_rect_x, y=earR_rect_y, w=ear_rect_w, h=ear_rect_h)
ear_left_inner.config(x=x - r * 0.45, y=ear_cy + ear_r * 0.15, r=int(ear_r * 0.42))
ear_right_inner.config(x=x + r * 0.45, y=ear_cy + ear_r * 0.15, r=int(ear_r * 0.42))
blush_left.config(x=x - r * 0.6, y=y + r * 0.1, r=int(blush_r))
blush_right.config(x=x + r * 0.6, y=y + r * 0.1, r=int(blush_r))
if pet_action != last_pet_action:
if pet_action == "sleep":
eye_left.config(r=0)
eye_right.config(r=0)
eye_left_dot.config(r=0)
eye_right_dot.config(r=0)
mouth_center.config(r=0)
elif pet_action == "blink":
eye_left.config(r=0)
eye_left_dot.config(r=0)
eye_right.config(r=1.5)
eye_right_dot.config(r=0.5)
mouth_center.config(r=0.8)
else:
eye_left.config(r=1.5)
eye_right.config(r=1.5)
eye_left_dot.config(r=0.5)
eye_right_dot.config(r=0.5)
mouth_center.config(r=0.8)
last_pet_action = pet_action
if pet_action in ["happy", "eat_wait1", "eat_jump", "eat_wait2"]:
eye_left.config(x=x - eye_offset, y=eye_y, r=2.2)
eye_right.config(x=x + eye_offset, y=eye_y, r=2.2)
eye_left_dot.config(x=x - eye_offset + 0.7, y=eye_y - 0.7, r=0.7)
eye_right_dot.config(x=x + eye_offset + 0.7, y=eye_y - 0.7, r=0.7)
mouth_center.config(x=x, y=mouth_y, r=1.8)
sleep_z_text.config(text="")
elif pet_action == "sleep":
z_pos_x = x + r + 2
if z_pos_x > SCREEN_W - 12:
z_pos_x = x - r - 12
sleep_z_text.config(x=z_pos_x, y=y - r, text="Z")
elif pet_action == "daze":
eye_left.config(x=x - eye_offset, y=eye_y, r=1.1)
eye_right.config(x=x + eye_offset, y=eye_y, r=1.1)
eye_left_dot.config(x=x - eye_offset + 0.4, y=eye_y - 0.4, r=0.4)
eye_right_dot.config(x=x + eye_offset + 0.4, y=eye_y - 0.4, r=0.4)
mouth_center.config(x=x, y=mouth_y, r=0.8)
sleep_z_text.config(text="")
elif pet_action == "blink":
eye_right.config(x=x + eye_offset, y=eye_y, r=1.1)
eye_right_dot.config(x=x + eye_offset + 0.4, y=eye_y - 0.4, r=0.4)
mouth_center.config(x=x, y=mouth_y, r=0.8)
sleep_z_text.config(text="")
else:
eye_left.config(x=x - eye_offset, y=eye_y, r=1.5)
eye_right.config(x=x + eye_offset, y=eye_y, r=1.5)
eye_left_dot.config(x=x - eye_offset + 0.5, y=eye_y - 0.5, r=0.5)
eye_right_dot.config(x=x + eye_offset + 0.5, y=eye_y - 0.5, r=0.5)
sleep_z_text.config(text="")
7. 主循环,处理移动逻辑、进食、UI刷新、拖拽交互
while True:
start_time = time.time()
if time.time() - last_touch_time > TOUCH_TIMEOUT:
pressed = False
if pressed:
dist_touch_pet = math.hypot(touch_x - x, touch_y - y)
if dragging or dist_touch_pet < r + 12:
dragging = True
x = touch_x
y = touch_y
ear_max_extend = MAX_R * 0.45 + MAX_R * 0.55
x = max(ear_max_extend, min(SCREEN_W - ear_max_extend, x))
y = max(r + TOP_UI_HEIGHT, min(SCREEN_H - r - SAFE_MARGIN, y))
else:
dragging = False
action_timer += 1
walk_timer += 1
greeting_timer += 1
if greeting_timer >= greeting_interval:
current_greeting = random.choice(greeting_list)
greeting_timer = 0
elif greeting_timer >= greeting_show_time:
current_greeting = ""
if not dragging and pet_action not in ["eat_wait1", "eat_jump", "eat_wait2", "happy"] and action_timer >= action_duration:
pick_new_action()
action_timer = 0
dist_to_food = math.hypot(x - food_x, y - food_y)
if pet_action == "eat_wait1":
eat_timer += 1
if eat_timer >= wait1_frames:
pet_action = "eat_jump"
eat_timer = 0
elif pet_action == "eat_jump":
eat_timer += 1
if not dragging:
phase = eat_timer / jump_total_frames
offset_y = math.sin(4 * math.pi * phase) * 7
raw_y = jump_origin_y + offset_y
y = max(r + TOP_UI_HEIGHT, min(SCREEN_H - r - SAFE_MARGIN, raw_y))
if eat_timer >= jump_total_frames:
pet_action = "eat_wait2"
eat_timer = 0
if not dragging:
y = jump_origin_y
elif pet_action == "eat_wait2":
eat_timer += 1
if eat_timer >= wait2_frames:
pet_action = "walk"
eat_timer = 0
elif (pet_action == "walk" or pet_action == "happy") and not dragging:
if dist_to_food < food_detect_range:
pet_action = "happy"
angle = math.atan2(food_y - y, food_x - x)
vx = speed * math.cos(angle)
vy = speed * math.sin(angle)
else:
if pet_action == "happy":
pet_action = "walk"
margin = 12
near_edge = (x < margin) or (x > SCREEN_W - margin) or (y < margin + TOP_UI_HEIGHT) or (y > SCREEN_H - margin)
if near_edge:
angle = get_inner_angle()
vx = speed * math.cos(angle)
vy = speed * math.sin(angle)
walk_timer = 0
else:
if walk_timer >= walk_interval:
angle = random.uniform(0, math.pi * 2)
vx = speed * math.cos(angle)
vy = speed * math.sin(angle)
walk_timer = 0
nx = x + vx
ny = y + vy
ear_max_extend = MAX_R * 0.45 + MAX_R * 0.55
if ear_max_extend <= nx <= SCREEN_W - ear_max_extend:
x = nx
if r + TOP_UI_HEIGHT <= ny <= SCREEN_H - r - SAFE_MARGIN:
y = ny
if dist_to_food < r + food_r:
food_this_level += 1
r += r_gain_per_food
r = min(r, MAX_R)
food_x = random.randint(SAFE_MARGIN, SCREEN_W - SAFE_MARGIN)
food_y = random.randint(TOP_UI_HEIGHT + 10, SCREEN_H - SAFE_MARGIN)
food_obj.config(x=food_x, y=food_y)
pet_action = "eat_wait1"
eat_timer = 0
if not dragging:
jump_origin_y = y
if food_this_level >= food_need_per_level and level < max_level:
level += 1
food_this_level = 0
r = 14
level_text.config(text=f"Lv{level}/{max_level}")
if (pet_action == "walk" or pet_action == "happy") and not dragging:
trail.append((x, y))
if len(trail) > max_trail:
trail.pop(0)
else:
trail.clear()
progress = food_this_level / food_need_per_level
fill_w = bar_w * progress
fill_center_x = bar_left + fill_w / 2
progress_fg.config(x=fill_center_x, y=bar_center_y, w=fill_w, h=bar_h, r=3, color="#ffc81e")
for i, obj in enumerate(trail_objs):
if i < len(trail):
tx, ty = trail[i]
ratio = (i + 1) / max_trail
tr = int(r * 0.35 * ratio)
obj.config(x=tx, y=ty, r=tr)
else:
obj.config(r=0)
if current_greeting != "":
txt_w = len(current_greeting) * 7 + 8
txt_w = min(txt_w, MAX_BUBBLE_WIDTH)
txt_h = 16
if y + r + txt_h + 8 > SCREEN_H - SAFE_MARGIN:
by = int(y - r - txt_h - 6)
else:
by = int(y + r + 6)
bx = int(x - txt_w / 2)
bx = max(SAFE_MARGIN, min(bx, SCREEN_W - txt_w - SAFE_MARGIN))
bubble_center_x = bx + txt_w / 2
bubble_center_y = by + txt_h / 2
bubble_bg.config(x=bubble_center_x, y=bubble_center_y, w=txt_w, h=txt_h, r=4)
bubble_text.config(text=current_greeting, x=bx + 4, y=by + 2)
else:
bubble_bg.config(w=0, h=0)
bubble_text.config(text="")
action_map = {
"walk": "Walking",
"daze": "Dazing",
"blink": "Blinking",
"sleep": "Sleeping",
"happy": "Getting snack",
"eat_wait1": "Yum!",
"eat_jump": "Yum!",
"eat_wait2": "Yum!"
}
state_str = action_map.get(pet_action, "Walking")
if dragging:
state_str = "Dragging"
state_text.config(text=state_str)
update_pet()
elapsed = time.time() - start_time
if elapsed < FRAME_DELAY:
time.sleep(FRAME_DELAY - elapsed)
完整总代码
# -*- coding: utf-8 -*-
import time
import random
import math
from unihiker import GUI
SCREEN_W = 240
SCREEN_H = 320
SAFE_MARGIN = 16
TOP_UI_HEIGHT = 22
FPS = 33
FRAME_DELAY = 1.0 / FPS
BG_COLOR = "#ffffff"
MAX_BUBBLE_WIDTH = 120
gui = GUI()
touch_x = 0
touch_y = 0
pressed = False
last_touch_time = 0
TOUCH_TIMEOUT = 0.08
def mouse_move(x, y):
global touch_x, touch_y, pressed, last_touch_time
touch_x = x
touch_y = y
pressed = True
last_touch_time = time.time()
gui.on_mouse_move(mouse_move)
x = 120
y = 160
r = 14
MIN_R = 8
MAX_R = 28
r_gain_per_food = 0.4
speed = 0.8
vx = speed * random.choice([-1, 1])
vy = speed * random.choice([-1, 1])
dragging = False
last_pet_action = ""
max_level = 10
level = 1
food_this_level = 0
food_need_per_level = 10
pet_action = "walk"
action_timer = 0
action_duration = 0
wait1_frames = int(FPS * 0.5)
jump_total_frames = int(FPS * 0.6)
wait2_frames = int(FPS * 1.0)
eat_timer = 0
jump_origin_y = 0
walk_timer = 0
walk_interval = 40
food_r = 5
food_x = random.randint(SAFE_MARGIN, SCREEN_W - SAFE_MARGIN)
food_y = random.randint(TOP_UI_HEIGHT + 10, SCREEN_H - SAFE_MARGIN)
food_detect_range = 50
trail = []
max_trail = 10
greeting_list = [
" Hi!",
" Hello",
" Happy today?",
" Play with me",
" Want snack",
" Keep going",
" Hehe",
" I see you",
" Let's move",
" Take a rest"
]
greeting_interval = FPS * 11
greeting_show_time = FPS * 3
greeting_timer = 0
current_greeting = ""
def get_inner_angle():
margin = 12
if x < margin:
low = -math.pi / 2
high = math.pi / 2
elif x > SCREEN_W - margin:
low = math.pi / 2
high = math.pi * 1.5
elif y < margin + TOP_UI_HEIGHT:
low = 0
high = math.pi
elif y > SCREEN_H - margin:
low = math.pi
high = math.pi * 2
else:
low = 0
high = math.pi * 2
return random.uniform(low, high)
def pick_new_action():
global pet_action, action_duration
rand_val = random.random()
if rand_val < 0.55:
pet_action = "walk"
action_duration = random.randint(80, 180)
elif rand_val < 0.80:
pet_action = "daze"
action_duration = random.randint(40, 90)
elif rand_val < 0.92:
pet_action = "blink"
action_duration = random.randint(8, 20)
else:
pet_action = "sleep"
action_duration = random.randint(100, 200)
bar_top = 4
bar_h = 6
bar_left = 50
bar_right = SCREEN_W - SAFE_MARGIN
bar_w = bar_right - bar_left
bar_center_x = bar_left + bar_w / 2
bar_center_y = bar_top + bar_h / 2
progress_bg = gui.fill_round_rect(x=bar_center_x, y=bar_center_y, w=bar_w, h=bar_h, r=3, color="#a0a0a0")
progress_fg = gui.fill_round_rect(x=bar_center_x, y=bar_center_y, w=bar_w, h=bar_h, r=3, color="#ffc81e")
level_text = gui.draw_text(text=f"Lv{level}/{max_level}", x=4, y=2, font_size=14, color="#000000")
food_obj = gui.fill_circle(x=food_x, y=food_y, r=food_r, color="#ffd700")
pet_body = gui.fill_circle(x=x, y=y, r=int(r), color="#82dcb4")
ear_left_circle = gui.fill_circle(x=x - r * 0.45, y=y - r + r * 0.35, r=int(r * 0.55), color="#82dcb4")
ear_right_circle = gui.fill_circle(x=x + r * 0.45, y=y - r + r * 0.35, r=int(r * 0.55), color="#82dcb4")
ear_rect_w = r * 0.55 * 2
ear_rect_h = r * 0.55
ear_left_rect = gui.fill_rect(
x=(x - r * 0.45) - ear_rect_w / 2,
y=(y - r + r * 0.35),
w=ear_rect_w,
h=ear_rect_h,
color="#82dcb4"
)
ear_right_rect = gui.fill_rect(
x=(x + r * 0.45) - ear_rect_w / 2,
y=(y - r + r * 0.35),
w=ear_rect_w,
h=ear_rect_h,
color="#82dcb4"
)
ear_left_inner = gui.fill_circle(x=x - r * 0.45, y=(y - r + r * 0.35) + (r*0.55)*0.15, r=int((r*0.55)*0.42), color="#ffb4c8")
ear_right_inner = gui.fill_circle(x=x + r * 0.45, y=(y - r + r * 0.35) + (r*0.55)*0.42, r=int((r*0.55)*0.42), color="#ffb4c8")
eye_left = gui.fill_circle(x=x - max(3, r / 3), y=y - r * 0.25, r=1.5, color="#000000")
eye_right = gui.fill_circle(x=x + max(3, r / 3), y=y - r * 0.25, r=1.5, color="#000000")
eye_left_dot = gui.fill_circle(x=x - max(3, r / 3)+0.5, y=y - r * 0.25 -0.5, r=0.5, color="#ffffff")
eye_right_dot = gui.fill_circle(x=x + max(3, r / 3)+0.5, y=y - r * 0.25 -0.5, r=0.5, color="#ffffff")
blush_left = gui.fill_circle(x=x - r*0.6, y=y + r*0.1, r=int(r*0.22), color="#fa9ab2")
blush_right = gui.fill_circle(x=x + r*0.6, y=y + r*0.1, r=int(r*0.22), color="#fa9ab2")
mouth_center = gui.fill_circle(x=x, y=y + r * 0.22, r=0.8, color="#000000")
sleep_z_text = gui.draw_text(text="", x=x + r + 2, y=y - r, font_size=12, color="#000000")
bubble_bg = gui.fill_round_rect(x=120, y=200, w=0, h=0, r=4, color="#505050")
bubble_text = gui.draw_text(text="", x=80, y=200, font_size=12, color="#ffffff")
state_text = gui.draw_text(text="Walking", x=5, y=SCREEN_H - 18, font_size=12, color="#000000")
trail_objs = []
for _ in range(max_trail):
c = gui.fill_circle(x=0, y=0, r=0, color="#82dcb4")
trail_objs.append(c)
def update_pet():
global last_pet_action
eye_offset = max(3, r / 3)
eye_y = y - r * 0.25
mouth_y = y + r * 0.22
blush_r = r * 0.22
ear_r = r * 0.55
ear_d = r * 0.35
ear_cy = y - r + ear_d
ear_rect_w = ear_r * 2
ear_rect_h = ear_r
earL_rect_x = (x - r * 0.45) - ear_rect_w / 2
earL_rect_y = ear_cy
earR_rect_x = (x + r * 0.45) - ear_rect_w / 2
earR_rect_y = ear_cy
pet_body.config(x=x, y=y, r=int(r))
ear_left_circle.config(x=x - r * 0.45, y=ear_cy, r=int(ear_r))
ear_right_circle.config(x=x + r * 0.45, y=ear_cy, r=int(ear_r))
ear_left_rect.config(x=earL_rect_x, y=earL_rect_y, w=ear_rect_w, h=ear_rect_h)
ear_right_rect.config(x=earR_rect_x, y=earR_rect_y, w=ear_rect_w, h=ear_rect_h)
ear_left_inner.config(x=x - r * 0.45, y=ear_cy + ear_r * 0.15, r=int(ear_r * 0.42))
ear_right_inner.config(x=x + r * 0.45, y=ear_cy + ear_r * 0.15, r=int(ear_r * 0.42))
blush_left.config(x=x - r * 0.6, y=y + r * 0.1, r=int(blush_r))
blush_right.config(x=x + r * 0.6, y=y + r * 0.1, r=int(blush_r))
if pet_action != last_pet_action:
if pet_action == "sleep":
eye_left.config(r=0)
eye_right.config(r=0)
eye_left_dot.config(r=0)
eye_right_dot.config(r=0)
mouth_center.config(r=0)
elif pet_action == "blink":
eye_left.config(r=0)
eye_left_dot.config(r=0)
eye_right.config(r=1.5)
eye_right_dot.config(r=0.5)
mouth_center.config(r=0.8)
else:
eye_left.config(r=1.5)
eye_right.config(r=1.5)
eye_left_dot.config(r=0.5)
eye_right_dot.config(r=0.5)
mouth_center.config(r=0.8)
last_pet_action = pet_action
if pet_action in ["happy", "eat_wait1", "eat_jump", "eat_wait2"]:
eye_left.config(x=x - eye_offset, y=eye_y, r=2.2)
eye_right.config(x=x + eye_offset, y=eye_y, r=2.2)
eye_left_dot.config(x=x - eye_offset + 0.7, y=eye_y - 0.7, r=0.7)
eye_right_dot.config(x=x + eye_offset + 0.7, y=eye_y - 0.7, r=0.7)
mouth_center.config(x=x, y=mouth_y, r=1.8)
sleep_z_text.config(text="")
elif pet_action == "sleep":
z_pos_x = x + r + 2
if z_pos_x > SCREEN_W - 12:
z_pos_x = x - r - 12
sleep_z_text.config(x=z_pos_x, y=y - r, text="Z")
elif pet_action == "daze":
eye_left.config(x=x - eye_offset, y=eye_y, r=1.1)
eye_right.config(x=x + eye_offset, y=eye_y, r=1.1)
eye_left_dot.config(x=x - eye_offset + 0.4, y=eye_y - 0.4, r=0.4)
eye_right_dot.config(x=x + eye_offset + 0.4, y=eye_y - 0.4, r=0.4)
mouth_center.config(x=x, y=mouth_y, r=0.8)
sleep_z_text.config(text="")
elif pet_action == "blink":
eye_right.config(x=x + eye_offset, y=eye_y, r=1.1)
eye_right_dot.config(x=x + eye_offset + 0.4, y=eye_y - 0.4, r=0.4)
mouth_center.config(x=x, y=mouth_y, r=0.8)
sleep_z_text.config(text="")
else:
eye_left.config(x=x - eye_offset, y=eye_y, r=1.5)
eye_right.config(x=x + eye_offset, y=eye_y, r=1.5)
eye_left_dot.config(x=x - eye_offset + 0.5, y=eye_y - 0.5, r=0.5)
eye_right_dot.config(x=x + eye_offset + 0.5, y=eye_y - 0.5, r=0.5)
sleep_z_text.config(text="")
while True:
start_time = time.time()
if time.time() - last_touch_time > TOUCH_TIMEOUT:
pressed = False
if pressed:
dist_touch_pet = math.hypot(touch_x - x, touch_y - y)
if dragging or dist_touch_pet < r + 12:
dragging = True
x = touch_x
y = touch_y
ear_max_extend = MAX_R * 0.45 + MAX_R * 0.55
x = max(ear_max_extend, min(SCREEN_W - ear_max_extend, x))
y = max(r + TOP_UI_HEIGHT, min(SCREEN_H - r - SAFE_MARGIN, y))
else:
dragging = False
action_timer += 1
walk_timer += 1
greeting_timer += 1
if greeting_timer >= greeting_interval:
current_greeting = random.choice(greeting_list)
greeting_timer = 0
elif greeting_timer >= greeting_show_time:
current_greeting = ""
if not dragging and pet_action not in ["eat_wait1", "eat_jump", "eat_wait2", "happy"] and action_timer >= action_duration:
pick_new_action()
action_timer = 0
dist_to_food = math.hypot(x - food_x, y - food_y)
if pet_action == "eat_wait1":
eat_timer += 1
if eat_timer >= wait1_frames:
pet_action = "eat_jump"
eat_timer = 0
elif pet_action == "eat_jump":
eat_timer += 1
if not dragging:
phase = eat_timer / jump_total_frames
offset_y = math.sin(4 * math.pi * phase) * 7
raw_y = jump_origin_y + offset_y
y = max(r + TOP_UI_HEIGHT, min(SCREEN_H - r - SAFE_MARGIN, raw_y))
if eat_timer >= jump_total_frames:
pet_action = "eat_wait2"
eat_timer = 0
if not dragging:
y = jump_origin_y
elif pet_action == "eat_wait2":
eat_timer += 1
if eat_timer >= wait2_frames:
pet_action = "walk"
eat_timer = 0
elif (pet_action == "walk" or pet_action == "happy") and not dragging:
if dist_to_food < food_detect_range:
pet_action = "happy"
angle = math.atan2(food_y - y, food_x - x)
vx = speed * math.cos(angle)
vy = speed * math.sin(angle)
else:
if pet_action == "happy":
pet_action = "walk"
margin = 12
near_edge = (x < margin) or (x > SCREEN_W - margin) or (y < margin + TOP_UI_HEIGHT) or (y > SCREEN_H - margin)
if near_edge:
angle = get_inner_angle()
vx = speed * math.cos(angle)
vy = speed * math.sin(angle)
walk_timer = 0
else:
if walk_timer >= walk_interval:
angle = random.uniform(0, math.pi * 2)
vx = speed * math.cos(angle)
vy = speed * math.sin(angle)
walk_timer = 0
nx = x + vx
ny = y + vy
ear_max_extend = MAX_R * 0.45 + MAX_R * 0.55
if ear_max_extend <= nx <= SCREEN_W - ear_max_extend:
x = nx
if r + TOP_UI_HEIGHT <= ny <= SCREEN_H - r - SAFE_MARGIN:
y = ny
if dist_to_food < r + food_r:
food_this_level += 1
r += r_gain_per_food
r = min(r, MAX_R)
food_x = random.randint(SAFE_MARGIN, SCREEN_W - SAFE_MARGIN)
food_y = random.randint(TOP_UI_HEIGHT + 10, SCREEN_H - SAFE_MARGIN)
food_obj.config(x=food_x, y=food_y)
pet_action = "eat_wait1"
eat_timer = 0
if not dragging:
jump_origin_y = y
if food_this_level >= food_need_per_level and level < max_level:
level += 1
food_this_level = 0
r = 14
level_text.config(text=f"Lv{level}/{max_level}")
if (pet_action == "walk" or pet_action == "happy") and not dragging:
trail.append((x, y))
if len(trail) > max_trail:
trail.pop(0)
else:
trail.clear()
progress = food_this_level / food_need_per_level
fill_w = bar_w * progress
fill_center_x = bar_left + fill_w / 2
progress_fg.config(x=fill_center_x, y=bar_center_y, w=fill_w, h=bar_h, r=3, color="#ffc81e")
for i, obj in enumerate(trail_objs):
if i < len(trail):
tx, ty = trail[i]
ratio = (i + 1) / max_trail
tr = int(r * 0.35 * ratio)
obj.config(x=tx, y=ty, r=tr)
else:
obj.config(r=0)
if current_greeting != "":
txt_w = len(current_greeting) * 7 + 8
txt_w = min(txt_w, MAX_BUBBLE_WIDTH)
txt_h = 16
if y + r + txt_h + 8 > SCREEN_H - SAFE_MARGIN:
by = int(y - r - txt_h - 6)
else:
by = int(y + r + 6)
bx = int(x - txt_w / 2)
bx = max(SAFE_MARGIN, min(bx, SCREEN_W - txt_w - SAFE_MARGIN))
bubble_center_x = bx + txt_w / 2
bubble_center_y = by + txt_h / 2
bubble_bg.config(x=bubble_center_x, y=bubble_center_y, w=txt_w, h=txt_h, r=4)
bubble_text.config(text=current_greeting, x=bx + 4, y=by + 2)
else:
bubble_bg.config(w=0, h=0)
bubble_text.config(text="")
action_map = {
"walk": "Walking",
"daze": "Dazing",
"blink": "Blinking",
"sleep": "Sleeping",
"happy": "Getting snack",
"eat_wait1": "Yum!",
"eat_jump": "Yum!",
"eat_wait2": "Yum!"
}
state_str = action_map.get(pet_action, "Walking")
if dragging:
state_str = "Dragging"
state_text.config(text=state_str)
update_pet()
elapsed = time.time() - start_time
if elapsed < FRAME_DELAY:
time.sleep(FRAME_DELAY - elapsed)
✨玩法说明
1. 将完整代码复制粘贴到Mind+的Python积木模式-手动编辑,上传到行空板,程序立刻运行。
2. 直接用手指触摸屏幕上的小怪兽,就可以拖拽它到处溜达。
3. 等待金色食物随机刷新,小怪兽靠近食物会进入开心状态,吃到食物会触发弹跳进食动画。
4. 吃够指定数量食物宠物就升级,最高10级。
5. 宠物会自主切换多种行为:闲逛、发呆、眨眼、睡觉,睡觉的时候会冒出“Z”符号,还会时不时弹出对话气泡。
效果展示





-------------------------------------
二次改造小思路
玩够原版还可以继续DIY,简单改几个参数就能做出不一样的效果:
- 修改 greeting_list 列表,替换成中文对话;
- 修改颜色十六进制码,给小怪兽换一套皮肤配色;
- 修改 max_level ,调整宠物最大等级;
- 修改 food_need_per_level ,控制每一级需要吃掉多少份食物。
这个项目把GUI绘图、触摸交互、状态机动画、等级系统结合在一起,很适合行空板新手学习参考。如果大家有好玩的修改想法,欢迎评论区一起交流!




