Add beginnings of game code.

This commit is contained in:
Matt Low 2020-01-23 23:59:42 +04:00
parent 909b50b47d
commit 89a7bc031c
18 changed files with 702 additions and 0 deletions

View File

@ -0,0 +1,121 @@
package com.me.asteroids;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Polygon;
import com.badlogic.gdx.math.Vector2;
import com.me.common.Random;
public final class AsteroidFactory {
public static final Random rand = new Random();
int vertexCount;
float size;
float sizeVariation;
float angleVariation;
boolean sizeRelativeToLast;
public AsteroidFactory setVertexCount(int vertexCount) {
this.vertexCount = vertexCount;
return this;
}
public AsteroidFactory setSize(float size) {
this.size = size;
return this;
}
public AsteroidFactory setSizeVariation(float sizeVariation) {
this.sizeVariation = sizeVariation;
return this;
}
public AsteroidFactory sizeRelativeToLast() {
this.sizeRelativeToLast = true;
return this;
}
public AsteroidFactory sizeRelativeToInitial() {
this.sizeRelativeToLast = false;
return this;
}
public AsteroidFactory setAngleVariation(float angleVariation) {
this.angleVariation = angleVariation;
return this;
}
private void validate() {
if (vertexCount <= 2) {
throw new IllegalStateException(String.format("Illegal vertexCount: %d. Must be >= 3.", vertexCount));
}
if (size <= 0) {
throw new IllegalStateException(String.format("Illegal vertexCount: %f. Must be > 0.", size));
}
if (sizeVariation < 0) {
throw new IllegalStateException(String.format("Illegal sizeVariation: %f. Must be >= 0.", sizeVariation));
}
if (sizeVariation > size) {
throw new IllegalStateException(String.format("Illegal sizeVariation: %f. Must be <= size.", sizeVariation));
}
if (angleVariation < 0) {
throw new IllegalStateException(String.format("Illegal angleVariation: %f. Must be >= 0.", angleVariation));
}
if (angleVariation > MathUtils.PI2 / vertexCount / 2) {
throw new IllegalStateException(String.format("Illegal angleVariation: %f. May cause vertexes positions to swap.", angleVariation));
}
}
private Vector2 applyAngleVariation(Vector2 vertex) {
if (angleVariation > 0) {
float half = angleVariation * 0.5f;
vertex.rotateRad(rand.nextFloat(-half, half));
}
return vertex;
}
private float applySizeVariation(float size) {
if (sizeVariation > 0) {
float half = sizeVariation * 0.5f;
float variation = rand.nextFloat(-half, half);
if (sizeRelativeToLast) {
size += variation;
size = MathUtils.clamp(size, this.size - half, this.size + half);
} else {
size = this.size + variation;
}
}
return size;
}
public Polygon generate() {
validate();
float angleStep = MathUtils.PI2 / vertexCount;
// Pick a random starting angle
float startAngle = rand.nextFloat() * MathUtils.PI2;
Vector2 dir = new Vector2(MathUtils.cos(startAngle), MathUtils.sin(startAngle));
float lastSize = size;
float[] vertices = new float[vertexCount * 2];
for (int i = 0; i < vertexCount; i++) {
Vector2 vertex = dir.cpy();
vertex = applyAngleVariation(vertex);
lastSize = applySizeVariation(lastSize);
vertex.scl(lastSize);
vertices[i * 2] = vertex.x;
vertices[(i * 2) + 1] = vertex.y;
dir.rotateRad(angleStep);
}
return new Polygon(vertices);
}
}

View File

@ -0,0 +1,39 @@
package com.me.asteroids;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.me.asteroids.screens.GameScreen;
import com.me.common.Game;
public class Asteroids extends ApplicationAdapter {
public Graphics graphics;
private Game game;
@Override
public void create () {
graphics = new Graphics(Constants.WIDTH, Constants.HEIGHT);
graphics.initialize();
game = new Game();
game.setNextScreen(new GameScreen(graphics));
}
@Override
public void render () {
game.update(Gdx.graphics.getDeltaTime());
game.render();
}
@Override
public void dispose () {
graphics.dispose();
game.dispose();
}
@Override
public void resize(int width, int height) {
graphics.setScreenSize(width, height);
}
}

View File

@ -0,0 +1,11 @@
package com.me.asteroids;
public class Constants {
public static final int WIDTH = 800;
public static final int HEIGHT = 600;
public static final int HALF_WIDTH = WIDTH / 2;
public static final int HALF_HEIGHT = HEIGHT / 2;
}

View File

@ -0,0 +1,63 @@
package com.me.asteroids;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
public class Graphics {
private int worldWidth, worldHeight;
private int screenWidth, screenHeight;
private Camera camera;
private Viewport viewport;
private ShapeRenderer shapeRenderer;
public Graphics(int worldWidth, int worldHeight) {
this.worldWidth = worldWidth;
this.worldHeight = worldHeight;
this.screenWidth = Gdx.graphics.getWidth();
this.screenHeight = Gdx.graphics.getHeight();
this.camera = new OrthographicCamera();
this.viewport = new FitViewport(worldHeight, worldWidth, camera);
this.shapeRenderer = new ShapeRenderer();
}
public void initialize() {
Gdx.gl.glClearColor(0, 0, 0, 1);
updateDimensions();
}
public void reset() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
public void setScreenSize(int width, int height) {
screenWidth = width;
screenHeight = height;
updateDimensions();
}
public void dispose() {
shapeRenderer.dispose();
}
public ShapeRenderer getShapeRenderer() {
return shapeRenderer;
}
private void updateDimensions() {
viewport.setWorldSize(worldWidth, worldHeight);
viewport.update(screenWidth, screenHeight, true);
shapeRenderer.setProjectionMatrix(camera.combined);
}
}

View File

@ -0,0 +1,28 @@
package com.me.asteroids;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import java.lang.Math;
public final class Utils {
public static float rotate(float rotation, float degrees) {
rotation += degrees;
if (rotation < 0) {
rotation = 360 - rotation;
} else if (rotation > 360) {
rotation -= 360;
}
return rotation;
}
public static Vector2 setUnitVectorAngle(Vector2 vector, float degrees) {
return vector.set((float) Math.cos(degrees * MathUtils.degreesToRadians), (float) Math.sin(degrees * MathUtils.degreesToRadians));
}
public static Vector2 setUnitVectorAngleRad(Vector2 vector, float radians) {
return vector.set((float) Math.cos(radians), (float) Math.sin(radians));
}
}

View File

@ -0,0 +1,29 @@
package com.me.asteroids.screens;
import com.me.asteroids.Graphics;
import com.me.common.Screen;
public class GameScreen extends Screen {
Graphics graphics;
public GameScreen(Graphics graphics) {
this.graphics = graphics;
}
@Override
public void setup() {
}
@Override
public void update(float dt) {
graphics.reset();
}
@Override
public void dispose() {
}
}

View File

@ -0,0 +1,40 @@
package com.me.common;
public class Game {
private Screen screen;
private Screen nextScreen;
public void update(float dt) {
if (nextScreen != null) {
handleScreeUpdate();
}
if (screen != null) {
screen.update(dt);
}
}
public void render() {
if (screen != null) {
screen.render();
}
}
private void handleScreeUpdate() {
if (screen != null) screen.dispose();
nextScreen.setup();
screen = nextScreen;
nextScreen = null;
}
public void setNextScreen(Screen screen) {
nextScreen = screen;
}
public void dispose() {
screen.dispose();
}
}

View File

@ -0,0 +1,8 @@
package com.me.common;
public class MockRenderer implements Renderer {
@Override
public void render() {}
}

View File

@ -0,0 +1,13 @@
package com.me.common;
public class MockScreen extends Screen {
public void setup() {
setRenderer(new MockRenderer());
}
public void update(float dt) {}
public void dispose() {}
}

View File

@ -0,0 +1,21 @@
package com.me.common;
public class Random extends java.util.Random {
public Random() {
super();
}
public Random(long seed) {
super(seed);
}
public float nextFloat(float min, float max) {
return min + (nextFloat() * Math.abs(min - max));
}
public int nextInt(int min, int max) {
return min + nextInt(Math.abs(min - max));
}
}

View File

@ -0,0 +1,7 @@
package com.me.common;
public interface Renderer {
void render();
}

View File

@ -0,0 +1,23 @@
package com.me.common;
public abstract class Screen {
private Renderer renderer;
public abstract void setup();
public abstract void update(float dt);
public abstract void dispose();
public void setRenderer(Renderer renderer) {
this.renderer = renderer;
}
public void render() {
if (renderer != null) {
renderer.render();
}
}
}

View File

@ -0,0 +1,5 @@
package com.me.common.ecs;
public abstract class Component {
}

View File

@ -0,0 +1,41 @@
package com.me.common.ecs;
public class ComponentBag {
private Component[] items;
private int size;
public ComponentBag() {
this.items = new Component[16];
this.size = items.length;
}
public Component get(int index) {
if (index > size) {
throw new IndexOutOfBoundsException("index > size");
}
return items[index];
}
public void insert(int index, Component item) {
if (index >= size) {
grow((int) (index * 1.5f));
}
items[index] = item;
}
public void remove(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException("index must be < size");
}
items[index] = null;
}
private void grow(int newSize) {
Component[] newItems = new Component[newSize];
System.arraycopy(this.items, 0, newItems, 0, size);
size = newSize;
this.items = newItems;
}
}

View File

@ -0,0 +1,78 @@
package com.me.common.ecs;
import java.util.HashMap;
import java.util.Map;
public class ComponentType {
private static final Map<Class<? extends Component>, ComponentType> types = new HashMap<>();
private static final ComponentType[] typeById = new ComponentType[Long.SIZE];
private static long nextBit = 1l;
private static int nextId = 0;
private long bits;
private int id;
private ComponentType() {
this.bits = nextBit;
this.id = nextId++;
this.nextBit <<= 1;
}
protected long getBits() {
return bits;
}
protected int getId() {
return this.id;
}
protected static long getMaskBits(Class<? extends Component>... components) {
long mask = 0l;
for (Class<? extends Component> clazz : components) {
mask |= getTypeBits(clazz);
}
return mask;
}
protected static long getTypeBits(Class<? extends Component> component) {
return getComponentType(component).getBits();
}
protected static int getTypeId(Class<? extends Component> component) {
return getComponentType(component).getId();
}
protected static ComponentType getById(int id) {
return typeById[id];
}
protected boolean isTypeInMask(long mask) {
return (bits & mask) == mask;
}
protected static void registerComponentType(Class<? extends Component> component) {
ComponentType type = types.get(component);
if (type != null) {
throw new IllegalArgumentException(component.getName() + " has already been registered.");
}
type = new ComponentType();
types.put(component, type);
typeById[type.getId()] = type;
}
protected static ComponentType getComponentType(Class<? extends Component> component) {
ComponentType type = types.get(component);
if (type == null) {
throw new IllegalArgumentException(component.getName() + " has not been registered.");
}
return type;
}
protected static int getRegisteredComponentTypeCount() {
return types.size();
}
}

View File

@ -0,0 +1,90 @@
package com.me.common.ecs;
import com.badlogic.gdx.utils.Array;
public class Engine {
private Array<Entity> entities;
private ComponentBag[] components;
private Array<EntitySystem> systems;
public Engine() {
this.entities = new Array<>();
this.systems = new Array<>();
}
public void registerComponentClass(Class<? extends Component> clazz) {
ComponentType.registerComponentType(clazz);
}
public void registerSystem(EntitySystem system) {
this.systems.add(system);
}
public void ready() {
this.components = new ComponentBag[ComponentType.getRegisteredComponentTypeCount()];
for (int i = 0; i < components.length; i++) {
components[i] = new ComponentBag();
}
}
public void update(float dt) {
for (EntitySystem system : systems) {
system.preProcessing();
updateSystem(system, dt);
system.postProcessing();
}
}
public Entity createEntity() {
Entity entity = new Entity(this);
entities.add(entity);
return entity;
}
public void removeEntity(Entity entity) {
removeAllEntityComponents(entity.getId());
entities.removeValue(entity, true);
}
private void removeAllEntityComponents(int entityId) {
for (int i = 0; i < components.length; i++) {
components[i].insert(entityId, null);
}
}
protected void addEntityComponent(Entity entity, Component component) {
ComponentType type = ComponentType.getComponentType(component.getClass());
components[type.getId()].insert(entity.getId(), component);
entity.addComponentType(type);
}
protected void removeEntityComponent(Entity entity, Component component) {
ComponentType type = ComponentType.getComponentType(component.getClass());
components[type.getId()].remove(entity.getId());
entity.removeComponentType(type);
}
protected <T extends Component> T getEntityComponent(Entity entity, Class<T> clazz) {
ComponentType type = ComponentType.getComponentType(clazz);
return clazz.cast(components[type.getId()].get(entity.getId()));
}
protected void updateSystem(EntitySystem system, float dt) {
for (Entity entity : entities) {
if (!entity.isActive()) {
continue;
}
// Check if this system is interested in this entity
if ((entity.getComponentBits() & system.getTypeMask()) != system.getTypeMask()) {
continue;
}
// If so, process the entity
system.processEntity(entity, dt);
}
}
}

View File

@ -0,0 +1,60 @@
package com.me.common.ecs;
public final class Entity {
private static int nextId = 0;
private Engine engine;
private int id;
private boolean active;
private long componentBits;
protected Entity(Engine engine) {
this.engine = engine;
this.active = false;
this.id = nextId++;
}
public int getId() {
return id;
}
public boolean isActive() {
return active;
}
public void activate() {
this.active = true;
}
public void deactivate() {
this.active = false;
}
public <T extends Component> T getComponent(Class<T> clazz) {
return engine.getEntityComponent(this, clazz);
}
public void addComponent(Component component) {
engine.addEntityComponent(this, component);
}
public void removeComponent(Component component) {
engine.removeEntityComponent(this, component);
}
protected void addComponentType(ComponentType type) {
componentBits |= type.getBits();
}
protected void removeComponentType(ComponentType type) {
componentBits &= ~type.getBits();
}
protected long getComponentBits() {
return componentBits;
}
}

View File

@ -0,0 +1,25 @@
package com.me.common.ecs;
public abstract class EntitySystem {
private long typeBits;
public EntitySystem(Class<? extends Component>... components) {
typeBits = ComponentType.getMaskBits(components);
}
/**
* @return the type mask for this system. Only entities containing all component types specified
* by the mask will be processed by it.
*/
public long getTypeMask() {
return typeBits;
}
public void preProcessing() {}
public abstract void processEntity(Entity entity, float dt);
public void postProcessing() {}
}