Java tutorial
/* * 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; import com.agateau.pixelwheels.bonus.BonusPool; import com.agateau.pixelwheels.bonus.BonusSpot; import com.agateau.pixelwheels.bonus.GunBonus; import com.agateau.pixelwheels.bonus.MineBonus; import com.agateau.pixelwheels.bonus.MissileBonus; import com.agateau.pixelwheels.bonus.TurboBonus; import com.agateau.pixelwheels.gameobjet.GameObject; import com.agateau.pixelwheels.gamesetup.GameInfo; import com.agateau.pixelwheels.map.Track; import com.agateau.pixelwheels.stats.TrackResult; import com.agateau.pixelwheels.stats.TrackStats; import com.agateau.pixelwheels.racer.AIPilot; import com.agateau.pixelwheels.racer.LapPositionComponent; import com.agateau.pixelwheels.racer.PlayerPilot; 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.racescreen.CountDown; import com.agateau.pixelwheels.sound.AudioManager; import com.agateau.pixelwheels.utils.Box2DUtils; import com.agateau.pixelwheels.vehicledef.VehicleCreator; import com.agateau.pixelwheels.vehicledef.VehicleDef; import com.agateau.utils.Assert; import com.badlogic.gdx.maps.MapObject; 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.ContactListener; import com.badlogic.gdx.physics.box2d.Manifold; import com.badlogic.gdx.physics.box2d.World; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.PerformanceCounter; import com.badlogic.gdx.utils.PerformanceCounters; import com.badlogic.gdx.utils.Sort; import java.util.Comparator; /** * Contains all the information and objects running in the world */ public class GameWorld implements ContactListener, Disposable { public enum State { COUNTDOWN, RUNNING, FINISHED } public static final float BOX2D_TIME_STEP = 1f / 60f; public static final int VELOCITY_ITERATIONS = 6; public static final int POSITION_ITERATIONS = 2; private final PwGame mGame; private Track mTrack; private final CountDown mCountDown; private final World mBox2DWorld; private float mTimeAccumulator = 0; private Array<BonusPool> mBonusPools = new Array<BonusPool>(); private final Array<Racer> mRacers = new Array<Racer>(); private final Array<Racer> mPlayerRacers = new Array<Racer>(); private State mState = State.COUNTDOWN; private final Array<GameObject> mActiveGameObjects = new Array<GameObject>(); private final PerformanceCounter mBox2DPerformanceCounter; private final PerformanceCounter mGameObjectPerformanceCounter; public GameWorld(PwGame game, GameInfo gameInfo, PerformanceCounters performanceCounters) { mGame = game; mBox2DWorld = new World(new Vector2(0, 0), true); mBox2DWorld.setContactListener(this); mTrack = gameInfo.getTrack(); mTrack.init(); mCountDown = new CountDown(this, game.getAudioManager(), game.getAssets().soundAtlas); mBox2DPerformanceCounter = performanceCounters.add("- box2d"); mGameObjectPerformanceCounter = performanceCounters.add("- g.o"); setupRacers(gameInfo.getEntrants()); setupRoadBorders(); setupBonusSpots(); setupBonusPools(); } public Track getTrack() { return mTrack; } public World getBox2DWorld() { return mBox2DWorld; } public Racer getPlayerRacer(int playerId) { return mPlayerRacers.get(playerId); } public Array<Racer> getPlayerRacers() { return mPlayerRacers; } public Array<Racer> getRacers() { return mRacers; } public Array<BonusPool> getBonusPools() { return mBonusPools; } public Vehicle getPlayerVehicle(int id) { return mPlayerRacers.get(id).getVehicle(); } public Array<GameObject> getActiveGameObjects() { return mActiveGameObjects; } public void addGameObject(GameObject object) { mActiveGameObjects.add(object); } public int getPlayerRank(int playerId) { Racer racer = mPlayerRacers.get(playerId); return getRacerRank(racer); } public CountDown getCountDown() { return mCountDown; } public int getRacerRank(Racer racer) { for (int idx = mRacers.size - 1; idx >= 0; --idx) { if (mRacers.get(idx) == racer) { return idx + 1; } } return -1; } /** * Normalized rank goes from 0 to 1, where 0 is for the first racer and 1 is for the last one * If there is only one racer, returns 0 */ public float getRacerNormalizedRank(Racer racer) { if (mRacers.size == 1) { return 0; } return (getRacerRank(racer) - 1) / (float) (mRacers.size - 1); } /** * Sort racers, listing racers which have driven the longest first, * so it returns 1 if racer1 has driven less than racer2 */ private static Comparator<Racer> sRacerComparator = new Comparator<Racer>() { @Override public int compare(Racer racer1, Racer racer2) { LapPositionComponent c1 = racer1.getLapPositionComponent(); LapPositionComponent c2 = racer2.getLapPositionComponent(); if (!c1.hasFinishedRace() && c2.hasFinishedRace()) { return 1; } if (c1.hasFinishedRace() && !c2.hasFinishedRace()) { return -1; } if (c1.getLapCount() < c2.getLapCount()) { return 1; } if (c1.getLapCount() > c2.getLapCount()) { return -1; } float d1 = c1.getLapDistance(); float d2 = c2.getLapDistance(); return Float.compare(d2, d1); } }; public void act(float delta) { mCountDown.act(delta); mBox2DPerformanceCounter.start(); // fixed time step // max frame time to avoid spiral of death (on slow devices) float frameTime = Math.min(delta, 0.25f); mTimeAccumulator += frameTime; while (mTimeAccumulator >= BOX2D_TIME_STEP) { mBox2DWorld.step(BOX2D_TIME_STEP, VELOCITY_ITERATIONS, POSITION_ITERATIONS); mTimeAccumulator -= BOX2D_TIME_STEP; } mBox2DPerformanceCounter.stop(); mGameObjectPerformanceCounter.start(); for (int idx = mActiveGameObjects.size - 1; idx >= 0; --idx) { GameObject obj = mActiveGameObjects.get(idx); obj.act(delta); if (obj.isFinished()) { mActiveGameObjects.removeIndex(idx); if (obj instanceof Disposable) { ((Disposable) obj).dispose(); } } } mGameObjectPerformanceCounter.stop(); // Skip finished racers so that they keep the position they had when they crossed the finish // line, even if they continue a bit after it int fromIndex; for (fromIndex = 0; fromIndex < mRacers.size; ++fromIndex) { if (!mRacers.get(fromIndex).getLapPositionComponent().hasFinishedRace()) { break; } } Sort.instance().sort(mRacers.items, sRacerComparator, fromIndex, mRacers.size); boolean allFinished = true; for (Racer racer : mPlayerRacers) { if (!racer.getLapPositionComponent().hasFinishedRace()) { allFinished = false; break; } } if (allFinished) { setState(State.FINISHED); } } private void onFinished() { TrackStats stats = mGame.getGameStats().getTrackStats(mTrack.getId()); for (int idx = 0; idx < mRacers.size; ++idx) { Racer racer = mRacers.get(idx); racer.markRaceFinished(); GameInfo.Entrant entrant = racer.getEntrant(); int points = mRacers.size - idx; entrant.addPoints(points); LapPositionComponent lapPositionComponent = racer.getLapPositionComponent(); entrant.addRaceTime(lapPositionComponent.getTotalTime()); if (entrant.isPlayer()) { Racer.RecordRanks ranks = racer.getRecordRanks(); // TODO find another way to get the name String name = racer.getVehicle().getName(); ranks.lapRecordRank = stats.addResult(TrackStats.ResultType.LAP, new TrackResult(name, lapPositionComponent.getBestLapTime())); ranks.totalRecordRank = stats.addResult(TrackStats.ResultType.TOTAL, new TrackResult(name, lapPositionComponent.getTotalTime())); } } } private void setupRacers(Array<GameInfo.Entrant> entrants) { VehicleCreator creator = new VehicleCreator(mGame.getAssets(), this); Assets assets = mGame.getAssets(); final float startAngle = 90; Array<Vector2> positions = mTrack.findStartTilePositions(); positions.reverse(); AudioManager audioManager = mGame.getAudioManager(); for (int idx = 0; idx < entrants.size; ++idx) { Assert.check(idx < positions.size, "Too many entrants"); GameInfo.Entrant entrant = entrants.get(idx); VehicleDef vehicleDef = assets.findVehicleDefById(entrant.getVehicleId()); Vehicle vehicle = creator.create(vehicleDef, positions.get(idx), startAngle); Racer racer = new Racer(assets, audioManager, this, vehicle, entrant); if (entrant.isPlayer()) { GameInfo.Player player = (GameInfo.Player) entrant; PlayerPilot pilot = new PlayerPilot(assets, this, racer, mGame.getConfig(), player.getIndex()); racer.setPilot(pilot); mPlayerRacers.add(racer); } else { racer.setPilot(new AIPilot(this, mTrack, racer)); } addGameObject(racer); mRacers.add(racer); } } private void setupRoadBorders() { for (MapObject object : mTrack.getBorderObjects()) { Body body = Box2DUtils.createStaticBodyForMapObject(mBox2DWorld, object); Box2DUtils.setCollisionInfo(body, CollisionCategories.WALL, CollisionCategories.RACER | CollisionCategories.EXPLOSABLE | CollisionCategories.RACER_BULLET); Box2DUtils.setBodyRestitution(body, GamePlay.instance.borderRestitution / 10.0f); } } private void setupBonusSpots() { for (Vector2 pos : mTrack.findBonusSpotPositions()) { BonusSpot spot = new BonusSpot(mGame.getAssets(), mGame.getAudioManager(), this, pos.x, pos.y); addGameObject(spot); } } private void setupBonusPools() { addPool(new BonusPool<GunBonus>(GunBonus.class, mGame.getAssets(), this, mGame.getAudioManager()), new float[] { 0, 1, 1 }); addPool(new BonusPool<MineBonus>(MineBonus.class, mGame.getAssets(), this, mGame.getAudioManager()), new float[] { 2, 1, 0 }); addPool(new BonusPool<TurboBonus>(TurboBonus.class, mGame.getAssets(), this, mGame.getAudioManager()), new float[] { 0, 1, 2 }); addPool(new BonusPool<MissileBonus>(MissileBonus.class, mGame.getAssets(), this, mGame.getAudioManager()), new float[] { 0, 1, 1 }); } private void addPool(BonusPool pool, float[] counts) { pool.setCounts(new float[] { 2, 1, 0 }); mBonusPools.add(pool); } @Override public void beginContact(Contact contact) { Object userA = contact.getFixtureA().getBody().getUserData(); Object userB = contact.getFixtureB().getBody().getUserData(); if (userA instanceof Collidable) { ((Collidable) userA).beginContact(contact, contact.getFixtureB()); } if (userB instanceof Collidable) { ((Collidable) userB).beginContact(contact, contact.getFixtureA()); } } @Override public void endContact(Contact contact) { Object userA = contact.getFixtureA().getBody().getUserData(); Object userB = contact.getFixtureB().getBody().getUserData(); if (userA instanceof Collidable) { ((Collidable) userA).endContact(contact, contact.getFixtureB()); } if (userB instanceof Collidable) { ((Collidable) userB).endContact(contact, contact.getFixtureA()); } } @Override public void preSolve(Contact contact, Manifold oldManifold) { Object userA = contact.getFixtureA().getBody().getUserData(); Object userB = contact.getFixtureB().getBody().getUserData(); if (userA instanceof Collidable) { ((Collidable) userA).preSolve(contact, contact.getFixtureB(), oldManifold); } if (userB instanceof Collidable) { ((Collidable) userB).preSolve(contact, contact.getFixtureA(), oldManifold); } } @Override public void postSolve(Contact contact, ContactImpulse impulse) { Object userA = contact.getFixtureA().getBody().getUserData(); Object userB = contact.getFixtureB().getBody().getUserData(); if (userA instanceof Collidable) { ((Collidable) userA).postSolve(contact, contact.getFixtureB(), impulse); } if (userB instanceof Collidable) { ((Collidable) userB).postSolve(contact, contact.getFixtureA(), impulse); } } public State getState() { return mState; } public void startRace() { setState(State.RUNNING); } private void setState(State state) { if (mState == state) { return; } mState = state; if (mState == State.FINISHED) { onFinished(); } } @Override public void dispose() { if (mTrack != null) { mTrack.dispose(); } for (GameObject gameObject : mActiveGameObjects) { if (gameObject instanceof Disposable) { ((Disposable) gameObject).dispose(); } } mActiveGameObjects.clear(); } public void forgetTrack() { mTrack = null; } }