萌趣赛博电子宠物,养一只属于你的屏幕小怪兽

2026-10-156

哈喽各位创客小伙伴!最近参加社区造点好玩的活动,拿行空板做了一款桌面电子小宠物。之前版本存在耳朵裁切、进度条溢出等小bug,我反复调试修复完成,现在分享完整项目给大家。

这只小怪兽拥有完整的生命行为:会闲逛溜达、发呆放空、眨眼、呼呼大睡,睡觉还会冒“Z”符号;屏幕随机刷新金色小零食,小家伙看到食物会兴奋,吃到零食还会弹跳庆祝,吃够食物就能升级;支持手指直接拖拽把玩,移动的时候身后还会拖着淡淡的渐变尾迹;定时弹出趣味对话气泡,UI界面带等级进度条,底部实时显示宠物当前状态。所有界面做了边界保护,耳朵、文字、对话框不会被屏幕边缘切掉,直接复制代码即可跑通。

------

萌趣赛博电子宠物,养一只属于你的屏幕小怪兽_image_1.webp

-----

硬件准备

行空板 ×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”符号,还会时不时弹出对话气泡。

 

效果展示

萌趣赛博电子宠物,养一只属于你的屏幕小怪兽_image_2.webp

萌趣赛博电子宠物,养一只属于你的屏幕小怪兽_image_3.webp

萌趣赛博电子宠物,养一只属于你的屏幕小怪兽_image_4.webp

萌趣赛博电子宠物,养一只属于你的屏幕小怪兽_image_5.webp

萌趣赛博电子宠物,养一只属于你的屏幕小怪兽_image_6.webp

 

-------------------------------------

二次改造小思路

玩够原版还可以继续DIY,简单改几个参数就能做出不一样的效果:

  1. 修改 greeting_list 列表,替换成中文对话;
  2. 修改颜色十六进制码,给小怪兽换一套皮肤配色;
  3. 修改 max_level ,调整宠物最大等级;
  4. 修改 food_need_per_level ,控制每一级需要吃掉多少份食物。

 

这个项目把GUI绘图、触摸交互、状态机动画、等级系统结合在一起,很适合行空板新手学习参考。如果大家有好玩的修改想法,欢迎评论区一起交流!

创作许可协议

本项目采用 CC BY(署名) 进行许可。

评论(0)
- 没有更多了 -