Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@JtheDroid
Created May 14, 2017 17:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JtheDroid/814ad3d463e0b537ee3ac4bf168b6659 to your computer and use it in GitHub Desktop.
Save JtheDroid/814ad3d463e0b537ee3ac4bf168b6659 to your computer and use it in GitHub Desktop.
TowerDefense3D by JtheDroid - All tabs in one file
import java.awt.Robot;
Robot robot;
float highQuality = 5; //adjust the quality of the "terrain"----------------------------------------------------------------------------------------------------------------------
float lowQuality = 20;
PShape ground;
Game gameObject; //Objekt zur Verwaltung des Spielstatus
Mouse mouse; //Objekt, um Maus-Events zu verfolgen
Camera camera;
float normalTextSize = 12; //Um die Textgröße nach einer Änderung wieder zurückzusetzen //Aus irgendeinem Grund unscharf
boolean keys[] = new boolean[1000];
boolean keyCodes[] = new boolean[10000];
void setup()
{
fullScreen(P3D);
mouse = new Mouse(0, 0, 1920, 1080);
gameObject = new Game();
camera = new Camera(500, 500, 400);
noCursor();
//gameObject.notifications.add(new NotificationOverlay("Test", "Okay"));
try {
robot = new Robot();
}
catch(Exception e) {
println(e);
}
ground = makeGround();
frameRate(60);
}
void draw() {
if (frameCount%round(frameRate/2)==0)println(round(frameRate*10f)/10f, "fps ", "X:", mouseX, "Y: ", mouseY, "vX:", mouse.X, "vY: ", mouse.Y, gameObject.bullets.size(), "bullets ", gameObject.enemies.size(), "enemies", gameObject.towers.size(), "towers"); //Jede halbe Sekunde Infos ausgeben
background(0);
translate(0, 10000, -500);
pushMatrix();
translate(-20000, 2000, -20000);
rotateX(PI/2);
scale(2);
shape(ground);
popMatrix();
rotateX(PI/4);
gameObject.run();
camera.update();
mouse.reset();
if (keyPressed) testStuff();
resetMouse();
}
void keyPressed() {
if (!(key==CODED)) keys[key] = true;
else keyCodes[keyCode] = true;
testForKeys();
}
void keyReleased() {
if (!(key==CODED)) keys[key] = false;
else keyCodes[keyCode] = false;
}
void testStuff() {
if (key=='x') printWayPoints(gameObject.way);
}
class Bullet extends PositionedObject {
float wayTraveled;
float maxWay;
float speed;
float damage;
float upAngle;
PVector start;
PVector target;
Bullet() {
setGame();
setSpeed();
damage = 1;
}
Bullet(float X, float Y, float Angle, float UpAngle, float MaxWay, Tower Parent, Enemy Target) {
this();
x = X;
y = Y;
angle = Angle;
maxWay = MaxWay;
upAngle = UpAngle;
countBullets();
start = new PVector(Parent.x, Parent.y, Parent.z);
if (Target == null) target = null;
else target = new PVector(Target.x, Target.y, Target.z);
}
Bullet(float X, float Y, float Z, float Angle, float UpAngle, float MaxWay, Tower Parent, Enemy Target) {
this();
x = X;
y = Y;
z = Z;
angle = Angle;
maxWay = MaxWay;
upAngle = UpAngle;
countBullets();
start = new PVector(Parent.x, Parent.y, Parent.z);
if (Target == null) target = null;
else target = new PVector(Target.x, Target.y, Target.z);
}
Bullet(float X, float Y, float Angle, float UpAngle, Tower Parent, Enemy Target) {
this(X, Y, Angle, UpAngle, 0, -1, Parent, Target); //maybe change -1 to Tower.UNLIMITED_RANGE
}
Bullet(PVector v, float Angle, float UpAngle, Tower Parent, Enemy Target) { //for x,y,z
this(v.x, v.y, Angle, UpAngle, -1, Parent, Target); //maybe change -1 to Tower.UNLIMITED_RANGE
z = v.z;
}
void setZ() {
if (target==null) {
Enemy Target = returnNearestEnemy(game.enemies);
target = new PVector(Target.x, Target.y, Target.z);
}
float partOfWay = map(getDistance(new Waypoint(start.x, start.y)), 0, new Waypoint(target.x, target.y).getDistance(new Waypoint(start.x, start.y)), 0, 1);
z = map(sin(partOfWay*PI), 0, 1, 0, 100) + map(partOfWay, 0, 1, start.z, target.z);
}
void actions() {
setZ();
if (testForCollision(returnNearestEnemy(game.enemies), 20)) delete();
moveForward();
deleteIfOutOfScreen();
deleteIfWayTraveled();
}
void setSpeed() {
speed = 10;
}
boolean testForCollision(PositionedObject po, float dist) {
if (po==null) return false;
return (getDistance(po)<dist);
}
void show() {
pushMatrix();
pushStyle();
translate(x, y, z);
rotate(angle);
rect(0, 0, 8, 2);
popStyle();
popMatrix();
}
void moveForward() {
super.moveForward(speed);
//super.moveForward3D(speed,upAngle);
wayTraveled+=speed;
}
void deleteIfOutOfScreen() {
if (!this.isInsideRect(mouse.areaX, mouse.areaY, mouse.areaXSize, mouse.areaYSize)) {
game.bullets.remove(this);
//this = null;
}
}
void deleteIfWayTraveled() {
if (maxWay > 0 && maxWay != Tower.UNLIMITED_RANGE)
if (wayTraveled>=maxWay) game.bullets.remove(this);
}
void delete() {
game.bullets.remove(this);
game.explosions.add(new Explosion(x, y, 20));
returnNearestEnemy(game.enemies).health-=damage;
}
}
class Camera {
float x, y, z;
float angle, upAngle;
float lookAtX, lookAtY, lookAtZ;
float xLook, yLook, zLook;
float movementSpeed;
float fov;
Camera(float X, float Y, float Z, float lX, float lY, float lZ) {
this(X, Y, Z);
lookAt(lX, lY, lZ);
}
Camera(float X, float Y, float Z, float Angle, float UpAngle) {
this(X, Y, Z);
angle = Angle;
upAngle = UpAngle;
updateWithAngle();
}
Camera(float X, float Y, float Z) {
x = X;
y = Y;
z = Z;
movementSpeed = 15;
fov = PI/2;
}
void updateWithAngle() {
upAngle = constrain(upAngle, -89.9, 89.9);
angle %= 360;
xLook = cos(radians(angle)) * sin(radians(upAngle + 90)); //maybe change to radians instead of converting
yLook = cos(radians(upAngle + 90));
zLook = sin(radians(angle)) * sin(radians(upAngle + 90));
lookAt(x + xLook, y + yLook, z + zLook);
}
void updateWithCoords(float X, float Y, float Z) {
lookAt(ease(lookAtX, 0.1, X), ease(lookAtY, 0.1, Y), ease(lookAtZ, 0.1, Z));
}
void easePosTo(float X, float Y, float Z) { //change name!
Z+=1000;
Y-=750;
if (abs(x-X)>200) x=ease(x, 0.01, X);
if (abs(y-Y)>200) y=ease(y, 0.01, Y);
if (abs(z-Z)>500) z=ease(z, 0.01, Z);
}
void lookAt(float X, float Y, float Z) {
lookAtX = X;
lookAtY = Y;
lookAtZ = Z;
}
void update() {
camera(x, y, z, lookAtX, lookAtY, lookAtZ, 0, 1, 0);
perspective(fov, float(width)/float(height), 0.1, 100000);
}
void moveFreeCam() {
//if (keyPressed) {
Float radAngle = null;
if (keys['w']) {
radAngle = radians(angle);
}
if (keys['a']) {
radAngle = radians(angle+270);
}
if (keys['s']) {
radAngle = radians(angle+180);
}
if (keys['d']) {
radAngle = radians(angle+90);
}
if (keyCodes[SHIFT]) y+=movementSpeed;
if (keys[' ']) y-=movementSpeed;
if (radAngle != null)
{
z+=sin(radAngle)*movementSpeed;
x+=cos(radAngle)*movementSpeed;
}
//}
}
}
class Enemy extends PositionedObject {
Healthbar hbar;
float speed;
float health;
float maxHealth;
ArrayList<Waypoint> path;
float damage;
Enemy(ArrayList<Waypoint> Way, float Speed, float Damage, float MaxHealth) {
setGame();
path = (ArrayList)Way.clone();
Waypoint start;
if (path.size()>0) {
start = path.get(0);
x = start.x;
y = start.y;
path.remove(0);
} else game.enemies.remove(this);
speed = Speed;
maxHealth = MaxHealth;
health = maxHealth;
hbar = new Healthbar(50, this, color(0, 50), color(0, 100), color(255, 0, 0, 100), color(0, 0));
damage = Damage;
}
Enemy(ArrayList<Waypoint> Way, float Speed, float Damage) {
this(Way, Speed, Damage, 100);
}
Enemy(ArrayList<Waypoint> Way, float Speed) {
this(Way, Speed, 20);
}
Enemy(ArrayList<Waypoint> Way) {
this(Way, 1);
}
void actions() {
move();
}
void move() {
if (path.size()>0) {
easeRotationTo(path.get(0), 0.05*speed);
moveForward(speed);
if (dist(x, y, path.get(0).x, path.get(0).y)<10) path.remove(0);
}
}
void show() {
pushMatrix();
pushStyle();
fill(255, 200, 200);
//stroke(255, 0, 0);
noStroke();
translate(x, y, z);
rotate(angle);
//ellipse(0, 0, 20, 20);
sphere(20);
translate(20,0);
sphere(5);
//ellipse(0, 0, 5, 5);
translate(-10,0);
rotate(-angle);
pushMatrix();
rotateX(-PI/2);
translate(0,-15);
health = constrain(health, 0, maxHealth);
hbar.show();
popMatrix();
popMatrix();
popStyle();
if (health<=0) delete();
}
void delete() {
game.enemies.remove(this);
game.explosions.add(new Explosion(this.x, this.y, 50));
game.shop.money+=10; //change?
}
}
class Explosion extends PositionedObject {
float size;
float partOfMaxSize; //0 - 1
Explosion(float X, float Y, float Size) {
setGame();
x = X;
y = Y;
size = Size;
partOfMaxSize = 0;
}
void actions() {
grow();
}
void show() {
if (frameRate>50) {
pushStyle();
pushMatrix();
fill(255, 0, 0);
translate(x, y, z);
ellipse(0, 0, size*partOfMaxSize, size*partOfMaxSize);
popStyle();
popMatrix();
}
}
void grow() {
partOfMaxSize+=0.1;
if (partOfMaxSize>1) game.explosions.remove(this);
}
}
class Game {
GameState state;
Shop shop; //Objekt zur Verwaltung des Shops (Türme platzieren etc.)
TerrainGenerator tGen;
PointToDefend ptd;
int spawnTimer;
int lastSpawned;
int gameStarted;
int totalBullets;
int totalEnemies;
int totalTowers;
MenuButton resetButton;
ArrayList<MenuButton> menuButtons;
ArrayList<ArrayList<Waypoint>> ways; //Liste aller Wege
PShape path3D; //Die 3D-Darstellung von path
float[][] heightMap;
PShape path; //Die "Route" der Gegner
ArrayList<Enemy> enemies; //Liste der Gegner
ArrayList<Bullet> bullets; //Liste der Kugeln/Projektile
ArrayList<Explosion> explosions; //Liste für alle Explosionen
ArrayList<Waypoint> way; //Liste der Wegpunkte
ArrayList<Tower> towers; //Liste der Türme
ArrayList<NotificationOverlay> notifications; //Liste der Benachrichtigungen
Game() {
tGen = new TerrainGenerator();
menuButtons = new ArrayList<MenuButton>();
addMenuButtons();
ways = new ArrayList<ArrayList<Waypoint>>();
addWays();
notifications = new ArrayList<NotificationOverlay>();
setup();
}
void setup() {
shop = new Shop();
resetButton = new MenuButton("Reset", new Runnable() {
public void run() {
gameObject.setup();
}
}
, mouse.areaXEnd, mouse.areaY, 200, 200, 4);
spawnTimer = 3000;
lastSpawned = 0;
state = GameState.MENU;
ptd = null;
path = null;
path3D = null;
heightMap = null;
gameStarted = 0;
totalBullets = 0;
totalEnemies = 0;
totalTowers = 0;
enemies = new ArrayList<Enemy>();
bullets = new ArrayList<Bullet>();
explosions = new ArrayList<Explosion>();
way = new ArrayList<Waypoint>();
towers = new ArrayList<Tower>();
}
void run() {
pushMatrix();
mouse.update();
for (int i = notifications.size()-1; i>=0; i--) notifications.get(i).switchState();
switch(state) {
case MENU:
menu();
break;
case RUNNING:
runGame();
break;
case PAUSED:
gamePaused();
break;
case PATH_CREATION:
createPath();
break;
case PATH_SELECTION:
selectPath();
break;
case INFO:
info();
break;
}
for (int i = notifications.size()-1; i>=0; i--) notifications.get(i).actions();
mouse.showArea();
resetButton.show();
mouse.show();
popMatrix();
}
void runGame() {
if (heightMap==null) heightMap = createHeightMap(path);
getTerrainIfDone();
if (millis()-spawnTimer > lastSpawned) {
enemies.add(new Enemy(way));
countEnemies();
lastSpawned = millis();
spawnTimer *= 0.99;
}
int frameSpeed = round(60f/frameRate); //Um bei niedriger frameRate in etwa die gleiche Geschwindigkeit beizubehalten
translate(0, 0, 1);
for (int s = 0; s < frameSpeed; s++) for (int i = bullets.size()-1; i>=0; i--) bullets.get(i).actions(); //Aktionen für alle Kugeln/Projektile (Bewegen, Auf Berührung Testen etc.)
for (Bullet b : bullets) b.show(); //Alle Kugeln/Projektile anzeigen
translate(0, 0, 1);
for (int s = 0; s < frameSpeed; s++) for (int i = enemies.size()-1; i>=0; i--) enemies.get(i).actions(); //Aktionen für alle Gegner (Bewegen etc.)
for (int i = enemies.size()-1; i>=0; i--) enemies.get(i).show(); //Alle Gegner anzeigen
translate(0, 0, 1);
for (int s = 0; s < frameSpeed; s++) for (int i = towers.size()-1; i>=0; i--) towers.get(i).actions(); //Aktionen für alle Türme (Bewegen, Schießen etc.)
for (Tower t : towers) t.show(); //Alle Türme anzeigen
translate(0, 0, 1);
for (int s = 0; s < frameSpeed; s++) for (int i = explosions.size()-1; i>=0; i--) explosions.get(i).actions();
for (Explosion e : explosions) e.show(); //Alle Explosionen anzeigen
//Für diese for-Schleifen kann nicht die "advanced" for-Schleife (oder auch for-each-Schleife) verwendet werden, (z.B. for(Bullet b : bullets){})
//da die Objekte während des Schleifendurchlaufes gelöscht werden können, was sonst zu einer ConcurrentModificationException führen würde, da sich die Größe der Liste geändert hat
shop.show(); //Shop anzeigen (und entsprechende Aktionen ausführen)
showPath();
}
void gamePaused() {
if (heightMap==null) heightMap = createHeightMap(path);
getTerrainIfDone();
translate(0, 0, 1);
for (Bullet b : bullets) b.show(); //Alle Kugeln/Projektile anzeigen
translate(0, 0, 1);
for (int i = enemies.size()-1; i>=0; i--) enemies.get(i).show(); //Alle Gegner anzeigen
translate(0, 0, 1);
for (Tower t : towers) t.show(); //Alle Türme anzeigen
translate(0, 0, 1);
for (Explosion e : explosions) e.show(); //Alle Explosionen anzeigen
showPath();
}
void createPath() {
if (heightMap==null) heightMap = createHeightMap(path);
new MenuButton("Finish", new Runnable() {
public void run() {
gameObject.gameStarted = millis();
gameObject.state = GameState.RUNNING;
gameObject.makePath();
}
}
, mouse.areaXEnd, mouse.areaYEnd-100, 200, 200, 10).show();
if (isPointInsideRect(mouse.X, mouse.Y, mouse.areaX, mouse.areaY, mouse.areaXSize, mouse.areaYSize) && mouse.clicked()) {
way.add(new Waypoint(mouse.X, mouse.Y));
makePath();
heightMap = createHeightMap(path);
}
getTerrainIfDone();
showPath();
}
void makePath() {
path = calcPath(way);
Waypoint last = way.get(way.size()-1);
ptd = new PointToDefend(last.x, last.y, 100);
make3dPath();
}
void make3dPath() {
makeLow3dPath();
tGen = new TerrainGenerator();
tGen.setScale(state==GameState.PATH_CREATION ? lowQuality : highQuality);
tGen.start();
}
void makeLow3dPath() {
path3D = create3dPath(path, 30);
}
void getTerrainIfDone() {
if (tGen.done) {
path3D = tGen.getTerrain();
}
}
void selectPath() {
ArrayList<MenuButton> buttons = new ArrayList<MenuButton>();
int i = 1;
for (ArrayList<Waypoint> way : ways) {
buttons.add(new MenuButtonWaySelector("Way"+Integer.toString(i), way, 1, 1, 1, 1));
i++;
}
ListView lv = new ListView(buttons);
lv.show();
}
void menu() {
for (MenuButton mb : menuButtons) mb.show();
}
void addMenuButtons() {
menuButtons.add(new MenuButton("Create Path", new Runnable() {
public void run() {
gameObject.state = GameState.PATH_CREATION;
}
}
, mouse.areaX+100, 100, 500, 100));
menuButtons.add(new MenuButton("Select Path", new Runnable() {
public void run() {
gameObject.state = GameState.PATH_SELECTION;
}
}
, mouse.areaX+100, 300, 500, 100));
menuButtons.add(new MenuButton("Exit", new Runnable() {
public void run() {
exit();
}
}
, mouse.areaX+100, mouse.areaYEnd-200, 500, 100));
menuButtons.add(new MenuButton("Info", new Runnable() {
public void run() {
gameObject.state = GameState.INFO;
}
}
, mouse.areaX+100, mouse.areaYEnd-400, 500, 100));
}
void addWays() {
ArrayList<ArrayList<Waypoint>> Ways = new ArrayList<ArrayList<Waypoint>>();
Ways.add(createWayList(40, 762, 958, 30));
Ways.add(createWayList(31.0, 719.0, 80.0, 46.0, 189.0, 718.0, 242.0, 43.0, 337.0, 717.0, 395.0, 45.0, 478.0, 717.0, 558.0, 41.0, 629.0, 716.0, 715.0, 47.0, 776.0, 710.0, 871.0, 43.0, 979.0, 37.0));
for (int i = 0; i<Ways.size(); i++) Ways.set(i, rescaleWay(Ways.get(i), 0, 0, 1000, 800, mouse.areaX, mouse.areaY, mouse.areaXSize, mouse.areaYSize)); //the first two were made using the old screen resolution
ways.addAll(Ways);
ways.add(createWayList(68.0, 42.0, 1844.0, 42.0, 1857.0, 1044.0, 65.0, 1046.0, 78.0, 166.0, 67.0, 132.0));
ways.add(createWayList(90.0, 69.0, 1828.0, 1015.0, 1840.0, 69.0, 102.0, 1023.0, 61.0, 168.0));
ways.add(createWayList(72.0, 42.0, 1828.0, 920.0, 1787.0, 997.0, 103.0, 162.0, 89.0, 269.0, 1561.0, 1018.0, 1346.0, 1012.0, 83.0, 368.0, 78.0, 485.0, 1134.0, 1002.0, 936.0, 1017.0, 80.0, 582.0, 81.0, 701.0, 701.0, 990.0, 444.0, 1011.0, 87.0, 823.0, 88.0, 984.0, 203.0, 1002.0));
ways.add(createWayList(939.0, 512.0, 1029.0, 515.0, 1022.0, 420.0, 809.0, 434.0, 810.0, 612.0, 1165.0, 600.0, 1131.0, 293.0, 652.0, 287.0, 638.0, 758.0, 1345.0, 736.0, 1302.0, 136.0, 377.0, 133.0, 354.0, 933.0, 1649.0, 884.0, 1638.0, 99.0));
ways.add(createWayList(85.0, 58.0, 110.0, 311.0, 449.0, 88.0, 284.0, 547.0, 714.0, 214.0, 677.0, 724.0, 1082.0, 346.0, 1063.0, 839.0, 1395.0, 456.0, 1431.0, 898.0, 1734.0, 595.0, 1749.0, 897.0));
}
void showPath() {
if (path3D!=null) shape(path3D);
//if (path!=null) shape(path); //Pfad anzeigen
if (ptd!=null) ptd.show();
}
void info() {
translate(-mouse.areaXSize/2, -mouse.areaYSize/2);
scale(2);
if (mouse.clicked()) state = GameState.MENU;
pushStyle();
fill(255);
textSize(70);
text("Info - TowerDefense3D by JtheDroid", mouse.areaX+100, mouse.areaY+50);
textSize(30);
text("This game is a tower defense game, the goal is to hold back the enemies as long as you can.\nYou have to protect a point, you lose if too many enemies reach it.\nThe longer you play, the faster the enemies spawn.\n\nYou can purchase towers in the shop in the bottom right.\n\nPress TAB to switch camera modes,\nin free camera mode, use WASD to move, SHIFT to descend and SPACE to ascend.\nUse P to pause and resume a running game.\nYou can always get back to the menu using the RESET button on the right.", mouse.areaX+100, mouse.areaY+100, mouse.areaXSize-200, mouse.areaYSize-200);
textSize(20);
text("Click to leave", mouse.areaX+100, mouse.areaYEnd-50);
popStyle();
textSize(normalTextSize);
}
}
enum GameState {
MENU, RUNNING, PAUSED, PATH_CREATION, PATH_SELECTION, INFO
}
class Healthbar extends PositionedObject {
float size; //length
float maxValue;
Enemy parentEnemy;
PointToDefend parentPoint;
color background;
color bar;
color backgroundStroke;
color barStroke;
Healthbar(float Size, float MaxValue, color bg, color bgS, color Bar, color BarS) {
setGame();
size = Size;
maxValue = MaxValue;
background = bg;
backgroundStroke = bgS;
bar = Bar;
barStroke = BarS;
parentEnemy = null;
parentPoint = null;
}
Healthbar(float Size, PointToDefend Parent, color bg, color bgS, color Bar, color BarS) {
this(Size, Parent.health, bg, bgS, Bar, BarS);
parentPoint = Parent;
parentEnemy = null;
}
Healthbar(float Size, Enemy Parent, color bg, color bgS, color Bar, color BarS) {
this(Size, Parent.health, bg, bgS, Bar, BarS);
parentEnemy = Parent;
parentPoint = null;
}
void show() {
if (parentEnemy != null) {
float value = parentEnemy.health;
float x = -size/2;
float y = -20;
show(value, x, y);
} else if (parentPoint != null) {
float value = parentPoint.health;
float x = -size/2;
float y = -30;
show(value, x, y);
}
}
void show(float value, float x, float y) {
pushMatrix();
pushStyle();
translate(x, y, z); //change!
stroke(100);
fill(0);
rect(0, 0, size, size*0.2);
stroke(0);
fill(255);
rect(0, 0, size*value/maxValue, size*0.2);
popStyle();
popMatrix();
}
//void show(float);
}
class ListView {
float x, y, xSize, ySize;
ArrayList<MenuButton> buttons; //ignoring size values for MenuButtons, they will be reset
float scroll = 0;
float entryHeight;
float yDist;
ListView(ArrayList<MenuButton> Buttons, float EntryHeight, float YDist, float X, float Y, float xS, float yS) {
buttons = Buttons;
entryHeight = EntryHeight;
yDist = YDist;
x = X;
y = Y;
xSize = xS;
ySize = yS;
initButtons();
}
ListView(ArrayList<MenuButton> Buttons, float EntryHeight, float YDist) {
this(Buttons, EntryHeight, YDist, mouse.areaX, mouse.areaY, mouse.areaXSize, mouse.areaYSize);
}
ListView(ArrayList<MenuButton> Buttons) {
this(Buttons, 50, 10, mouse.areaX, mouse.areaY, mouse.areaXSize, mouse.areaYSize);
}
void show() {
pushMatrix();
if (buttons.size() * (entryHeight + yDist) > ySize) showScrollBar();
//TODO: cut off List when scroll is active
for (MenuButton mb : buttons) showEntry(mb);
popMatrix();
}
void initButtons(){
int i = 0;
for (MenuButton mb : buttons) {
mb.x = x;
mb.y = y + (i * (entryHeight+yDist));
mb.ySize = entryHeight;
mb.xSize = xSize;
mb.calcTextSize();
i++;
}
}
void showEntry(MenuButton mb) {
mb.show(); //change?
}
void showScrollBar() {
//display scrollbar and change scroll (variable)
}
}
class MenuButton extends PositionedObject {
String text;
Runnable action;
float xSize, ySize;
color standard, hover, pressed; //fill
float textSize;
int highlights;
MenuButton(String Text, Runnable r, float X, float Y, float xS, float yS, float TextSize, color Standard, color Hover, color Pressed) {
text = Text;
action = r;
x = X;
y = Y;
xSize = xS;
ySize = yS;
standard = Standard;
hover = Hover;
pressed = Pressed;
textSize = TextSize;
highlights = 0;
}
MenuButton(String Text, Runnable r, float X, float Y, float xS, float yS, color Standard, color Hover, color Pressed) {
this(Text, r, X, Y, xS, yS, 0, Standard, Hover, Pressed);
calcTextSize();
}
MenuButton(String Text, Runnable r, float X, float Y, float xS, float yS, float TextSize, int Highlights) {
this(Text, r, X, Y, xS, yS, TextSize, color(150), color(200), color(100));
highlights = Highlights;
}
MenuButton(String Text, Runnable r, float X, float Y, float xS, float yS, float TextSize) {
this(Text, r, X, Y, xS, yS, TextSize, color(150), color(200), color(100));
}
MenuButton(String Text, Runnable r, float X, float Y, float xS, float yS) {
this(Text, r, X, Y, xS, yS, 0, color(150), color(200), color(100));
calcTextSize();
}
MenuButton(String Text, Runnable r, float X, float Y, float xS, float yS, int Highlights) {
this(Text, r, X, Y, xS, yS, 0, color(150), color(200), color(100));
calcTextSize();
highlights = Highlights;
}
void calcTextSize() {
textSize = xSize/text.length()*1.8;
textSize = constrain(textSize, 1, ySize);
}
void show() {
pushStyle();
//stroke();
switch(this.state()) {
case IDLE:
fill(standard);
break;
case HOVER:
fill(hover);
break;
case PRESSED:
fill(pressed);
break;
case CLICKED:
action.run();
break;
}
pushMatrix();
rect(x, y, xSize, ySize);
translate(0, 0, 1);
fill(0);
textSize(textSize); //verbessern
text(text, x, y+ySize*3/4); //verbessern
noFill();
stroke(255);
for (int i = 0; i < highlights; i++) {
translate(0, 0, i*5);
rect(x, y, xSize, ySize);
}
popMatrix();
popStyle();
textSize(normalTextSize);
}
MenuButtonState state() {
if (mouseOver()) {
if (clicked()) return MenuButtonState.CLICKED;
else if (pressed()) return MenuButtonState.PRESSED;
else return MenuButtonState.HOVER;
} else return MenuButtonState.IDLE;
}
boolean pressed() {
return mouseOver() && mousePressed;
}
boolean clicked() {
return mouseOver() && mouse.clicked();
}
boolean hover() {
return mouseOver();
}
boolean mouseOver() {
return isPointInsideRect(mouse.X, mouse.Y, x, y, xSize, ySize);
/*
if ((mouse.X > x && mouse.X < x+xSize)
&&(mouse.Y > y && mouse.Y < y+ySize)) return true;
else return false;
*/
}
}
enum MenuButtonState {
IDLE, HOVER, PRESSED, CLICKED
}
class MenuButtonWaySelector extends MenuButton {
ArrayList<Waypoint> way;
MenuButtonWaySelector(String Text, ArrayList<Waypoint> Way, float X, float Y, float xS, float yS) {
super(Text, null, X, Y, xS, yS);
way = Way;
setRunnable();
}
MenuButtonWaySelector(String Text, ArrayList<Waypoint> Way, float X, float Y, float xS, float yS, float TextSize) {
super(Text, null, X, Y, xS, yS, TextSize);
way = Way;
setRunnable();
}
MenuButtonWaySelector(String Text, ArrayList<Waypoint> Way, float X, float Y, float xS, float yS, color Standard, color Hover, color Pressed) {
super(Text, null, X, Y, xS, yS, Standard, Hover, Pressed);
way = Way;
setRunnable();
}
MenuButtonWaySelector(String Text, ArrayList<Waypoint> Way, float X, float Y, float xS, float yS, float TextSize, color Standard, color Hover, color Pressed) {
super(Text, null, X, Y, xS, yS, TextSize, Standard, Hover, Pressed);
way = Way;
setRunnable();
}
void selectWay() {
gameObject.way = way;
gameObject.makePath();
gameObject.state = GameState.RUNNING;
}
void setRunnable() {
action = new Runnable() {
public void run() {
selectWay();
}
};
}
}
class Missile extends Bullet {
TargetMode mode;
Enemy targetEnemy; //if mode == RANDOM_ENEMY
MissileLauncher parent;
Missile(float X, float Y, float Z, float Angle, float MaxWay, TargetMode Mode, MissileLauncher Parent) {
setGame();
x = X;
y = Y;
angle = Angle;
maxWay = -1;
setSpeed();
damage = 20;
maxWay = MaxWay;
mode = Mode;
setEnemyIfRandom();
parent = Parent;
}
Missile(float X, float Y, float Z, float Angle, float MaxWay, MissileLauncher Parent) {
this(X, Y, Z, Angle, MaxWay, TargetMode.NEAREST_ENEMY, Parent);
}
void setEnemyIfRandom() {
if (mode == TargetMode.RANDOM_ENEMY) targetEnemy = selectEnemy();
}
@Override
void setSpeed() {
speed = 2.5; //?
}
void show() {
pushMatrix();
pushStyle();
translate(x, y, z);
rotate(angle);
rect(0, 0, 16, 4);
popStyle();
popMatrix();
//if (mode == TargetMode.RANDOM_ENEMY) drawLineTo(targetEnemy);
}
void setZ() {
if (parent==null) return;
int maxZ = 250;
float distToParent = getDistance(parent);
if (distToParent < 100) {
z = map(distToParent, 0, 100, parent.z, maxZ);
return;
}
Enemy nearest = returnNearestEnemy(game.enemies);
float distToEnemy = getDistance(nearest);
if (distToEnemy<100) {
z = map(distToEnemy, 0, 100, nearest.z, maxZ);
return;
} else z = maxZ;
}
void moveForward() {
if (mode == TargetMode.RANDOM_ENEMY) {
if (!game.enemies.contains(targetEnemy)) setEnemyIfRandom();
easeRotationTo(targetEnemy, 0.05);
} else easeRotationTo(selectEnemy(), 0.05);
super.moveForward(speed);
wayTraveled+=speed;
Enemy nearest = returnNearestEnemy(game.enemies);
if (testForCollision(nearest, 20)) {
delete();
if (nearest.health<=0) nearest.delete();
}
}
Enemy selectEnemy() {
Enemy e;
if (game.enemies.size()==0) return null;
switch(mode) {
case NEAREST_ENEMY:
e = returnNearestEnemy(game.enemies);
break;
case FIRST_ENEMY:
e = game.enemies.get(0);
break;
case RANDOM_ENEMY:
e = game.enemies.get(round(random(game.enemies.size()-1)));
break;
default:
e = returnNearestEnemy(game.enemies);
}
return e;
}
}
class MissileLauncher extends Tower {
MissileLauncher(float X, float Y) {
super(X, Y, 2000, new Missile(0, 0, 0, 0, 0, null), 2000);
angle = getAngle(returnNearestEnemy(game.enemies));
}
MissileLauncher(float X, float Y, float Z) {
this(X, Y);
z = Z;
}
@Override
void shoot() {
if (readyToShoot() && checkForEnemiesInRange()) {
game.bullets.add(new Missile(x, y, z, angle, range/*,TargetMode.RANDOM_ENEMY*/, this));
setTimer();
}
}
@Override
void turn() {
}
}
class Mouse {
int lastClicked;
int timeout; //ms
boolean pressedLastFrame;
float X, Y;
float Z;
boolean moved;
float areaX, areaY;
float areaXSize, areaYSize;
float areaXEnd, areaYEnd;
//float areaXrot, areaYrot, areaZrot;
MouseMode mode;
Mouse(float x, float y, float AreaX, float AreaY, float AreaXSize, float AreaYSize) {
X = x;
Y = y;
areaX = AreaX;
areaY = AreaY;
areaXSize = AreaXSize;
areaYSize = AreaYSize;
areaXEnd = areaX+areaXSize;
areaYEnd = areaY+areaYSize;
mode = MouseMode.NORMAL;
}
Mouse(float AreaX, float AreaY, float AreaXSize, float AreaYSize) {
this(0, 0, AreaX, AreaY, AreaXSize, AreaYSize);
}
Mouse() {
this(0, 0, width, height);
}
void show() {
pushMatrix();
pushStyle();
switch(mode) {
case NORMAL:
translate(X, Y);
fill(moved? color(255, 150, 150) : color(255, 0, 0));
stroke(moved? color(255, 150, 150) : color(255, 0, 0));
strokeWeight(5);
if (isPointInsideRect(X, Y, areaX, areaY, areaXSize, areaYSize) && gameObject.path3D != null) line(0, 0, 0, 0, 0, getZ(X-areaX, Y-areaY)+15);
else line(0, 0, 0, 0, 0, 20);
noStroke();
sphere(10);
if (isPointInsideRect(X, Y, areaX, areaY, areaXSize, areaYSize) && gameObject.path3D != null) {
translate(0, 0, getZ(X-areaX, Y-areaY));
sphere(10);
}
//for (int i = 0; i<10; i++) {
// pushMatrix();
// translate(0, 0, i*10);
// ellipse(0, 0, 20, 20);
// popMatrix();
//}
break;
case FREE_CAM:
//float dist = 10;
//translate(camera.xLook * dist, camera.yLook * dist, camera.zLook * dist);
//sphere(10);
break;
}
popStyle();
popMatrix();
}
void update() {
//rotateX(areaXrot);
//rotateX(areaYrot);
//rotateX(areaZrot);
float xMove = mouseX-pmouseX;
float yMove = mouseY-pmouseY;
if (abs(xMove) > 100 || abs(yMove) > 100) return;
switch(mode) {
case NORMAL:
float addedSpace = gameObject.shop.mode == ShopMode.PLACE ? 0 : 400;
X += xMove;
Y += yMove;
X = constrain(X, areaX - addedSpace, areaXEnd + addedSpace);
Y = constrain(Y, areaY - addedSpace, areaYEnd + addedSpace);
moved = (xMove != 0 || yMove != 0);
camera.updateWithCoords(modelX(X, Y, Z), modelY(X, Y, Z), modelZ(X, Y, Z)); //just an idea --> using the position without translations and rotations //IMPORTANT: Think about adding areaX... coords (areaZ needed?), add those ( maybe different translations needed?)
camera.easePosTo(modelX(X, Y, Z), modelY(X, Y, Z), modelZ(X, Y, Z));
break;
case FREE_CAM:
camera.angle+=xMove;
camera.upAngle-=yMove;
camera.moveFreeCam();
camera.updateWithAngle();
break;
}
}
void reset() {
pressedLastFrame = mousePressed;
}
boolean clicked() {
if (!mousePressed && pressedLastFrame && timerDone()) {
lastClicked = millis();
pressedLastFrame = false; //prevents click from triggering multiple events
return true;
} else return false;
}
boolean timerDone() {
return lastClicked+timeout<millis();
}
void setTimer(int ms) {
timeout = ms;
}
void showArea() {
pushMatrix();
pushStyle();
noFill();
stroke(255);
strokeWeight(3);
translate(areaX, areaY);
rect(0, 0, areaXSize, areaYSize);
popStyle();
popMatrix();
}
}
enum MouseMode {
NORMAL, FREE_CAM
}
class NotificationOverlay {
Game game;
float x, y, z;
float showAtX, showAtY;
String messageHead;
String messageBody;
float partOfWay;
NotificationOverlayState state;
float xSize, ySize;
float xStart, yStart;
NotificationOverlay(String msgHead, String msgBody, float ShowAtX, float ShowAtY, float XStart, float YStart, float XSize, float YSize) {
messageHead = msgHead;
messageBody = msgBody;
showAtX = ShowAtX;
showAtY = ShowAtY;
x = XStart;
y = YStart;
xStart = x;
yStart = y;
partOfWay = 0;
state = NotificationOverlayState.FLY_IN;
xSize = XSize;
ySize = YSize;
game = gameObject;
z = 200;
}
NotificationOverlay(String msgHead, String msgBody, float XSize, float YSize) {
this(msgHead, msgBody, mouse.areaXEnd/2, mouse.areaYEnd/2, 0, mouse.areaYEnd/2, XSize, YSize);
}
NotificationOverlay(String msgHead, String msgBody) {
this(msgHead, msgBody, mouse.areaXEnd/2, mouse.areaYEnd/2, 0, mouse.areaYEnd/2, 400, 300);
xStart-=xSize/2;
}
void actions() {
pushMatrix();
pushStyle();
translate(x, y, z);
translateToMiddleOfRect();
//switchState();
partOfWay += 0.02;
show();
popStyle();
popMatrix();
textSize(normalTextSize);
}
void show() {
scale(2);
fill(0, 150);
rect(0, 0, xSize, ySize);
translate(0, 0, 1);
fill(255);
textSize(30);
text(messageHead, 0, 0, xSize, 40);
textSize(20);
text(messageBody, 0, 40, xSize, ySize-40);
}
void switchState() {
switch(state) {
case FLY_IN:
x = map(partOfWay, 0, 1, xStart, showAtX);
y = map(partOfWay, 0, 1, yStart, showAtY);
if (partOfWay > 1) state = NotificationOverlayState.DISPLAY;
break;
case DISPLAY:
if (mouse.clicked()) {
partOfWay = 0;
state = NotificationOverlayState.FLY_OUT;
}
break;
case FLY_OUT:
x = map(partOfWay, 0, 1, showAtX, showAtX + (showAtX - xStart));
y = map(partOfWay, 0, 1, showAtY, showAtY + (showAtY - yStart));
if (partOfWay > 1) delete();
break;
}
}
void translateToMiddleOfRect() {
translate(-xSize/2, -ySize/2);
}
void delete() {
game.notifications.remove(this);
}
}
enum NotificationOverlayState {
FLY_IN, DISPLAY, FLY_OUT
}
class Plane extends Tower {
float aimPointX, aimPointY;
float speed;
int framesOutsideWindow;
int flyOver;
Plane(float X, float Y) {
super(mouse.areaX, mouse.areaY, 0, new Bullet(), UNLIMITED_RANGE, TargetMode.RANDOM_ENEMY);
aimPointX = X;
aimPointY = Y;
flyOver = 1;
speed = 5;
angle = getAngle(aimPointX, aimPointY);
z = 200;
}
void shoot() {
if (readyToShoot() && checkForEnemiesInRange()) {
for (int i=0; i<20; i++) {
Enemy target;
switch(mode) {
case RANDOM_ENEMY:
target = game.enemies.get(round(random(game.enemies.size()-1)));
game.bullets.add(new Bullet(new PVector(x, y, z), getAngle(getSmartAimingPoint(target)), 0, this, target));
break;
case NEAREST_ENEMY:
target = returnNearestEnemy(game.enemies);
game.bullets.add(new Bullet(new PVector(x, y, z), getAngle(getSmartAimingPoint(target)), 0, this, target));
break;
case FIRST_ENEMY:
target = game.enemies.get(0);
game.bullets.add(new Bullet(new PVector(x, y, z), getAngle(getSmartAimingPoint(target)), 0, this, target));
break;
}
}
setTimer();
}
}
@Override
void turn() { //Das "Flugzeug" dreht sich nicht, sondern bewegt sich, trotzdem wird, um eine Verzweigung beim "Abarbeiten" der Türme zu vermeiden, die gleiche Funktion verwendet
move();
}
void move() {
x+=cos(angle)*speed;
y+=sin(angle)*speed;
if (!isInsideRect(mouse.areaX, mouse.areaY, mouse.areaXSize, mouse.areaYSize)) {
if (framesOutsideWindow>60) {
flyOver++;
if (flyOver==2) {
x=mouse.areaXEnd;
y=mouse.areaY;
angle = getAngle(aimPointX, aimPointY);
} else delete();
} else framesOutsideWindow++;
}
}
void delete() {
game.towers.remove(this);
}
}
class PointToDefend extends PositionedObject {
Healthbar hbar;
float health;
float maxHealth;
color goodColor, badColor;
PointToDefend(float X, float Y, float Health) {
setGame();
x = X;
y = Y;
maxHealth = Health;
health = Health;
hbar = new Healthbar(50, this, color(255, 255), color(0, 255), color(255, 0, 0, 100), color(0, 0));
goodColor = color(0, 255, 0, 200);
badColor = color(255, 0, 0, 100);
}
String loseString(){
return "You lost after "+ (round(millis()/1000)-round(game.gameStarted/1000))+" seconds.\nIn this game, "+game.totalEnemies+" enemies appeared, "+game.totalBullets+" bullets were fired and you bought "+game.totalTowers+"towers in the shop.";
}
void show() {
pushMatrix();
pushStyle();
translate(x, y, z);
noStroke();
fill(lerpColor(goodColor, badColor, map(health, maxHealth, 0, 0, 1)));
sphere(20);
if (getDistance(returnNearestEnemy(game.enemies))<20) {
Enemy enemy = returnNearestEnemy(game.enemies);
health-=enemy.damage;
game.enemies.remove(enemy);
}
rotateX(-PI/2);
hbar.show();
if (health<=0) {
game.notifications.add(new NotificationOverlay("You lose.",loseString()));
game.setup();
}
popStyle();
popMatrix();
}
}
abstract class PositionedObject { //Superclass für die anderen Objekte, abstract --> soll keine Objekte erzeugen können
float x, y, z = 0;
float angle;
Game game; //(Fast) Jedes Objekt hat das Game-Objekt gespeichert, um auf die darin enthaltenen Listen etc. zugreifen zu können
void setGame() {
game = gameObject;
}
void show() { //for debugging
pushMatrix();
translate(x, y, z);
ellipse(0, 0, 10, 10);
popMatrix();
}
void drawLineTo(PositionedObject po) { //for debugging
if (po==null) return;
pushStyle();
stroke(255, 0, 0);
line(x, y, po.x, po.y);
popStyle();
}
void drawAngleLine() { //for debugging
pushStyle();
stroke(0, 0, 255);
line(x, y, x+cos(angle)*1000, y+sin(angle)*1000);
popStyle();
}
void moveTo(float X, float Y) {
x = X;
y = Y;
}
void moveForward(float dist) {
x+=cos(angle)*dist;
y+=sin(angle)*dist;
}
/*
void moveForward3D(float dist, float upAngle) {
x+=cos(radians(angle)) * sin(radians(upAngle))*dist;
y+=sin(radians(angle)) * sin(radians(upAngle ))*dist;
z+=cos(radians(upAngle + 90))*dist;
}
*/
void moveForward3D(float dist, float upAngle) {
x+=cos(angle) * sin(upAngle)*dist;
y+=sin(angle) * sin(upAngle)*dist;
z+=cos(upAngle)*dist;
}
void easeRotationTo(PositionedObject po, float partOfDifference) {
if (po==null) return;
easeRotation(getAngle(po), partOfDifference);
}
void rotateTo(PositionedObject po) {
if (po==null) return;
angle = getAngle(po);
}
void moveToObject(PositionedObject po) {
if (po==null) return;
this.x = po.x;
this.y = po.y;
}
float getAngle(float x, float y) {
return atan2(y-this.y, x-this.x);
}
float getAngle(PositionedObject po) {
if (po==null)return angle;
return atan2(po.y-this.y, po.x-this.x);
}
float getUpAngle(PositionedObject po) {
if (po==null)return angle;
return acos(po.z-this.z/dist(x,y,z,po.x,po.y,po.z));
}
/*
float getUpAngle(PositionedObject po) {
if (po==null)return angle;
return atan2(po.y-this.y, po.z-this.z);
}
*/
float getDistance(PositionedObject po) {
if (po==null)return 99999999999f;
return dist(x, y, po.x, po.y);
}
float getDistance(float X, float Y) {
return dist(x, y, X, Y);
}
void easeRotation(float goal, float amt) {
if (goal-angle<-PI) angle-=TWO_PI;
else if (goal-angle>PI) angle+=TWO_PI;
angle = angle+amt*(goal-angle);
}
Enemy returnNearestEnemy(ArrayList<Enemy> eList) {
Enemy e = null; //Der aktuell nächste Gegner wird hier gespeichert
if (eList==null) return null;
for (Enemy lE : eList) {
if (e==null) e = lE;
else if (dist(lE.x, lE.y, this.x, this.y)<dist(e.x, e.y, this.x, this.y)) e = lE; //Wenn der Gegner von der Liste näher ist als der gespeicherte oder wenn dieser noch nicht initialisiert ist, speichere den Gegner von der Liste.
}
return e;
}
boolean isInsideRect(float X, float Y, float xSize, float ySize) {
return isPointInsideRect(x, y, X, Y, xSize, ySize);
//return(x>X && x<X+xSize && y>Y && y<Y+ySize);
}
}
class RoundFireTower extends Tower {
RoundFireTower(float X, float Y) {
super(X, Y, 200, new Bullet(), 300);
setGame();
x = X;
y = Y;
timeout = 200;
//timeout = 10;
exampleBullet = new Bullet();
range = 300;
}
RoundFireTower(float X, float Y, float Z) {
this(X, Y);
z = Z;
}
//see comments for alternative method
void shoot() {
if (readyToShoot() && checkForEnemiesInRange()) {
for (int i=0; i<20; i++) { //remove
game.bullets.add(new Bullet(x, y, z, random(TWO_PI), 0, range, this, null));
//bullets.add(new Bullet(x, y, angle+=PI/100));
} //remove
setTimer();
}
}
@Override
void turn() {
//angle+=PI/10;
}
}
class Shop {
float xSize, ySize;
int money;
ArrayList<ShopItem> items = new ArrayList<ShopItem>();
HashMap<ShopItemType, Integer> costs = new HashMap<ShopItemType, Integer>();
HashMap<ShopItemType, String> names = new HashMap<ShopItemType, String>();
HashMap<ShopItemType, Runnable> spawnCommands = new HashMap<ShopItemType, Runnable>();
HashMap<ShopItemType, Runnable> previews = new HashMap<ShopItemType, Runnable>();
ShopItem selected;
ShopMode mode;
Game game;
Shop() {
setGame();
setPreviews();
setSpawnCommands();
setCosts();
setNames();
mode = ShopMode.SHOW;
money = 10; //change
addItems();
xSize = 250*items.size();//125*items.size();
ySize = 300;
}
void setGame() {
game = gameObject;
}
void addItems() {
items.add(new ShopItem(ShopItemType.NORMAL_TOWER, this));
items.add(new ShopItem(ShopItemType.ROUND_FIRE_TOWER, this));
items.add(new ShopItem(ShopItemType.MISSILE_LAUNCHER, this));
items.add(new ShopItem(ShopItemType.PLANE, this));
}
void select(ShopItem Selected) {
selected = Selected;
}
void setCosts() { //not sure about these prices
costs.put(ShopItemType.NORMAL_TOWER, 10);
costs.put(ShopItemType.ROUND_FIRE_TOWER, 50);
costs.put(ShopItemType.MISSILE_LAUNCHER, 30);
costs.put(ShopItemType.PLANE, 1000);
}
void setNames() {
names.put(ShopItemType.NORMAL_TOWER, "Normal Tower");
names.put(ShopItemType.ROUND_FIRE_TOWER, "Roundfire-Tower");
names.put(ShopItemType.MISSILE_LAUNCHER, "Missile Launcher");
names.put(ShopItemType.PLANE, "Plane");
}
void setSpawnCommands() {
spawnCommands.put(ShopItemType.NORMAL_TOWER, new Runnable() {
public void run() {
gameObject.towers.add(new Tower(mouse.X, mouse.Y, getZ(mouse.X - mouse.areaX, mouse.Y - mouse.areaY)));
}
}
);
spawnCommands.put(ShopItemType.ROUND_FIRE_TOWER, new Runnable() {
public void run() {
gameObject.towers.add(new RoundFireTower(mouse.X, mouse.Y, getZ(mouse.X - mouse.areaX, mouse.Y - mouse.areaY)));
}
}
);
spawnCommands.put(ShopItemType.MISSILE_LAUNCHER, new Runnable() {
public void run() {
gameObject.towers.add(new MissileLauncher(mouse.X, mouse.Y, getZ(mouse.X - mouse.areaX, mouse.Y - mouse.areaY)));
}
}
);
spawnCommands.put(ShopItemType.PLANE, new Runnable() {
public void run() {
gameObject.towers.add(new Plane(mouse.X, mouse.Y));
}
}
);
}
void setPreviews() {
previews.put(ShopItemType.NORMAL_TOWER, new Runnable() {
public void run() {
Tower t = new Tower(mouse.X, mouse.Y, getZ(mouse.X - mouse.areaX, mouse.Y - mouse.areaY));
t.show();
}
}
);
previews.put(ShopItemType.ROUND_FIRE_TOWER, new Runnable() {
public void run() {
Tower t = new RoundFireTower(mouse.X, mouse.Y, getZ(mouse.X - mouse.areaX, mouse.Y - mouse.areaY));
t.angle = frameCount * 0.01;
t.show();
}
}
);
previews.put(ShopItemType.MISSILE_LAUNCHER, new Runnable() {
public void run() {
Tower t = new MissileLauncher(mouse.X, mouse.Y, getZ(mouse.X - mouse.areaX, mouse.Y - mouse.areaY));
t.show();
}
}
);
previews.put(ShopItemType.PLANE, new Runnable() {
public void run() {
Plane p = new Plane(mouse.X, mouse.Y);
p.show();
pushStyle();
fill(255);
stroke(0);
ellipse(p.aimPointX, p.aimPointY, 20, 20);
popStyle();
}
}
);
}
void show() {
pushMatrix();
pushStyle();
switch(mode) {
case SHOW:
PVector translate = saveTranslate(mouse.areaXEnd-xSize, mouse.areaYEnd + 100);
rect(0, 0, xSize, ySize+50);
pushMatrix();
fill(0);
translate(xSize/2, 5);
translate(0, 0, 1);
textSize(40);
text("Money: "+Integer.toString(money), 0, 0, xSize/2, 50);
popMatrix();
translate.add(saveTranslate(0, 60));
for (ShopItem si : items) {
translate.add(saveTranslate(10, 0));
translate(0, 0, 1);
si.show(translate);
translate(0, 0, -1);
translate.add(saveTranslate(xSize/items.size()-10, 0));
}
break;
case PLACE:
pushMatrix();
selected.preview.run();
popMatrix();
pushMatrix();
PVector translate1 = saveTranslate(mouse.areaXEnd-100, mouse.areaYEnd);
fill(255);
rect(0, 0, 100, 70);
fill(0);
textSize(50);
text("Cancel", 0, 0, 100, 70);
if (mouse.clicked()) {
if (!isPointInsideRect(mouse.X, mouse.Y, translate1.x, translate1.y, (xSize/10), (ySize/10) )) {
if (getZ(mouse.X - mouse.areaX, mouse.Y - mouse.areaY) > 5 || selected.sit == ShopItemType.PLANE) selected.purchase();
else {
gameObject.notifications.add(new NotificationOverlay("Geht nicht.", "Türme können nicht so dicht am Weg gesetzt werden."));
break;
}
}
selected = null;
mode = ShopMode.SHOW;
}
popMatrix();
break;
}
popStyle();
popMatrix();
textSize(normalTextSize);
}
}
enum ShopMode {
SHOW, PLACE
}
class ShopItem {
ShopItemType sit;
Shop shop;
int cost;
String name;
Runnable spawnCommand;
Runnable preview;
Game game;
ShopItem(ShopItemType SIT, Shop Shop) {
setGame();
shop = Shop;
sit = SIT;
cost = shop.costs.get(sit);
name = shop.names.get(sit);
spawnCommand = shop.spawnCommands.get(sit);
preview = shop.previews.get(sit);
}
void setGame() {
game = gameObject;
}
boolean mouseOver(PVector translate) {
return isPointInsideRect(mouse.X, mouse.Y, translate.x, translate.y, shop.xSize/shop.items.size()-20, shop.ySize-20);
}
void purchase() {
if (removeMoney(cost)) {
spawnCommand.run();
countTowers();
}
}
boolean removeMoney(int cost) {
if (shop.money>=cost) {
shop.money-=cost;
return true;
} else return false;
}
void show(PVector translate) {
pushMatrix();
pushStyle();
fill(mouseOver(translate) ? ((shop.money>=cost) ? color(200) : color(255, 0, 0)) : color(255) );
rect(0, 0, shop.xSize/shop.items.size()-20, shop.ySize-20);
if (mouseOver(translate) && mouse.clicked() && shop.money>=cost) {
shop.select(this);
shop.mode = ShopMode.PLACE;
}
translate(5, 10);
fill(0);
translate(0, 0, 1);
textSize(40);
text(shop.costs.get(sit)+"\n"+shop.names.get(sit), 0, 0, shop.xSize/shop.items.size()-25, shop.ySize-20);
popStyle();
popMatrix();
}
}
enum ShopItemType {
NORMAL_TOWER, ROUND_FIRE_TOWER, MISSILE_LAUNCHER, PLANE
}
class TerrainGenerator extends Thread {
PShape terrain;
boolean done;
float scale;
TerrainGenerator() {
done = false;
}
@Override
void run() {
createTerrain();
}
void setScale(float Scale) {
scale = Scale;
}
void createTerrain() {
while (true) {
if ( gameObject != null && gameObject.path != null) {
println("terrainGen start");
terrain = create3dPath(gameObject.path, scale);
done = true;
return;
}
}
}
PShape getTerrain() {
done = false;
return terrain;
}
}
class Tower extends PositionedObject {
float timeout; //millis
float lastShot;
Bullet exampleBullet; //Eine Kugel, die vom jeweiligen Turm geschossen wird, um mit der Geschwindigkeit den Zielpunkt zu berechnen
float range; //display ellipse when mouseOver
static final int UNLIMITED_RANGE = 999999999; //Statische Variable, damit darauf zugegriffen werden kann, ohne ein Objekt zu erstellen
TargetMode mode;
Tower(float X, float Y, float Z, float Timeout, Bullet ExampleBullet, float Range, TargetMode Mode) {
setGame();
x = X;
y = Y;
z = Z;
timeout = Timeout;
exampleBullet = ExampleBullet;
range = Range;
mode = Mode;
}
Tower(float X, float Y, float Timeout, Bullet ExampleBullet, float Range, TargetMode Mode) {
this(X, Y, 0, Timeout, ExampleBullet, Range, Mode);
}
Tower(float X, float Y, float Z, float Timeout, Bullet ExampleBullet, float Range) {
this(X, Y, Z, Timeout, ExampleBullet, Range, TargetMode.NEAREST_ENEMY);
}
Tower(float X, float Y, float Timeout, Bullet ExampleBullet, float Range) {
this(X, Y, Timeout, ExampleBullet, Range, TargetMode.NEAREST_ENEMY);
}
Tower(float X, float Y, float Z) {
this(X, Y, Z, 50, new Bullet(), UNLIMITED_RANGE, TargetMode.NEAREST_ENEMY);
}
Tower(float X, float Y) {
this(X, Y, 50, new Bullet(), UNLIMITED_RANGE, TargetMode.NEAREST_ENEMY);
}
void actions() {
turn();
shoot();
}
void show() {
pushStyle();
pushMatrix();
translate(x, y, z);
rotate(angle);
fill(255);
noStroke();
//ellipse(0, 0, 50, 50);
sphere(25);
fill(255, 100);
sphere(checkForEnemiesInRange() ? map(sin(map(frameCount%30, 0, 30, 0, TWO_PI)), -1, 1, 25, 35) : map(sin(map(frameCount%60, 0, 60, 0, TWO_PI)), -1, 1, 25, 35));
translate(0, -5);
stroke(0);
fill(255);
rect(0, 0, 50, 10);
popMatrix();
popStyle();
if (mouseOver()) showRange();
}
void showRange() {
pushStyle();
fill(255, 0, 0, 100);
noStroke();
pushMatrix();
translate(x, y, z);
if (range < Tower.UNLIMITED_RANGE && (range < dist(0, 0, mouse.areaXSize, mouse.areaYSize))) sphere(range);
popMatrix();
popStyle();
}
boolean mouseOver() {
return getDistance(mouse.X, mouse.Y) < 30;
}
boolean readyToShoot() {
return millis()>lastShot+timeout;
}
void setTimer() {
lastShot = millis();
}
void shoot() {
if (readyToShoot() && checkForEnemiesInRange()) {
game.bullets.add(new Bullet(x, y, z, angle+=random(-PI/100, PI/100), 0, range, this, selectEnemy()));
setTimer();
}
}
boolean checkForEnemiesInRange() {
if (game.enemies.size()>0) {
ArrayList<Enemy> enemiesInRange = new ArrayList<Enemy>();
for (Enemy e : game.enemies) if (getDistance(e)<=range+10) enemiesInRange.add(e);
if (enemiesInRange.size()>0) return true;
else return false;
} else return false;
}
Waypoint getSmartAimingPoint(Enemy e) {
if (e==null)return null;
float X = e.x+cos(e.angle)*e.speed*getDistance(e)/exampleBullet.speed;
float Y = e.y+sin(e.angle)*e.speed*getDistance(e)/exampleBullet.speed;
return new Waypoint(X, Y);
}
void turnTowardsEnemy(Enemy e) {
easeRotationTo(getSmartAimingPoint(e), 0.1); //maybe adjust easing speed
}
void turn() {
if (game.enemies.size()==0) return;
turnTowardsEnemy(selectEnemy());
}
Enemy selectEnemy() {
Enemy e;
if (game.enemies.size()==0) return null;
switch(mode) {
case NEAREST_ENEMY:
e = returnNearestEnemy(game.enemies);
break;
case FIRST_ENEMY:
e = game.enemies.get(0);
break;
case RANDOM_ENEMY:
e = game.enemies.get(round(random(game.enemies.size()-1)));
break;
default:
e = returnNearestEnemy(game.enemies);
}
return e;
}
}
enum TargetMode {
RANDOM_ENEMY, NEAREST_ENEMY, FIRST_ENEMY
}
void resetMouse() {
if (mouseX >= width-1) {
robot.mouseMove(1, mouseY);
} else if (mouseX <= 0) {
robot.mouseMove(width-2, mouseY);
}
if (mouseY >= height-1) {
robot.mouseMove(mouseX, 1);
} else if (mouseY <= 0) {
robot.mouseMove(mouseX, height-2);
}
}
float ease(float val, float amt, float goal) { //angleichen/annähern (--> "easing")
return val+amt*(goal-val);
}
PVector saveTranslate(float x, float y) { //Um (z.B. für mouseOver-Tests) mit den translate-Werten weiter rechnen zu können
translate(x, y);
return new PVector(x, y);
}
boolean isPointInsideRect(float x, float y, float rectX, float rectY, float xSize, float ySize) { //Test, ob sich ein Punkt innerhalb des angegebenen Rechtecks befindet
//ellipse(x,y,10,10);
//rect(rectX,rectY,xSize,ySize); //visualization
return(x>rectX && x<rectX+xSize && y>rectY && y<rectY+ySize);
}
float getAngle(float x1, float y1, float x2, float y2) { //Berechnet den Winkel zwischen zwei Punkten
return atan2(y2-y1, x2-x1);
}
boolean circleCollision(float x1, float y1, float rad1, float x2, float y2, float rad2) { //Testet, ob sich zwei Kreise überschneiden
return dist(x1, y1, x2, y2)>rad1+rad2;
}
boolean rectCollision(float x1, float y1, float xSize1, float ySize1, float x2, float y2, float xSize2, float ySize2) { //Testet, ob sich zwei Rechtecke überschneiden
return abs((x1+xSize1/2)-(x2+xSize2/2))>xSize1/2+xSize2/2 || abs((y1+ySize1/2)-(y2+ySize2/2))>ySize1/2+ySize2/2;
}
boolean boxCollision(float x1, float y1, float z1, float xSize1, float ySize1, float zSize1, float x2, float y2, float z2, float xSize2, float ySize2, float zSize2) { //Testet, ob sich zwei Quader überschneiden
return abs((x1+xSize1/2)-(x2+xSize2/2))>xSize1/2+xSize2/2 || abs((y1+ySize1/2)-(y2+ySize2/2))>ySize1/2+ySize2/2 || abs((z1+zSize1/2)-(z2+zSize2/2))>zSize1/2+zSize2/2; //untested, but should work
}
PShape calcPath(ArrayList<Waypoint> way) { //Erzeugt ein PShape-Objekt, was den Pfad darstellt, welchen die Gegner zurücklegen
PShape path = createShape();
Enemy e = new Enemy(way);
path.beginShape();
path.noFill();
path.strokeWeight(50);
path.stroke(255, 0, 0, 150);
int stepsForLastPoint = 0; //Zähler für Schleifendurchläufe bis zum Erreichen eines Punktes
while (e.path.size()>0) {
if (stepsForLastPoint>100) { //Es dauert zu lange, bis der nachste Punkt erreicht wird
Waypoint problem = e.path.get(0); //Den nächsten Punkt auswählen, er ist vermutlich der Auslöser des Problems
e.path.remove(problem); //Den Punkt aus der temporären Liste entfernen
way.remove(problem); //Den Punkt aus der globalen Liste entfernen
if (e.path.size()==0) break;
}
int pathSize = e.path.size();
path.vertex(e.x, e.y);
for (int i=0; i<20; i++) e.move();
if (e.path.size() != pathSize) stepsForLastPoint = 0; //Wenn der nächste Punkt erreicht wurde, Zähler zurücksetzen
else stepsForLastPoint++;
}
path.endShape();
return path;
}
ArrayList<Waypoint> createWayList(float ... coords) { //beliebig viele Parameter
if (coords.length % 2 != 0) return null; //Es müssen jeweils ein X- und ein Y-Wert zusammengehören!
float[] xList = new float[coords.length/2]; //Listen für X- und Y-Werte erstellen
float[] yList = new float[coords.length/2];
for (int i = 0; i<coords.length/2; i++) {
xList[i] = coords[i*2];
yList[i] = coords[i*2+1];
}
if (xList.length != yList.length) return null;
ArrayList<Waypoint> list = new ArrayList<Waypoint>();
for (int i = 0; i<xList.length; i++) list.add(new Waypoint(xList[i], yList[i]));
return list;
}
void printWayPoints(ArrayList<Waypoint> way) {
String pointString = "";
for (int i = 0; i<way.size(); i++) {
float coordX = way.get(i).x;
float coordY = way.get(i).y;
pointString += Float.toString(coordX) + ",";
pointString += Float.toString(coordY);
if (i != way.size()-1) pointString += ",";
}
println("ways.add(createWayList("+pointString+"));");
}
ArrayList<Waypoint> rescaleWay(ArrayList<Waypoint> way, float oldX, float oldY, float oldXS, float oldYS, float newX, float newY, float newXS, float newYS) { //Skaliert einen bestehenden Weg/Pfad neu, der in einer anderen Größe erstellt wurde
ArrayList<Waypoint> newWay = new ArrayList<Waypoint>();
for (Waypoint wp : way) {
newWay.add(new Waypoint((wp.x-oldX)*newXS/oldXS+newX, (wp.y-oldY)*newYS/oldYS+newY));
}
return newWay;
}
PShape create3dPath(PShape Path, float scale) {
PShape path3d = createShape(GROUP);
for (float i = 0; i<mouse.areaYSize/scale; i++) {
PShape pathRow = createShape();
pathRow.beginShape(QUAD_STRIP);
pathRow.noStroke();
for (float j = 0; j<=mouse.areaXSize/scale; j++) {
float x, y, z;
x = mouse.areaX+j*scale;
y = mouse.areaY+i*scale;
z = calcPathZ(x, y, Path);
pathRow.fill(z < 0 ? 50 : lerpColor(color(100), color(255), map(z, 0, pow(100, 1.1), 0, 1)));
pathRow.vertex(x, y, z);
x = mouse.areaX+(j)*scale;
y = mouse.areaY+(i+1)*scale;
z = calcPathZ(x, y, Path);
pathRow.fill(z < 0 ? 50 : lerpColor(color(100), color(255), map(z, 0, pow(100, 1.1), 0, 1)));
pathRow.vertex(x, y, z);
}
pathRow.endShape();
path3d.addChild(pathRow);
}
return path3d;
}
float calcPathZ(float x, float y, PShape Path) {
float z;
if (Path != null && Path.getVertexCount() > 0) {
float distToPath = 999999;
for (int p = 0; p<Path.getVertexCount(); p++) {
float distToVertex = dist(Path.getVertexX(p), Path.getVertexY(p), x, y);
if (distToVertex<distToPath) distToPath = distToVertex;
}
z = constrain(map(distToPath, 0, dist(0, 0, mouse.areaXSize, mouse.areaYSize), -10, 500), -10, 100);
z = pow(z+10, 1.1)-10;
} else z = -5;
return z;
}
float[][] createHeightMap(PShape Path) {
float[][] map = new float[round(mouse.areaXSize)][round(mouse.areaYSize)];
for (int i = 0; i<mouse.areaYSize; i++) {
for (int j = 0; j<mouse.areaXSize; j++) {
float x, y, z;
x = mouse.areaX+j;
y = mouse.areaY+i;
z = calcPathZ(x, y, Path);
map[j][i] = z;
}
}
return map;
}
PShape makeGround() {
PShape Ground = createShape(GROUP);
float xSize = 20000,
ySize = 20000;
float scale = 100;
for (float i = 0; i<ySize/scale; i++) {
float alpha = 75; //map(noise(frameCount/10),0,1,0,255); //50; //map(i,0,ySize/scale-1,100,200);
PShape row = createShape();
row.beginShape(QUAD_STRIP);
row.noStroke();
for (float j = 0; j<=xSize/scale; j++) {
for (int iOff = 0; iOff<=1; iOff++) {
float x, y, z;
x = j*scale;
y = (i+iOff)*scale;
z = map(noise(x/1000, y/1000), 0, 1, -500, 500);
row.fill(random(255), random(255), random(255), alpha);
row.vertex(x, y, z);
}
row.rotateX(PI/100);
}
row.endShape();
Ground.addChild(row);
}
return Ground;
}
float getZ(float X, float Y) {
float[][] heightMap = gameObject.heightMap;
if (heightMap == null) return 10;
int x = round(X);
int y = round(Y);
x = constrain(x, 0, heightMap.length-1);
y = constrain(y, 0, heightMap[x].length-1);
return heightMap[round(x)][round(y)];
}
void testForKeys() {
if (keyCode == TAB) mouse.mode = mouse.mode == MouseMode.FREE_CAM ? MouseMode.NORMAL : MouseMode.FREE_CAM; //Free-Cam-Modus umschalten
if (key =='p') gameObject.state = gameObject.state == GameState.PAUSED ? GameState.RUNNING : gameObject.state == GameState.RUNNING ? GameState.PAUSED : gameObject.state; //Pause umschalten, sonst nichts ändern
}
void countBullets() {
gameObject.totalBullets++;
}
void countTowers() {
gameObject.totalTowers++;
}
void countEnemies() {
gameObject.totalEnemies++;
}
class Waypoint extends PositionedObject {
Waypoint(float X, float Y) {
setGame();
x = X;
y = Y;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment