小能豆

制作滚动条但不一致

py

所以我只是在胡乱尝试做一个简单的滚动条。基本上,有一些按钮和一个滚动条。我所做的就是,如果滚动条向下移动,则将按钮向上移动;如果滚动条向上移动,则将按钮向下移动。这是代码。

import pygame
import time

pygame.init()

window = pygame.display.set_mode((1200, 600))
font = pygame.font.Font(pygame.font.get_default_font(), 30)

ys = [] ##stores y-positions for the buttons
for y in range(25):
    ys.append((y * 101) + 100)

bar = pygame.Rect(550, 100, 10, (1 / len(ys) if len(ys) > 0 else 1) * 500)

scrollStart = False
updateButtons_Y = False

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    mousePressed = pygame.mouse.get_pressed()[0]
    mousePos = pygame.mouse.get_pos()

    window.fill((255, 255, 255))

    if (bar.y < 100): bar.y = 100
    if (bar.y + bar.height) > 500: bar.y = 500 - bar.height    
    pygame.draw.rect(window, (0, 0, 0), bar)

    if mousePressed and bar.collidepoint(mousePos) and not scrollStart:
        scrollStart = True
        scrollPosY = mousePos[1]

    if scrollStart:
        pygame.draw.rect(window, (255, 0, 0), bar)
        bar.y += mousePos[1] - scrollPosY
        if bar.y > 100  and bar.y + bar.height < 500:   
            for i in range(len(ys)):
                ys[i] -= (mousePos[1] - scrollPosY) * len(ys) * 0.3
        scrollPosY = mousePos[1]

    if not mousePressed and scrollStart:
        scrollStart = False        

    for i, y in enumerate(ys):
        pygame.draw.rect(window, (0, 0, 0), (10, y, 500, 100), 3)
        surf = font.render(f'{i}', True, (0, 0, 0))
        window.blit(surf, (100, y + 40))

    pygame.display.flip()

我遇到的问题是,在上下滚动时,它们会逐渐错位。例如,这是它的开始方式。请注意左侧的栏和按钮如何在同一级别上开始。

WEgTT.png

以下是上下滚动几次后的结果。

PYIEK.png

我认为这就是问题所在:

if scrollStart:
        pygame.draw.rect(window, (255, 0, 0), bar)
        bar.y += mousePos[1] - scrollPosY
        if bar.y > 100  and bar.y + bar.height < 500:   
            for i in range(len(ys)):
                ys[i] -= (mousePos[1] - scrollPosY) * len(ys) * 0.3
        scrollPosY = mousePos[1]

mousePos[1] - scrollPosY根据鼠标移动的速度而变化,因此每帧的变化量不一致。为了解决这个问题,我将其替换为

if scrollStart:
        pygame.draw.rect(window, (255, 0, 0), bar)
        if mousePos[1] - scrollPosY != 0:
            speed = 1
            direction = math.copysign(speed, mousePos[1] - scrollPosY)
            bar.y += direction 
            if bar.y > 100  and bar.y + bar.height < 500:
                for i in range(len(ys)):
                    ys[i] -= direction   
        scrollPosY = mousePos[1]

这解决了该问题,但现在移动感觉不自然(滚动条移动得太慢或按钮移动得太快)。

所以我的问题是:有没有办法让它们保持一致,同时以不同的速度移动?


阅读 33

收藏
2024-11-18

共1个答案

小能豆

你的问题确实出在滚动时位置计算的精确度上,特别是在将鼠标位置的变化量直接应用到按钮 ys 列表中的偏移值时。这种方法可能会由于累积误差和非线性缩放而导致错位。

为了实现一致且自然的滚动效果,同时支持不同速度的滚动条和内容移动,你可以将滚动条的位置和按钮位置的映射分开,并通过一种比例关系来连接它们。


修复方法

核心思想是基于滚动条的位置百分比来计算按钮的整体偏移量。这样,滚动条和按钮的移动保持一致且无累积误差。

修改代码

以下是改进后的代码,其中滚动条和按钮之间有一个固定的比例关系:

import pygame

pygame.init()

window = pygame.display.set_mode((1200, 600))
font = pygame.font.Font(pygame.font.get_default_font(), 30)

# Initialize button positions
ys = [(y * 101) + 100 for y in range(25)]

# Scrollbar properties
bar = pygame.Rect(550, 100, 10, max(500 // len(ys), 20))
scroll_area_top = 100
scroll_area_bottom = 500

scrollStart = False

# Total scrollable height (extra space occupied by all buttons)
content_height = len(ys) * 101
scroll_ratio = (content_height - (scroll_area_bottom - scroll_area_top)) / (scroll_area_bottom - scroll_area_top)

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    mousePressed = pygame.mouse.get_pressed()[0]
    mousePos = pygame.mouse.get_pos()

    window.fill((255, 255, 255))

    # Clamp scrollbar position
    bar.y = max(scroll_area_top, min(bar.y, scroll_area_bottom - bar.height))
    pygame.draw.rect(window, (0, 0, 0), bar)

    # Handle mouse drag
    if mousePressed and bar.collidepoint(mousePos) and not scrollStart:
        scrollStart = True
        scrollPosY = mousePos[1]

    if scrollStart:
        pygame.draw.rect(window, (255, 0, 0), bar)
        delta = mousePos[1] - scrollPosY
        bar.y += delta
        scrollPosY = mousePos[1]

    if not mousePressed and scrollStart:
        scrollStart = False

    # Calculate button offset based on scrollbar position
    scroll_percent = (bar.y - scroll_area_top) / (scroll_area_bottom - scroll_area_top - bar.height)
    offset = -scroll_percent * (content_height - (scroll_area_bottom - scroll_area_top))

    # Draw buttons
    for i, y in enumerate(ys):
        button_y = y + offset
        if scroll_area_top - 100 < button_y < scroll_area_bottom:  # Only draw visible buttons
            pygame.draw.rect(window, (0, 0, 0), (10, button_y, 500, 100), 3)
            surf = font.render(f'{i}', True, (0, 0, 0))
            window.blit(surf, (100, button_y + 40))

    pygame.display.flip()

代码的核心改动点

  1. 滚动比例计算
  2. 使用 scroll_percent 表示滚动条在允许范围内的位置百分比。
  3. 滚动条移动的 y 值被线性映射到按钮的偏移量范围。

  4. 偏移计算与按钮位置更新分离

  5. 按钮的 y 坐标被动态计算为其初始位置加上基于滚动百分比的偏移量。
  6. 这样,不直接修改 ys 列表,避免了累积误差。

  7. 可见性优化

  8. 只绘制当前在滚动区域内的按钮,提高性能。

  9. 滚动条最小高度

  10. 为滚动条设置了一个最小高度,防止按钮数量过多时滚动条过小。

优点

  1. 一致性
    滚动条和按钮移动的速度通过比例关系保持一致,无需手动调整滚动速度。

  2. 无累积误差
    每次绘制时都重新计算按钮位置,避免误差积累。

  3. 自然的滚动体验
    滚动条移动时,按钮会以固定比例偏移,滚动行为平滑且易于控制。


改进方向

  • 鼠标滚轮支持
    可以捕获 pygame.MOUSEWHEEL 事件,让用户通过滚轮进行滚动。

  • 更灵活的滚动条宽度和高度
    滚动条的大小可以动态调整以适配内容的总高度。


运行以上代码后,你会发现滚动行为更加直观,按钮和滚动条的位置始终保持同步,滚动感觉也更流畅。

2024-11-18