Monday, 30 March 2015

Using Java & Eclipse to Write a Game - Pt 2

The next part of our tutorial will be to add some dynamism to our 2D graphics.

The first thing we'll need to do is be able to click it with the mouse.

If you recall from the last tutorial we had the window be listened to, by the canvas, how might we listen to things clicking on the canvas?... Well on the canvas itself we add a mouse listener... What's going to listen?... Well the canvas implements the mouse listener interface, so the canvas is going to listen to it's own mouse events.


With a new boolean member variable called "m_LeftButton", which is set false when the program starts, we change the "mousePressed" and "mouseReleased" override functions already in our code:


With these setting and un-setting the new flag, we can use the flag to override the colour being drawn... Prooving that the mouse is working...


So running the application now, whenever we click or hold the left mouse button, the flashing stops.

Functional, but a bit boring... How about every time we click we drop a spot which slowly expands until it disappears?...

Well, this is going to need animation... First, lets seed from the mouse where the drops are going to be placed... The first thing is we need to use a class which can list things and grow or shrink... This is going to be a queue...


A queue works just as they do with us people, a person joins the end of the line and someone else moves off the head of the line... And we alll get there in the end... That's how our queue of seeded expanding dots is going to work... Now in Java, and other languages, there are special types of classes, called "Generics" which are classes which have one kind of functionality, but which you can say what type of other thing they work on... So you might have a class called "Engine", and it comes in two types "Engine<Petrol>" and "Engine<Diesel>".  Engine is always an engine, but we change the fuel inside.

So the same with our queue in the code, the Queue is going to work on Dimensions inside, so we can queue up Dimensions, which are really an X and a Y position linked together... Perfect to represent where the mouse was clicked.


So, in the Mouse pressed handler, we add a call to a new function to add a seed, which takes the X and Y of the mouse at that moment.


The AddSeed function, fleshed out now, it creates the linked list (which is a type of queue - yes this is a Java thing you can't just do "new Queue<Dimension>") and it adds any dimension to it.

The comment on that function gives a clue as to what we need do next... Instead of adding the expanding dots straight off, we've just stored their seed, the point to grow them from... When do we start them growing?

Well, in the Run function loop we could update the scene at intervals... Just like we render at intervals... In fact, I'd like to update just before I draw the graphics, so I could grow things, or spin things, or flash things before I draw the scene.

We'll therefore know that approximately every 33 milliseconds our scene updates... So if something is rotating at 5 rpm, we know to turn it by the right amount!

And the function itself...


But, we have a problem, we're defining these "Dot" functions, but nothing knows what a Dot is...

So, we need a new class, a Dot class...


Creating the class, first of all we see the braces are now correct for our specifications from part 1, unlike the first class we created.


And we have new tabs to let us swap between files.

The first thing our Dot class needs is a constructor, and it has to take two integers for the location of the dot.  So, we can go a head and flesh the Dot out, to get the original class compiling...


The moment we're done, the original code should fall into line:


There was one minor problem with the original code, that was that, unbeknownst to myself, the Dimension class actually contains two "Doubles", so to use them in our "Dot" the width and height calls had to be converted into integers.

This conversion is called "Casting", and in java it is done by putting the type you intended in brackets at the head of the type you have, thus:


So, our code now, each time  we press the mouse puts a point into the queue, and from each queued point it spawns a dot... Now we need to make those Dot's draw and expand over time....


When we come back to the update function, we now need to create from seeds, when we have seeds in the queue.... And also only update the dots when we have dots...


Finally, each dot needs to update over time, so we loop through the list of dots and call their update function with the update time.


Now, we need to draw the updated list of dots, so need a draw function to go through each dot and call it's own draw with the graphics object we pass from the frame...

After we draw the background colour, we now need to draw the dots...


And, because it does my head in, remove the blue/yellow background, we'll just keep it blue...


Each time we click, we planted a seed, and that seed now grows...


And finally, we still see red when we plant the see...


This concludes this session... However, I leave you with a challenge.

Challenge
Eventually the dots will fill the screen, we don't want everything to be pink... After a minute, we want the dots to stop growing, then after one minute one second, we want them to disappear...  How would you code that?

Hint?  Dot Update Function.

--- Dot.java ---

package display.display;

public class Dot
{
private final int c_MinimumSize = 1;
private final int c_PixelsPerSecond = 2;
private int m_OriginX, m_OriginY;
private long m_Age;
public Dot(int p_X, int p_Y)
{
m_Age = 0;
m_OriginX = p_X;
m_OriginY = p_Y;
}
public void Update(long p_Delta)
{
m_Age += p_Delta;
}
public void Draw(java.awt.Graphics2D p_Graphics)
{
// Calculate the radius
int l_RadiusInPixels = c_MinimumSize + ((int)(m_Age/1000)*c_PixelsPerSecond);
int l_half = (int)(l_RadiusInPixels / 2);
int l_startX = m_OriginX - l_half;
int l_startY = m_OriginY - l_half;
p_Graphics.setColor(java.awt.Color.pink);
p_Graphics.fillOval(l_startX, l_startY, l_RadiusInPixels, l_RadiusInPixels);
}
}

---- MainWindow.java ----

package display.display;

import java.awt.Canvas;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

public class MainWindow extends Canvas implements MouseListener, WindowListener 
{
// The minimum Resolution Values
private final int c_MinWidth = 800;
private final int c_MinHeight = 600;
// The master game loop exit flag
private boolean m_GameRunning;
private boolean m_LeftButton = false;
// The seeds of our clicks
private java.util.Queue<java.awt.Dimension> m_Seeds = null;
// The list of expanding dots
private java.util.ArrayList<Dot> m_Dots = null;
// The window frame
private javax.swing.JFrame m_WindowFrame;
// The drawing strategy (refreshing)
private java.awt.image.BufferStrategy m_Strategy;
// Constructor - can be private, we only internal
// members of this class (i.e. main) will be calling
// to create a main window.
private MainWindow ()
{
CreateWindow();
AddPanelAndCanvas();
CreateBufferingStrategy();
}
// Create the buffering strategy
private void CreateBufferingStrategy()
{
this.setIgnoreRepaint(true);  // Ignore the windows paint event
this.createBufferStrategy(2); // Two buffers, one on and one off screen
m_Strategy = this.getBufferStrategy();
}
// Adds a panel to the JFrame, and this panel
// fills the whole area of the window, into that
// we then add our Canvas, which is "this"
private void AddPanelAndCanvas()
{
// The panel in the middle area
javax.swing.JPanel l_Panel = (javax.swing.JPanel)m_WindowFrame.getContentPane();
l_Panel.setPreferredSize(new java.awt.Dimension(c_MinWidth, c_MinHeight));
l_Panel.setLayout(null);
// Set our Canvas properties of size
this.setBounds(0,  0, c_MinWidth, c_MinHeight);
// Add the canvas to the panel
l_Panel.add(this);
}
// Create the Window - we're going to use Swing
private void CreateWindow()
{
m_WindowFrame = new javax.swing.JFrame("Dungeon"); // This is the window title text 
m_WindowFrame.addWindowListener(this); // Adding "this" to the frame
// means our MainWindow code as
// it is running will receive the
// Window events, like close & move
// Direct the mouse events to the
// canvas itself
this.addMouseListener(this);
m_WindowFrame.setSize(c_MinWidth, c_MinHeight);
m_WindowFrame.setVisible(true);
}
@Override
public void windowActivated(WindowEvent arg0) 
{
// TODO Auto-generated method stub

}

@Override
public void windowClosed(WindowEvent arg0) 
{
// TODO Auto-generated method stub

}

@Override
public void windowClosing(WindowEvent arg0) 
{
// TODO Auto-generated method stub
m_GameRunning = false;
}

@Override
public void windowDeactivated(WindowEvent arg0) 
{
// TODO Auto-generated method stub

}

@Override
public void windowDeiconified(WindowEvent arg0) 
{
// TODO Auto-generated method stub

}

@Override
public void windowIconified(WindowEvent arg0) 
{
// TODO Auto-generated method stub

}

@Override
public void windowOpened(WindowEvent arg0) 
{
// TODO Auto-generated method stub

}

@Override
public void mouseClicked(MouseEvent arg0)
{
// TODO Auto-generated method stub

}

@Override
public void mouseEntered(MouseEvent arg0)
{
// TODO Auto-generated method stub

}

@Override
public void mouseExited(MouseEvent arg0)
{
// TODO Auto-generated method stub

}

// Adds a seed to the list of places the
// mouse was clicked, since we last
// updated
private void AddSeed(int p_X, int p_Y)
{
if ( m_Seeds == null )
{
m_Seeds = new java.util.LinkedList<java.awt.Dimension>();
}
m_Seeds.add(new java.awt.Dimension(p_X, p_Y));
}
@Override
public void mousePressed(MouseEvent p_Event)
{
// Left click
if ( p_Event.getButton() == MouseEvent.BUTTON1 )
{
m_LeftButton = true;
AddSeed(p_Event.getX(), p_Event.getY());
}
}

@Override
public void mouseReleased(MouseEvent p_Event)
{
if ( p_Event.getButton() == MouseEvent.BUTTON1 )
{
m_LeftButton = false;
}
}
// Add a Dot
private void AddDot(Dot p_newDot)
{
if ( m_Dots == null )
{
m_Dots = new java.util.ArrayList<Dot>();
}
m_Dots.add(p_newDot);
}
// Update the scene
private void Update(long p_Milliseconds)
{
// There are seeds
if ( m_Seeds != null )
{
// if we have seeds, add them
if ( !m_Seeds.isEmpty() )
{
// Unqueue each seed point and
// create a new expanding Dot
java.awt.Dimension l_Location = m_Seeds.remove();
Dot l_newDot = new Dot((int)l_Location.getWidth(), (int)l_Location.getHeight());
// They maybe called Width and Height, but
// really we're using them as two numbers...
AddDot(l_newDot);
}
}
if ( m_Dots != null && !m_Dots.isEmpty() )
{
// For each dot, update them
for (int i = 0; i < m_Dots.size(); ++i)
{
m_Dots.get(i).Update(p_Milliseconds);
}
}
}
// Draw the dots
private void DrawDots(java.awt.Graphics2D p_Graphics)
{
if ( m_Dots != null && !m_Dots.isEmpty())
{
for (int i = 0; i < m_Dots.size(); ++i)
{
m_Dots.get(i).Draw(p_Graphics);
}
}
}
/// The function we "run" from the main
/// function, to go into a loop, rendering
/// the game
private void Run ()
{
m_GameRunning = true;
System.out.println("Starting Run");
// Game timing/update delta
long l_Time = System.currentTimeMillis();
long l_now;
long l_delta;
while (m_GameRunning)
{
// Get the time now, and work out the difference
l_now = System.currentTimeMillis();
l_delta = l_now - l_Time;
// Only if we are at the time, draw
if ( l_delta > 33 )
{
Update(l_delta);
// Get the graphics into which
// we can draw
java.awt.Graphics2D l_Graphics = (java.awt.Graphics2D)m_Strategy.getDrawGraphics();
// Decide between green on the mouse
if ( m_LeftButton )
{
l_Graphics.setColor(java.awt.Color.red);
}
else
{
l_Graphics.setColor(java.awt.Color.blue);
}
l_Graphics.fillRect(0, 0, this.getWidth(), this.getHeight());
// After the update of the background colour,
// call to draw the dots
DrawDots(l_Graphics);
// Show this frame of graphics
// we've drawn
m_Strategy.show();
// because we did a draw, set time to now
l_Time = l_now;
}
Yield();
}
System.out.println("Exiting Run");
}
// Quick and dirty yield function, to
// release the CPU time whilst in the
// run loop
private void Yield()
{
try
{
Thread.sleep(2);
}
catch (Exception e)
{
System.out.println("Exception [" + e + "]");
}
}
public static void main(String[] args)
{
// TODO Auto-generated method stub
System.out.println ("Hello World");
MainWindow l_Instance = new MainWindow();
l_Instance.Run();
}
}

No comments:

Post a Comment