com.agateau.pixelwheels.racer.Racer.java Source code

Java tutorial

Introduction

Here is the source code for com.agateau.pixelwheels.racer.Racer.java

Source

/*
 * Copyright 2017 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.racer;

import com.agateau.pixelwheels.Assets;
import com.agateau.pixelwheels.GamePlay;
import com.agateau.pixelwheels.GameWorld;
import com.agateau.pixelwheels.ZLevel;
import com.agateau.pixelwheels.bonus.Bonus;
import com.agateau.pixelwheels.bonus.BonusPool;
import com.agateau.pixelwheels.gameobjet.AudioClipper;
import com.agateau.pixelwheels.gameobjet.GameObjectAdapter;
import com.agateau.pixelwheels.gamesetup.GameInfo;
import com.agateau.pixelwheels.racescreen.Collidable;
import com.agateau.pixelwheels.racescreen.CollisionCategories;
import com.agateau.pixelwheels.sound.AudioManager;
import com.badlogic.gdx.graphics.g2d.Batch;
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.Contact;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.Manifold;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;

/**
 * A racer
 */
public class Racer extends GameObjectAdapter implements Collidable, Disposable {
    private final GameWorld mGameWorld;
    private final Vehicle mVehicle;
    private final VehicleRenderer mVehicleRenderer;
    private final GroundCollisionHandlerComponent mGroundCollisionHandlerComponent;
    private final SpinningComponent mSpinningComponent;
    private final LapPositionComponent mLapPositionComponent;
    private final AudioComponent mAudioComponent;
    private final Array<Component> mComponents = new Array<Component>();
    private final Array<Collidable> mCollidableComponents = new Array<Collidable>();
    private final GameInfo.Entrant mEntrant;

    private Pilot mPilot;

    // State
    private Bonus mBonus;
    private final RecordRanks mRecordRanks = new RecordRanks();

    public static class RecordRanks {
        public int lapRecordRank = -1;
        public int totalRecordRank = -1;

        public boolean brokeRecord() {
            return lapRecordRank > -1 || totalRecordRank > -1;
        }
    }

    interface Component {
        void act(float delta);
    }

    private class PilotSupervisorComponent implements Component {
        @Override
        public void act(float delta) {
            if (mLapPositionComponent.hasFinishedRace() || mSpinningComponent.isActive()
                    || mGroundCollisionHandlerComponent
                            .getState() != GroundCollisionHandlerComponent.State.NORMAL) {
                mVehicle.setAccelerating(false);
            } else {
                mPilot.act(delta);
            }
        }
    }

    public Racer(Assets assets, AudioManager audioManager, GameWorld gameWorld, Vehicle vehicle,
            GameInfo.Entrant entrant) {
        mGameWorld = gameWorld;
        mLapPositionComponent = new LapPositionComponent(gameWorld.getTrack(), vehicle);
        mSpinningComponent = new SpinningComponent(vehicle);

        mVehicle = vehicle;
        mVehicle.setRacer(this);
        mVehicle.setCollisionInfo(CollisionCategories.RACER, CollisionCategories.WALL | CollisionCategories.RACER
                | CollisionCategories.RACER_BULLET | CollisionCategories.EXPLOSABLE);

        mEntrant = entrant;

        mVehicleRenderer = new VehicleRenderer(assets, mVehicle);
        mGroundCollisionHandlerComponent = new GroundCollisionHandlerComponent(assets, mGameWorld, this,
                mLapPositionComponent);

        PilotSupervisorComponent supervisorComponent = new PilotSupervisorComponent();

        mAudioComponent = new AudioComponent(assets.soundAtlas, audioManager, this);

        addComponent(mLapPositionComponent);
        addComponent(mVehicle);
        addComponent(mGroundCollisionHandlerComponent);
        addComponent(mSpinningComponent);
        addComponent(supervisorComponent);
        addComponent(new BonusSpotHitComponent(this));
        addComponent(mAudioComponent);

        if (GamePlay.instance.createSpeedReport) {
            Probe probe = new Probe("speed.dat");
            mVehicle.setProbe(probe);
            addComponent(probe);
        }
    }

    private void addComponent(Component component) {
        mComponents.add(component);
        if (component instanceof Collidable) {
            mCollidableComponents.add((Collidable) component);
        }
    }

    public RecordRanks getRecordRanks() {
        return mRecordRanks;
    }

    public GameInfo.Entrant getEntrant() {
        return mEntrant;
    }

    public Pilot getPilot() {
        return mPilot;
    }

    public void setPilot(Pilot pilot) {
        mPilot = pilot;
    }

    public Vehicle getVehicle() {
        return mVehicle;
    }

    public Bonus getBonus() {
        return mBonus;
    }

    public LapPositionComponent getLapPositionComponent() {
        return mLapPositionComponent;
    }

    public AudioComponent getAudioComponent() {
        return mAudioComponent;
    }

    public AudioManager getAudioManager() {
        return mAudioComponent.getAudioManager();
    }

    public void spin() {
        if (mSpinningComponent.isActive()) {
            return;
        }
        mSpinningComponent.start();
        looseBonus();
    }

    /**
     * Returns the angle the camera should use to follow the vehicle.
     * This is the same as Vehicle.getAngle() except when spinning,
     * in which case we return the original angle, to avoid too much
     * camera shaking, especially when "rotate screen" option is off.
     */
    public float getCameraAngle() {
        if (mSpinningComponent.isActive()) {
            return mSpinningComponent.getOriginalAngle();
        } else {
            return mVehicle.getAngle();
        }
    }

    @Override
    public void beginContact(Contact contact, Fixture otherFixture) {
        for (Collidable collidable : mCollidableComponents) {
            collidable.beginContact(contact, otherFixture);
        }
    }

    @Override
    public void endContact(Contact contact, Fixture otherFixture) {
        for (Collidable collidable : mCollidableComponents) {
            collidable.endContact(contact, otherFixture);
        }
    }

    @Override
    public void preSolve(Contact contact, Fixture otherFixture, Manifold oldManifold) {
        Object other = otherFixture.getBody().getUserData();
        mAudioComponent.onCollision();
        if (other instanceof Racer) {
            contact.setEnabled(false);
            applySimplifiedRacerCollision((Racer) other);
        }

        for (Collidable collidable : mCollidableComponents) {
            collidable.preSolve(contact, otherFixture, oldManifold);
        }
    }

    /**
     * Simplifies collisions between vehicles to make the game easier to play:
     * bump them but do not change their direction
     */
    private Vector2 mTmp = new Vector2();

    private void applySimplifiedRacerCollision(Racer other) {
        Body body1 = getVehicle().getBody();
        Body body2 = other.getVehicle().getBody();

        mTmp.set(body2.getLinearVelocity()).sub(body1.getLinearVelocity());
        float deltaV = mTmp.len();

        final float k = GamePlay.instance.simplifiedCollisionKFactor
                * MathUtils.clamp(deltaV / GamePlay.instance.simplifiedCollisionMaxDeltaV, 0, 1);
        mTmp.set(body2.getWorldCenter()).sub(body1.getWorldCenter()).nor().scl(k);

        body2.applyLinearImpulse(mTmp, body2.getWorldCenter(), true);
        mTmp.scl(-1);
        body1.applyLinearImpulse(mTmp, body1.getWorldCenter(), true);
    }

    @Override
    public void postSolve(Contact contact, Fixture otherFixture, ContactImpulse impulse) {
        for (Collidable collidable : mCollidableComponents) {
            collidable.postSolve(contact, otherFixture, impulse);
        }
    }

    @Override
    public void dispose() {
        for (Racer.Component component : mComponents) {
            if (component instanceof Disposable) {
                ((Disposable) component).dispose();
            }
        }
    }

    @Override
    public void act(float delta) {
        for (Racer.Component component : mComponents) {
            component.act(delta);
        }

        if (mBonus != null) {
            mBonus.act(delta);
        }
    }

    public void selectBonus() {
        float normalizedRank = mGameWorld.getRacerNormalizedRank(this);

        Array<BonusPool> pools = mGameWorld.getBonusPools();
        float totalCount = 0;
        for (BonusPool pool : pools) {
            totalCount += pool.getCountForNormalizedRank(normalizedRank);
        }

        // To avoid allocating an array of the counts for each normalized rank, we subtract counts
        // from pick, until it is less than 0, at this point we are on the selected pool
        float pick = MathUtils.random(0f, totalCount);
        BonusPool pool = null;
        for (int idx = 0; idx < pools.size; ++idx) {
            pool = pools.get(idx);
            pick -= pool.getCountForNormalizedRank(normalizedRank);
            if (pick < 0) {
                break;
            }
        }
        if (pool == null) {
            pool = pools.get(pools.size - 1);
        }

        mBonus = (Bonus) pool.obtain();
        mBonus.onPicked(this);
    }

    public void triggerBonus() {
        if (mBonus == null) {
            return;
        }
        mBonus.trigger();
    }

    /**
     * Called by bonuses when they are done
     */
    public void resetBonus() {
        mBonus = null;
    }

    /**
     * Called when something bad happens to the racer, causing her to loose her bonus
     */
    public void looseBonus() {
        if (mBonus != null) {
            mBonus.onOwnerHit();
        }
    }

    @Override
    public void draw(Batch batch, ZLevel zLevel) {
        mVehicleRenderer.draw(batch, zLevel);
    }

    @Override
    public void audioRender(AudioClipper clipper) {
        mAudioComponent.render(clipper);
    }

    @Override
    public float getX() {
        return mVehicle.getX();
    }

    @Override
    public float getY() {
        return mVehicle.getY();
    }

    public VehicleRenderer getVehicleRenderer() {
        return mVehicleRenderer;
    }

    public void markRaceFinished() {
        mLapPositionComponent.markRaceFinished();
    }

    @Override
    public String toString() {
        return "<racer pilot=" + mPilot + " vehicle=" + mVehicle + ">";
    }
}