You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python终端游戏中实现文本框闪烁光标?

解决终端游戏中阻塞输入下的闪烁光标问题

核心思路

你的get_key()是阻塞式的,导致主线程卡在等待按键时无法处理光标闪烁。解决关键是将输入监听与屏幕渲染解耦,以下两种方案均可实现需求:


方案一:多线程分离输入与渲染

保留现有get_key()函数,用后台线程处理按键监听,主线程负责定时切换光标状态并重绘屏幕,改动最小。

实现步骤

  1. 用线程安全队列传递按键结果,避免线程冲突。
  2. 启动后台线程持续监听按键,将结果存入队列。
  3. 主线程循环:
    • 从队列取出按键,更新屏幕内容与光标位置。
    • 定时切换光标显示状态(显示/隐藏)。
    • 清屏后重绘屏幕,根据光标状态决定是否渲染█。

代码示例

import threading
import queue
import time
import os

# 假设这是你已有的自定义get_key()函数
def get_key():
    if os.name == 'nt':
        import msvcrt
        return msvcrt.getch().decode('utf-8')
    else:
        import sys, termios, tty
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(fd)
            key = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return key

def main():
    # 初始化屏幕参数
    WIDTH, HEIGHT = 40, 10
    screen = [[' ' for _ in range(WIDTH)] for _ in range(HEIGHT)]
    cursor_row, cursor_col = 0, 0
    cursor_visible = True
    blink_interval = 0.5  # 光标闪烁间隔
    last_blink_time = time.time()

    # 线程安全队列存储按键
    key_queue = queue.Queue()

    # 后台按键监听线程
    def key_listener():
        while True:
            key = get_key()
            key_queue.put(key)

    threading.Thread(target=key_listener, daemon=True).start()

    while True:
        # 处理队列中的按键
        while not key_queue.empty():
            key = key_queue.get()
            # 处理退出、退格、回车等逻辑
            if key == '\x1b':  # ESC键退出
                return
            elif key in ('\b', '\x7f'):  # 退格键
                if cursor_col > 0:
                    cursor_col -= 1
                    screen[cursor_row][cursor_col] = ' '
            elif key == '\r':  # 回车键
                cursor_row += 1
                cursor_col = 0
                if cursor_row >= HEIGHT:
                    # 屏幕滚动逻辑(可选)
                    screen.pop(0)
                    screen.append([' ' for _ in range(WIDTH)])
                    cursor_row = HEIGHT - 1
            elif len(key) == 1 and cursor_col < WIDTH:
                # 普通字符输入
                screen[cursor_row][cursor_col] = key
                cursor_col += 1

        # 更新光标闪烁状态
        current_time = time.time()
        if current_time - last_blink_time >= blink_interval:
            cursor_visible = not cursor_visible
            last_blink_time = current_time

        # 清屏(ANSI转义序列比os.system更高效)
        print('\033[H\033[J', end='')

        # 重绘屏幕
        for row_idx in range(HEIGHT):
            line = ''.join(screen[row_idx])
            # 渲染光标
            if row_idx == cursor_row and cursor_visible:
                line = line[:cursor_col] + '█' + line[cursor_col+1:]
            print(line)

        # 控制循环频率,降低CPU占用
        time.sleep(0.01)

if __name__ == '__main__':
    main()

方案二:非阻塞输入替换阻塞函数

改写get_key()为非阻塞版本,主线程可同时检查输入和处理光标闪烁,无需多线程。

实现步骤

  1. 利用select或终端属性设置,将输入改为非阻塞模式。
  2. 主线程循环:
    • 检查是否有按键输入,有则处理。
    • 定时切换光标状态,重绘屏幕。

代码示例(Linux/macOS)

import sys
import termios
import tty
import select
import time
import os

def get_key_non_blocking():
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setcbreak(fd)
        # 0秒超时,立即返回是否有输入
        if select.select([sys.stdin], [], [], 0)[0]:
            return sys.stdin.read(1)
        return None
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

def main():
    WIDTH, HEIGHT = 40, 10
    screen = [[' ' for _ in range(WIDTH)] for _ in range(HEIGHT)]
    cursor_row, cursor_col = 0, 0
    cursor_visible = True
    blink_interval = 0.5
    last_blink_time = time.time()

    while True:
        # 检查非阻塞输入
        key = get_key_non_blocking()
        if key is not None:
            if key == '\x1b':
                return
            elif key in ('\b', '\x7f'):
                if cursor_col > 0:
                    cursor_col -= 1
                    screen[cursor_row][cursor_col] = ' '
            elif key == '\r':
                cursor_row += 1
                cursor_col = 0
                if cursor_row >= HEIGHT:
                    screen.pop(0)
                    screen.append([' ' for _ in range(WIDTH)])
                    cursor_row = HEIGHT - 1
            elif len(key) == 1 and cursor_col < WIDTH:
                screen[cursor_row][cursor_col] = key
                cursor_col += 1

        # 更新光标闪烁
        current_time = time.time()
        if current_time - last_blink_time >= blink_interval:
            cursor_visible = not cursor_visible
            last_blink_time = current_time

        # 清屏并渲染
        print('\033[H\033[J', end='')
        for row_idx in range(HEIGHT):
            line = ''.join(screen[row_idx])
            if row_idx == cursor_row and cursor_visible:
                line = line[:cursor_col] + '█' + line[cursor_col+1:]
            print(line)

        time.sleep(0.01)

if __name__ == '__main__':
    main()

注意事项

  • 清屏方式:优先用ANSI转义序列\033[H\033[J,比os.system('cls/clear')更快,减少屏幕闪烁。
  • 线程安全:多线程方案必须用线程安全队列传递按键,避免直接共享变量导致冲突。
  • 终端兼容性:Windows与Linux/macOS的终端输入API不同,get_key()需对应调整。
  • CPU占用:循环中加入time.sleep(0.01)避免主线程过度占用资源。

内容的提问来源于stack exchange,提问作者Dhananjoy Bhuyan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 04:05:01