com.agateau.pixelwheels.bonus.Missile.java Source code

Java tutorial

Introduction

Here is the source code for com.agateau.pixelwheels.bonus.Missile.java

Source

/*
 * Copyright 2018 Aurlien Gteau <mail@agateau.com>
 *
 * This file is part of Pixel Wheels.
 *
 * Tiny Wheels is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.agateau.pixelwheels.bonus;

import com.agateau.pixelwheels.Assets;
import com.agateau.pixelwheels.Constants;
import com.agateau.pixelwheels.GameWorld;
import com.agateau.pixelwheels.ZLevel;
import com.agateau.pixelwheels.debug.DebugShapeMap;
import com.agateau.pixelwheels.gameobjet.AnimationObject;
import com.agateau.pixelwheels.gameobjet.AudioClipper;
import com.agateau.pixelwheels.gameobjet.Explosable;
import com.agateau.pixelwheels.gameobjet.GameObjectAdapter;
import com.agateau.pixelwheels.racer.Racer;
import com.agateau.pixelwheels.racer.Vehicle;
import com.agateau.pixelwheels.racescreen.Collidable;
import com.agateau.pixelwheels.racescreen.CollisionCategories;
import com.agateau.pixelwheels.sound.AudioManager;
import com.agateau.pixelwheels.sound.SoundPlayer;
import com.agateau.pixelwheels.utils.BodyRegionDrawer;
import com.agateau.pixelwheels.utils.Box2DUtils;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.Joint;
import com.badlogic.gdx.physics.box2d.Manifold;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.physics.box2d.joints.WeldJointDef;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.Pool;
import com.badlogic.gdx.utils.ReflectionPool;

/**
 * A player bullet
 */
public class Missile extends GameObjectAdapter implements Collidable, Pool.Poolable, Disposable, Explosable {
    private static final ReflectionPool<Missile> sPool = new ReflectionPool<Missile>(Missile.class);

    private static final float WIDTH = 44;
    private static final float HEIGHT = 13;
    private static final float DURATION = 3;

    private static final float LOCK_DISTANCE = 40;
    private static final float LOCK_ARC = 120;
    private static final float WAITING_DENSITY = 0.0000001f;
    private static final float SHOT_DENSITY = 0.0001f;
    private static final Color TARGETED_COLOR = new Color(1, 1, 1, 0.7f);
    private static final Color LOCKED_COLOR = new Color(1, 0.3f, 0.3f, 0.9f);
    private SoundPlayer mSoundPlayer;

    enum Status {
        WAITING, SHOT, LOCKED
    }

    // Init-once fields
    private final BodyDef mBodyDef = new BodyDef();
    private final WeldJointDef mJointDef = new WeldJointDef();
    private final PolygonShape mShape = new PolygonShape();
    private final BodyRegionDrawer mDrawer = new BodyRegionDrawer();
    private final ClosestRacerFinder mRacerFinder = new ClosestRacerFinder(LOCK_DISTANCE, LOCK_ARC);
    private final MissileGuidingSystem mGuidingSystem = new MissileGuidingSystem();
    private Assets mAssets;

    private final DebugShapeMap.Shape mDebugShape = new DebugShapeMap.Shape() {
        @Override
        public void draw(ShapeRenderer renderer) {
            renderer.begin(ShapeRenderer.ShapeType.Line);
            renderer.setColor(1, 0, 0, 1);

            Vector2 origin = mBody.getWorldCenter();
            float angle = mBody.getAngle() * MathUtils.radDeg;
            renderer.line(origin, mRacerFinder.getLeftVertex(origin, angle));
            renderer.line(origin, mRacerFinder.getRightVertex(origin, angle));
            renderer.end();
        }
    };

    // Init-at-pool-reuse fields
    private GameWorld mGameWorld;
    private AudioManager mAudioManager;
    private Racer mShooter;
    private Body mBody;

    // Moving fields
    private float mTime;
    private Joint mJoint;
    private Status mStatus;
    private boolean mNeedShootSound;
    private Racer mTarget;

    public Missile() {
        mBodyDef.type = BodyDef.BodyType.DynamicBody;
        mBodyDef.bullet = true;
        mShape.setAsBox(WIDTH * Constants.UNIT_FOR_PIXEL / 2, HEIGHT * Constants.UNIT_FOR_PIXEL / 2);
    }

    public static Missile create(Assets assets, GameWorld gameWorld, AudioManager audioManager, Racer shooter) {
        Missile object = sPool.obtain();
        object.mAssets = assets;
        object.mGameWorld = gameWorld;
        object.mAudioManager = audioManager;
        object.setFinished(false);
        object.mRacerFinder.setIgnoredRacer(shooter);
        Vehicle vehicle = shooter.getVehicle();
        object.mShooter = shooter;
        object.mBodyDef.position.set(vehicle.getX(), vehicle.getY());
        object.mBodyDef.angle = vehicle.getAngle() * MathUtils.degRad;

        object.mBody = gameWorld.getBox2DWorld().createBody(object.mBodyDef);
        object.mBody.createFixture(object.mShape, WAITING_DENSITY);
        object.mBody.setUserData(object);
        Box2DUtils.setCollisionInfo(object.mBody, CollisionCategories.RACER_BULLET,
                CollisionCategories.WALL | CollisionCategories.RACER | CollisionCategories.EXPLOSABLE);

        object.mStatus = Status.WAITING;
        object.mNeedShootSound = false;
        object.mTarget = null;
        object.initJoint();

        object.mGuidingSystem.init(object.mBody);

        gameWorld.addGameObject(object);

        DebugShapeMap.put(object, object.mDebugShape);

        return object;
    }

    public boolean hasTarget() {
        return mTarget != null;
    }

    private void initJoint() {
        Vehicle vehicle = mShooter.getVehicle();
        Body vehicleBody = vehicle.getBody();
        mJointDef.bodyA = vehicleBody;
        mJointDef.bodyB = mBody;
        mJointDef.localAnchorA.set(vehicleBody.getLocalCenter());
        mJointDef.localAnchorB.set(mBody.getLocalCenter());
        mJoint = mGameWorld.getBox2DWorld().createJoint(mJointDef);
    }

    public void shoot() {
        resetJoint();
        mBody.getFixtureList().first().setDensity(SHOT_DENSITY);
        mBody.resetMassData();
        mBody.setAngularVelocity(0);
        mStatus = Status.SHOT;
        mTime = 0;
        mNeedShootSound = true;
    }

    @Override
    public void reset() {
        // Do not reset the joint here: it crashes for some reason.
        // The joint is deleted when the body is destroyed anyway.
        mGameWorld.getBox2DWorld().destroyBody(mBody);
        mBody = null;
        DebugShapeMap.remove(this);
    }

    private void resetJoint() {
        if (mJoint != null) {
            mGameWorld.getBox2DWorld().destroyJoint(mJoint);
            mJoint = null;
        }
    }

    @Override
    public void dispose() {
        sPool.free(this);
    }

    @Override
    public void act(float delta) {
        switch (mStatus) {
        case WAITING:
            actWaiting();
            break;
        case SHOT:
            actShot(delta);
            break;
        case LOCKED:
            actLocked(delta);
            break;
        }
    }

    private void actWaiting() {
        findTarget();
    }

    private void actShot(float delta) {
        findTarget();
        if (mTarget != null) {
            mStatus = Status.LOCKED;
        }
        mGuidingSystem.act(null);
        consumeTime(delta);
    }

    private void actLocked(float delta) {
        mGuidingSystem.act(mTarget.getPosition());
        consumeTime(delta);
    }

    private void consumeTime(float delta) {
        mTime += delta;
        if (mTime >= DURATION) {
            explode();
        }
    }

    private void findTarget() {
        World world = mGameWorld.getBox2DWorld();
        mTarget = mRacerFinder.find(world, mBody.getWorldCenter(), mBody.getAngle() * MathUtils.radDeg);
    }

    @Override
    public void draw(Batch batch, ZLevel zLevel) {
        if (zLevel == ZLevel.FLYING) {
            // Draw the shadow at Z_FLYING so that the shadow is drawn *over* its vehicle
            drawShadow(batch);
            drawMissile(batch);
            drawTarget(batch);
            if (mStatus != Status.WAITING) {
                drawReactorFire(batch);
            }
        }
    }

    private void drawTarget(Batch batch) {
        if (mTarget == null) {
            return;
        }
        batch.setColor(mStatus == Status.LOCKED ? LOCKED_COLOR : TARGETED_COLOR);
        TextureRegion region = mAssets.target;
        float w = Constants.UNIT_FOR_PIXEL * region.getRegionWidth();
        float h = Constants.UNIT_FOR_PIXEL * region.getRegionHeight();
        batch.draw(region, mTarget.getX() - w / 2, mTarget.getY() - h / 2, w, h);
        batch.setColor(Color.WHITE);
    }

    private void drawMissile(Batch batch) {
        mDrawer.setBatch(batch);
        mDrawer.draw(mBody, mAssets.missile);
    }

    private void drawReactorFire(Batch batch) {
        TextureRegion region = mAssets.turboFlame.getKeyFrame(mTime, true);
        Vector2 center = mBody.getPosition();
        float angle = mBody.getAngle();
        float w = Constants.UNIT_FOR_PIXEL * region.getRegionWidth();
        float h = Constants.UNIT_FOR_PIXEL * region.getRegionHeight();
        float refH = Constants.UNIT_FOR_PIXEL * -WIDTH / 2;
        float x = center.x + refH * MathUtils.cos(angle);
        float y = center.y + refH * MathUtils.sin(angle);
        batch.draw(region, x - w / 2, y - h, // pos
                w / 2, h, // origin
                w, h, // size
                1, 1, // scale
                angle * MathUtils.radDeg - 90);
    }

    private void drawShadow(Batch batch) {
        mDrawer.setBatch(batch);
        mDrawer.drawShadow(mBody, mAssets.missile);
    }

    @Override
    public void audioRender(AudioClipper clipper) {
        if (mNeedShootSound) {
            mSoundPlayer = mAudioManager.createSoundPlayer(mAssets.soundAtlas.get("missile"));
            mSoundPlayer.setVolume(clipper.clip(this));
            mSoundPlayer.play();

            mNeedShootSound = false;
        }
    }

    @Override
    public float getX() {
        return mBody.getPosition().x;
    }

    @Override
    public float getY() {
        return mBody.getPosition().y;
    }

    public void remove() {
        setFinished(true);
    }

    @Override
    public void explode() {
        Vector2 pos = mBody.getPosition();
        AnimationObject obj = mAssets.createExplosion(mAudioManager, pos.x, pos.y);
        mGameWorld.addGameObject(obj);
        mSoundPlayer.stop();
        setFinished(true);
    }

    @Override
    public void beginContact(Contact contact, Fixture otherFixture) {
    }

    @Override
    public void endContact(Contact contact, Fixture otherFixture) {
    }

    @Override
    public void preSolve(Contact contact, Fixture otherFixture, Manifold oldManifold) {
        if (isFinished()) {
            return;
        }
        if (mStatus == Status.WAITING) {
            contact.setEnabled(false);
            return;
        }
        Object other = otherFixture.getBody().getUserData();
        if (other == mShooter) {
            contact.setEnabled(false);
            return;
        }

        explode();
        if (other instanceof Racer) {
            ((Racer) other).spin();
        } else if (other instanceof Explosable) {
            ((Explosable) other).explode();
        }
    }

    @Override
    public void postSolve(Contact contact, Fixture otherFixture, ContactImpulse impulse) {

    }
}