/* * The MIT License (MIT) * * FXGL - JavaFX Game Library * * Copyright (c) 2015-2017 AlmasB (almaslvl@gmail.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.almasb.fxglgames.spaceinvaders; import com.almasb.fxgl.annotation.SetEntityFactory; import com.almasb.fxgl.annotation.Spawns; import com.almasb.fxgl.app.FXGL; import com.almasb.fxgl.ecs.Entity; import com.almasb.fxgl.entity.*; import com.almasb.fxgl.entity.component.CollidableComponent; import com.almasb.fxgl.entity.control.ExpireCleanControl; import com.almasb.fxgl.entity.control.OffscreenCleanControl; import com.almasb.fxgl.entity.control.ProjectileControl; import com.almasb.fxgl.physics.BoundingShape; import com.almasb.fxgl.physics.HitBox; import com.almasb.fxgl.physics.PhysicsComponent; import com.almasb.fxgl.service.AssetLoader; import com.almasb.fxgl.texture.Texture; import com.almasb.fxglgames.spaceinvaders.component.HPComponent; import com.almasb.fxglgames.spaceinvaders.component.InvincibleComponent; import com.almasb.fxglgames.spaceinvaders.component.OwnerComponent; import com.almasb.fxglgames.spaceinvaders.component.SubTypeComponent; import com.almasb.fxglgames.spaceinvaders.control.*; import com.google.inject.Singleton; import javafx.geometry.Point2D; import javafx.scene.effect.BlendMode; import javafx.scene.effect.DropShadow; import javafx.scene.effect.Glow; import javafx.scene.paint.Color; import javafx.scene.text.Text; import javafx.util.Duration; import org.jbox2d.dynamics.BodyType; import org.jbox2d.dynamics.FixtureDef; import java.util.Random; import static com.almasb.fxglgames.spaceinvaders.Config.LEVEL_START_DELAY; /** * @author Almas Baimagambetov (AlmasB) (almaslvl@gmail.com) */ @SetEntityFactory @Singleton public final class SpaceInvadersFactory implements EntityFactory { private static final AssetLoader assetLoader = FXGL.getAssetLoader(); private static final Random random = new Random(); @Spawns("Background") public Entity newBackground(SpawnData data) { return Entities.builder() .viewFromNode(assetLoader.loadTexture("background/background.png", Config.WIDTH, Config.HEIGHT)) .renderLayer(RenderLayer.BACKGROUND) .build(); } @Spawns("Meteor") public Entity newMeteor(SpawnData data) { double w = FXGL.getSettings().getWidth(); double h = FXGL.getSettings().getHeight(); double x = 0, y = 0; // these are deliberately arbitrary to create illusion of randomness if (random.nextBoolean()) { // left or right if (random.nextBoolean()) { x = -50; } else { x = w + 50; } y = random.nextInt((int)h); } else { // top or bot if (random.nextBoolean()) { y = -50; } else { y = h + 50; } x = random.nextInt((int) w); } GameEntity meteor = new GameEntity(); meteor.getPositionComponent().setValue(x, y); String textureName = "background/meteor" + (random.nextInt(4) + 1) + ".png"; meteor.getViewComponent().setTexture(textureName); meteor.getViewComponent().setRenderLayer(new RenderLayer() { @Override public String name() { return "METEORS"; } @Override public int index() { return 1001; } }); meteor.addControl(new MeteorControl()); // add offscreen clean a bit later so that they are not cleaned from start FXGL.getMasterTimer() .runOnceAfter(() -> { meteor.addControl(new OffscreenCleanControl()); }, Duration.seconds(5)); return meteor; } @Spawns("Player") public GameEntity newPlayer(SpawnData data) { Texture texture = assetLoader.loadTexture("player2.png"); texture.setPreserveRatio(true); texture.setFitHeight(40); return Entities.builder() .from(data) .type(SpaceInvadersType.PLAYER) .viewFromNodeWithBBox(texture) .with(new CollidableComponent(true), new InvincibleComponent()) .with(new PlayerControl()) .build(); } @Spawns("Enemy") public Entity newEnemy(SpawnData data) { return Entities.builder() .from(data) .type(SpaceInvadersType.ENEMY) .viewFromNodeWithBBox(assetLoader .loadTexture("enemy" + ((int)(Math.random() * 3) + 1) + ".png") .toAnimatedTexture(2, Duration.seconds(2))) .with(new CollidableComponent(true), new HPComponent(2)) .with(new EnemyControl()) .build(); } @Spawns("Bullet") public Entity newBullet(SpawnData data) { GameEntity owner = data.get("owner"); GameEntity bullet = new GameEntity(); bullet.getTypeComponent().setValue(SpaceInvadersType.BULLET); Point2D center = Entities.getBBox(owner) .getCenterWorld() .add(-8, 20 * (owner.isType(SpaceInvadersType.PLAYER) ? -1 : 1)); bullet.getPositionComponent().setValue(center); bullet.addComponent(new CollidableComponent(true)); bullet.getViewComponent().setView(new EntityView(assetLoader.loadTexture("tank_bullet.png")), true); bullet.addControl(new ProjectileControl(new Point2D(0, owner.isType(SpaceInvadersType.PLAYER) ? -1 : 1), 10 * 60)); bullet.addComponent(new OwnerComponent(Entities.getType(owner).getValue())); bullet.addControl(new OffscreenCleanControl()); bullet.setProperty("dead", false); return bullet; } @Spawns("Laser") public Entity newLaser(SpawnData data) { GameEntity owner = data.get("owner"); GameEntity bullet = new GameEntity(); bullet.getTypeComponent().setValue(SpaceInvadersType.BULLET); Point2D center = Entities.getBBox(owner) .getCenterWorld() .add(-4.5, -20); bullet.getPositionComponent().setValue(center); bullet.getBoundingBoxComponent().addHitBox(new HitBox("HIT", BoundingShape.box(9, 20))); bullet.addComponent(new CollidableComponent(true)); bullet.addComponent(new OwnerComponent(Entities.getType(owner).getValue())); bullet.addControl(new OffscreenCleanControl()); bullet.addControl(new BulletControl(500)); DropShadow shadow = new DropShadow(22, Color.DARKBLUE); shadow.setInput(new Glow(0.8)); EntityView view = new EntityView(); view.addNode(assetLoader.loadTexture("laser1.png")); Texture t = assetLoader.loadTexture("laser2.png"); t.relocate(-2, -20); view.addNode(t); view.setEffect(shadow); bullet.getViewComponent().setView(view); return bullet; } @Spawns("LaserHit") public Entity newLaserHit(SpawnData data) { return Entities.builder() .at(data.getX() - 15, data.getY() - 15) .viewFromNode(assetLoader.loadTexture("laser_hit.png", 15, 15)) .with(new LaserHitControl()) .build(); } @Spawns("Wall") public Entity newWall(SpawnData data) { return Entities.builder() .from(data) .type(SpaceInvadersType.WALL) .viewFromTextureWithBBox("wall.png") .with(new CollidableComponent(true), new HPComponent(7)) .build(); } @Spawns("Bonus") public Entity newBonus(SpawnData data) { BonusType type = data.get("type"); return Entities.builder() .from(data) .type(SpaceInvadersType.BONUS) .viewFromTextureWithBBox(type.textureName) .with(new SubTypeComponent(type), new CollidableComponent(true)) .with(new BonusControl()) .build(); } @Spawns("Explosion") public Entity newExplosion(SpawnData data) { GameEntity explosion = Entities.builder() .at(data.getX() - 40, data.getY() - 40) // texture is 256x256, we want smaller, 80x80 // it has 48 frames, hence 80 * 48 .viewFromNode(assetLoader.loadTexture("explosion.png", 80 * 48, 80).toAnimatedTexture(48, Duration.seconds(2))) .with(new ExpireCleanControl(Duration.seconds(1.8))) .build(); // slightly better looking effect explosion.getView().setBlendMode(BlendMode.ADD); return explosion; } @Spawns("LevelInfo") public Entity newLevelInfo(SpawnData data) { Text levelText = FXGL.getUIFactory().newText("Level " + FXGL.getApp().getGameState().getInt("level"), Color.AQUAMARINE, 44); PhysicsComponent pComponent = new PhysicsComponent(); pComponent.setBodyType(BodyType.DYNAMIC); pComponent.setOnPhysicsInitialized(() -> pComponent.setLinearVelocity(0, 5)); FixtureDef fixtureDef = new FixtureDef(); fixtureDef.setDensity(0.05f); fixtureDef.setRestitution(0.3f); pComponent.setFixtureDef(fixtureDef); GameEntity levelInfo = Entities.builder() .at(FXGL.getAppWidth() / 2 - levelText.getLayoutBounds().getWidth() / 2, 0) .viewFromNodeWithBBox(levelText) .with(pComponent) .with(new ExpireCleanControl(Duration.seconds(LEVEL_START_DELAY))) .build(); levelInfo.setOnActive(() -> { Entities.builder() .at(0, FXGL.getAppHeight() / 2) .bbox(new HitBox("ground", BoundingShape.box(FXGL.getAppWidth(), 100))) .with(new PhysicsComponent()) .with(new ExpireCleanControl(Duration.seconds(LEVEL_START_DELAY))) .buildAndAttach(FXGL.getApp().getGameWorld()); }); return levelInfo; } }