One small step for others, one giant leap for ME!
In my previous blog post, I was talking about how I failed to extend the Rectangle class, and had to step back and learn a few more basics. Now approximately 8 hours later, a lot of pages, videos and other things (completely unrelated to Java), I’ve managed to get it working.
For me this a huge step in the right direction, and when the penny finally hit, it didn’t take more than a few minutes to actually change the code to reflect what I wanted to do.
Again, the original spawnRaindrop() code:
1 2 3 4 5 6 7 8 9 | private void spawnRaindrop() { Rectangle raindrop = new Rectangle(); raindrop.x = MathUtils.random(0, 800-48); raindrop.y = 480; raindrop.width = 48; raindrop.height = 48; raindrops.add(raindrop) lastDropTime = TimeUtils.nanoTime(); } |
private void spawnRaindrop() {
Rectangle raindrop = new Rectangle();
raindrop.x = MathUtils.random(0, 800-48);
raindrop.y = 480;
raindrop.width = 48;
raindrop.height = 48;
raindrops.add(raindrop)
lastDropTime = TimeUtils.nanoTime();
}
And the modified version
1 2 3 4 5 6 7 8 9 10 | private void spawnRaindrop() { Raindrop raindrop = new Raindrop(); raindrop.x = MathUtils.random(0, 800-48); raindrop.y = 480; raindrop.width = 48; raindrop.height = 48; raindrop.speed = MathUtils.random(50,200); raindrops.add(raindrop); lastDropTime = TimeUtils.nanoTime(); } |
private void spawnRaindrop() {
Raindrop raindrop = new Raindrop();
raindrop.x = MathUtils.random(0, 800-48);
raindrop.y = 480;
raindrop.width = 48;
raindrop.height = 48;
raindrop.speed = MathUtils.random(50,200);
raindrops.add(raindrop);
lastDropTime = TimeUtils.nanoTime();
}
The Raindrop class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package com.badlogic.drop; import com.badlogic.gdx.math.Rectangle; public class Raindrop extends Rectangle { float speed; public Raindrop() { super(); } public Raindrop(float x, float y, float width, float height, float speed) { super(x, y, width, height); this.speed = speed; } } |
package com.badlogic.drop;
import com.badlogic.gdx.math.Rectangle;
public class Raindrop extends Rectangle {
float speed;
public Raindrop() {
super();
}
public Raindrop(float x, float y, float width, float height, float speed) {
super(x, y, width, height);
this.speed = speed;
}
}
And the entire Drop.java code with the new shiny Raindrop class in action (I took the liberty of copying directly from the tutorial page since I had mangled the original code somewhat awful)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | package com.badlogic.drop; import java.util.Iterator; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.TimeUtils; public class Drop implements ApplicationListener { Texture dropImage; Texture bucketImage; Sound dropSound; Music rainMusic; SpriteBatch batch; OrthographicCamera camera; Rectangle bucket; Array<Raindrop> raindrops; long lastDropTime; @Override public void create() { // load the images for the droplet and the bucket, 48x48 pixels each dropImage = new Texture(Gdx.files.internal("droplet.png")); bucketImage = new Texture(Gdx.files.internal("bucket.png")); // load the drop sound effect and the rain background "music" dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav")); rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3")); // start the playback of the background music immediately rainMusic.setLooping(true); rainMusic.play(); // create the camera and the SpriteBatch camera = new OrthographicCamera(); camera.setToOrtho(false, 800, 480); batch = new SpriteBatch(); // create a Rectangle to logically represent the bucket bucket = new Rectangle(); bucket.x = 800 / 2 - 48 / 2; // center the bucket horizontally bucket.y = 20; // bottom left corner of the bucket is 20 pixels above the bottom screen edge bucket.width = 48; bucket.height = 48; // create the raindrops array and spawn the first raindrop raindrops = new Array<Raindrop>(); spawnRaindrop(); } private void spawnRaindrop() { Raindrop raindrop = new Raindrop(); raindrop.x = MathUtils.random(0, 800-48); raindrop.y = 480; raindrop.width = 48; raindrop.height = 48; raindrop.speed = MathUtils.random(0,50); raindrops.add(raindrop); lastDropTime = TimeUtils.nanoTime(); } @Override public void render() { // clear the screen with a dark blue color. The // arguments to glClearColor are the red, green // blue and alpha component in the range [0,1] // of the color to be used to clear the screen. Gdx.gl.glClearColor(0, 0, 0.2f, 1); Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); // tell the camera to update its matrices. camera.update(); // tell the SpriteBatch to render in the // coordinate system specified by the camera. batch.setProjectionMatrix(camera.combined); // begin a new batch and draw the bucket and // all drops batch.begin(); batch.draw(bucketImage, bucket.x, bucket.y); for(Rectangle raindrop: raindrops) { batch.draw(dropImage, raindrop.x, raindrop.y); } batch.end(); // process user input if(Gdx.input.isTouched()) { Vector3 touchPos = new Vector3(); touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0); camera.unproject(touchPos); bucket.x = touchPos.x - 48 / 2; } if(Gdx.input.isKeyPressed(Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.getDeltaTime(); if(Gdx.input.isKeyPressed(Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.getDeltaTime(); // make sure the bucket stays within the screen bounds if(bucket.x < 0) bucket.x = 0; if(bucket.x > 800 - 48) bucket.x = 800 - 48; // check if we need to create a new raindrop if(TimeUtils.nanoTime() - lastDropTime > 1000000000) spawnRaindrop(); // move the raindrops, remove any that are beneath the bottom edge of // the screen or that hit the bucket. In the later case we play back // a sound effect as well. Iterator<Raindrop> iter = raindrops.iterator(); while(iter.hasNext()) { Raindrop raindrop = iter.next(); raindrop.y -= raindrop.speed * Gdx.graphics.getDeltaTime(); if(raindrop.y + 48 < 0) iter.remove(); if(raindrop.overlaps(bucket)) { dropSound.play(); iter.remove(); } } } @Override public void dispose() { // dispose of all the native resources dropImage.dispose(); bucketImage.dispose(); dropSound.dispose(); rainMusic.dispose(); batch.dispose(); } @Override public void resize(int width, int height) { } @Override public void pause() { } @Override public void resume() { } } |
package com.badlogic.drop;
import java.util.Iterator;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.TimeUtils;
public class Drop implements ApplicationListener {
Texture dropImage;
Texture bucketImage;
Sound dropSound;
Music rainMusic;
SpriteBatch batch;
OrthographicCamera camera;
Rectangle bucket;
Array<Raindrop> raindrops;
long lastDropTime;
@Override
public void create() {
// load the images for the droplet and the bucket, 48x48 pixels each
dropImage = new Texture(Gdx.files.internal("droplet.png"));
bucketImage = new Texture(Gdx.files.internal("bucket.png"));
// load the drop sound effect and the rain background "music"
dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"));
rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3"));
// start the playback of the background music immediately
rainMusic.setLooping(true);
rainMusic.play();
// create the camera and the SpriteBatch
camera = new OrthographicCamera();
camera.setToOrtho(false, 800, 480);
batch = new SpriteBatch();
// create a Rectangle to logically represent the bucket
bucket = new Rectangle();
bucket.x = 800 / 2 - 48 / 2; // center the bucket horizontally
bucket.y = 20; // bottom left corner of the bucket is 20 pixels above the bottom screen edge
bucket.width = 48;
bucket.height = 48;
// create the raindrops array and spawn the first raindrop
raindrops = new Array<Raindrop>();
spawnRaindrop();
}
private void spawnRaindrop() {
Raindrop raindrop = new Raindrop();
raindrop.x = MathUtils.random(0, 800-48);
raindrop.y = 480;
raindrop.width = 48;
raindrop.height = 48;
raindrop.speed = MathUtils.random(0,50);
raindrops.add(raindrop);
lastDropTime = TimeUtils.nanoTime();
}
@Override
public void render() {
// clear the screen with a dark blue color. The
// arguments to glClearColor are the red, green
// blue and alpha component in the range [0,1]
// of the color to be used to clear the screen.
Gdx.gl.glClearColor(0, 0, 0.2f, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
// tell the camera to update its matrices.
camera.update();
// tell the SpriteBatch to render in the
// coordinate system specified by the camera.
batch.setProjectionMatrix(camera.combined);
// begin a new batch and draw the bucket and
// all drops
batch.begin();
batch.draw(bucketImage, bucket.x, bucket.y);
for(Rectangle raindrop: raindrops) {
batch.draw(dropImage, raindrop.x, raindrop.y);
}
batch.end();
// process user input
if(Gdx.input.isTouched()) {
Vector3 touchPos = new Vector3();
touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
camera.unproject(touchPos);
bucket.x = touchPos.x - 48 / 2;
}
if(Gdx.input.isKeyPressed(Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.getDeltaTime();
if(Gdx.input.isKeyPressed(Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.getDeltaTime();
// make sure the bucket stays within the screen bounds
if(bucket.x < 0) bucket.x = 0;
if(bucket.x > 800 - 48) bucket.x = 800 - 48;
// check if we need to create a new raindrop
if(TimeUtils.nanoTime() - lastDropTime > 1000000000) spawnRaindrop();
// move the raindrops, remove any that are beneath the bottom edge of
// the screen or that hit the bucket. In the later case we play back
// a sound effect as well.
Iterator<Raindrop> iter = raindrops.iterator();
while(iter.hasNext()) {
Raindrop raindrop = iter.next();
raindrop.y -= raindrop.speed * Gdx.graphics.getDeltaTime();
if(raindrop.y + 48 < 0) iter.remove();
if(raindrop.overlaps(bucket)) {
dropSound.play();
iter.remove();
}
}
}
@Override
public void dispose() {
// dispose of all the native resources
dropImage.dispose();
bucketImage.dispose();
dropSound.dispose();
rainMusic.dispose();
batch.dispose();
}
@Override
public void resize(int width, int height) {
}
@Override
public void pause() {
}
@Override
public void resume() {
}
}I hope this is helpful to someone else, if not at least I have some place to find this when I wake up and have forgotten all about it.
Drop 1
Me 1
Your move Java!
On another note, I really need to add a couple of things to this wordpress blog. Color coded syntax and an easier way to add the code would be a start. Also I’ve been asked to add some syndication for the entire site, so I’ll look into that sometime soon as well.
Here’s what I would have done:
public class Raindrop { Rectangle rect; float speed; public Raindrop() { rect = new Rectangle(); } public Raindrop(float x, float y, float width, float height, float speed) { rect = new Rectangle(x, y, width, height); this.speed = speed; } public Rectangle getRectangle(){ return rect; } }You get the idea… You’ll obviously have to adjust a few other things in the code to make it work. Basically this will help you keep a consistent interface externally if the Rectangle class ever changes (which in this case is outside of your control).
Anyway, nothing really wrong with your approach. I just prefer to “encapsulate” things so it doesn’t bite me in the butt later
Also too much inheritance layers will make code really hard to read since you have to jump through a lot of files to figure out what’s really going on..
Keep it flat and performance will be much better as well!
Cheers,
Bach
Great information Bach, thanks a bunch for that. Beer is on me.
It would be nice if you can remove the right panel with archives and rss feed on the “post” view, I don’t know how to explain well but it takes away a lot of space for the important stuff (the text and the code). You can keep them enable in the main view of the blog. Just saying
Yeah arielsan, the site will probably change quite a bit. It was not originally intended for the content that is now on it. RSS was added as a quick extra thing but I plan to put it in the bottom. For now it just means its there.
The syndication on each post will be removed, was actually just about to do that.
Totally agree to what Bach says. You should generally use composition (put things together in a class, e.g. speed and rect above) over inheritance (extend Rectangle).
Keep it up!
To add to this: http://www.javaworld.com/jw-11-1998/jw-11-techniques.html?page=1
Interesting discussion on the topic.
Cheers,
Bach