- Cấu trúc game
A. Khởi tạo Project Libgdx:
Chạy công cụ LibGDX Setup
Cấu hình dự án trong LibGDX Setup
Tạo dự án
Mở dự án với Android Studio
B. Cấu trúc thư mục
- assets:
Để lưu trữ tất cả các tài nguyên của trò chơi như hình ảnh, font chữ.
- Cấu trúc Package:
package ca.grasley.spaceshooter
- EnemyShip :
Để mô tả và điều khiển hành vi của tàu địch trong trò chơi
package ca.grasley.spaceshooter; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.Vector2; class EnemyShip extends Ship { Vector2 directionVector; float timeSinceLastDirectionChange = 0 ; float directionChangeFrequency = 0.75f; public EnemyShip(float xCentre, float yCentre, float width, float height, float movementSpeed, int shield, float laserWidth, float laserHeight, float laserMovementSpeed, float timeBetweenShots, TextureRegion shipTextureRegion, TextureRegion shieldTextureRegion, TextureRegion laserTextureRegion) { super(xCentre, yCentre, width, height, movementSpeed, shield, laserWidth, laserHeight, laserMovementSpeed, timeBetweenShots, shipTextureRegion, shieldTextureRegion, laserTextureRegion); directionVector = new Vector2(0, -1); } public Vector2 getDirectionVector() { return directionVector; } private void randomizeDirectionVector() { double bearing = SpaceShooterGame.random.nextDouble()*6.283185; //0 to 2*PI directionVector.x = (float)Math.sin(bearing); directionVector.y = (float)Math.cos(bearing); } @Override public void update(float deltaTime) { super.update(deltaTime); timeSinceLastDirectionChange += deltaTime; if (timeSinceLastDirectionChange > directionChangeFrequency) { randomizeDirectionVector(); timeSinceLastDirectionChange -= directionChangeFrequency; } } @Override public Laser[] fireLasers() { Laser[] laser = new Laser[2]; laser[0] = new Laser(boundingBox.x + boundingBox.width * 0.18f, boundingBox.y - laserHeight, laserWidth, laserHeight, laserMovementSpeed, laserTextureRegion); laser[1] = new Laser(boundingBox.x + boundingBox.width * 0.82f, boundingBox.y - laserHeight, laserWidth, laserHeight, laserMovementSpeed, laserTextureRegion); timeSinceLastShot = 0; return laser; } @Override public void draw(Batch batch) { batch.draw(shipTextureRegion, boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height); if (shield > 0) { batch.draw(shieldTextureRegion, boundingBox.x, boundingBox.y - boundingBox.height * 0.2f, boundingBox.width, boundingBox.height); } } }
- Exeplosion
Để mô tả hành vi và hiển thị của một vụ nổ.
package ca.grasley.spaceshooter; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.Rectangle; class Explosion { private Animation<TextureRegion> explosionAnimation; private float explosionTimer; private Rectangle boundingBox; Explosion(Texture texture, Rectangle boundingBox, float totalAnimationTime) { this.boundingBox = boundingBox; //split texture TextureRegion[][] textureRegion2D = TextureRegion.split(texture, 64, 64); //convert to 1D array TextureRegion[] textureRegion1D = new TextureRegion[16]; int index = 0; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { textureRegion1D[index] = textureRegion2D[i][j]; index++; } } explosionAnimation = new Animation<TextureRegion>(totalAnimationTime/16, textureRegion1D); explosionTimer = 0; } public void update(float deltaTime) { explosionTimer += deltaTime; } public void draw (SpriteBatch batch) { batch.draw(explosionAnimation.getKeyFrame(explosionTimer), boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height); } public boolean isFinished() { return explosionAnimation.isAnimationFinished(explosionTimer); } }
- GameScreen
package ca.grasley.spaceshooter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.Screen; import com.badlogic.gdx.graphics.Camera; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.viewport.StretchViewport; import com.badlogic.gdx.utils.viewport.Viewport; import java.util.Arrays; import java.util.LinkedList; import java.util.ListIterator; import java.util.Locale; class GameScreen implements Screen { //screen private Camera camera; private Viewport viewport; //graphics private SpriteBatch batch; private TextureAtlas textureAtlas; private Texture explosionTexture; private TextureRegion[] backgrounds; private float backgroundHeight; //height of background in World units private TextureRegion playerShipTextureRegion, playerShieldTextureRegion, enemyShipTextureRegion, enemyShieldTextureRegion, playerLaserTextureRegion, enemyLaserTextureRegion; //timing private float[] backgroundOffsets = {0, 0, 0, 0}; private float backgroundMaxScrollingSpeed; private float timeBetweenEnemySpawns = 1f; private float enemySpawnTimer = 0; //world parameters private final float WORLD_WIDTH = 72; private final float WORLD_HEIGHT = 128; private final float TOUCH_MOVEMENT_THRESHOLD = 5f; //game objects private PlayerShip playerShip; private LinkedList<EnemyShip> enemyShipList; private LinkedList<Laser> playerLaserList; private LinkedList<Laser> enemyLaserList; private LinkedList<Explosion> explosionList; private int score = 0; //Heads-Up Display BitmapFont font; float hudVerticalMargin, hudLeftX, hudRightX, hudCentreX, hudRow1Y, hudRow2Y, hudSectionWidth; GameScreen() { camera = new OrthographicCamera(); viewport = new StretchViewport(WORLD_WIDTH, WORLD_HEIGHT, camera); //set up the texture atlas textureAtlas = new TextureAtlas("images.atlas"); //setting up the background backgrounds = new TextureRegion[4]; backgrounds[0] = textureAtlas.findRegion("Starscape00"); backgrounds[1] = textureAtlas.findRegion("Starscape01"); backgrounds[2] = textureAtlas.findRegion("Starscape02"); backgrounds[3] = textureAtlas.findRegion("Starscape03"); backgroundHeight = WORLD_HEIGHT * 2; backgroundMaxScrollingSpeed = (float) (WORLD_HEIGHT) / 4; //initialize texture regions playerShipTextureRegion = textureAtlas.findRegion("playerShip2_blue"); enemyShipTextureRegion = textureAtlas.findRegion("enemyRed3"); playerShieldTextureRegion = textureAtlas.findRegion("shield2"); enemyShieldTextureRegion = textureAtlas.findRegion("shield1"); enemyShieldTextureRegion.flip(false, true); playerLaserTextureRegion = textureAtlas.findRegion("laserBlue03"); enemyLaserTextureRegion = textureAtlas.findRegion("laserRed03"); explosionTexture = new Texture("explosion.png"); //set up game objects playerShip = new PlayerShip(WORLD_WIDTH / 2, WORLD_HEIGHT / 4, 10, 10, 48, 3, 0.4f, 4, 45, 0.5f, playerShipTextureRegion, playerShieldTextureRegion, playerLaserTextureRegion); enemyShipList = new LinkedList<>(); playerLaserList = new LinkedList<>(); enemyLaserList = new LinkedList<>(); explosionList = new LinkedList<>(); batch = new SpriteBatch(); prepareHUD(); } private void prepareHUD() { //Create a BitmapFont from our font file FreeTypeFontGenerator fontGenerator = new FreeTypeFontGenerator(Gdx.files.internal("EdgeOfTheGalaxyRegular-OVEa6.otf")); FreeTypeFontGenerator.FreeTypeFontParameter fontParameter = new FreeTypeFontGenerator.FreeTypeFontParameter(); fontParameter.size = 72; fontParameter.borderWidth = 3.6f; fontParameter.color = new Color(1, 1, 1, 0.3f); fontParameter.borderColor = new Color(0, 0, 0, 0.3f); font = fontGenerator.generateFont(fontParameter); //scale the font to fit world font.getData().setScale(0.08f); //calculate hud margins, etc. hudVerticalMargin = font.getCapHeight() / 2; hudLeftX = hudVerticalMargin; hudRightX = WORLD_WIDTH * 2 / 3 - hudLeftX; hudCentreX = WORLD_WIDTH / 3; hudRow1Y = WORLD_HEIGHT - hudVerticalMargin; hudRow2Y = hudRow1Y - hudVerticalMargin - font.getCapHeight(); hudSectionWidth = WORLD_WIDTH / 3; } @Override public void render(float deltaTime) { batch.begin(); //scrolling background renderBackground(deltaTime); detectInput(deltaTime); playerShip.update(deltaTime); spawnEnemyShips(deltaTime); ListIterator<EnemyShip> enemyShipListIterator = enemyShipList.listIterator(); while (enemyShipListIterator.hasNext()) { EnemyShip enemyShip = enemyShipListIterator.next(); moveEnemy(enemyShip, deltaTime); enemyShip.update(deltaTime); enemyShip.draw(batch); } //player ship playerShip.draw(batch); //lasers renderLasers(deltaTime); //detect collisions between lasers and ships detectCollisions(); //explosions updateAndRenderExplosions(deltaTime); //hud rendering updateAndRenderHUD(); batch.end(); } private void updateAndRenderHUD() { //render top row labels font.draw(batch, "Score", hudLeftX, hudRow1Y, hudSectionWidth, Align.left, false); font.draw(batch, "Shield", hudCentreX, hudRow1Y, hudSectionWidth, Align.center, false); font.draw(batch, "Lives", hudRightX, hudRow1Y, hudSectionWidth, Align.right, false); //render second row values font.draw(batch, String.format(Locale.getDefault(), "%06d", score), hudLeftX, hudRow2Y, hudSectionWidth, Align.left, false); font.draw(batch, String.format(Locale.getDefault(), "%02d", playerShip.shield), hudCentreX, hudRow2Y, hudSectionWidth, Align.center, false); font.draw(batch, String.format(Locale.getDefault(), "%02d", playerShip.lives), hudRightX, hudRow2Y, hudSectionWidth, Align.right, false); } private void spawnEnemyShips(float deltaTime) { enemySpawnTimer += deltaTime; if (enemySpawnTimer > timeBetweenEnemySpawns) { enemyShipList.add(new EnemyShip(SpaceShooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 10, 10, 48, 1, 0.3f, 5, 50, 0.8f, enemyShipTextureRegion, enemyShieldTextureRegion, enemyLaserTextureRegion)); enemySpawnTimer -= timeBetweenEnemySpawns; } } private void detectInput(float deltaTime) { //keyboard input //strategy: determine the max distance the ship can move //check each key that matters and move accordingly float leftLimit, rightLimit, upLimit, downLimit; leftLimit = -playerShip.boundingBox.x; downLimit = -playerShip.boundingBox.y; rightLimit = WORLD_WIDTH - playerShip.boundingBox.x - playerShip.boundingBox.width; upLimit = (float) WORLD_HEIGHT / 2 - playerShip.boundingBox.y - playerShip.boundingBox.height; if (Gdx.input.isKeyPressed(Input.Keys.RIGHT) && rightLimit > 0) { playerShip.translate(Math.min(playerShip.movementSpeed * deltaTime, rightLimit), 0f); } if (Gdx.input.isKeyPressed(Input.Keys.UP) && upLimit > 0) { playerShip.translate(0f, Math.min(playerShip.movementSpeed * deltaTime, upLimit)); } if (Gdx.input.isKeyPressed(Input.Keys.LEFT) && leftLimit < 0) { playerShip.translate(Math.max(-playerShip.movementSpeed * deltaTime, leftLimit), 0f); } if (Gdx.input.isKeyPressed(Input.Keys.DOWN) && downLimit < 0) { playerShip.translate(0f, Math.max(-playerShip.movementSpeed * deltaTime, downLimit)); } //touch input (also mouse) if (Gdx.input.isTouched()) { //get the screen position of the touch float xTouchPixels = Gdx.input.getX(); float yTouchPixels = Gdx.input.getY(); //convert to world position Vector2 touchPoint = new Vector2(xTouchPixels, yTouchPixels); touchPoint = viewport.unproject(touchPoint); //calculate the x and y differences Vector2 playerShipCentre = new Vector2( playerShip.boundingBox.x + playerShip.boundingBox.width / 2, playerShip.boundingBox.y + playerShip.boundingBox.height / 2); float touchDistance = touchPoint.dst(playerShipCentre); if (touchDistance > TOUCH_MOVEMENT_THRESHOLD) { float xTouchDifference = touchPoint.x - playerShipCentre.x; float yTouchDifference = touchPoint.y - playerShipCentre.y; //scale to the maximum speed of the ship float xMove = xTouchDifference / touchDistance * playerShip.movementSpeed * deltaTime; float yMove = yTouchDifference / touchDistance * playerShip.movementSpeed * deltaTime; if (xMove > 0) xMove = Math.min(xMove, rightLimit); else xMove = Math.max(xMove, leftLimit); if (yMove > 0) yMove = Math.min(yMove, upLimit); else yMove = Math.max(yMove, downLimit); playerShip.translate(xMove, yMove); } } } private void moveEnemy(EnemyShip enemyShip, float deltaTime) { //strategy: determine the max distance the ship can move float leftLimit, rightLimit, upLimit, downLimit; leftLimit = -enemyShip.boundingBox.x; downLimit = (float) WORLD_HEIGHT / 2 - enemyShip.boundingBox.y; rightLimit = WORLD_WIDTH - enemyShip.boundingBox.x - enemyShip.boundingBox.width; upLimit = WORLD_HEIGHT - enemyShip.boundingBox.y - enemyShip.boundingBox.height; float xMove = enemyShip.getDirectionVector().x * enemyShip.movementSpeed * deltaTime; float yMove = enemyShip.getDirectionVector().y * enemyShip.movementSpeed * deltaTime; if (xMove > 0) xMove = Math.min(xMove, rightLimit); else xMove = Math.max(xMove, leftLimit); if (yMove > 0) yMove = Math.min(yMove, upLimit); else yMove = Math.max(yMove, downLimit); enemyShip.translate(xMove, yMove); } private void detectCollisions() { //for each player laser, check whether it intersects an enemy ship ListIterator<Laser> laserListIterator = playerLaserList.listIterator(); while (laserListIterator.hasNext()) { Laser laser = laserListIterator.next(); ListIterator<EnemyShip> enemyShipListIterator = enemyShipList.listIterator(); while (enemyShipListIterator.hasNext()) { EnemyShip enemyShip = enemyShipListIterator.next(); if (enemyShip.intersects(laser.boundingBox)) { //contact with enemy ship if (enemyShip.hitAndCheckDestroyed(laser)) { enemyShipListIterator.remove(); explosionList.add( new Explosion(explosionTexture, new Rectangle(enemyShip.boundingBox), 0.7f)); score += 100; } laserListIterator.remove(); break; } } } //for each enemy laser, check whether it intersects the player ship laserListIterator = enemyLaserList.listIterator(); while (laserListIterator.hasNext()) { Laser laser = laserListIterator.next(); if (playerShip.intersects(laser.boundingBox)) { //contact with player ship if (playerShip.hitAndCheckDestroyed(laser)) { explosionList.add( new Explosion(explosionTexture, new Rectangle(playerShip.boundingBox), 1.6f)); playerShip.shield = 10; playerShip.lives--; } laserListIterator.remove(); } } } private void updateAndRenderExplosions(float deltaTime) { ListIterator<Explosion> explosionListIterator = explosionList.listIterator(); while (explosionListIterator.hasNext()) { Explosion explosion = explosionListIterator.next(); explosion.update(deltaTime); if (explosion.isFinished()) { explosionListIterator.remove(); } else { explosion.draw(batch); } } } private void renderLasers(float deltaTime) { //create new lasers //player lasers if (playerShip.canFireLaser()) { Laser[] lasers = playerShip.fireLasers(); playerLaserList.addAll(Arrays.asList(lasers)); } //enemy lasers ListIterator<EnemyShip> enemyShipListIterator = enemyShipList.listIterator(); while (enemyShipListIterator.hasNext()) { EnemyShip enemyShip = enemyShipListIterator.next(); if (enemyShip.canFireLaser()) { Laser[] lasers = enemyShip.fireLasers(); enemyLaserList.addAll(Arrays.asList(lasers)); } } //draw lasers //remove old lasers ListIterator<Laser> iterator = playerLaserList.listIterator(); while (iterator.hasNext()) { Laser laser = iterator.next(); laser.draw(batch); laser.boundingBox.y += laser.movementSpeed * deltaTime; if (laser.boundingBox.y > WORLD_HEIGHT) { iterator.remove(); } } iterator = enemyLaserList.listIterator(); while (iterator.hasNext()) { Laser laser = iterator.next(); laser.draw(batch); laser.boundingBox.y -= laser.movementSpeed * deltaTime; if (laser.boundingBox.y + laser.boundingBox.height < 0) { iterator.remove(); } } } private void renderBackground(float deltaTime) { //update position of background images backgroundOffsets[0] += deltaTime * backgroundMaxScrollingSpeed / 8; backgroundOffsets[1] += deltaTime * backgroundMaxScrollingSpeed / 4; backgroundOffsets[2] += deltaTime * backgroundMaxScrollingSpeed / 2; backgroundOffsets[3] += deltaTime * backgroundMaxScrollingSpeed; //draw each background layer for (int layer = 0; layer < backgroundOffsets.length; layer++) { if (backgroundOffsets[layer] > WORLD_HEIGHT) { backgroundOffsets[layer] = 0; } batch.draw(backgrounds[layer], 0, -backgroundOffsets[layer], WORLD_WIDTH, backgroundHeight); } } @Override public void resize(int width, int height) { viewport.update(width, height, true); batch.setProjectionMatrix(camera.combined); } @Override public void pause() { } @Override public void resume() { } @Override public void hide() { } @Override public void show() { } @Override public void dispose() { batch.dispose(); } }
- Leser
package ca.grasley.spaceshooter; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.Rectangle; class Laser { //position and dimensions Rectangle boundingBox; //laser physical characteristics float movementSpeed; //world units per second //graphics TextureRegion textureRegion; public Laser(float xCentre, float yBottom, float width, float height, float movementSpeed, TextureRegion textureRegion) { this.boundingBox = new Rectangle(xCentre - width / 2, yBottom, width, height); this.movementSpeed = movementSpeed; this.textureRegion = textureRegion; } public void draw(Batch batch) { batch.draw(textureRegion, boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height); } // public Rectangle getBoundingBox() { // return boundingBox; // } }
- PlayerShip
package ca.grasley.spaceshooter; import com.badlogic.gdx.graphics.g2d.TextureRegion; class PlayerShip extends Ship { int lives; public PlayerShip(float xCentre, float yCentre, float width, float height, float movementSpeed, int shield, float laserWidth, float laserHeight, float laserMovementSpeed, float timeBetweenShots, TextureRegion shipTextureRegion, TextureRegion shieldTextureRegion, TextureRegion laserTextureRegion) { super(xCentre, yCentre, width, height, movementSpeed, shield, laserWidth, laserHeight, laserMovementSpeed, timeBetweenShots, shipTextureRegion, shieldTextureRegion, laserTextureRegion); lives = 3; } @Override public Laser[] fireLasers() { Laser[] laser = new Laser[2]; laser[0] = new Laser(boundingBox.x + boundingBox.width * 0.07f, boundingBox.y + boundingBox.height * 0.45f, laserWidth, laserHeight, laserMovementSpeed, laserTextureRegion); laser[1] = new Laser(boundingBox.x + boundingBox.width * 0.93f, boundingBox.y + boundingBox.height * 0.45f, laserWidth, laserHeight, laserMovementSpeed, laserTextureRegion); timeSinceLastShot = 0; return laser; } }
- Ship
package ca.grasley.spaceshooter; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.Rectangle; abstract class Ship { //ship characteristics float movementSpeed; //world units per second int shield; //position & dimension Rectangle boundingBox; //laser information float laserWidth, laserHeight; float laserMovementSpeed; float timeBetweenShots; float timeSinceLastShot = 0; //graphics TextureRegion shipTextureRegion, shieldTextureRegion, laserTextureRegion; public Ship(float xCentre, float yCentre, float width, float height, float movementSpeed, int shield, float laserWidth, float laserHeight, float laserMovementSpeed, float timeBetweenShots, TextureRegion shipTextureRegion, TextureRegion shieldTextureRegion, TextureRegion laserTextureRegion) { this.movementSpeed = movementSpeed; this.shield = shield; this.boundingBox = new Rectangle(xCentre - width / 2, yCentre - height / 2, width, height); this.laserWidth = laserWidth; this.laserHeight = laserHeight; this.laserMovementSpeed = laserMovementSpeed; this.timeBetweenShots = timeBetweenShots; this.shipTextureRegion = shipTextureRegion; this.shieldTextureRegion = shieldTextureRegion; this.laserTextureRegion = laserTextureRegion; } public void update(float deltaTime) { timeSinceLastShot += deltaTime; } public boolean canFireLaser() { return (timeSinceLastShot - timeBetweenShots >= 0); } public abstract Laser[] fireLasers(); public boolean intersects(Rectangle otherRectangle) { return boundingBox.overlaps(otherRectangle); } public boolean hitAndCheckDestroyed(Laser laser) { if (shield > 0) { shield--; return false; } return true; } public void translate(float xChange, float yChange) { boundingBox.setPosition(boundingBox.x+xChange, boundingBox.y+yChange); } public void draw(Batch batch) { batch.draw(shipTextureRegion, boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height); if (shield > 0) { batch.draw(shieldTextureRegion, boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height); } } }
- SpaceShooterGame
package ca.grasley.spaceshooter; import com.badlogic.gdx.Game; import java.util.Random; public class SpaceShooterGame extends Game { GameScreen gameScreen; public static Random random = new Random(); @Override public void create() { gameScreen = new GameScreen(); setScreen(gameScreen); } @Override public void dispose() { gameScreen.dispose(); } @Override public void render() { super.render(); } @Override public void resize(int width, int height) { gameScreen.resize(width, height); } }
2. Source code
Thành viên: Trương Tuấn Anh , Lê Quang Hoàng , Nguyễn Đoàn Trọng Lưu
Lớp: 22CDTH11
link github : github.com/ttuannanhh/SpaceShooter