How to pass canvas reference to class - java

I'm doing a simple game and I have a class that I call GameLoop and inside the run() method the code is passing the reference of the canvas to a method that draws a circle. This code is just some testcode.
My question is how do I pass the reference of the canvas if I want to create several sprite objects(classes) with a circle and put them in a list when the game starts? I have done this before, but then I used bitmap in each sprite object and just passed a reference of the image, but in this case I'm not sure how I would do since the canvas is inside the run() method, and I want to create my sprite objects in a method like initializeGameObjects() once in the beginning. I hope my question isn't unclear!? Preciate some help! Thanks!
// Game loop ---------------------------------------
#Override
public void run() {
// TODO Auto-generated method stub
while (gameRunning) {
if (!surfaceHolder.getSurface().isValid())
continue;
canvas = surfaceHolder.lockCanvas();
// Call method to draw objects on screen
drawObjects(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
// End game loop ---------------------------------------
// Method that draw everything on canvas
private void drawObjects(Canvas canvas) {
// Test
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);
// Clear screen with black color
canvas.drawRGB(0, 0, 0);
// Draw a circle
canvas.drawCircle(100, 100, 100, paint);
}

I would implement Runnable and construct a Thread. This way you can have a "setup" method which ultimately calls a "start" method. The "start" method would draw to the canvas before starting the thread.
This is actually better practice for this kind of stuff. You may want to init stuff before starting a thread.
public class Game implements Runnable {
private Thread thisThread = new Thread(this);
private boolean gameRunning = false;
public void startGame() {
gameRunning = true;
//Draw to canvas
thisThread.start();
}
#Override
public void run() {
while(gameRunning) {
//Draw other objects
}
}
}

Define an interface:
public interface Renderable{
public void renderToCanvas(Canvas c);
}
Define a List of Renderables:
private List<Renderable> sprites = new ArrayList<Renderable>();
create methods to add/remove sprites, make sure they are synchronized:
public synchronized void addSprite(Renderable r){
sprites.add(r);
}
public synchronized void removeSprite(Renderable r){
sprites.remove(r);
}
This is drawing method, also synchronized, so you don't end up drawing and modifying sprite list at same time. This is either called in a loop by other thread, or a view's onDraw() method:
public synchronized void draw(Canvas c){
// lock canvas if needed
// common drawing code
for(Renderable r :sprites){
r.renderToCanvas(c);
}
// release canvas if locked
}
Now, you are free to define any type of sprite, Circle, Square or whatever,
public class Circle implements Renderable{
private Paint mPaint;
private float mRadius;
public Circle(Paint p, float r){
mPaint = p;
mRadius = r;
}
#Override
public void renderToCanvas(Canvas c){
c.drawCircle(100, 100, mRadius, mPaint);
}
}
and add it to sprite list:
addSprite(new Circle(new Paint(),20f));
Note: I used synchronized for simplicity, but there are better alternatives to it like atomic flags.These are required only if you want to add/remove sprites while game loop is running. If you setup all sprites before starting game loop, this is not a problem.

Related

How to clear a JPanel

I am working on a 2d game using Swing. Before, I used to render my objects and player on a jPanel over the
panel.repaint();
method and would override the paint methode in the panel class. Then I learned about the concept of moving the render code to a Render class which looks like this:
public class Renderer{
public void render(Graphics g, Game game){
game.getObjects.forEach(gameObject -> g.drawImage(....);
}
}
With that code there is always a drawing on a drawing, ....
The problem with this is that I can't (or don't know how to) call the super method repaint() of the panel.
I would like to keep the Render class because the code is much more structured. Any advice on how to reset a jPanel?
I tried using panel.repaint() before calling the render method but I just got a blank screen.
public class Renderer {
public void render(Game game, Graphics graphics) {
Player player = game.getPlayer();
graphics.drawImage(player.getImage(), (int)player.getPosition().getX(), (int)player.getPosition().getY(), null);
}
}
public class Game{
private static Game instance;
private GamePanel gamePanel;
private Player player;
private Renderer renderer;
private boolean isRunning = true;
private final int MAX_FPS = 60;
private Game() {
initialize();
startGameLoop();
}
private void initialize() {
renderer = new Renderer();
player = Player.getInstance();
gamePanel = GamePanel.getInstance(this);
GameWindow.getInstance(gamePanel);
}
private void startGameLoop() {
double timePerCycle = 1_000_000_000 / MAX_FPS;
int updates = 0;
long lastInfo = System.currentTimeMillis();
long timeBefore = System.nanoTime();
while(isRunning) {
if(System.nanoTime() - timeBefore >= timePerCycle) {
timeBefore = System.nanoTime();
update();
render();
updates++;
}
if(System.currentTimeMillis() - lastInfo >= 1000) {
System.out.printf("UPS: %d\n", (updates / (( System.currentTimeMillis() - lastInfo) / 1000)));
lastInfo = System.currentTimeMillis();
updates = 0;
}
}
}
private void render() {
Graphics graphics = gamePanel.getGraphics();
renderer.render(this, graphics);
graphics.dispose();
}
To clear the panel you can employ a boolean in paintComponent and fill in the rectangle via g.fillRect(x,y, width, height).
Her is one possible example. Where boolean clearScreen is an instance field.
public void clear() {
clearScreen = true; // tested in paintComponent
repaint();
clearScreen = false;
}
Here are some other suggestions.
don't override paint for JPanel. Use paintComponent.
first statement should be super.paintComponent(g). This is what allows panel.setBackground() to work, among other things as it calls the overridden method to perform additional functionality.
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// your stuff here
}
Use a Swing Timer for controlling repaint cycles.
painting and event handling are done in the EventDispatch thread. So processing should be kept to a minimum. Any computations required for your game should be done outside that thread and when possible, only the actual invocation of the graphics methods should be done in the paintComponent method.
If done properly, subsequent calls to repaint() will not add to what is there. Each call must redraw everything including changes.
For more information check out How to paint
There are many examples of painting on this site. Search for them using [Swing] and [Graphics] tags. Here is one that employs some of the above. Also note that Swing components enable double buffering by default

Java Drawing Multiple Squares in Same JFrame

I am trying to make an animation with multiple thread. I want to paint n squares where this n comes from commend-line argument. Every square has their x-y coordinates, colors and speed. They are moving to the right of the frame with different speed, color and coordinates. Since I am using multi thread I assume I have to control each squares. So I have to store each square object in the ArrayList. However, I am having trouble with painting those squares. I can paint one square but when I try to paint multiple squares, it does not show. Here what I have done so far:
DrawSquare.java
import java.awt.Graphics;
import javax.swing.JPanel;
public class DrawSquare extends JPanel {
public Square square;
public DrawSquare() {
square = new Square();
}
#Override
public void paintComponents(Graphics g) {
// TODO Auto-generated method stub
super.paintComponents(g);
}
#Override
public void paint(Graphics g) {
// TODO Auto-generated method stub
super.paint(g);
g.setColor(square.getC());
g.fillRect(square.getX(), square.getY(), square.getR(), square.getR());
}
}
Square.java
import java.awt.Color;
import java.util.Random;
public class Square {
private int x,y,r,s;
private Color c;
private Random random;
public Square() {
random = new Random();
x = random.nextInt(100) + 30;
y = random.nextInt(100) + 30;
r = random.nextInt(50) + 20;
s = random.nextInt(20) + 5;
c = new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255));
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getR() {
return r;
}
public int getS() {
return s;
}
public Color getC() {
return c;
}
}
Animation.java
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Animation extends JFrame implements Runnable {
private JPanel panel;
private DrawSquare square;
public Animation() {
}
public static void main(String[] args) {
Animation w = new Animation();
DrawSquare square = new DrawSquare();
JFrame f = new JFrame("Week 9");
int n = Integer.parseInt(args[0]);
f.setVisible(true);
f.setSize(700,700);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setResizable(false);
for(int i=0; i<n; i++) {
f.getContentPane().add(square);
}
}
#Override
public void run() {
// TODO Auto-generated method stub
}
}
So, starting with...
public class DrawSquare extends JPanel {
public Square square;
public DrawSquare() {
square = new Square();
}
#Override
public void paintComponents(Graphics g) {
// TODO Auto-generated method stub
super.paintComponents(g);
}
#Override
public void paint(Graphics g) {
// TODO Auto-generated method stub
super.paint(g);
g.setColor(square.getC());
g.fillRect(square.getX(), square.getY(), square.getR(), square.getR());
}
}
As general recommendation, it's preferred to put custom painting in the paintComponent method (note, there's no s at the end)
When paint is called, the Graphics context has already been translated to the component coordinate position. This means that 0x0 is the top/left corner of the component, this also means that...
g.fillRect(square.getX(), square.getY(), square.getR(), square.getR());
is painting the rect at x + x x y + y, which will, at the very least, paint the rect in the wrong position, at worst paint it beyond the visible scope of the component.
You're also not providing any sizing hints for the component, so it's default size will be 0x0, which prevent it from been painted.
Since I am using multi thread I assume I have to control each squares.
Well, since I can't really see what's driving the animation, I imagine that when you say "multi thread" you're suggesting that each square has it's own Thread. In this case, that's a bad idea. Let's put aside the thread synchronisation issues for a moment, more threads doesn't equate to more work you can do, at some point, it will begin to degrade the system performance.
In most cases, a single, well managed thread, is all you really need. You also have to understand that Swing is NOT thread safe. This means that you shouldn't update the UI (or states that the UI relies on) from outside the context of the Event Dispatching Thread.
So, while you're thread can update the position of the rects, you need to take care to ensure that they are not been painted while they are been update. Once you've updated the state, you then need to trigger a paint pass (which is trivial in of itself)
So I have to store each square object in the ArrayList.
Yep, good start
However, I am having trouble with painting those squares. I can paint one square but when I try to paint multiple squares, it does not show.
Okay, so instead of using multiple components, use one. Run through your ArrayList within the paintComponent method of this component and paint all the rects to it. This provides a much simpler way to manage things like bounds detection, as you have only one container to worry about.
I'd highly recommend you have a look at:
Java Bouncing Ball which demonstrates many of the concepts discussed here
Concurrency in Swing
How to use Swing Timers
Performing Custom Painting
Painting in AWT and Swing

LibGdx Screen: render and lifecycle

I have a problem, I think its related to Screen render and its lifecycle.
Basically I have two screens (Menu and Game). In GameScreen render method i call World.update and after that my Render. In hide method (of GameScreen) i dispose of the SpriteBatch from Redner class.
So when I change the screen from Game to Menu (within World.update) Java crashes. As far as I can tell, the dispose is making the crash.
So my question is, when i set a new screen in the middle of the render cycle, is that render cycle still going to finish with its old screen? Meaning, am I calling batch.dispose before the rendering was finished, and that is why i get the problem?
Thank you for all the help
public class GameScreen extends AbstractGameScreen {
private static final String TAG = GameScreen.class.getName();
private WorldController worldController;
private WorldRenderer worldRenderer;
private boolean paused;
public GameScreen(Game game) {
super(game);
}
#Override
public void render(float deltaTime) {
// Do not update game world when paused
if (!paused) {
// Update game world by the time that has passed since last render time
worldController.update(deltaTime);
}
// Sets the clear screen color to: Cornflower Blue
Gdx.gl.glClearColor(0x64 / 255.0f, 0x95 / 255.0f, 0xed / 255.0f, 0xff / 255.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// Render game world to screen
worldRenderer.render();
}
#Override
public void resize(int width, int height) {
worldRenderer.resize(width, height);
}
#Override
public void show() { // Similar as create method
worldController = new WorldController(game);
worldRenderer = new WorldRenderer(worldController);
Gdx.input.setCatchBackKey(true);
}
#Override
public void hide() { // Similar to dispose method
worldRenderer.dispose();
Gdx.input.setCatchBackKey(false);
}
#Override
public void pause() {
paused = true;
}
#Override
public void resume() {
super.resume();
// Only called on Android
paused = false;
}
}
That's basically correct. The screen that calls setScreen from within its render method will have hide called on itself and then will continue through the rest of its code in its render method. So you are killing your sprite batch right before trying to draw with it.
So don't call dispose from within your hide method. In fact, it is probably bad practice for a Screen to ever call dispose on itself. You can reserve that for the Game class that owns it. For example, you could do something like this in your game class:
#Override
public void render() {
super.render();
if (getScreen() != gameScreen && gameScreen != null) {
gameScreen.dispose();
gameScreen = null;
}
}
By the way, you should probably put the SpriteBatch in your Game subclass and let all the different screens share it. It's a fairly big object to be allocating and deallocating for no reason.

Control sprite rotation (it's spinning like crazy)

So, I have created a texture, and then a sprite.
On my render() method, I am check for user input. If the user has touched/clicked, then I want my sprite to rotate 90 degrees ONCE.
Right now the rotation works. However, it rotates multiple times per click!
How can I make it rotate only once per touch? I have a feeling that I might have to use delta time, and that occurs because the render method is being called frequently, but I don't know how to fix it... Thanks!
public class MyGame extends ApplicationAdapter {
SpriteBatch batch;
Texture img;
Sprite sprite;
#Override
public void create () {
batch = new SpriteBatch();
img = new Texture("badlogic.jpg");
sprite = new Sprite(img);
}
#Override
public void render () {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
sprite.draw(batch);
batch.end();
if (Gdx.input.isTouched()) {
rotateRight();
}
}
private void rotateRight() {
sprite.setRotation(sprite.getRotation() - 90);
}
}
Right now you are polling input inside of your render method. Polling simply checks the status of the input (is it touched or not) and does not care for any actual "event" occurred.
Instead of this you need to look for input events via event handling as this will give you access to the actual event of the screen being touched or untouched. You can do this by implementing InputProcessor which will give you access to override a bunch of touch event methods so you can execute your rotate method only on the event of the touch.
Example:
public void update()
{
Gdx.input.setInputProcessor(new InputProcessor() {
#Override
public boolean TouchDown(int x, int y, int pointer, int button) {
if (button == Input.Buttons.LEFT) {
rotateRight();
return true;
}
return false
}
});
}
Don't forget to call your update() method from your render method. I put it in a separate function just for readability and not to clog up your rendering code.
You can also have your class implement InputProcessor and override the methods in your class so you do not have to do it inline as I did above.
if (Gdx.input.justTouched() && Gdx.input.isTouched()) {
rotateRight();
}

Image does not immediately get replaced after calling repaint()+revalidate()

I'm making a game/simulator and I'm using the following method to load an Image into my current Java project:
public static Image getImage(String name) {
URL url = ImageLoader.class.getClassLoader().getResource("resources/" + name);
Image img = Toolkit.getDefaultToolkit().createImage(url);
ImageLoader.tracker.addImage(img, 1);
try {
ImageLoader.tracker.waitForID(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return img;
}
This loads the images into my class that is drawing all items including the player (who has 4 different images for each direction he can face: north, south, east, west):
private Image player = ImageLoader.getImage("playerEast.png");
private Image playerEast = ImageLoader.getImage("playerEast.png");
private Image playerWest = ImageLoader.getImage("playerWest.png");
private Image playerSouth = ImageLoader.getImage("playerSouth.png");
private Image playerNorth = ImageLoader.getImage("playerNorth.png");
The class that loads above images paints them in the area like such:
public class TerritoryPanel extends JPanel implements Observer {
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
//Draw player
g.drawImage(player, (x, y, this);
}
I'm trying to update the pics with this method in the same class:
public void rotateEast(){
player = playerEast;
repaint();
revalidate();
}
..but just calling this rotateEast() does not update the picture immediately, it only gets updated afterwards when my regular Observer update() cycle calls repaint(), this happens every second so the missing repaint of rotateEast is visible:
#Override
public void update(Observable o, Object arg) {
if (EventQueue.isDispatchThread()) {
this.repaint();
} else { // Simulation-Thread
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
repaint();
}
});
}
}
Why does the repaint() from rotateEast seem to have no effect? Thank you very much in forward
but just calling this rotateEast() does not update the picture immediately, it only gets updated afterwards when my regular Observer update() cycle calls repaint(),
Don't know if it will make a difference but the normal logic is:
revalidate(); // to invoke the layout manager
repaint(); // to repaint all the components
Even if it doesn't make a difference in this case this order you should always use when dynamically changing the property of a component including adding/remove components from a panel.

Categories