繁体   English   中英

QGraphicsScene上的抽动动作QGraphicsItem

[英]Jerking movements QGraphicsItem on a QGraphicsScene

我正在尝试写我的小游戏。 为此,我编写了一些最简单的代码:左右。 但是我有一个问题:如果我长时间按住移动键,动作会变得生涩,尽管这并没有指示代码中的任何内容(在记录上,我一直一直按向右箭头,仅此而已) 。

通常,方块会平稳移动,但有时会出现这些急剧的跳跃。

在此处输入图片说明

关于代码。

game_scene.cpp:

game_scene::game_scene()
{
    QTimer *update_timer = new QTimer();
    update_timer->setInterval(1000 / 30);
    connect(update_timer, &QTimer::timeout, this, &game_scene::update_rect);
    update_timer->start();
}

void game_scene::keyPressEvent(QKeyEvent *event)
{
    if (main_character ==  nullptr)
    {
        return;
    }

    std::thread *thd = nullptr;
    if (event->key() == Qt::Key_Right && !moving_right)
    {
        // move right
        moving_right = true;
        thd = new std::thread(&game_scene::move_right, this);
    }

    if (thd != nullptr)
    {
        thd->detach();
    }
}

void game_scene::keyReleaseEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Right)
    {
        // move right
        moving_right = false;
    }
}

void game_scene::move_right()
{
    while (moving_right)
    {
        x += main_character->move_right();
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
}

character.cpp

int character::move_right()
{
    x += speed;
    return speed;
}

void character::paint(QPainter *painter, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/)
{
    QPolygon polygon;
    polygon << QPoint(x, y) << QPoint(x, y + height) << QPoint(x + width, y + height) << QPoint(x + width, y);
    painter->setBrush(Qt::red);
    painter->drawPolygon(polygon);
}

分配一个单独的流来处理“行走”,这是必要的,以便可以从几个地方(行走,跳跃,其他任何地方)影响“角色”。

我在某处错了吗? 也许线程太多? 但是,还有什么更好的方法呢?

在游戏中,当您想要实现100%流畅的运动错觉时,您需要考虑实际物理显示(以及图形卡生成的视频信号)的行为。

显示屏是从上到下,从左到右的“绘制”屏幕,视频信号由每个像素的数据组成,这相当于大量数据,因此通常需要占用两帧之间几乎所有的可用时间(60Hz = 16.66) ..ms,100Hz = 10ms,...),并且特定像素的信号基于建立信号时视频ram中的“当前”值。

这些东西通常与“垂直回扫周期”同步,这是视频信号的时间,当使用旧的CRT显示器重新配置其磁体以将“光束”从显示器的右下角移回左上角时,类似..视频信号在每条扫描线的末尾都包含较短的空闲时间,以便为经典的CRT管时间将“光束”从右侧移到左侧...实际上我不确定现代HDMI信号是否仍包含这些空闲回扫时间,因为LCD显示器不需要它们,但是它肯定包含同步标记,以使显示器知道整个帧和行在哪里开始/结束...并且信号的建立仍在时间上连续。

在旧的8位计算机上,人们甚至设法以完美的代码计时来竞争“光束”并在读取视频ram内容之前/之后修改内存,以产生“不可能的”输出,例如在单个8x8字符中具有更多颜色,而视频模式规范建议每个字符只能使用两种颜色,等等。

因为您根本不用理会视频信号的时序,所以很有可能全部在Qt,窗口合成器和图形卡驱动程序的管理中。 这些很有可能会缓冲您的绘画动作,并在适当的时间与显示同步翻转新的屏幕缓冲区(新图像),以防止大量的闪烁/撕裂(可能是“ VSYNC ON”之类的机制)。 因此,这意味着如果您幸运地弄乱了两个视频帧之间的坐标,并且在对象之间移动了恒定的像素量,则生成的图像将是100%流利的动画。

如果线程在某事上窒息,或者它的周期与显示不同步,并且确实使对象每帧移动了不同数量的像素,则该运动看起来会“生涩”。

(但如果它“跳”了很多像素,那么代码中可能会出现一些多线程错误,使得该对象真正在位置上跳跃,由于与视频帧不同步,上述的混响应该是更像是两帧之间的线程+ -1额外移动调用)。

总体而言,旨在流畅地输出60 FPS(以60Hz分辨率显示)的(经典2D简化)游戏通常根本不使用线程,而是在显示的节拍中打上主循环,在开始时从准备好的缓冲区中翻转新图像。垂直回扫(因此显示屏将在下一帧显示),然后清除其他缓冲区,读取输入,处理“物理”,计算所有物体的新位置,并将最终的新图像绘制到第二个缓冲区中,该图像将显示在下一个缓冲区中循环播放,如果还剩一些时间,它们将等待显示完成当前帧。.当然,所有这些都必须适合〜16ms,以实现流畅的60FPS。

因此,如果您真的很喜欢流利的动画,则应检查Qt是否有要通知每个视频帧的API。我不知道Qt API,但这些高级API通常可以让您编写自己的“绘画”例程,并且您每次都可以使窗口内容无效,以强制框架持续不断地调用您的“绘画”(框架通常与视频信号本身进行同步,尤其是当它们具有某种方法来设置“ vsync on”或窗口合成器为…或者他们可能会向某种API通知回溯事件(不太可能使用Qt这样的高级API)。 并使用无限的主循环创建更严格的非线程版本,为每个显示框架准备新图像。

在现代游戏中,通常使用多线程来减轻CPU主线程的负担,但是负责视频输出的部分仍在很大程度上了解显示属性(刷新率和帧开始的时间),并围绕它们同步动作。

借助一台像样的现代机器在屏幕上移动一个简单的2D正方形图像,您应该有大量的空闲CPU时间以琐碎的单线程主循环方式完成此操作,而不必诉诸于8/16位计算机时代的复杂技巧,当由于内存限制,屏幕的双缓冲通常甚至都不可用时,必须对主循环代码进行定时,以对视频内存进行更改,以免破坏屏幕上产生的图像并保持错觉完美。

编辑:关于的额外评论

“如果我长时间按住移动键” ...

从体系结构的角度来看,上面的文章为什么您的方法很不幸,并且变得“笨拙”,但我没有在gif中具体说明。

问题是,您有不同的线程来移动“主角”(正方形)并滚动“场景”(轴),试图将正方形保持在中间。 因为所有这些都是在独立线程中发生的,没有进行任何同步,所以有时场景在更新坐标之前会读取正方形坐标,因此它将场景移至旧位置,然后重绘场景,但读取了新的正方形位置,因此显示再往前走,那么在下一帧中,您终于将其卡回到中间。

即使您将在这些线程之间进行同步(例如,先将场景的方形位置复制到本地副本中,然后再解决场景的其他特征(基于所有“实时”值的这些副本,而忽略它们的进一步更改),然后从此本地场景绘制场景复制),有时您的线程滴答声很可能会与视频滴答声发生冲突,在帧的边缘附近(有时在此处,有时在此处)滴答,从而产生了另一个讨厌的来源。 由于您的滴答声似乎遍地都是(33,33ms,50ms),它们可能会意外地开始彼此同步并发出视频信号,但是在某些时候,动作会以特别不和谐的顺序发生,从而产生更多的杂音。比平常。

我也猜想 (因为您没有发布moving_right的定义, moving_right您在Debug模式下执行了所有操作,并且没有正确使用互斥体/原子类型在不同线程之间进行通信,因此一旦尝试“释放”此版本,它可能会变得更糟(如果moving_right是简单的bool while (moving_right) ,则线程处理程序中的while (moving_right)将创建无限循环,因为优化程序不知道该值可能会在当前线程之外更改。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM