Previous
Posting Freak
  
Posts: 3,645
Joined: May 2008
Reputation: 81
|
[SOURCE] GameDev#1 - Magnet Thingy :: Previous
Although noone seems to care, I thought I'd share the source of tat one with you and explain you what it does / how it works or something along those lines.
I had actually hoped all those others who promised to do something had done something. Now I sad.
Check out the Event here and my attempt here (including the full source code).
Let me just quote myself here, being lazy:
(02-29-2012 02:16 PM)Previous Wrote: ![[Image: bUums.png]](http://i.imgur.com/bUums.png)
The cyan rectangle is movable, all others are fixed. You can add magnets everywhere and the movable one can move through them. Red magnets attract the cyan one, blue magnets repel it.
Clicking the left or middle button places/removes magnets, right mouse button changes the polarity of the movable one which we'll call the "player".
That's what we want to do. That means, we'll have to create a window and draw some colored squares. We also need to process input and move the movable magnet.
I've made that program in Java for platform independency and because anyone can get the Java development environment for free (just google for the JDK and find it on the Java website).
I'm not going to explain how to set up the JDK, the internet can help you with that. It also depends on your OS anyways. If you have trouble setting the PATH in windows, I could help you with that, I guess.
Now let's take a look at the code, step by step.
Mind you that this is not a real tutorial. I don't even know all the things myself. I might use some wrong terms and my explanations might be incorrect. I'm taking no responsibility!
Code:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
import javax.swing.Timer;
Now what is that?
When you create a Java program, you don't want to code every single thing you need yourself. You don't want to write the code that creates a window and all that. The good thing is that Java comes packed with tons of elements that you can use. However, we need to tell it what we want to use first. We do this by "importing" so-called "packages".
For example, "import javax.swing.JFrame" tells Java that we want to use a JFrame, which is basically a window. We also need a few other things to process mouse clicking and drawing.
I personally have no idea when I need to import what for he things I use, so I usually end up googling "java JFrame import" and google tells me what I need to import to use a JFrame. How I know I need a JFrame to open a window in the first place? Google!
Code:
// main class of app
public class GameDevMagnets {
// main method - app starts at this point
public static void main(String[] ags) {
MainFrame frame = new MainFrame(); // create main window ...
frame.setVisible(true); // ... and show it
}
}
Every Java application needs a main class that includes a main method. The main method is the part of code that is called when the program is started. The program will only do what you tell it to do in this main method.
As you can see, we have out main method in a "class" called "GameDevMagnets". "GameDevMagnets" also has to be the name of the file (plus the ".java" extension) - Java checks the file name to see where it has to search the main method. But Previous, what is a class? Oh well........ That's hard to explain!
Think of a Java program as a machine that is made of different smaller parts. Sometimes, you only need one part to create the full machine, sometimes you need a few. The small parts are called "classes". Or, to b e more precise, a "class" is the blueprint of a part - it holds all the information you need to built it. You can also derive parts from other parts, but we'll talk about that later.
So, the class GameDevMagnets is the core part of out machine. It holds the main method. The head of the main method always looks the same - "public static void main(String[] ags)". Always. Don't think about the "String[] ags" part - we don't need that here.
So all we do here is creating a window and making is visible. "MainFrame" as another class, the blueprint that tells Java how to make out window. We call the window "frame" because I am not witty. But wait - we haven't covered the blueprint for MainFrame yet!
Code:
// class for our main window
class MainFrame extends JFrame implements ActionListener {
// constructor - called to create the window
public MainFrame() {
/*...*/
}
// draw window -> draw magnets
@Override
public void paint(Graphics g) {
/*...*/
}
// event called by timer
public void actionPerformed(ActionEvent e) {
/*...*/
}
private static final double forceScale = 512;
private int[][] magnets; // array of magnets
private int playerX, playerY; // "player" position
private int playerPolarity; // polarity of "player"
private Timer timer; // timer to keep everything going
}
Ignore the dots, we'll fill in those blanks later. Let us deal with an overview of the MainFrame first.
Now this is quite comlicated and advanced.
Notice "extends JFrame"? I've talked about JFrames earlier - a JFrame is a Window. However, the JFrame is very basic and abstract as they are menat to be used for all kinds of windows. That is why we make our own blueprint which "extends" a JFrame. Basically, we make a copy of the blueprint for JFrame and do some modifications to make it suit our needs. This way, we can use everything a JFrame has in our new MainFrame without needing to code it ourself.
But there's more: "implements ActionListener". Now what is that? "ActionListener" is an interface - something similar to a class, but different. An interface is not a blueprint, but a list of specifications. By "implementing" it, we tell Java that our class MainFrame meets the specifications of an "ActionListener". While we can only inherit from one class, we can implement as many interfaces as we want. You'll see soon why we need the ActionListener specification.
We are not making a lot of changes. We mainly replace ("override") the paint method of the JFrame - each JFrame has one of those which is called whenever the window needs to be redrawn. The paint method takes care of painting the window, border, buttons (if any) et cetera. However, we also want to draw our squares and thus we have to write a new paint method.
Furthermore, we add a new method "actionPerformed". Remember the interface? This is the specification of an ActionListener - we need to implement a method "public void actionPerformed(ActionEvent e)". But why? Because we want a moving magnet! That's right. Our moving magnet shall move across the window, but we don't want it to jump right to its destination. Thus, we need a timer. The timer is responsible for calling actionPerformed every few milliseconds. However, the timer needs to know that we do have that certain method - that is why we implement ActionListener. Because of that, the timer knows that he can do it! So, whenever the timer acts, we can move our magnet a few pixels to simulate continuous movement!
The last few lines tell Java that each MainFrame needs a few boxes to put some information in - the only way we can remember things! For example our magnets and the player position and polarity. We also say that our MainFrame needs a timer - you know why.
Code:
// constructor - called to create the window
public MainFrame() {
super("TSR GameDev Magnets");
// I want a window with 256x256px within the borders
setSize(256, 256); // sets size including borders
setResizable(false); // user can't change window size - influences border width
pack(); // can't get border widths without calling this
// set size again, now adding border widths to get an inner size of the desired dimensions:
setSize(getInsets().left + 256 + getInsets().right, getInsets().top + 256 + getInsets().bottom);
// other window options
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); // close app when frame is closed
setLocationRelativeTo(null); // center frame
createBufferStrategy(2); // use doublebuffering to reduce flickering
playerX = playerY = 120; // initial "player" position: center
playerPolarity = 1;
magnets = new int[16][16]; // 16x16 Grid of magnets (16x16 pixels each)
// process mouse clicks received by the window
addMouseListener(new MouseAdapter() { /*...*/ });
timer = new Timer(20, this);
timer.setInitialDelay(0);
timer.start();
}
Constructor? Yep - that is the part of the blueprint that defines what shall be done when an instance (actual object, the thing we built from our blueprint) of our class is created.
So, what do we do when creating our window? Something super! No, really. Calling "super()" says that we want to do everything the class we inherited from - JFrame - does on creation. We also set the window title in this step! Then we set the size of our window and various options (all provided by the parent class, JFrame), the comments should explain everything. getInsets helps us to find out how thick the borders are and everything!
We initialize the player position and create a field of magnets. We have a 256x256 pixel window and each magnet should be 16x16 pixels large so 16x16 magnets fit on the window neatly aligned to a grid. What we do is kinda like creating a table with 16x16 cells and each cell holds the state of a magnet - we use ints here, that means we want to use whole numbers (1, 2, 3, -6, ...) (we actually only need 0 -inactive-, 1 -polarity 1- and 2 -polarity 2-).
To capture mouse clicks, we need a "MouseListener" - an instance of another class ("MouseAdapter") which can detect and react to mouse clicks. I'll talk about that next.
Finally, we set the options for our timer - we want it to call actionPerformed every 20 ms and start imediately.
Code:
// process mouse clicks received by the window
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
int mouseX = (e.getX() - getInsets().left) / 16; // 16x16 grid
int mouseY = (e.getY() - getInsets().top) / 16;
if (e.getButton() == MouseEvent.BUTTON1) { // left mouse button clicked
// change magnet polarity to 1
if (magnets[mouseX][mouseY] == 1) {
magnets[mouseX][mouseY] = 0; // remove magnet
} else {
magnets[mouseX][mouseY] = 1; // polarity 1
}
} else if (e.getButton() == MouseEvent.BUTTON2) { // middle mouse button clicked
// change magnet polarity to 2
if (magnets[mouseX][mouseY] == 2) {
magnets[mouseX][mouseY] = 0; // remove magnet
} else {
magnets[mouseX][mouseY] = 2; // polarity 2
}
} else if (e.getButton() == MouseEvent.BUTTON3) { // right mouse button clicked
// change "player" polarity
if (playerPolarity == 1) {
playerPolarity = 2; // polarity 1
} else {
playerPolarity = 1; // polarity 2
}
}
}
});
Here, we create a new object from the blueprint "MouseAdapter" and add a minor modification without creating an inheriting blueprint first. That looks weird, doesn't it? I've got to say I didn't know such a thing was possible... However, it gives us an advantage: We can access all of MainFrames data from within the MouseAdapter! That would not be possible if we had made a separate blueprint for a modified MouseAdapter.
We tell the MouseAdapter what to do on a mouse click. Refer to the code to see what is done. The MouseEvent e holds all intel we need: The mouse position and which button was clicked.
Code:
// draw window -> draw magnets
@Override
public void paint(Graphics g) {
super.paint(g); //do what a normal window does
//draw magnets
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
if (magnets[i][j] == 1) {
g.setColor(Color.RED);
g.fillRoundRect(getInsets().left + (i * 16), getInsets().top + (j * 16), 16, 16, 0, 0);
} else if (magnets[i][j] == 2) {
g.setColor(Color.BLUE);
g.fillRoundRect(getInsets().left + (i * 16), getInsets().top + (j * 16), 16, 16, 0, 0);
}
}
}
//draw "player"
if (playerPolarity == 1) {
g.setColor(Color.MAGENTA);
} else {
g.setColor(Color.CYAN);
}
g.fillRoundRect(getInsets().left + playerX, getInsets().top + playerY, 16, 16, 0, 0);
}
I've told you earlier that we replace the JFrame's paint method with our own. However, we still want to draw whatever the JFrame would have drawn! Thus, we call the parent's paint method by referring to super (remember how we used super in the constructor)?
The paint method is called by the program when neccessary without us doing anything. Java gives it a reference to a Graphics object - don't ask me! All I know is that this Graphics object called "g" kinda represents the window area and whatever we draw on g will be drawn on the window!
We draw all magnets from our magnet table thingy by going through each column and row using two for-loops. A for-loop basically has a counter variable (i.e. i, j) which will be increased by one very step (i++), starting by 0, ending by 15 (depending on what you define)!
Then we draw the player.
Drawing Squares is easy, we just tell that g to fill a rectangly starting by an upper-left position with a certain width and height and without rounded borders. The color has to be set earlier. RED, BLUE etc are predefinedby Java. I bet you can define your own colors, but don't ask me how! You can also draw lines and ellipses and stuff, google will help you (I haven't looked into that).
Now all that is left is to move the movable player magnet!
Code:
// event called by timer
public void actionPerformed(ActionEvent e) {
// calculate traction
double tractionX, tractionY;
tractionX = tractionY = 0;
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
if (magnets[i][j] != 0) { // if magnet is active
// distance between "player" and magnet:
int dx, dy; // absolute distance
int sx, sy; // sign of distance (left/top -> negative)
if (playerX >= (i * 16)) {
dx = playerX - (i * 16);
sx = 1;
} else {
dx = (i * 16) - playerX;
sx = -1;
}
if (playerY >= (j * 16)) {
dy = playerY - (j * 16);
sy = 1;
} else {
dy = (j * 16) - playerY;
sy = -1;
}
// magnet polarity effect: toggle sign
if (magnets[i][j] != playerPolarity) {
sx = -sx;
sy = -sy;
}
// effect on traction depends on distance - far magnets are weak
tractionX += sx * (forceScale - dx);
tractionY += sy * (forceScale - dy);
}
}
}
// move "player" accodring to traction
playerX += Math.round(tractionX / forceScale);
playerY += Math.round(tractionY / forceScale);
//don't let "player" leave the window
if (playerX > 240) { playerX = 240; }
else if (playerX < 0) { playerX = 0; }
if (playerY > 240) { playerY = 240; }
else if (playerY < 0) { playerY = 0; }
repaint(); // redraw the window to show changes
}
Ohokey. To be honest, the timer is not the only one who could call this method. However, the timer is the only one who will do it since we did not create any other object with that magic ability. Otherwise, we would have to find out what action actually occured by analyzing the given "ActionEvent e".
Now we want to calculate in which direction the player has to move. Thus, we determine the traction which the magnets put on the player. I have no idea how I do that!
Let me try to explain. So, for every magnet, we calculate the distance to the player (for x and y seperately). Each magnet effect the overall traction. However, distant magnets are weak - I achieve that by subtracting the actual distancy from a "maximal distance" (forceScale). I called it "forceScale" because the size of that value effects how much weaker distand magnets get. Not that I was lazy and the weakening is linear.
The "sign" might be a little bit confusing. It determines whether the effect on the overall traction is positive or negative - whether the player needs to move up or down, left or right. Thus, the opposing polarity of a magnet toggles the sign to change that direction.
That's it, basically! If you have questions, go ahead and ask. I'm quite sure my comments weren't really beginner-friendly. But again, this is not really meant to be a tutorial but rather to gibt a brief overview on this thing because why knot?
Code:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
import javax.swing.Timer;
// class for our main window
class MainFrame extends JFrame implements ActionListener {
// constructor - called to create the window
public MainFrame() {
super("TSR GameDev Magnets");
// I want a window with 256x256px within the borders
setSize(256, 256); // sets size including borders
setResizable(false); // user can't change window size - influences border width
pack(); // can't get border widths without calling this
// set size again, now adding border widths to get an inner size of the desired dimensions:
setSize(getInsets().left + 256 + getInsets().right, getInsets().top + 256 + getInsets().bottom);
// other window options
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); // close app when frame is closed
setLocationRelativeTo(null); // center frame
createBufferStrategy(2); // use doublebuffering to reduce flickering
playerX = playerY = 120; // initial "player" position: center
playerPolarity = 1;
magnets = new int[16][16]; // 16x16 Grid of magnets (16x16 pixels each)
// process mouse clicks received by the window
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
int mouseX = (e.getX() - getInsets().left) / 16; // 16x16 grid
int mouseY = (e.getY() - getInsets().top) / 16;
if (e.getButton() == MouseEvent.BUTTON1) { // left mouse button clicked
// change magnet polarity to 1
if (magnets[mouseX][mouseY] == 1) {
magnets[mouseX][mouseY] = 0; // remove magnet
} else {
magnets[mouseX][mouseY] = 1; // polarity 1
}
} else if (e.getButton() == MouseEvent.BUTTON2) { // middle mouse button clicked
// change magnet polarity to 2
if (magnets[mouseX][mouseY] == 2) {
magnets[mouseX][mouseY] = 0; // remove magnet
} else {
magnets[mouseX][mouseY] = 2; // polarity 2
}
} else if (e.getButton() == MouseEvent.BUTTON3) { // right mouse button clicked
// change "player" polarity
if (playerPolarity == 1) {
playerPolarity = 2; // polarity 1
} else {
playerPolarity = 1; // polarity 2
}
}
}
});
timer = new Timer(20, this);
timer.setInitialDelay(0);
timer.start();
}
// draw window -> draw magnets
@Override
public void paint(Graphics g) {
super.paint(g); //do what a normal window does
//draw magnets
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
if (magnets[i][j] == 1) {
g.setColor(Color.RED);
g.fillRoundRect(getInsets().left + (i * 16), getInsets().top + (j * 16), 16, 16, 0, 0);
} else if (magnets[i][j] == 2) {
g.setColor(Color.BLUE);
g.fillRoundRect(getInsets().left + (i * 16), getInsets().top + (j * 16), 16, 16, 0, 0);
}
}
}
//draw "player"
if (playerPolarity == 1) {
g.setColor(Color.MAGENTA);
} else {
g.setColor(Color.CYAN);
}
g.fillRoundRect(getInsets().left + playerX, getInsets().top + playerY, 16, 16, 0, 0);
}
// event called by timer
public void actionPerformed(ActionEvent e) {
// calculate traction
double tractionX, tractionY;
tractionX = tractionY = 0;
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
if (magnets[i][j] != 0) { // if magnet is active
// distance between "player" and magnet:
int dx, dy; // absolute distance
int sx, sy; // sign of distance (left/top -> negative)
if (playerX >= (i * 16)) {
dx = playerX - (i * 16);
sx = 1;
} else {
dx = (i * 16) - playerX;
sx = -1;
}
if (playerY >= (j * 16)) {
dy = playerY - (j * 16);
sy = 1;
} else {
dy = (j * 16) - playerY;
sy = -1;
}
// magnet polarity effect: toggle sign
if (magnets[i][j] != playerPolarity) {
sx = -sx;
sy = -sy;
}
// effect on traction depends on distance - far magnets are weak
tractionX += sx * (forceScale - dx);
tractionY += sy * (forceScale - dy);
}
}
}
// move "player" accodring to traction
playerX += Math.round(tractionX / forceScale);
playerY += Math.round(tractionY / forceScale);
//don't let "player" leave the window
if (playerX > 240) { playerX = 240; }
else if (playerX < 0) { playerX = 0; }
if (playerY > 240) { playerY = 240; }
else if (playerY < 0) { playerY = 0; }
repaint(); // redraw the window to show changes
}
private static final double forceScale = 512;
private int[][] magnets; // array of magnets
private int playerX, playerY; // "player" position
private int playerPolarity; // polarity of "player"
private Timer timer; // timer to keep everything going
}
// main class of app
public class GameDevMagnets {
// main method - app starts at this point
public static void main(String[] ags) {
MainFrame frame = new MainFrame(); // create main window ...
frame.setVisible(true); // ... and show it
}
}
Do with it whatever you want! Eat it if you like!
Avatar courtesy of Mighty Jetters
(This post was last modified: 03-02-2012 02:02 PM by Previous.)
|
|