Cấu Trúc Game:
1. Khởi tạo project LibgDX:
- Cài đặt các thông số như mục lưu trữ, package, JDK, Desktop, Box2D,…
2. Cấu trúc Package.
.package com.fgdev.game.desktop;
3.Cấu trúc thư mục
- android
- core
- desktop
GameScreenLogic:
package com.fgdev.game.logics; import com.badlogic.gdx.Application; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.maps.tiled.TiledMap; import com.badlogic.gdx.maps.tiled.TiledMapTileLayer; import com.badlogic.gdx.maps.tiled.TmxMapLoader; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer; import com.badlogic.gdx.physics.box2d.World; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.TimeUtils; import com.badlogic.gdx.utils.viewport.FitViewport; import com.badlogic.gdx.utils.viewport.Viewport; import com.fgdev.game.Constants; import com.fgdev.game.entitiles.Player; import com.fgdev.game.entitiles.enemies.AdventureGirl; import com.fgdev.game.entitiles.enemies.Bone; import com.fgdev.game.entitiles.enemies.Dino; import com.fgdev.game.entitiles.enemies.Enemy; import com.fgdev.game.entitiles.enemies.Ghost; import com.fgdev.game.entitiles.enemies.Knight; import com.fgdev.game.entitiles.enemies.Robot; import com.fgdev.game.entitiles.enemies.Santa; import com.fgdev.game.entitiles.enemies.Zombie; import com.fgdev.game.entitiles.objects.Clouds; import com.fgdev.game.entitiles.tiles.box.BoxObject; import com.fgdev.game.entitiles.tiles.box.Crate; import com.fgdev.game.entitiles.tiles.item.Coin; import com.fgdev.game.entitiles.tiles.item.Feather; import com.fgdev.game.entitiles.tiles.item.ItemObject; import com.fgdev.game.helpers.B2WorldCreator; import com.fgdev.game.helpers.BackgroundTiledMapRenderer; import com.fgdev.game.helpers.ScoreIndicator; import com.fgdev.game.helpers.WorldContactListener; import com.fgdev.game.screens.DirectedGame; import com.fgdev.game.screens.GameOverOverlay; import com.fgdev.game.screens.JoystickOverlay; import com.fgdev.game.screens.LevelStartScreen; import com.fgdev.game.screens.MenuScreen; import com.fgdev.game.utils.Assets; import com.fgdev.game.utils.AudioManager; import com.fgdev.game.utils.GamePreferences; import com.fgdev.game.utils.ValueManager; import static com.fgdev.game.Constants.GRAVITY; import static com.fgdev.game.Constants.POSITION_ITERATIONS; import static com.fgdev.game.Constants.PPM; import static com.fgdev.game.Constants.STEP_TIME; import static com.fgdev.game.Constants.VELOCITY_ITERATIONS; import static com.fgdev.game.Constants.V_WIDTH; import static com.fgdev.game.Constants.WINDOW_HEIGHT; public class GameScreenLogic implements Disposable { private static final String TAG = GameScreenLogic.class.getName(); private DirectedGame game; private World world; // Tiled map_old variables private TmxMapLoader mapLoader; private TiledMap map; private BackgroundTiledMapRenderer renderer; // Camera & Batch private OrthographicCamera camera; private OrthographicCamera cameraGUI; private Viewport gamePort; private SpriteBatch batch; // Shader private ShaderProgram shaderMonochrome; // Box2d variables private Box2DDebugRenderer b2dr; private B2WorldCreator creator; // Objects private Player player; // Decoration private Clouds clouds; // ScoreIndicator private ScoreIndicator scoreIndicator; // HelperScreen private GameOverOverlay gameOverOverlay; private JoystickOverlay joystickOverlay; // Turn on / off debug private boolean isDebug; // Limit private float cameraLeftLimit; private float cameraRightLimit; // Map Width private float mapWidth; // Background private Texture background; private float accumulator = 0; private boolean isCheckNextLevel; private boolean isCheckFallWater; private boolean isCheckGameOver; public GameScreenLogic(DirectedGame game) { this.game = game; isDebug = GamePreferences.instance.debug; isCheckNextLevel = true; isCheckFallWater = true; isCheckGameOver = true; init(); } public void init() { initCamera(); initMap(); initLevel(); initObject(); mapWidth = ((TiledMapTileLayer) map.getLayers().get(0)).getWidth(); cameraLeftLimit = V_WIDTH / 2; cameraRightLimit = mapWidth - V_WIDTH / 2; } private void initCamera() { // Init batch batch = new SpriteBatch(); // Create cam used to follow mario through cam world camera = new OrthographicCamera(); // Create a FitViewport to maintain virtual aspect ratio despite screen size gamePort = new FitViewport(Constants.V_WIDTH, Constants.V_HEIGHT, camera); //initially set our gamcam to be centered correctly at the start of camera.position.set(gamePort.getWorldWidth() / 2, gamePort.getWorldHeight() / 2, 0); // Camera gui cameraGUI = new OrthographicCamera(Constants.WINDOW_WIDTH, Constants.WINDOW_HEIGHT); cameraGUI.position.set(0, 0, 0); cameraGUI.setToOrtho(true); // flip y-axis cameraGUI.update(); } private void initMap() { // Init world world = new World(GRAVITY, true); world.setContactListener(new WorldContactListener()); // Load our map_old and setup our map_old renderer mapLoader = new TmxMapLoader(); map = mapLoader.load(ValueManager.instance.mapPath); background = ValueManager.instance.background; renderer = new BackgroundTiledMapRenderer(map, 1 / PPM , background); b2dr = new Box2DDebugRenderer(); // decoration clouds = new Clouds(V_WIDTH * 100); clouds.getPosition().set(0, 2); } private void initLevel () { player = new Player(world); } private void initObject() { // Shader shaderMonochrome = new ShaderProgram( Gdx.files.internal(Constants.shaderMonochromeVertex), Gdx.files.internal(Constants.shaderMonochromeFragment)); if (!shaderMonochrome.isCompiled()) { String msg = "Could not compile shader program: " + shaderMonochrome.getLog(); throw new GdxRuntimeException(msg); } // Game over overlay gameOverOverlay = new GameOverOverlay(batch, cameraGUI); // Joysticks joystickOverlay = new JoystickOverlay(batch, this); // Init score indicator scoreIndicator = new ScoreIndicator(this, batch); creator = new B2WorldCreator(world, map, scoreIndicator); } private void resetPlayer() { world.destroyBody(player.getBody()); isCheckFallWater = true; player = new Player(world); } public void update (float deltaTime) { accumulator += Math.min(deltaTime, 0.25f); if (accumulator >= STEP_TIME) { accumulator -= STEP_TIME; world.step(STEP_TIME, VELOCITY_ITERATIONS, POSITION_ITERATIONS); } // Handle Debug Input // handleDebugInput(deltaTime); // Update player player.update(deltaTime); // Update clouds clouds.update(deltaTime); // Update object updateTile(deltaTime); // update ScoreIndicator scoreIndicator.update(deltaTime); if (ValueManager.instance.livesVisual > ValueManager.instance.lives) { ValueManager.instance.livesVisual = Math.max(ValueManager.instance.lives, ValueManager.instance.livesVisual - 1 * deltaTime); } if (ValueManager.instance.scoreVisual < ValueManager.instance.score) ValueManager.instance.scoreVisual = Math.min(ValueManager.instance.score, ValueManager.instance.scoreVisual + 250 * deltaTime); if (!ValueManager.instance.isGameOver() && player.isPlayerFalling() && isCheckFallWater) { isCheckFallWater = false; AudioManager.instance.play(Assets.instance.sounds.water); player.playerDie(); } // Next Level if (ValueManager.instance.isNextLevel) { ValueManager.instance.timeNextLevel -= deltaTime; player.climb(); if (ValueManager.instance.timeNextLevel < 0 && isCheckNextLevel) { isCheckNextLevel = false; if (ValueManager.instance.levelCurrent > ValueManager.instance.totalLevel) { backToMenu(); } else { game.setScreen(new LevelStartScreen(game)); } } } // Check game over if (ValueManager.instance.isGameOver() && isCheckGameOver) { ValueManager.instance.timeLeftGameOverDelay -= deltaTime; if (ValueManager.instance.timeLeftGameOverDelay < 0) { backToMenu(); } } else { if (!player.isDead()) { handleInput(deltaTime); } else { ValueManager.instance.timeLeftLiveLost -= deltaTime; if (ValueManager.instance.timeLeftLiveLost < 0) resetPlayer(); } } } private void updateTile(float deltaTime) { // Update BoxObject BoxObject boxObject; int len = creator.getActiveBoxObjects().size; for (int i = len; --i >= 0;) { boxObject = creator.getActiveBoxObjects().get(i); boxObject.update(deltaTime); if (player.getX() + V_WIDTH / 2 + 10 > boxObject.getX() && boxObject.getBody() != null) boxObject.getBody().setActive(true); if (boxObject.isDestroyed()) { creator.getActiveBoxObjects().removeIndex(i); if (boxObject instanceof Crate) creator.getCratePool().free((Crate) boxObject); } } // Update ItemObject ItemObject itemObject; len = creator.getActiveItemObjects().size; for (int i = len; --i >= 0;) { itemObject = creator.getActiveItemObjects().get(i); itemObject.update(deltaTime); if (player.getX() + V_WIDTH / 2 + 10 > itemObject.getX() && itemObject.getBody() != null) itemObject.getBody().setActive(true); if (itemObject.isDestroyed()) { creator.getActiveItemObjects().removeIndex(i); if (itemObject instanceof Coin) creator.getCoinPool().free((Coin) itemObject); else if (itemObject instanceof Feather) creator.getFeatherPool().free((Feather) itemObject); } } // Update enemies Enemy enemy; len = creator.getActiveEnemies().size; for (int i = len; --i >= 0;) { enemy = creator.getActiveEnemies().get(i); enemy.update(deltaTime); if (player.getX() + V_WIDTH / 2 + 10 > enemy.getX() && enemy.getBody() != null) enemy.getBody().setActive(true); if (enemy.isDestroyed()) { creator.getActiveEnemies().removeIndex(i); if (enemy instanceof Zombie) creator.getZombiePool().free((Zombie) enemy); else if (enemy instanceof Santa) creator.getSantaPool().free((Santa) enemy); else if (enemy instanceof Robot) creator.getRobotPool().free((Robot) enemy); else if (enemy instanceof Knight) creator.getKnightPool().free((Knight) enemy); else if (enemy instanceof Ghost) creator.getGhostPool().free((Ghost) enemy); else if (enemy instanceof Dino) creator.getDinoPool().free((Dino) enemy); else if (enemy instanceof AdventureGirl) private void renderGui (SpriteBatch batch) { // draw collected gold coins icon + text // (anchored to top left edge) renderGuiScore(batch); // draw extra lives icon + text (anchored to top right edge) renderGuiExtraLive(batch); // draw FPS text (anchored to bottom right edge) if (GamePreferences.instance.showFpsCounter) renderGuiFpsCounter(batch); // draw Game Over renderGuiOverlay(batch); // draw collected feather icon (anchored to top left edge) renderGuiFeatherPowerup(batch); // draw ScoreIndicator scoreIndicator.draw(); } private void renderDebug() { //render our Box2DDebugLines b2dr.render(world, camera.combined); } public void resize(int width, int height) { // updated our game viewport gamePort.update(width, height); cameraGUI.viewportHeight = Constants.WINDOW_HEIGHT; cameraGUI.viewportWidth = (Constants.WINDOW_HEIGHT / (float)height) * (float)width; cameraGUI.position.set(cameraGUI.viewportWidth / 2, cameraGUI.viewportHeight / 2, 0); cameraGUI.update(); joystickOverlay.resize(width, height); } private void renderGuiScore (SpriteBatch batch) { float x = -15; float y = -15; float offsetX = 50; float offsetY = 50; if (ValueManager.instance.scoreVisual < ValueManager.instance.score) { long shakeAlpha = System.currentTimeMillis() % 360; float shakeDist = 1.5f; offsetX += MathUtils.sinDeg(shakeAlpha * 2.2f) * shakeDist; offsetY += MathUtils.sinDeg(shakeAlpha * 2.9f) * shakeDist; } batch.draw(Assets.instance.goldCoin.goldCoin, x, y, offsetX, offsetY, WINDOW_HEIGHT / 6.3f, WINDOW_HEIGHT / 6.3f, 0.35f, -0.35f, 0); Assets.instance.fonts.textFontNormal.draw(batch, "" + (int) ValueManager.instance.scoreVisual, x + 75, y + 40); } private void renderGuiExtraLive (SpriteBatch batch) { float x = cameraGUI.viewportWidth - 50 - Constants.LIVES_START * 50; float y = -15; for (int i = 0; i < Constants.LIVES_START; i++) { if (ValueManager.instance.lives <= i) batch.setColor(0.5f, 0.5f, 0.5f, 0.5f); batch.draw(GamePreferences.instance.isGirl ? Assets.instance.playerGirl.head : Assets.instance.playerBoy.head, x + i * 50, y, 50, 50, WINDOW_HEIGHT / 5.3f, WINDOW_HEIGHT / 5.3f, 0.35f, -0.35f, 0); batch.setColor(1, 1, 1, 1); } if (ValueManager.instance.lives >= 0 && ValueManager.instance.livesVisual > ValueManager.instance.lives) { int i = ValueManager.instance.lives; float alphaColor = Math.max(0, ValueManager.instance.livesVisual - ValueManager.instance.lives - 0.5f); float alphaScale = 0.35f * (2 + ValueManager.instance.lives - ValueManager.instance.livesVisual) * 2; float alphaRotate = -45 * alphaColor; batch.setColor(1.0f, 0.7f, 0.7f, alphaColor); batch.draw(GamePreferences.instance.isGirl ? Assets.instance.playerGirl.head : Assets.instance.playerBoy.head, x + i * 50, y, 50, 50, WINDOW_HEIGHT / 5.3f, WINDOW_HEIGHT / 5.3f, alphaScale, -alphaScale, alphaRotate); batch.setColor(1, 1, 1, 1); } } private void renderGuiFpsCounter (SpriteBatch batch) { float x = cameraGUI.viewportWidth - 55; float y = cameraGUI.viewportHeight - 15; int fps = Gdx.graphics.getFramesPerSecond(); if (fps >= 45) { // 45 or more FPS show up in green Assets.instance.fonts.defaultNormal.setColor(0, 1, 0, 1); } else if (fps >= 30) { // 30 or more FPS show up in yellow Assets.instance.fonts.defaultNormal.setColor(1, 1, 0, 1); } else { // less than 30 FPS show up in red Assets.instance.fonts.defaultNormal.setColor(1, 0, 0, 1); } Assets.instance.fonts.defaultNormal.draw(batch, "FPS: " + fps, x, y); Assets.instance.fonts.defaultNormal.setColor(1, 1, 1, 1); // white } private void renderGuiOverlay (SpriteBatch batch) { if (ValueManager.instance.isGameOver()) { gameOverOverlay.render(Gdx.graphics.getDeltaTime()); } } private void renderGuiFeatherPowerup (SpriteBatch batch) { float x = -15; float y = 30; float timeLeftFeatherPowerup = player.getTimeLeftFeatherPowerup(); if (timeLeftFeatherPowerup > 0) { // Start icon fade in/out if the left power-up time // is less than 4 seconds. The fade interval is set // to 5 changes per second. if (timeLeftFeatherPowerup < 4) { if (((int)(timeLeftFeatherPowerup * 5) % 2) != 0) { batch.setColor(1, 1, 1, 0.5f); } } batch.draw(Assets.instance.feather.feather, x, y, 50, 50, WINDOW_HEIGHT / 6.3f, WINDOW_HEIGHT / 6.3f, 0.35f, -0.35f, 0); batch.setColor(1, 1, 1, 1); Assets.instance.fonts.textFontSmall.draw(batch, "" + (int)timeLeftFeatherPowerup, x + 60, y + 57); } } public void backToMenu () { // switch to menu screen game.setScreen(new MenuScreen(game)); isCheckGameOver = false; } private void handleInput(float deltaTime) { if (Gdx.input.isKeyPressed(Input.Keys.A) || Gdx.input.isKeyPressed(Input.Keys.LEFT) || joystickOverlay.isLeftPressed()) { player.left(); } if (Gdx.input.isKeyPressed(Input.Keys.D) || Gdx.input.isKeyPressed(Input.Keys.RIGHT) || joystickOverlay.isRightPressed()) { player.right(); } if (Gdx.input.isKeyPressed(Input.Keys.W) || Gdx.input.isKeyPressed(Input.Keys.UP) || joystickOverlay.isUpPressed()) { player.jump(); } if (Gdx.input.isKeyPressed(Input.Keys.S) || Gdx.input.isKeyPressed(Input.Keys.DOWN) || joystickOverlay.isDownPressed()) { player.down(); } if (Gdx.input.isKeyJustPressed(Input.Keys.Q) || joystickOverlay.isMeleePressed()) { player.attack(); } if (Gdx.input.isKeyJustPressed(Input.Keys.E) || joystickOverlay.isThrowPressed()) { player.attackThrow(); } if (Gdx.input.isKeyPressed(Input.Keys.X) || joystickOverlay.isClimPressed()) { player.climb(); } if (Gdx.input.isKeyJustPressed(Input.Keys.C) || joystickOverlay.isJumpThrowPressed()) { player.jumpThrow(); } // Hacking if (Gdx.input.isKeyJustPressed(Input.Keys.NUM_1)) { renderer.setBackground(Assets.instance.textures.background1); } if (Gdx.input.isKeyJustPressed(Input.Keys.NUM_2)) { renderer.setBackground(Assets.instance.textures.background2); } if (Gdx.input.isKeyJustPressed(Input.Keys.NUM_3)) { renderer.setBackground(Assets.instance.textures.background3); } if (Gdx.input.isKeyJustPressed(Input.Keys.NUM_3)) { renderer.setBackground(Assets.instance.textures.background3); } if (Gdx.input.isKeyJustPressed(Input.Keys.NUM_4)) { renderer.setBackground(Assets.instance.textures.background4); } if (Gdx.input.isKeyJustPressed(Input.Keys.NUM_5)) { renderer.setBackground(Assets.instance.textures.background5); } if (Gdx.input.isKeyJustPressed(Input.Keys.COMMA)) { ValueManager.instance.lives++; } if (Gdx.input.isKeyJustPressed(Input.Keys.SLASH)) { player.setFeatherPowerup(true); } } private void handleDebugInput(float deltaTime) { if (Gdx.app.getType() != Application.ApplicationType.Desktop) return; // Selected Sprite Controls float sprMoveSpeed = 5 * deltaTime; if (Gdx.input.isKeyPressed(Input.Keys.A)) {} if (Gdx.input.isKeyPressed(Input.Keys.D)) {} if (Gdx.input.isKeyPressed(Input.Keys.W)) {} if (Gdx.input.isKeyPressed(Input.Keys.S)) {} } public OrthographicCamera getCamera() { return camera; } public SpriteBatch getBatch() { return batch; } public OrthographicCamera getCameraGUI() { return cameraGUI; } public ScoreIndicator getScoreIndicator() { return scoreIndicator; } public float getMapWidth() { return mapWidth; } public JoystickOverlay getJoystickOverlay() { return joystickOverlay; } @Override public void dispose() { map.dispose(); renderer.dispose(); world.dispose(); b2dr.dispose(); scoreIndicator.dispose(); shaderMonochrome.dispose(); background.dispose(); gameOverOverlay.dispose(); joystickOverlay.dispose(); batch.dispose(); } }
AdventureGirl:
package com.fgdev.game.entitiles.enemies; import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.maps.MapObject; import com.badlogic.gdx.maps.objects.RectangleMapObject; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.physics.box2d.BodyDef; import com.badlogic.gdx.physics.box2d.World; import com.badlogic.gdx.utils.Pool; import com.fgdev.game.entitiles.Player; import com.fgdev.game.entitiles.bullets.EnemyBullet; import com.fgdev.game.helpers.ScoreIndicator; import com.fgdev.game.utils.Assets; import com.fgdev.game.utils.BodyFactory; import static com.fgdev.game.Constants.PPM; public class AdventureGirl extends Enemy implements Pool.Poolable { public static final String TAG = AdventureGirl.class.getName(); public static final int SHOOT = 0; public static final int MELEE = 1; public enum State { IDLE, RUN, DEAD, SHOOT, JUMP, MELEE, SLIDE } private State currentState; private State previousState; private Animation adventureGirlIdle; private Animation adventureGirlRun; private Animation adventureGirlDead; private Animation adventureGirlShoot; private Animation adventureGirlMelee; private Animation adventureGirlSlide; private Animation adventureGirlJump; private boolean isRun; private boolean isShoot; private boolean isMelee; private boolean isSlide; private float timeDelayIdle = 1; private float timeDelayRun = 2; public AdventureGirl(World world, ScoreIndicator scoreIndicator) { super(world, scoreIndicator); adventureGirlIdle = Assets.instance.adventureGirl.animIdle; adventureGirlRun = Assets.instance.adventureGirl.animRun; adventureGirlDead = Assets.instance.adventureGirl.animDead; adventureGirlShoot = Assets.instance.adventureGirl.animShoot; adventureGirlMelee = Assets.instance.adventureGirl.animMelee; adventureGirlSlide = Assets.instance.adventureGirl.animSlide; adventureGirlJump = Assets.instance.adventureGirl.animJump; } public void init(MapObject mapObject, int type) { this.mapObject = mapObject; this.type = type; currentState = State.IDLE; previousState = State.IDLE; isRun = true; isDead = false; isShoot = false; isMelee = false; isSlide = false; speed = 1.5f; // Extend Abstract init(); setRegion((TextureRegion) adventureGirlIdle.getKeyFrame(stateTimer)); } @Override public void reset() { currentState = State.IDLE; previousState = State.IDLE; isRun = true; isDead = false; isShoot = false; isMelee = false; isSlide = false; speed = 1f; } public void update(float dt) { if (timeDelayIdle > 0) { timeDelayIdle -= dt; if (timeDelayIdle < 0) timeDelayRun = 3; } if (isRun) { if (timeDelayRun > 0) { timeDelayRun -= dt; running(); } if (timeDelayRun < 0 && timeDelayIdle < 0) { previousState = State.SHOOT; isShoot = true; } } // spawn handleSpawningBullet(); // update bullets for (EnemyBullet bullet : bullets) { bullet.update(dt); } // clean for (int i = 0; i < bullets.size; i++) { if (!bullets.get(i).isAlive()) { bullets.removeIndex(i); } } super.update(dt); } @Override protected void setBoundForRegion() { currentState = getState(); switch (currentState) { case DEAD: setBounds(0, 0, 91 * 2 / PPM, 91 * 2 / PPM); break; case RUN: case SHOOT: case SLIDE: case JUMP: case MELEE: case IDLE: default: setBounds(0, 0, 96 * 2 / PPM, 81 * 2 / PPM); break; } } @Override protected TextureRegion getFrame(float dt) { currentState = getState(); TextureRegion region; //depending on the state, get corresponding animation KeyFrame switch (currentState) { case RUN: region = (TextureRegion) adventureGirlRun.getKeyFrame(stateTimer, true); break; case DEAD: region = (TextureRegion) adventureGirlDead.getKeyFrame(stateTimer); break; case SHOOT: region = (TextureRegion) adventureGirlShoot.getKeyFrame(stateTimer); if (adventureGirlShoot.isAnimationFinished(stateTimer)) { if (isShoot) { float x = runningRight ? 1f : -1f; float y = -0.1f; // if you want to spawn a new bullet: addSpawnBullet(body.getPosition().x + x, body.getPosition().y + y, runningRight ? true : false); isShoot = false; timeDelayIdle = 1; } } break; case MELEE: region = (TextureRegion) adventureGirlMelee.getKeyFrame(stateTimer, true); break; case SLIDE: region = (TextureRegion) adventureGirlSlide.getKeyFrame(stateTimer); break; case JUMP: region = (TextureRegion) adventureGirlJump.getKeyFrame(stateTimer); break; case IDLE: default: region = (TextureRegion) adventureGirlIdle.getKeyFrame(stateTimer,true); break; } //if player is running left and the texture isnt facing left... flip it. if ((body.getLinearVelocity().x < 0 || !runningRight) && !region.isFlipX()) { region.flip(true, false); runningRight = false; } //if player is running right and the texture isnt facing right... flip it. else if ((body.getLinearVelocity().x > 0 || runningRight) && region.isFlipX()) { region.flip(true, false); runningRight = true; } //if the current state is the same as the previous state increase the state timer. //otherwise the state has changed and we need to reset timer. stateTimer = currentState == previousState ? stateTimer + dt : 0; //update previous state previousState = currentState; return region; } private State getState() { if (isDead) return State.DEAD; else if (isMelee) return State.MELEE; else if (isSlide) return State.SLIDE; else if (body.getLinearVelocity().x != 0) return State.RUN; else if (isShoot) return State.SHOOT; // if none of these return then he must be standing else return State.IDLE; } @Override public void draw(Batch batch) { super.draw(batch); // draw bullets for (EnemyBullet bullet : bullets) { bullet.draw(batch); } } private void makeBoxRobotBody(float posx, float posy) { float width = (96 - 45)/ PPM; float height = (81 - 10) / PPM; // create adventureGirl body = bodyFactory.makeBoxPolyBody( posx, posy, width, height, BodyFactory.ENEMY_DISABLE_PLAYER, BodyDef.BodyType.DynamicBody, this ); // create foot sensor bodyFactory.makeShapeSensor(body, width, 10 / PPM, new Vector2(0, (-height - 60) / PPM), 0, BodyFactory.ENEMY_SENSOR, this ); // create keep shape bodyFactory.makeEdgeSensor(body, new Vector2(0, (-height - 60) / PPM), new Vector2(6.8f / PPM / 6, 6.8f / PPM * 3), BodyFactory.ENEMY, this ); } protected void defineEnemy() { Rectangle rect = ((RectangleMapObject) mapObject).getRectangle(); makeBoxRobotBody( (rect.x + rect.width / 2) / PPM, (rect.y + rect.height / 2) / PPM ); } @Override public int score() { return 100; } @Override public void killed() { super.killed(); setRegion((TextureRegion) adventureGirlDead.getKeyFrame(stateTimer)); isDead = true; isRun = false; becomeDead(); } @Override public void beginAttack(Player player) { setRegion((TextureRegion) adventureGirlMelee.getKeyFrame(stateTimer)); player.playerDie(); isMelee = true; isRun = false; body.getLinearVelocity().x = 0; } @Override public void endAttack(Player player) { setRegion((TextureRegion) adventureGirlIdle.getKeyFrame(stateTimer)); isRun = true; isMelee = false; } }
Enemies:
package com.fgdev.game.entitiles.enemies; import com.badlogic.gdx.graphics.g2d.Sprite; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.maps.MapObject; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.physics.box2d.*; import com.badlogic.gdx.utils.Array; import com.fgdev.game.Constants; import com.fgdev.game.entitiles.Player; import com.fgdev.game.entitiles.bullets.EnemyBullet; import com.fgdev.game.entitiles.bullets.SpawningBullet; import com.fgdev.game.entitiles.tiles.item.ItemObject; import com.fgdev.game.helpers.ScoreIndicator; import com.fgdev.game.utils.Assets; import com.fgdev.game.utils.AudioManager; import com.fgdev.game.utils.BodyFactory; import com.fgdev.game.utils.ValueManager; import java.util.LinkedList; public abstract class Enemy extends Sprite { protected World world; protected Body body; protected BodyFactory bodyFactory; protected MapObject mapObject; protected int type; protected ScoreIndicator scoreIndicator; protected float speed; protected boolean runningRight; protected float stateTimer; protected boolean toBeDestroyed; protected boolean destroyed; protected boolean isDead; protected float timeDelayDie = 3; protected Array<EnemyBullet> bullets; protected LinkedList<SpawningBullet> bulletSpawnQueue; public Enemy(World world, ScoreIndicator scoreIndicator) { this.world = world; this.scoreIndicator = scoreIndicator; bodyFactory = BodyFactory.getInstance(world); // for spawning bullets bullets = new Array<EnemyBullet>(); bulletSpawnQueue = new LinkedList<SpawningBullet>(); } public void init() { stateTimer = 0; toBeDestroyed = false; destroyed = false; defineEnemy(); body.setActive(false); } public void update(float dt) { if (body == null) return; if (isDead) { timeDelayDie -= dt; if (timeDelayDie < 0) { queueDestroy(); // Sound ValueManager.instance.score += score(); scoreIndicator.addScoreItem(getX(), getY(), score()); isDead = false; } } if (toBeDestroyed && !destroyed) { if (body != null) { final Array<JointEdge> list = body.getJointList(); while (list.size > 0) { world.destroyJoint(list.get(0).joint); } body.setUserData(null); world.destroyBody(body); body = null; destroyed = true; setSize(0, 0); return; } } setBoundForRegion(); setRegion(getFrame(dt)); setPosition(body.getPosition().x - getWidth() / 2, body.getPosition().y - getHeight() / 2); } protected void running() { if (body != null) { checkMovingDirection(); float velocityY = body.getLinearVelocity().y; if (runningRight) { body.setLinearVelocity(new Vector2(speed, velocityY)); } else { body.setLinearVelocity(new Vector2(-speed, velocityY)); } } } public abstract void init(MapObject mapObject, int type); protected abstract void defineEnemy(); protected abstract void setBoundForRegion(); protected abstract TextureRegion getFrame(float dt); public abstract int score(); public void killed() { AudioManager.instance.play(Assets.instance.sounds.enemy_dead); } public abstract void beginAttack(Player player); public abstract void endAttack(Player player); protected void queueDestroy() { toBeDestroyed = true; } public boolean isDestroyed() { return destroyed; } public Body getBody() { return body; } public Vector2 getPosition() { return body.getPosition(); } protected void becomeDead() { Filter filter; for (Fixture fixture : body.getFixtureList()) { filter = fixture.getFilterData(); filter.maskBits = Constants.GROUND_BIT; fixture.setFilterData(filter); } } protected void checkMovingDirection() { Vector2 p1; Vector2 p2; RayCastCallback rayCastCallback = new RayCastCallback() { @Override public float reportRayFixture(Fixture fixture, Vector2 point, Vector2 normal, float fraction) { if (fixture.getUserData() instanceof ItemObject) { return 1; } if (fraction < 1.0f && fixture.getUserData().getClass() != Player.class) { runningRight = !runningRight; } return 0; } }; if (runningRight) { p1 = new Vector2(body.getPosition().x + 1f, body.getPosition().y); p2 = new Vector2(p1).add(0.1f, 0); world.rayCast(rayCastCallback, p1, p2); } else { p1 = new Vector2(body.getPosition().x - 1f, body.getPosition().y); p2 = new Vector2(p1).add(-0.1f, 0); world.rayCast(rayCastCallback, p1, p2); } } protected World getWorld() { return world; } public void addSpawnBullet(float x, float y, boolean movingRight) { AudioManager.instance.play(Assets.instance.sounds.bullet_enemy); bulletSpawnQueue.add(new SpawningBullet(x, y, movingRight)); } public void handleSpawningBullet() { if (bulletSpawnQueue.size() > 0) { SpawningBullet spawningBullet = bulletSpawnQueue.poll(); bullets.add(new EnemyBullet(getWorld(), spawningBullet.x, spawningBullet.y, spawningBullet.movingRight)); } } }
SourceCode:
link Githup:https://github.com/nguyenvananhtu/FGNinjaAdventure
Thành viên: Nguyễn Văn Anh Tú, Lê Đình Lời.