LibGDX-Lập trình game FGNinJa nture

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.