1

The program I am working on includes a class named GameForm that extends JFrame. This form is going to contain a map (just a series of rectangles), as well as certain objects on the map.

However, I would not be able to draw all of these objects with a single paintComponent(Graphics g) function, since not all objects in the game always have to be drawn at the same time. For example, the drawMap() function would only be called when the form first loads, whereas all other drawing functions would be called after each turn.

However, from what I have read (and please correct me if I am wrong), only one paintComponent function is allowed in the class, and other functions cannot make use of its Graphics2D object.

Are there any ideas as to how this can be implemented?

user2938543
  • 327
  • 1
  • 6
  • 12
  • You can pass the `Graphics2D` object around to other methods though. – BitNinja Aug 29 '14 at 18:38
  • 2
    You could take a look at [this question](http://stackoverflow.com/questions/25419808/frames-painting-at-different-times), if you scroll down, there are three different buffering strategies, which have the core graphics painted to a screen buffer in layers, the map, which is pretty static, the environment would come next and effects ontop of that. It's a little roughy, but you should get the idea. – MadProgrammer Aug 29 '14 at 20:26

2 Answers2

1

People who are new to Swing / GUI programming often imagine JFrames to be like a draw surface or paper. However, you will have to get used to the fact that this is not the case.

First of all, a GUI program has some kind of EDT (Event Dispatch Thread). This thread is where all the GUI action happens. Updating the GUI and responding to user input happens here. This is necessary because user interaction and programmatic changes to the GUI need to be synchronized well.

Back to the topic, a JFrame is basically just a rectangle that is registered to the System to be your "draw surface". Rather than just painting on top of it, you are asked to paint it.

That's what paintComponent(Graphics) is good for. Obviously, you don't want to paint all the time. It just works like:

  1. user opens your window
  2. system tells your app: "hey, you wanted this surface, please paint it"
  3. the Graphics from paintComponent() is used to repaint your frame (quickly)
  4. your application remains inactive until the user makes the next input

If you want to animate your frame, you have to work like this:

  1. tell the system: "hey, I'd like to repaint my surface" (calling repaint())
  2. system calls paintComponent() and you repaint your stuff
  3. the next call must be delayed
  4. start over, paint the next image

Note that the delay is important because all of this happens on the holy EDT. The EDT handles everything and needs to "breathe" so the user can do stuff while you're doing your animation.

All in all, what you've learned is:

  1. Save all the state you need for painting in variables.
  2. When paintComponent() is called, draw onto the surface
  3. If you want to animate, call repaint() -> paintComponent() will be called
  4. never block the EDT

Last thing to consider: don't use JFrame to paint directly to it.
Rather than that, add a JPanel to the frame and override its paintComponent() method.

J4v4
  • 780
  • 4
  • 9
  • All of this is just a simplified version of how it really works. Obviously, you should use runLater at some point, but this is really too much for a beginner. "Waiting" just means that the next operation should be delayed rather than using sleep(). I'll edit to clarify this. – J4v4 Aug 29 '14 at 19:01
  • I'm assuming that the image is drawn quickly and I wasn't even talking about actually double buffering, I was just talking about converting your state data to g.paintXXX calls. – J4v4 Aug 29 '14 at 19:02
  • Thanks for the improvements, now it's a valid +1 answer in my book... still, I wasn't just picky - it's just that I did it exactly this way when I was a Java newbie (`repaint()`, EDT calling `paintComponent()`, which in turned generated/prepared and drawn the gfx, then wait/delay) - and it worked *terribly* - tearing, lags, absurdly low performance (10 FPS with a barely dozen of animated objects etc). That's exactly why I pointed out this is a so-so idea, even for beginners. Using a dedicated back buffer for drawing and just swapping the buffers is what Swing does behind the scenes anyway... –  Aug 29 '14 at 19:15
  • @vaxquis 10fps, really, I have a couple of examples running roughly 25fps animating over 4500, component based, objects moving simultanously and 10,000, animated drawn objects, for [example](http://stackoverflow.com/questions/24131513/java-what-is-heavier-canvas-or-paintcomponent/24131544#24131544) – MadProgrammer Aug 29 '14 at 20:17
  • 1
    @MadProgrammer Like I said, it was about the time Java 5 was a fresh solution - believe me or not, but I suppose your example runs a bit slower than that on Athlon 3200+ with Java 5 and no discrete GPU card. Also, I prefer being able to draw 10M+ fully textured & lighted quads at ~100FPS (+/-, matched to screen refresh through vsync) than being able to draw 10k sprites at 25 FPS... –  Aug 29 '14 at 21:00
  • @vaxquis It all comes down to needs, do you need the additional overhead and complexity of a OpenGL solution for simple animation ;) – MadProgrammer Aug 29 '14 at 21:06
  • @MadProgrammer when using an high-level API such as LWJGL or JMonkeyEngine, I'd say it's actually not only faster in GPU/CPU terms, but also *easier* to use & learn than Swing/AWT, especially when animations or transformations have to happen - the APIs are quite simple and streamlined and there are both great tutorials for them as well as constant developer support - not to mention the knowledge in OpenGL is more portable than Swing knowledge IMO. –  Aug 29 '14 at 21:10
  • @vaxquis there's no disagreement. There is always a question of how much do you really need to know and do over what your willing to allow the framework/API to do for you. It also depends on if your work load is primarily Swing based on not (ie do you need to integrate into an existing app or not) but then I'd be worried about other things if that was the case. Personally, while 90% of the time I might not play at that level, I really want to know and understand how the process works, cause it means I have an idea of how to work with the API and not against it – MadProgrammer Aug 29 '14 at 22:54
  • @MadProgrammer I completely agree with you, with the sole exception made for APIs that are lacking, since I don't see much sense in learning a flawed solution - and Swing obviously *is* a lacking library, quick google for "java swing criticism" and "java swing problems" shows it better than I could. Also, this is *exactly* why both SWT and NEWT were made - because AWT is deprecated & graphically ugly, and Swing is bloated, chaotic and backwards-compatible in the worst possible way. Still, for somebody actually *working* with Swing codebase, understanding internals is a must. –  Aug 29 '14 at 23:08
  • This answer describes *passive* rendering only. Another approach is *active* rendering, described here: https://docs.oracle.com/javase/tutorial/extra/fullscreen/rendering.html – Evgeni Sergeev Jul 18 '16 at 04:41
0

Generally speaking, what you wish to achieve can be done in a couple of ways. It's strictly related to so-called sprites (http://en.wikipedia.org/wiki/Sprite_%28computer_graphics%29) and image buffering (http://en.wikipedia.org/wiki/Multiple_buffering). Simplest approaches would be:

a) in paintComponent() of a JPanel added to your JFrame generate the resulting image by processing all the input data/user events/machine state,

b) you can prepare & store the overlay as e.g. BufferedImage, updating it as needed, and then paint it over your JFrame during a single call - the state of JFrame will be updated only on paint events (paint(), paintComponents() etc, so you must force invalidation by hand if the map changes without direct JFrame interaction (resizing the window, covering it with other frames etc), e.g. by calling repaint() etc.

c) you can get the drawing context by calling getGraphics() (http://docs.oracle.com/javase/7/docs/api/javax/swing/JFrame.html#getGraphics%28%29), and then using the returned object (probably casted to Graphics2D) as your canvas whenever the need arises. Note that this is actually the worst solution in terms of efficiency.

They ain't the only ones possible - I, for once, use OpenGL/JOGL for most of my 2D rendering needs, since it allows insane rendering speed with all the profits of 3D graphics [interpolation, scaling, rotations, alpha-blending, perspective, geometry morphing, shading etc] with only minimal functional overhead.

Also, note it is usually advisable to draw on a dedicated canvas component (e.g. JPanel) instead of global JFrame - it's connected to so-called lightweight vs heavyweight component difference and other OOP/Swing/AWT/EDT concerns; it also allows to hide the map and reuse the JFrame for something else with one simple JPanel#setVisible(false) call.

See java what is heavier: Canvas or paintComponent()? for more information.

Community
  • 1
  • 1
  • 1
    You should NEVER call getGraphics, this is not how custom painting is done. It can return null and and is only a snap shot of the last paint cycle. This approach can cause flickering as the paint system try's to update the screen in between the engines own paint cycles – MadProgrammer Aug 29 '14 at 20:05
  • JFrame doesn't have a paintComponent method – MadProgrammer Aug 29 '14 at 20:07
  • @MadProgrammer a) getGraphics *is* a weak solution, I never claimed otherwise; still, it returns `null` only if the component is not displayable, so it *can* be used to paint single frame changes from time to time. I've updated the answer accordingly, b) paintComponent*s*, yeah... IFTFY, as far as my experience in Swing goes (I moved to NEWT [http://jogamp.org/jogl/doc/NEWT-Overview.html] a long time ago...) –  Aug 29 '14 at 20:57