Developing Greenfoot games with Maven/IntelliJ/Eclipse
Table of Contents
Greenfoot is a game framework for programming beginners featuring an integrated development environment. Unfortunately it’s a pain to use when you are used to write code in a real IDE such as IntelliJ. This is partially because of the strange object creation workflow which involves using the context menu a lot and doing clicks for stuff that is faster when typed.
In preparation of a workshop I am going to co-lead with a few other guys from my company, I tried to find out if and how one can build Greenfoot projects without having the Greenfoot IDE installed, preferably using Maven.
And what can I say, you can. And it’s awesome! I’ve created a sample project that you can use as a reference.
Creating a new Project
So you want to make your own game. Great.
Placeholders
A few placeholders will be used in this tutorial, you’ll have to replace them if you plan to do copy & paste. I will use the following format:
placeholder
- explaination - sample value used in the reference project linked above
Here you go:
$package-group$
- The groupId of your package identifier -io.lerk
$package-artifact$
- The artifactId of your package identifier -greenfoot-maven-demo
$project-name$
- The name of your project -greenfoot-maven-demo
$main-class$
- The full identifier of the class containing themain
method (including full package name)io.lerk.demo.DemoApp
$world-class$
- The full identifier of the main World class -io.lerk.demo.worlds.MyWorld
$world-class-name$
- Only the name of the main world class -MyWorld
$world-class-package$
- Only the package of the class -io.lerk.demo.worlds
$world-class-path$
- The location of the world class relative tosrc/main/java
-io/lerk/demo/worlds
$world-image$
- The file name of the image to use as world background -crumpled-paper.jpg
$actor-class$
- The full identifier of the main World class -io.lerk.demo.actors.MyActor
$actor-class-name$
- Only the name of the class -MyActor
$actor-class-package$
- Only the package of the class -io.lerk.demo.actors
$actor-class-path$
- The location of the actor class relative tosrc/main/java
-io/lerk/demo/actors
$actor-image$
- The file name of the image to use as world background -some-dude.jpg
REMEMBER TO REPLACE THOSE PLACEHOLDERS!
Preparation
Let’s start by creating the project directory and going into it:
mkdir ~/$project-name$ && cd ~/$project-name$
Setting up Maven
In the project directory, create a new file called pom.xml
and put the following stuff into it:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>$package-group$</groupId>
<artifactId>$package-artifact$</artifactId>
<version>1.0-SNAPSHOT</version>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<groupId>com.github.lfuelling</groupId>
<artifactId>cleanfoot</artifactId>
<version>3.6.1</version>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<excludes>
<!-- exclude>**/log4j.properties</exclude -->
</excludes>
<archive>
<manifest>
<mainClass>$main-class$</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>$main-class$</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Repository and Dependencies
The repository is needed, because the dependencies are not available anywhere else for Maven.
On macOS, you can find the greenfoot.jar
at /Applications/Greenfoot.app/Resources/Java/extensions/greenfoot.jar
and you’ll have to download the greenfoot source code and build the bluej project with ant jar-core
to get the bluej.jar
.
As you can see, it’s a lot more convenient to just use the Maven dependencies.
EDIT: I have shut down my Nexus and as such also lost the JAR files.
An alternative is the cleanfoot
project I created, which is a fork of greenfoot where I patched out the analytics.
The pom.xml
above has been updated to use cleanfoot
.
Plugins
The plugins are needed because we need a valid manifest (generated by maven-jar-plugin
) and all the dependencies are required to be present in the jar file (handled by maven-assembly-plugin
).
Adding required Greenfoot stuff
Some files and directories are required by Greenfoot. Let’s take care of those first.
Directory structure
At first, we’ll need to create a few directories:
mkdir -p src/main/java src/main/resources/images src/main/resources/sounds
standalone.properties
Now create the file src/main/resources/standalone.properties
and put the following stuff into it:
project.name=$project-name$
main.class=$world-class$
scenario.lock=false
scenario.hideControls=false
The scenario.lock
property disables (hides) the speed seek bar and the act once button.
The scenario.hideControls
property hides the whole control bar.
This will also autostart the game.
project.greenfoot
The next required file is src/main/resources/project.greenfoot
.
Let’s use the following content for it:
# Class definitions
class.$world-class$.image=$world-image$
# Editor Settings
editor.fx.0.height=0
editor.fx.0.width=0
editor.fx.0.x=0
editor.fx.0.y=0
# Main Window Settings
mainWindow.height=800
mainWindow.width=850
mainWindow.x=40
mainWindow.y=40
# Misc Project Settings
package.numDependencies=0
package.numTargets=1
project.charset=UTF-8
readme.height=58
readme.name=@README
readme.width=47
readme.x=10
readme.y=10
# Target definitions (unknown use)
target1.height=50
target1.name=$world-class$
target1.showInterface=false
target1.type=ClassTarget
target1.typeParameters=
target1.width=80
target1.x=0
target1.y=0
# Misc/Unknown
version=3.0.0
world.lastInstantiated=$world-class$
README.txt
I was yet unable to find out where this is used. That’s why I’ll just put one word into it for now. Create it using:
echo "stub" > src/main/resources/README.txt
Adding classes
Let’s write some code.
World
At first, we create the world class we just defined in the project.greenfoot
file.
Remember to have the correct package structure. You will need to create the folders for it first:
mkdir -p src/main/java/$world-class-path$
Now you can create the file in src/main/java/$world-class-path$/$world-class-name$.java
.
The content should look like this:
package $world-class-package$;
import greenfoot.World;
/**
* World class.
*/
public class $world-class-name$ extends World {
/**
* Constructor for objects of class $world-class$.
*/
public MyWorld() {
super(850, 600, 1);
prepare();
}
/**
* Prepare the world for the start of the program.
* That is: create the initial objects and add them to the world.
* Z-Index is based on order of addition (last to be added is on top)
*/
private void prepare() {}
}
If you have not done this by now, you should add the world image defined as class.$world-class$.image
in project.greenfoot
into the src/main/resources/images
folder.
The file type can be .png
, .gif
, and .jp(e)g
and the name should not contain any spaces.
The first actor
Now let’s create a random actor. At first, create the missing package:
mkdir src/main/java/$actor-class-path$
Now we create the file src/main/java/$actor-class-path$/$actor-class-name$.java
with the following content:
package $actor-class-package$;
import greenfoot.Actor;
/**
* Write a description of class $actor-class-name$ here.
*
* @author (your name)
* @version (a version number or a date)
*/
public class $actor-class-name$ extends Actor {
/**
* Act - do whatever the $actor-class-name$ wants to do. This method is called whenever
* the 'Act' or 'Run' button gets pressed in the environment.
*/
@Override public void act() {
// Add your action code here.
}
}
As you might have realized by now, we also need to create an entry for it in the project.greenfoot
file.
It’s not important where you place it in the file, but I recommend putting similar keys together for better readability.
Put the following code into src/main/resources/project.greenfoot
:
class.$actor-class$.image=$actor-image$
Final steps
Adding and using sound files
I must confess, I didn’t read the Greenfoot docs too well.
Therefore I don’t know which audio files are supported, but I guess it just supports everything that Java does.
I’m using .wav
files in this example.
First you’ll need a sound file. Download one, record it, I don’t really care. I suggest that you follow the same naming principles as with the images (no spaces in file name).
Put the file in src/main/resources/sounds
and also create a new text file: src/main/resources/soundindex.list
.
Just put in the filename of your sounds, each in it’s own line.
No paths or anything, just the filename.
After that, you can play your sound by using the following method in your code:
Greenfoot.playSound("mysound.wav");
The main class
You will also have to have a class that starts your project. This is the place where you can do some initialization before the game loads like configuring the MenuBar on macOS, setting up logging, or something else.
Let’s create the file src/main/java/$main-class-path$/$main-class-name$.java
and put the following content into it:
package $main-class-package$;
import greenfoot.export.GreenfootScenarioMain;
/**
* @author (User Name)
*/
public class $main-class-name$ extends GreenfootScenarioApplication {
public static void main(String[] args) {
// load properties
initProperties();
// start Greenfoot
launch(args);
}
/**
* Initializes project properties.
*/
private static void initProperties() {
try (InputStream is = Soultraps.class.getClassLoader().getResourceAsStream("standalone.properties")) {
properties.load(is);
Config.initializeStandalone(new StandalonePropStringManager(properties));
} catch (IOException e) {
log.error("Error loading properties!", e);
}
}
}
This will start the Greenfoot launcher which in turn loads your game.
If you need to do any initialization, do it before the launch(args)
call.
Adding the actor into the world
If you start the game now, you’ll notice that the actor is not yet visible. That’s because we need to add it to the world first.
Add the following code into the prepare()
method in your world class:
$actor-class-name$ actor = new $actor-class-name$();
addObject(actor, 50, 50);
Also, you will maybe need to add the following below the package statement:
import $actor-class$;
Some IDEs do this automatically!
Let the actor move
To let the actor move, put the following code into the act()
method of the actor class:
if (Greenfoot.isKeyDown("up")) {
setLocation(getX(), getY() - 1);
} else if (Greenfoot.isKeyDown("down")) {
setLocation(getX(), getY() + 1);
}
if (Greenfoot.isKeyDown("left")) {
setLocation(getX() - 1, getY());
} else if (Greenfoot.isKeyDown("right")) {
setLocation(getX() + 1, getY());
}
Building and Running
Let’s try to build the project:
mvn clean install -DskipTests=true
If this command succeeds, you can try to run the game:
java -jar target/$project-name$-1.0-SNAPSHOT-jar-with-dependencies.jar
The game window should open now and if you click play, you should be able to move the actor using the arrow keys.
If this doesn’t work or you got an error while maven was building please check all of the files for leftover placeholders. Alternatively you might want to read the error message (if there is any) and solve your problem this way.
You can also contact me using social media or email. If I have the time and knowledge to support you, I will.
Update 1
have created a Yeoman generator that basically does what I describe in this post. You just have to answer a few questions and all the boilerplate code will be generated for you.
You can find installation and usage instructions either on GitHub or on NPM.
Update 2
I’ve found a way to build and publish the latest greenfoot jars automatically. The Greenfoot source used to build the JARs is now located on my GitLab and I use GitLab CI to publish any new tag to the Nexus.
The Nexus URL and Greenfoot version also was updated in the examples above.
Update 3
I’ve found the scenario.hideControls
property and added it to the samples above.
Additionally, since Greenfoot 3.6 requires Java >= 11, I’ve forked Greenfoot and BlueJ entirely, converted it from Ant to Maven and added my public font constructor fix. The project is on GitHub and it’s called cleanfoot.
To use it, you just have to replace the Greenfoot and BlueJ dependencies above with this single one:
<dependency>
<groupId>com.github.lfuelling</groupId>
<artifactId>cleanfoot</artifactId>
<version>3.6.1</version>
</dependency>
Update 4
Due to my Nexus crashing I had to transfer the cleanfoot
project to JitPack.
The post has been updated but the Yeoman generator will probably not work without fixing the generated code!