【问题标题】:Multiple 2D Graphics Drawing Functions in JavaJava 中的多个 2D 图形绘制函数
【发布时间】:2014-08-30 04:55:58
【问题描述】:

我正在处理的程序包括一个名为GameForm 的类,它扩展了JFrame。此表单将包含一张地图(只是一系列矩形)以及地图上的某些对象。

但是,我无法使用单个 paintComponent(Graphics g) 函数绘制所有这些对象,因为并非游戏中的所有对象都必须始终同时绘制。例如,drawMap() 函数只会在表单第一次加载时调用,而所有其他绘图函数将在每次转动后调用。

但是,根据我的阅读(如果我错了,请纠正我),类中只允许一个paintComponent函数,其他函数不能使用它的Graphics2D对象。

对于如何实现这一点有什么想法吗?

【问题讨论】:

  • 您可以将Graphics2D 对象传递给其他方法。
  • 你可以看看this question,如果你向下滚动,有三种不同的缓冲策略,将核心图形分层绘制到屏幕缓冲区,地图,这是相当静态的,接下来是环境,并在此基础上产生影响。这有点粗略,但你应该明白。

标签: java swing graphics2d


【解决方案1】:

刚接触 Swing / GUI 编程的人经常将JFrames 想象成一个绘图表面或纸张。但是,您将不得不习惯事实并非如此。

首先,GUI 程序具有某种 EDT(事件调度线程)。该线程是所有 GUI 操作发生的地方。更新 GUI 和响应用户输入发生在这里。这是必要的,因为用户交互和对 GUI 的编程更改需要很好地同步。

回到主题,JFrame 基本上只是一个在系统中注册为“绘图表面”的矩形。您被要求绘制它,而不是仅仅在它上面作画。

这就是 paintComponent(Graphics) 的好处。显然,你不想一直画画。它就像这样工作:

  1. 用户打开你的窗口
  2. 系统告诉您的应用:“嘿,您想要这个表面,请绘制它”
  3. paintComponent() 的图形用于重新绘制框架(快速)
  4. 在用户进行下一次输入之前,您的应用程序将保持非活动状态

如果你想为你的框架设置动画,你必须像这样工作:

  1. 告诉系统:“嘿,我想重新粉刷我的表面”(调用repaint()
  2. 系统调用 paintComponent() 然后你重新绘制你的东西
  3. 下一个呼叫必须延迟
  4. 重新开始,绘制下一张图片

请注意,延迟很重要,因为所有这些都发生在神圣的 EDT 上。 EDT 处理所有事情并需要“呼吸”,以便用户可以在您制作动画时做一些事情。

总而言之,你学到的是:

  1. 将绘制所需的所有状态保存在变量中。
  2. paintComponent() 被调用时,在表面上绘制
  3. 如果要制作动画,请致电repaint() -> 将调用paintComponent()
  4. 永远不要阻止 EDT

最后要考虑的事情:不要使用JFrame 直接对其进行绘画
而不是这样,将JPanel 添加到框架并覆盖其paintComponent() 方法。

【讨论】:

  • 所有这些只是其实际工作原理的简化版本。显然,您应该在某些时候使用 runLater,但这对于初学者来说实在是太多了。 “等待”只是意味着应该延迟下一个操作,而不是使用 sleep()。我将进行编辑以澄清这一点。
  • 我假设图像绘制得很快,我什至没有谈论实际的双缓冲,我只是在谈论将您的状态数据转换为 g.paintXXX 调用。
  • 感谢您的改进,现在它在我的书中是一个有效的 +1 答案......不过,我并不只是挑剔 - 只是当我还是一名 Java 新手时,我就是这样做的(repaint(),EDT 调用 paintComponent(),然后生成/准备和绘制 gfx,然后等待/延迟) - 它工作非常糟糕 - 撕裂、滞后、荒谬的低性能(10 FPS几乎没有十几个动画对象等)。这正是为什么我指出这是一个马马虎虎的想法,即使对于初学者也是如此。无论如何,使用专用的后台缓冲区进行绘图并交换缓冲区是 Swing 在幕后所做的......
  • @vaxquis 10fps,真的,我有几个示例运行大约 25fps 的动画,超过 4500 个基于组件的对象同时移动和 10,000 个动画绘制对象,example
  • @MadProgrammer 就像我说的那样,Java 5 是一个全新的解决方案的时候了——信不信由你,但我想你的示例运行速度比在 Athlon 3200+ 上使用 Java 5 和没有独立的 GPU 卡。此外,我更喜欢能够以 ~100FPS(+/-,与通过垂直同步的屏幕刷新匹配)绘制 10M+ 完全纹理和光照四边形,而不是能够以 25 FPS 绘制 10k 精灵......
【解决方案2】:

一般来说,您希望实现的目标可以通过多种方式完成。它与所谓的 sprites (http://en.wikipedia.org/wiki/Sprite_%28computer_graphics%29) 和图像缓冲 (http://en.wikipedia.org/wiki/Multiple_buffering) 密切相关。最简单的方法是:

a) 在JPanelpaintComponent() 中添加到您的JFrame,通过处理所有输入数据/用户事件/机器状态来生成结果图像,

b) 您可以准备和存储 overlay 为例如BufferedImage,根据需要对其进行更新,然后在一次调用期间将其绘制在您的 JFrame 上 - JFrame 的状态将仅在绘制事件时更新(paint()paintComponents() 等,因此您必须通过以下方式强制失效如果地图在没有直接 JFrame 交互的情况下发生更改(调整窗口大小,用其他框架覆盖窗口等),例如通过调用 repaint() 等,请处理。

c) 您可以通过调用getGraphics() (http://docs.oracle.com/javase/7/docs/api/javax/swing/JFrame.html#getGraphics%28%29) 获取绘图上下文,然后在需要时使用返回的对象(可能转换为Graphics2D)作为您的画布。请注意,就效率而言,这实际上是最差的解决方案。

它们不是唯一可能的——我曾经使用 OpenGL/JOGL 来满足我的大部分 2D 渲染需求,因为它可以在 3D 图形的所有收益的同时实现疯狂的渲染速度 [插值、缩放、旋转、alpha 混合、透视、几何变形、着色等],只需最少的功能开销。

另外,请注意通常建议使用 专用 画布组件(例如 JPanel)而不是全局 JFrame - 它与所谓的轻量级组件与重量级组件的差异以及其他OOP/Swing/AWT/EDT 问题;它还允许隐藏地图并通过一个简单的 JPanel#setVisible(false) 调用将 JFrame 重用于其他内容。

请参阅java what is heavier: Canvas or paintComponent()? 了解更多信息。

【讨论】:

  • 你永远不应该调用 getGraphics,这不是自定义绘制的方式。它可以返回 null 并且只是最后一个绘制周期的快照。当绘制系统尝试在引擎自己的绘制周期之间更新屏幕时,这种方法可能会导致闪烁
  • JFrame 没有paintComponent 方法
  • @MadProgrammer a) getGraphics 一个弱解决方案,我从来没有声称其他;尽管如此,它仅在组件不可显示时才返回null,因此它可以用于不时绘制单帧更改。我已经相应地更新了答案,b)paintComponents,是的...... IFTFY,就我在 Swing 的经验而言(我很久以前搬到了 NEWT [jogamp.org/jogl/doc/NEWT-Overview.html]... )
猜你喜欢
  • 2013-09-03
  • 2021-07-28
  • 1970-01-01
  • 2011-05-09
  • 2012-10-27
  • 1970-01-01
  • 2021-03-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多