Skip to content

Instantly share code, notes, and snippets.

@mcgivrer
Last active January 22, 2020 14:11
Show Gist options
  • Save mcgivrer/1936b8f53791b16bfa0ce7e96029e880 to your computer and use it in GitHub Desktop.
Save mcgivrer/1936b8f53791b16bfa0ce7e96029e880 to your computer and use it in GitHub Desktop.
A Java DemoGame to play with

DemoGame

This is a mono java class game demonstration to play with ABC of game development.

see gist project for details

Build with :

$> mvn clean compile

Play with :

$> mvn exec:java

McG.

/**
* Project -=-=( DemoGame )=-=-
*
* This project aims at demobstrate basics for simple game.
*
* @author Frédéric Delorme <frederic.delorme@gmail.com>
* @since 2019
*/
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import javax.swing.JFrame;
/**
* A <code>DemoGame</code> class to demonstrate some basics to create a simple
* java game.
*
* This will implement all needed operations from capturing user
* <code>input()</code>, <code>update(float)</code> of game compoenets to
* <code>render(Garphics2D)</code> the objects in field of view.
*
* @author Frédéric Delorme
* @since 2019
*/
public class DemoGame implements KeyListener {
private static long goIndex = 0;
public class Configuration {
private ResourceBundle bundle = ResourceBundle.getBundle("game");
public String title = bundle.getString("game.title");
public int width = Integer.parseInt(bundle.getString("game.screen.width"));
public int height = Integer.parseInt(bundle.getString("game.screen.height"));
public int debug = Integer.parseInt(bundle.getString("game.debug"));
public int nbObjects = Integer.parseInt(bundle.getString("game.scene.objects.max"));
public int nbLights = Integer.parseInt(bundle.getString("game.scene.lights.max"));
public float scale = Float.parseFloat(bundle.getString("game.screen.scale"));
}
/**
* GameObject type of renndering definition.
*
* The <code>GameObjectType</code> define the nature of the object to be
* display.
*
* @author Frédéric Delorme <frederic.delorme@gmail.com>
* @since 2019
*/
public enum GameObjectType {
RECTANGLE, CIRCLE, IMAGE
}
/**
* Type of Light to be rendered.
*/
public enum LightType {
CONE, SPHERE, AMBIANT;
}
/**
* Internal Class to manipulate simple Vector2D.
*
* @author Frédéric Delorme
*
*/
public class Vector2D {
public double x, y;
public Vector2D() {
x = 0.0;
y = 0.0;
}
/**
* @param x
* @param y
*/
public Vector2D(double x, double y) {
super();
this.x = x;
this.y = y;
}
/**
* Compute distance between this vector and the vector <code>v</code>.
*
* @param v
* the vector to compute distance with.
* @return
*/
public double distance(Vector2D v) {
double v0 = x - v.x;
double v1 = y - v.y;
return Math.sqrt(v0 * v0 + v1 * v1);
}
/**
* Normalization of this vector.
*/
public Vector2D normalize() {
// sets length to 1
//
double length = Math.sqrt(x * x + y * y);
if (length != 0.0) {
double s = 1.0f / (float) length;
x = x * s;
y = y * s;
}
return new Vector2D(x, y);
}
/**
* Add the <code>v</code> vector to this vector.
*
* @param v
* the vector to add to this vector.
* @return this.
*/
public Vector2D add(Vector2D v) {
x += v.x;
y += v.y;
return this;
}
/**
* Multiply vector by a factor.
*
* @param factor
* the factor to multiply the vector by.
* @return this.
*/
public Vector2D multiply(float factor) {
x *= factor;
y *= factor;
return this;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("(").append(x).append(",").append(y).append(")");
return builder.toString();
}
/**
* Substract the Vector2D v to this current and return result.
*
* @param v
* vector2D to be substracted.
* @return Vector2D resulting of substraction.
*/
public Vector2D substract(Vector2D v) {
return new Vector2D(x - v.x, y - v.y);
}
/**
* Dot product for current instance {@link Vector2D} and the <code>v1</code>
* vector.
*
* @param v1
* @return
*/
public double dot(Vector2D v1) {
return this.x * v1.x + this.y * v1.y;
}
}
/**
* The <code>GameObject</code> class defines any object managed and displayed by
* the game.
*
* @author Frédéric Delorme <frederic.delorme@gmail.com>
* @since 2019
*/
public class GameObject {
private final int id = (int) goIndex++;
public String name = "noname_" + id;
public BufferedImage imageBuffer;
public Vector2D position = new Vector2D();
public Vector2D size = new Vector2D();
public Vector2D speed = new Vector2D();
public Vector2D acceleration = new Vector2D();
public int layer = 0;
public boolean fixed = false;
public int priority = 0;
public Rectangle bbox;
public GameObjectType type;
public Color foregroundColor = Color.RED;
public Color shadowColor = new Color(0.0f, 0.0f, 0.0f, 0.7f);
public Color backgroundColor = Color.BLACK;
public Map<String, Object> attributes = new HashMap<>();
public GameObject(final String name, final double x, final double y, final double width, final double height) {
this.name = name;
this.position = new Vector2D(x,y);
this.size = new Vector2D(width, height);
this.type = GameObjectType.RECTANGLE;
}
public void update(final DemoGame dg, final long elapsed) {
position.x += speed.x * elapsed;
position.y += speed.y * elapsed;
}
public void setPosition(final double x, final double y) {
this.position.x = x;
this.position.y = y;
}
public void setSpeed(final double dx, final double dy) {
this.speed.x = dx;
this.speed.y = dy;
}
public void setAcceleration(final double ax, final double ay) {
this.acceleration.x = ax;
this.acceleration.y = ay;
}
}
/**
* The Camera Object will follow a target with a tweenfactor to delay camera
* move regarding object to be tracked.
*
* @author Frédéric Delorme <frederic.delorme@gmail.com>
* @since 2019
*/
class Camera extends GameObject {
private GameObject target;
private final double tweenFactor;
public Camera(final String name, final GameObject target, final double tweenFactor) {
super(name, 0, 0, 0, 0);
this.tweenFactor = tweenFactor;
this.target = target;
}
public void setTarget(final GameObject target) {
this.target = target;
}
@Override
public void update(final DemoGame dg, final long elapsed) {
position.x += position.x + (position.x - target.position.x) * elapsed * tweenFactor;
position.y += position.y + (position.y - target.position.y) * elapsed * tweenFactor;
}
public void preRender(final DemoGame dg, final Graphics2D g) {
g.translate(-position.x, -position.y);
}
public void postRender(final DemoGame dg, final Graphics2D g) {
g.translate(position.x, position.y);
}
}
/**
* Light is the LIght object for the Game.
*/
public class Light extends GameObject {
LightType type;
GameObject target;
float intensity;
float[] dist = { 0f, 1f };
Color[] colors = { new Color(0.0f, 0.0f, 0.0f, 0.0f), new Color(0.0f, 0.0f, 0.0f, 1.0f) };
public Light(final String name, final LightType type, final double x, final double y, final double radius,
final float intensity, final GameObject target) {
super(name, x, y, radius, radius);
this.intensity = intensity;
this.type = type;
this.target = target;
}
}
/**
* The rendering class to draw anything on screen.
*/
public class Renderer {
private DemoGame dg;
private JFrame jf;
BufferedImage screenBuffer;
BufferedImage lightBuffer;
BufferStrategy bs;
private final List<Camera> cameras = new ArrayList<>();
private Camera camera;
public Renderer(DemoGame dg) {
super();
this.dg = dg;
}
public void initialize() {
jf = new JFrame("DemoGame");
final Dimension dim = new Dimension((int) (screenWidth * screenScale), (int) (screenHeight * screenScale));
jf.setSize(dim);
jf.setPreferredSize(dim);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.addKeyListener(dg);
// create drawing buffers
screenBuffer = new BufferedImage(screenWidth, screenHeight, BufferedImage.TYPE_INT_ARGB);
lightBuffer = new BufferedImage(screenWidth, screenHeight, BufferedImage.TYPE_INT_ARGB);
// DisplayMode dm =
// GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode();
jf.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent componentEvent) {
float ratio = (float) dim.width / (float) dim.height;
float w = componentEvent.getComponent().getWidth();
Dimension dim = new Dimension((int) w, (int) (w / ratio));
jf.setSize(dim);
jf.setMaximumSize(dim);
jf.setMinimumSize(dim);
jf.setPreferredSize(dim);
jf.pack();
}
});
jf.setIgnoreRepaint(true);
jf.enableInputMethods(true);
jf.setLocationByPlatform(true);
jf.setLocationRelativeTo(null);
jf.setVisible(true);
bs = jf.getBufferStrategy();
if (bs == null) {
jf.createBufferStrategy(4);
bs = jf.getBufferStrategy();
}
jf.pack();
jf.setVisible(true);
}
/**
* constrain go position to the Screen size.
*
* @param go
*/
public void constrainToViewport(GameObject go) {
if (go.position.x + go.size.x > screenBuffer.getWidth()) {
go.position.x = screenBuffer.getWidth() - go.size.y;
go.speed.x = -go.speed.x;
}
if (go.position.y + go.size.y > screenBuffer.getHeight()) {
go.position.y = screenBuffer.getHeight() - go.size.y;
go.speed.y = -go.speed.y;
}
if (go.position.x < 0) {
go.position.x = 0;
go.speed.x = -go.speed.x;
}
if (go.position.y < 0) {
go.position.y = 0;
go.speed.y = -go.speed.y;
}
}
private void renderLight(DemoGame dg, Graphics2D g, Light l) {
switch (l.type) {
case SPHERE:
l.colors = new Color[] { l.foregroundColor, new Color(0.0f, 0.0f, 0.0f, 0.0f) };
final RadialGradientPaint rgp = new RadialGradientPaint(new Point((int) l.position.x, (int) l.position.y), (int) l.size.x,
l.dist, l.colors);
g.setPaint(rgp);
g.fill(new Ellipse2D.Double(l.position.x - l.size.x, l.position.y - l.size.x, l.size.x * 2, l.size.x * 2));
break;
case CONE:
// TODO implement the CONE light type
break;
case AMBIANT:
// TODO implement the AMBIANT light type
break;
}
}
private void renderGameObject(DemoGame dg, Graphics2D g, GameObject o) {
final double xRatio = (dg.screenWidth / o.position.x) * dg.light.position.x;
final double yRatio = (dg.screenHeight / o.position.y) * dg.light.position.x;
switch (o.type) {
case RECTANGLE:
g.setColor(o.shadowColor);
g.fillRect((int) (o.position.x + xRatio), (int) (o.position.y + yRatio), (int) o.size.x, (int) o.size.y);
g.setColor(o.foregroundColor);
g.fillRect((int) o.position.x, (int) o.position.y, (int) o.size.x, (int) o.size.y);
g.setColor(Color.BLACK);
g.drawRect((int) o.position.x, (int) o.position.y, (int) o.size.x, (int) o.size.y);
break;
case CIRCLE:
g.setColor(o.shadowColor);
g.fillOval((int) (o.position.x + xRatio), (int) (o.position.y + yRatio), (int) o.size.x, (int) o.size.y);
g.setColor(o.foregroundColor);
g.fillOval((int) o.position.x, (int) o.position.y, (int) o.size.x, (int) o.size.y);
g.setColor(Color.BLACK);
g.drawOval((int) o.position.x, (int) o.position.y, (int) o.size.x, (int) o.size.y);
break;
case IMAGE:
g.drawImage(o.imageBuffer, (int) o.position.x, (int) o.position.y, null);
break;
}
}
public void render(float realFPS) {
final Graphics2D g = (Graphics2D) screenBuffer.createGraphics();
final Graphics2D lg = (Graphics2D) lightBuffer.createGraphics();
// set Antialias for image and text rendering.
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
// RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
// clear image
g.setColor(Color.BLUE);
g.fillRect(0, 0, screenWidth, screenHeight);
// draw all objects
for (final GameObject go : objects) {
if (camera != null && !go.fixed) {
camera.preRender(dg, g);
}
renderGameObject(dg, g, go);
if (camera != null && !go.fixed) {
camera.postRender(dg, g);
}
}
if (debug > 1) {
g.setColor(new Color(0.6f, 0.6f, 0.6f, 0.6f));
g.fillRect(0, screenHeight - 20, screenWidth, 20);
g.setColor(Color.ORANGE);
g.setFont(g.getFont().deriveFont(7));
g.drawString(String.format("fps:%03d", realFps), 10, screenHeight - 8);
}
drawLights(g, false);
drawHUD(g);
g.dispose();
// render image to real screen (applying scale factor)
renderToScreen();
}
private void drawLights(Graphics2D gl, boolean clear) {
gl.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// gl.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
// RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
if (clear) {
// Clear Light buffer
gl.setColor(new Color(0.0f, 0.0f, 0.0f, 0.6f));
gl.clearRect(0, 0, lightBuffer.getWidth(), lightBuffer.getHeight());
}
// draw all Lights
// gl.setComposite(AlphaComposite.DstIn);
for (final Light light : lights) {
// move camera according to settings
if (camera != null && !light.fixed) {
camera.preRender(dg, gl);
}
renderLight(dg, gl, light);
// move camera back according to settings
if (camera != null && !light.fixed) {
camera.postRender(dg, gl);
}
}
}
private void drawHUD(final Graphics2D g) {
Color shadowColor = new Color(0.3f, 0.3f, 0.3f, 0.8f);
// Draw Score
final int score = (Integer) player.attributes.get("score");
drawOutLinedText(g, String.format("%05d", score), 4, 30, Color.WHITE, Color.BLACK, shadowColor, 4);
// Draw Life
final String lifeOn = new String("♥".getBytes(), StandardCharsets.UTF_8);
final String lifeOff = new String("♡".getBytes(), StandardCharsets.UTF_8);
final int life = (Integer) player.attributes.get("life");
final int maxLife = (Integer) player.attributes.get("life-max");
lifeStr = rep(life, lifeOn) + rep(maxLife - life, lifeOff);
drawOutLinedText(g, String.format("%s", lifeStr), screenWidth - 90, 30, Color.RED, Color.BLACK, shadowColor,
4);
}
private String rep(int a, final String k) {
if (a <= 0) {
return "";
} else {
a--;
return k + rep(a, k);
}
}
/**
* Switch from active camera to <code>name</code> camera.
*
* @param name name of the camera to be active.
* @return
*/
public Camera switchCamera(final String name) {
for (final Camera cam : renderer.cameras) {
if (cam.name.equals(name)) {
return cam;
}
}
return null;
}
private void renderToScreen() {
if (jf != null) {
final Graphics2D g = (Graphics2D) jf.getGraphics();
if (g != null) {
g.drawImage(screenBuffer, 0, 0, jf.getWidth(), jf.getHeight(), 0, 0, screenWidth, screenHeight,
Color.BLACK, null);
/*
* if (lights.size() > 0) { g.drawImage(lightBuffer, 0, 0, jf.getWidth(),
* jf.getHeight(), 0, 0, screenWidth, screenHeight, null); }
*/
g.dispose();
}
}
}
}
private boolean exitRequest = false;
private final long fps = 60;
private long realFps = 0;
private final String[] argc;
Renderer renderer;
private final boolean[] keys = new boolean[65536];
private final boolean[] previousKeys = new boolean[65536];
private int screenWidth = 320, screenHeight = 200;
private float screenScale = 2.0f;
private final List<Light> lights = new ArrayList<>();
private Light light;
private final List<GameObject> objects = new ArrayList<>();
private GameObject player;
private String lifeStr = "";
private int debug = 0;
public DemoGame(final String[] argc) {
super();
this.argc = argc;
}
public void initialize() {
Configuration config = new Configuration();
analyzeArgc(config);
renderer = new Renderer(this);
renderer.initialize();
load(config);
}
public void load(Configuration config) {
player = new GameObject("player", screenWidth / 2, screenHeight / 2, 16, 16);
player.foregroundColor = Color.BLUE;
player.priority = 10;
player.attributes.put("life", new Integer(6));
player.attributes.put("life-max", new Integer(6));
player.attributes.put("score", new Integer(0));
addObject(player);
for (int i = 0; i < config.nbObjects; i++) {
final double x = (Math.random() * screenWidth);
final double y = (Math.random() * screenHeight);
final float dx = (float) (Math.random() * 0.2f) + 0.05f;
final float dy = (float) (Math.random() * 0.2f) + 0.05f;
final GameObject go = new GameObject("object_" + i, x, y, 8, 8);
go.setSpeed(dx, dy);
go.priority = 100;
go.type = GameObjectType.CIRCLE;
go.foregroundColor = Color.ORANGE;
go.foregroundColor = new Color((float) Math.random(), (float) Math.random(), (float) Math.random());
addObject(go);
}
for (int i = 0; i < config.nbLights; i++) {
final Light light = new Light("light_" + i, LightType.SPHERE, (double) (Math.random() * screenWidth),
(double) (Math.random() * screenHeight), (double) (Math.random() * 300.0 + 50.0),
(float) Math.random(), null);
light.foregroundColor = new Color((float) Math.random(), (float) Math.random(), (float) Math.random(),
0.8f);
final float dx = (float) (Math.random() * 0.02f) + 0.005f;
final float dy = (float) (Math.random() * 0.02f) + 0.005f;
light.setSpeed(dx, dy);
addObject(light);
}
final Camera cam = new Camera("cam", player, 0.02f);
addObject(cam);
}
public void loop() {
long startTime = System.currentTimeMillis();
long previousTime = startTime;
long cumulatedTime = 0;
long framesCounter = 0;
while (!exitRequest) {
startTime = System.currentTimeMillis();
final long elapsed = startTime - previousTime;
input();
update(elapsed);
cumulatedTime += elapsed;
framesCounter += 1;
if (cumulatedTime > 1000) {
cumulatedTime = 0;
realFps = framesCounter;
framesCounter = 0;
}
render(realFps);
final long wait = (1000 / fps) - (System.currentTimeMillis() - startTime);
if (wait > 0) {
try {
Thread.sleep(wait);
} catch (final InterruptedException e) {
System.out.println(String.format("Unable to wait %d wait ms", wait));
}
}
previousTime = startTime;
}
}
public void input() {
if (keys[KeyEvent.VK_ESCAPE]) {
exitRequest = true;
}
player.setSpeed(0.0f, 0.0f);
if (keys[KeyEvent.VK_UP]) {
player.speed.y = -0.2f;
}
if (keys[KeyEvent.VK_DOWN]) {
player.speed.y = 0.2f;
}
if (keys[KeyEvent.VK_LEFT]) {
player.speed.x = -0.2f;
}
if (keys[KeyEvent.VK_RIGHT]) {
player.speed.x = 0.2f;
}
if (keys[KeyEvent.VK_SPACE]) {
}
}
public void update(final long elapsed) {
for (final GameObject go : objects) {
go.update(this, elapsed);
renderer.constrainToViewport(go);
}
}
public void render(final long realFps) {
renderer.render(realFps);
}
public void run() {
System.out.println("Run game");
initialize();
loop();
System.out.println("Game stopped");
System.exit(0);
}
public void keyTyped(final KeyEvent e) {
}
public void keyPressed(final KeyEvent e) {
this.previousKeys[e.getKeyCode()] = this.keys[e.getKeyCode()];
this.keys[e.getKeyCode()] = true;
}
public void keyReleased(final KeyEvent e) {
this.previousKeys[e.getKeyCode()] = this.keys[e.getKeyCode()];
this.keys[e.getKeyCode()] = false;
}
public void drawOutLinedText(final Graphics2D g, final String text, final int x, final int y, final Color textColor,
final Color borderColor, final Color shadowColor, final int shadowOffset) {
final Font f = g.getFont();
g.setFont(f.deriveFont(16.0f));
g.setColor(shadowColor);
g.drawString(text, x + shadowOffset, y + shadowOffset);
g.setColor(borderColor);
g.drawString(text, x - 1, y);
g.drawString(text, x, y - 1);
g.drawString(text, x + 1, y);
g.drawString(text, x, y + 1);
g.setColor(textColor);
g.drawString(text, x, y);
g.setFont(f);
}
/**
* Analyze the argc array and set corresponding values.
*/
private void analyzeArgc(Configuration config) {
screenWidth = config.width;
screenHeight = config.height;
screenScale = config.scale;
debug = config.debug;
for (final String arg : argc) {
System.out.println(String.format("arg: %s", arg));
if (arg.contains("=")) {
final String[] parts = arg.toLowerCase().split("=");
switch (parts[0]) {
// Debug parameter value
case "d":
case "debug":
this.debug = Integer.parseInt(parts[1]);
break;
// Height parameter value
case "h":
case "height":
this.screenHeight = Integer.parseInt(parts[1]);
break;
// Width parameter value
case "w":
case "width":
this.screenWidth = Integer.parseInt(parts[1]);
break;
// Scale parameter value
case "s":
case "scale":
this.screenScale = Float.parseFloat(parts[1]);
break;
// Unknown parameter !
default:
System.out.println(String.format("Unknown arguments '%s'", arg));
break;
}
}
}
}
/**
* Add a GameObject to the objects to be managed by the game.
*
* If the Gameobject is a <code>Camera</code> instance, it will be added to the
* <code>cameras</code> List, and if not camera active, set to default one.
*
* @param go the GameObject to be added to the game engine.
*/
public void addObject(final GameObject go) {
if (go instanceof Camera) {
if (renderer.camera == null) {
renderer.camera = (Camera) go;
}
renderer.cameras.add((Camera) go);
} else if (go instanceof Light) {
if (light == null) {
light = (Light) go;
}
lights.add((Light) go);
} else {
if (!objects.contains(go)) {
objects.add(go);
Collections.sort(objects, new Comparator<GameObject>() {
public int compare(final GameObject g1, final GameObject g2) {
return (g1.layer < g2.layer ? 1 : (g1.priority < g2.priority ? 1 : -1));
}
});
}
}
}
/**
* The main entry point for this awesome demo !
*/
public static void main(final String[] argc) {
final DemoGame dg = new DemoGame(argc);
dg.run();
}
}
game.title=test
game.screen.width=400
game.screen.height=260
game.screen.scale=2.0
game.debug=0
game.scene.objects.max=50
game.scene.lights.max=5
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>core</groupId>
<artifactId>oneclassgame</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>OneClassGame</name>
<description>A one class project (but with subclasses)</description>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.mainClass>DemoGame</project.mainClass>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>${project.mainClass}</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment