4 Commits

Author SHA1 Message Date
J. Neugebauer
a11e8607fc Merge branch 'main' into games 2024-12-02 18:59:23 +01:00
a94f785be4 First pass map mehanics and TiledMap 2022-12-21 16:16:42 +01:00
0c4182adb2 Merge branch 'main' into games 2022-12-19 16:02:07 +01:00
97f8eaeb49 Base classes for InputContexts 2022-07-07 08:10:53 +02:00
18 changed files with 300 additions and 688 deletions

View File

@@ -191,22 +191,6 @@ public class Vector extends Point2D.Double {
return new Vector(x, y);
}
public int getIntX() {
return (int)this.x;
}
public int getRoundedX() {
return (int)Math.round(this.x);
}
public int getIntY() {
return (int)this.y;
}
public int getRoundedY() {
return (int)Math.round(this.y);
}
/**
* Setzt die Komponenten dieses Vektors neu.
*

View File

@@ -0,0 +1,25 @@
package schule.ngb.zm.game;
import schule.ngb.zm.Constants;
import schule.ngb.zm.shapes.Bounds;
public class Camera {
double x, y, z = .0;
int width, height;
double zoom = 1.0;
public Camera() {
x = Constants.canvasWidth / 2.0;
y = Constants.canvasHeight / 2.0;
width = Constants.canvasWidth;
height = Constants.canvasHeight;
}
public Bounds getBounds() {
return new Bounds(x - width/2.0, y - height/2.0, width, height);
}
}

View File

@@ -0,0 +1,5 @@
package schule.ngb.zm.game;
public class InputContext {
}

View File

@@ -0,0 +1,71 @@
package schule.ngb.zm.game;
import javax.swing.*;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.concurrent.ScheduledThreadPoolExecutor;
public class InputType {
static final ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(1);
public class ActionInput extends InputType {
private int inputCode;
private String action;
private ActionListener al;
private double delay = 0.0;
private long lastTriggered = 0L;
private boolean repeats = false;
private boolean onPress = true;
private boolean pressed = false;
public void press( int inputCode, long time ) {
pressed = true;
if( onPress ) {
trigger(inputCode, time);
}
}
public void release( int inputCode, long time ) {
pressed = false;
if( !onPress ) {
trigger(inputCode, time);
}
}
public void trigger( int inputCode, long time ) {
if( shouldTrigger(inputCode, time) ) {
//al.actionTriggered(action);
lastTriggered = time;
}
}
public boolean shouldTrigger( int inputCode, long time ) {
return this.inputCode == inputCode && time - lastTriggered > delay;
}
public int hashCode() {
return inputCode;
}
}
public class TriggerInput extends InputType {
}
public class RangeInput extends InputType {
}
}

View File

@@ -0,0 +1,14 @@
package schule.ngb.zm.game;
import schule.ngb.zm.Layer;
import java.awt.Graphics2D;
public abstract class Map {
public abstract void render( Graphics2D g, Camera view );
}

View File

@@ -0,0 +1,30 @@
package schule.ngb.zm.game;
import schule.ngb.zm.util.Cache;
import schule.ngb.zm.util.io.ImageLoader;
import java.awt.Image;
import java.awt.image.BufferedImage;
public class Sprite {
public Image[] sprites;
public Sprite( String spriteFile, int cols, int rows ) {
BufferedImage sprite = ImageLoader.loadImage(spriteFile, false);
int w = sprite.getWidth() / cols;
int h = sprite.getHeight() / rows;
sprites = new Image[cols*rows];
for( int i = 0; i < cols; i++ ) {
for( int j = 0; j < rows; j++ ) {
sprites[j*cols + i] = ImageLoader.copyImage(
sprite.getSubimage(i*w, j*h, w, h)
);
}
}
}
}

View File

@@ -0,0 +1,28 @@
package schule.ngb.zm.game;
import schule.ngb.zm.Color;
import schule.ngb.zm.Constants;
import java.awt.Graphics2D;
public class Tile {
int c, r;
Color clr;
public Tile( int c, int r ) {
this.c = c;
this.r = r;
clr = Color.getHSBColor(((20-c)*(20-c)+(15-r)*(15-r))/625.0, .7, .8);
}
public void render( Graphics2D g, int x, int y, int size ) {
g.setColor(clr.getJavaColor());
g.fillRect(x, y, size, size);
g.setColor(Color.DARKGRAY.getJavaColor());
g.drawRect(x, y, size, size);
g.drawString("(" + c + "," + r + ")", x+3, y+13);
}
}

View File

@@ -0,0 +1,65 @@
package schule.ngb.zm.game;
import schule.ngb.zm.Constants;
import schule.ngb.zm.shapes.Bounds;
import schule.ngb.zm.util.Log;
import java.awt.Graphics2D;
public class TiledMap extends Map {
private int columns, rows, tileSize;
private Tile[][] tiles;
public TiledMap( int columns, int rows, int tileSize ) {
this.columns = columns;
this.rows = rows;
this.tileSize = tileSize;
tiles = new Tile[columns][rows];
}
public void setTile( int column, int row, Tile tile ) {
tiles[column][row] = tile;
}
public void render( Graphics2D g, Camera view ) {
Bounds viewBounds = view.getBounds();
double zoomFactor = Constants.map(view.zoom, -100, 100, -2.0, 2.0);
zoomFactor = Constants.limit(zoomFactor, -2.0, 2.0);
double zoomTileSize = zoomFactor * tileSize;
int minCol, maxCol, minRow, maxRow, dX, dY;
if( viewBounds.getMinX() < 0 ) {
minCol = 0;
dX = (int)(-1 * viewBounds.getMinX());
} else {
minCol = (int)(viewBounds.getMinX() / tileSize);
dX = -1 * (int)(viewBounds.getMinX()) % tileSize;
}
maxCol = Math.min((int)(viewBounds.getMaxX() / tileSize), columns-1);
if( viewBounds.getMinY() < 0 ) {
minRow = 0;
dY = (int)(-1 * viewBounds.getMinY());
} else {
minRow = (int)(viewBounds.getMinY() / tileSize);
dY = -1 * (int)(viewBounds.getMinY()) % tileSize;
}
maxRow = Math.min((int)(viewBounds.getMaxY() / tileSize), rows-1);
for( int r = minRow; r <= maxRow; r++ ) {
for( int c = minCol; c <= maxCol; c++ ) {
if( tiles[c][r] != null ) {
tiles[c][r].render(g, dX + (c-minCol) * tileSize, dY + (r-minRow) * tileSize, tileSize);
}
}
}
}
private static final Log LOG = Log.getLogger(TiledMap.class);
}

View File

@@ -1,77 +0,0 @@
package schule.ngb.zm.particles;
import schule.ngb.zm.Color;
import schule.ngb.zm.Vector;
import java.awt.Graphics2D;
public class BasicParticle extends Particle {
protected Color color, startColor, finalColor;
public BasicParticle() {
super();
}
public BasicParticle( Color startColor ) {
this(startColor, null);
}
public BasicParticle( Color startColor, Color finalColor ) {
super();
this.color = startColor;
this.startColor = startColor;
this.finalColor = finalColor;
}
public Color getColor() {
return color;
}
public void setColor( Color pColor ) {
this.color = pColor;
}
public Color getStartColor() {
return startColor;
}
public void setStartColor( Color pStartColor ) {
this.startColor = pStartColor;
}
public Color getFinalColor() {
return finalColor;
}
public void setFinalColor( Color pFinalColor ) {
this.finalColor = pFinalColor;
}
@Override
public void spawn( int pLifetime, Vector pPosition, Vector pVelocity ) {
super.spawn(pLifetime, pPosition, pVelocity);
this.color = this.startColor;
}
@Override
public void update( double delta ) {
super.update(delta);
if( isActive() && startColor != null && finalColor != null ) {
double t = 1.0 - lifetime / maxLifetime;
this.color = Color.interpolate(startColor, finalColor, t);
}
}
@Override
public void draw( Graphics2D graphics ) {
if( isActive() && this.color != null ) {
graphics.setColor(this.color.getJavaColor());
graphics.fillOval(position.getIntX() - 3, position.getIntY() - 3, 6, 6);
}
}
}

View File

@@ -1,43 +0,0 @@
package schule.ngb.zm.particles;
import schule.ngb.zm.Color;
public class BasicParticleFactory implements ParticleFactory {
private final Color startColor;
private final Color finalColor;
private boolean fadeOut = true;
public BasicParticleFactory() {
this(null, null);
}
public BasicParticleFactory( Color startColor ) {
this(startColor, null);
}
public BasicParticleFactory( Color startColor, Color finalColor ) {
this.startColor = startColor;
this.finalColor = finalColor;
}
public void setFadeOut( boolean pFadeOut ) {
this.fadeOut = pFadeOut;
}
@Override
public Particle createParticle() {
Color finalClr = finalColor;
if( fadeOut ) {
if( finalColor != null ) {
finalClr = new Color(finalColor, 0);
} else if( startColor != null ) {
finalClr = new Color(startColor, 0);
}
}
return new BasicParticle(startColor, finalClr);
}
}

View File

@@ -1,49 +0,0 @@
package schule.ngb.zm.particles;
import schule.ngb.zm.util.Log;
import java.util.Arrays;
import java.util.function.Supplier;
public class GenericParticleFactory<T extends Particle> implements ParticleFactory {
private final Class<T> type;
private final Supplier<T> supplier;
public GenericParticleFactory( Class<T> type, Object... params ) {
this.type = type;
// Create paramTypes array once
Class<?>[] paramTypes = new Class<?>[params.length];
for( int i = 0; i < params.length; i++ ) {
paramTypes[i] = params[i].getClass();
}
this.supplier = () -> {
T p = null;
try {
p = GenericParticleFactory.this.type.getDeclaredConstructor(paramTypes).newInstance(params);
} catch( Exception ex ) {
LOG.error( ex,
"Unable to create new Particle of type %s",
GenericParticleFactory.this.type.getCanonicalName()
);
}
return p;
};
}
public GenericParticleFactory( Supplier<T> supplier ) {
this.supplier = supplier;
this.type = (Class<T>)supplier.get().getClass();
}
@Override
public Particle createParticle() {
return this.supplier.get();
}
private static final Log LOG = Log.getLogger(GenericParticleFactory.class);
}

View File

@@ -1,60 +0,0 @@
package schule.ngb.zm.particles;
import schule.ngb.zm.Drawable;
import schule.ngb.zm.Updatable;
import schule.ngb.zm.Vector;
public abstract class Particle extends PhysicsObject implements Updatable, Drawable {
protected double maxLifetime = 0, lifetime = 0;
public Particle() {
super();
}
public void spawn( int pLifetime, Vector pPosition, Vector pVelocity ) {
this.maxLifetime = pLifetime;
this.lifetime = pLifetime;
this.position = pPosition.copy();
this.velocity = pVelocity.copy();
this.acceleration = new Vector();
}
@Override
public boolean isActive() {
return lifetime > 0;
}
@Override
public boolean isVisible() {
return isActive();
}
public double getLifetime() {
return lifetime;
}
public void setLifetime( double pLifetime ) {
this.lifetime = pLifetime;
}
public double getMaxLifetime() {
return maxLifetime;
}
public void setMaxLifetime( double pMaxLifetime ) {
this.maxLifetime = pMaxLifetime;
}
@Override
public void update( double delta ) {
super.update(delta);
// lifetime -= delta;
lifetime -= 1;
// TODO: (ngb) calculate delta based on lifetime?
}
}

View File

@@ -1,174 +0,0 @@
package schule.ngb.zm.particles;
import schule.ngb.zm.Drawable;
import schule.ngb.zm.Updatable;
import schule.ngb.zm.Vector;
import java.awt.Graphics2D;
public class ParticleEmitter implements Updatable, Drawable {
protected ParticleFactory particleFactory;
private int particlesPerFrame;
private int particleLifetime = 180;
private Particle[] particles;
private boolean active = false;
private Particle nextParticle;
public Vector position;
public Vector direction = new Vector();
public double strength = 100.0;
public int angle = 0;
public double randomness = 0.0;
// private Vortex vortex = null;
public ParticleEmitter( double pX, double pY, int pParticleLifetime, int pParticlesPerFrame, ParticleFactory pFactory ) {
this.position = new Vector(pX, pY);
this.particlesPerFrame = pParticlesPerFrame;
this.particleLifetime = pParticleLifetime;
this.particleFactory = pFactory;
// Create particle pool
this.particles = new Particle[particlesPerFrame * pParticleLifetime];
this.direction = Vector.random(8, 16).normalize();
// vortex = new Vortex(position.copy().add(-10, -10), -.2, 8);
}
@Override
public boolean isActive() {
return active;
}
@Override
public boolean isVisible() {
return active;
}
public void start() {
this.direction.normalize();
// Partikel initialisieren
for( int i = 0; i < particles.length; i++ ) {
particles[i] = particleFactory.createParticle();
}
active = true;
}
public void stop() {
for( int i = 0; i < particles.length; i++ ) {
particles[i].setLifetime(0);
particles[i] = null;
}
active = false;
}
private Particle getNextParticle() {
// TODO: improve by caching next particle
for( Particle p : particles ) {
if( p != null && !p.isActive() ) {
return p;
}
}
return null;
}
public void emitParticle() {
int ppf = particlesPerFrame;
Particle nextParticle = getNextParticle();
while( ppf > 0 && nextParticle != null ) {
int lifetime = (int) random(particleLifetime);
double rotation = (angle / 2.0) - (int) (Math.random() * angle);
Vector velocity = direction.copy().scale(strength).rotate(rotation);
velocity.scale(random());
nextParticle.spawn(lifetime, this.position, velocity);
nextParticle = getNextParticle();
ppf -= 1;
}
}
@Override
public void update( double delta ) {
emitParticle();
boolean _active = false;
for( Particle particle : particles ) {
if( particle != null ) {
if( particle.isActive() ) {
// if( vortex != null ) {
// vortex.attract(particle);
// }
particle.update(delta);
_active = true;
}
}
}
this.active = _active;
}
private double random() {
return 1.0 - (Math.random() * randomness);
}
private double random( double pZahl ) {
return pZahl * random();
}
@Override
public void draw( Graphics2D graphics ) {
java.awt.Color current = graphics.getColor();
for( Particle particle : particles ) {
if( particle != null && particle.isVisible() ) {
particle.draw(graphics);
}
}
// if( vortex != null ) {
// graphics.setColor(java.awt.Color.BLACK);
// double vscale = (4 * vortex.scale);
// graphics.fillOval((int) (vortex.position.x - vscale * .5), (int) (vortex.position.y - vscale * .5), (int) vscale, (int) vscale);
// }
graphics.setColor(current);
}
class Vortex {
Vector position;
double speed = 1.0, scale = 1.0;
public Vortex( Vector pPosition, double pSpeed, double pScale ) {
this.position = pPosition.copy();
this.scale = pScale;
this.speed = pSpeed;
}
public void attract( Particle pPartikel ) {
Vector diff = Vector.sub(pPartikel.position, this.position);
double dx = -diff.y * this.speed;
double dy = diff.x * this.speed;
double f = 1.0 / (1.0 + (dx * dx + dy * dy) / scale);
pPartikel.position.x += (diff.x - pPartikel.velocity.x) * f;
pPartikel.position.y += (diff.y - pPartikel.velocity.y) * f;
}
}
}

View File

@@ -1,7 +0,0 @@
package schule.ngb.zm.particles;
public interface ParticleFactory {
Particle createParticle();
}

View File

@@ -1,69 +0,0 @@
package schule.ngb.zm.particles;
import schule.ngb.zm.Updatable;
import schule.ngb.zm.Vector;
public abstract class PhysicsObject implements Updatable {
protected Vector position, velocity, acceleration;
protected double mass = 1.0;
public PhysicsObject() {
position = new Vector();
velocity = new Vector();
acceleration = new Vector();
}
public PhysicsObject( Vector pPosition ) {
position = pPosition.copy();
velocity = new Vector();
acceleration = new Vector();
}
public Vector getAcceleration() {
return acceleration;
}
public void setAcceleration( Vector pAcceleration ) {
this.acceleration = pAcceleration;
}
public double getMass() {
return mass;
}
public void setMass( double pMass ) {
this.mass = pMass;
}
public Vector getPosition() {
return position;
}
public void setPosition( Vector pPosition ) {
this.position = pPosition;
}
public Vector getVelocity() {
return velocity;
}
public void setVelocity( Vector pVelocity ) {
this.velocity = pVelocity;
}
public void accelerate( Vector pAcceleration ) {
acceleration.add(Vector.div(pAcceleration, mass));
}
@Override
public void update( double delta ) {
velocity.add(acceleration);
position.add(Vector.scale(velocity, delta));
acceleration.scale(0.0);
}
}

View File

@@ -1,35 +0,0 @@
package schule.ngb.zm.particles;
import schule.ngb.zm.Color;
import java.awt.Graphics2D;
public class StarParticle extends BasicParticle {
public StarParticle() {
super();
this.startColor = Color.PURE_GREEN;
}
public StarParticle( Color startColor ) {
this(startColor, null);
}
public StarParticle( Color startColor, Color finalColor ) {
super();
this.color = startColor;
this.startColor = startColor;
this.finalColor = finalColor;
}
@Override
public void draw( Graphics2D graphics ) {
if( isActive() && this.color != null ) {
graphics.setColor(this.color.getJavaColor());
graphics.drawLine((int) position.x - 3, (int) position.y - 3, (int) position.x + 3, (int) position.y + 3);
graphics.drawLine((int) position.x + 3, (int) position.y - 3, (int) position.x - 3, (int) position.y + 3);
}
}
}

View File

@@ -0,0 +1,62 @@
package schule.ngb.zm.game;
import schule.ngb.zm.Zeichenmaschine;
import java.awt.event.MouseWheelEvent;
class TiledMapTest extends Zeichenmaschine {
public static final int COLS = 40;
public static final int ROWS = 30;
public static final int TILE_SIZE = 40;
private TiledMap map;
private Camera cam;
public TiledMapTest() {
super(20*TILE_SIZE, 15*TILE_SIZE, "TiledMapTest");
}
@Override
public void setup() {
map = new TiledMap(COLS, ROWS, TILE_SIZE);
for( int i = 0; i < COLS; i++ ) {
for( int j = 0; j < ROWS; j++ ) {
map.setTile(i, j, new Tile(i, j));
}
}
cam = new Camera();
}
@Override
public void update( double delta ) {
}
@Override
public void draw() {
drawing.clear();
map.render(getDrawingLayer().getGraphics(), cam);
}
@Override
public void mouseDragged() {
cam.x -= (mouseX-pmouseX);
cam.y -= (mouseY-pmouseY);
}
@Override
public void mouseWheelMoved() {
MouseWheelEvent mwe = (MouseWheelEvent) mouseEvent;
cam.zoom += mwe.getWheelRotation();
}
public static void main( String[] args ) {
new TiledMapTest();
}
}

View File

@@ -1,158 +0,0 @@
package schule.ngb.zm.particles;
import schule.ngb.zm.*;
import schule.ngb.zm.layers.DrawableLayer;
import schule.ngb.zm.layers.DrawingLayer;
import java.awt.Graphics2D;
public class ParticleExample extends Testmaschine {
public static void main( String[] args ) {
new ParticleExample();
}
public ParticleExample() {
super();
}
ParticleEmitter pe1, pe2, pe3;
Rocket r;
public void setup() {
getLayer(DrawingLayer.class).hide();
background.setColor(0);
drawing.noStroke();
drawing.setFillColor(WHITE);
for( int i = 0; i < 1000; i++ ) {
drawing.point(random(0, canvasWidth), random(0, canvasHeight));
}
pe1 = new ParticleEmitter(
100, 100, 50, 5,
// new BasicParticleFactory(PINK, BLUE)
new GenericParticleFactory<Particle>(() -> {
return new Particle() {
@Override
public void draw( Graphics2D graphics ) {
graphics.setColor(Color.MAGENTA.getJavaColor());
graphics.rotate(Constants.radians(45), (int) position.x, (int) position.y);
graphics.drawRect((int) position.x - 3, (int) position.y - 3, 6, 6);
graphics.rotate(Constants.radians(-45), (int) position.x, (int) position.y);
}
};
})
);
pe1.randomness = .2;
pe1.angle = 45;
pe1.strength = 200;
pe2 = new ParticleEmitter(
300, 300, 50, 10,
new GenericParticleFactory(() -> new StarParticle(RED, new Color(BLUE, 55)))
//new GenericParticleFactory(StarParticle.class, RED, new Color(BLUE, 55))
);
pe2.direction = NORTH.asVector().scale(100);
pe2.randomness = .8;
pe2.angle = 90;
pe2.strength = 200;
pe3 = new ParticleEmitter(
100, 400, 20, 8,
new BasicParticleFactory(YELLOW, RED)
);
pe3.direction = SOUTH.asVector();
pe3.randomness = .33;
pe3.angle = 30;
DrawableLayer drawables = new DrawableLayer();
addLayer(drawables);
drawables.add(pe1, pe2, pe3);
pe1.start();
pe2.start();
pe3.start();
r = new Rocket(200, 400);
drawables.add(r);
r.start();
}
@Override
public void update( double delta ) {
pe1.update(delta);
pe2.update(delta);
pe3.update(delta);
pe3.position.add(NORTH.asVector().scale(Constants.map(runtime, 0, 1000, 0, 10) * delta));
if( r.isActive() ) {
r.update(delta);
}
}
class Rocket extends PhysicsObject implements Drawable {
ParticleEmitter trail;
private boolean starting = false;
private double acc = 4;
public Rocket( double x, double y ) {
super(new Vector(x, y));
trail = new ParticleEmitter(
x, y, 30, 6,
new BasicParticleFactory(YELLOW, RED)
);
trail.direction = SOUTH.asVector();
trail.randomness = .33;
trail.angle = 30;
trail.position = this.position;
}
public void start() {
starting = true;
trail.start();
}
@Override
public boolean isActive() {
return starting;
}
@Override
public boolean isVisible() {
return true;
}
@Override
public void update( double delta ) {
super.update(delta);
if( this.acceleration.lengthSq() < acc * acc ) {
this.accelerate(NORTHWEST.asVector().scale(acc));
}
trail.update(delta);
}
@Override
public void draw( Graphics2D graphics ) {
graphics.rotate(-Constants.radians(velocity.angle() + 180), position.getIntX(), position.getIntY());
trail.draw(graphics);
graphics.setColor(WHITE.getJavaColor());
graphics.fillRect(position.getIntX() - 6, position.getIntY() - 32, 12, 32);
graphics.fillPolygon(
new int[]{position.getIntX() - 6, position.getIntX(), position.getIntX() + 6},
new int[]{position.getIntY() - 32, position.getIntY() - 40, position.getIntY() - 32},
3
);
graphics.rotate(Constants.radians(velocity.angle() + 180), position.getIntX(), position.getIntY());
}
}
}