Skip to content

Instantly share code, notes, and snippets.

@komamitsu
Last active November 11, 2018 13:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save komamitsu/b470279b9577bb460655e990f3488f07 to your computer and use it in GitHub Desktop.
Save komamitsu/b470279b9577bb460655e990f3488f07 to your computer and use it in GitHub Desktop.
package com.mygdx.game;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL20;
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.math.Rectangle;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
public class MyGdxGame
extends ApplicationAdapter
{
private Scene scene = new TitleScene();
private static final int WINDOW_WIDTH = 800;
private static final int WINDOW_HEIGHT = 640;
@Override
public void create()
{
Gdx.graphics.setWindowedMode(WINDOW_WIDTH, WINDOW_HEIGHT);
}
@Override
public void render()
{
if (!scene.isInitialized()) {
scene.create();
}
if (scene.render()) {
scene.dispose();
if (scene instanceof TitleScene) {
scene = new GameScene();
}
else if (scene instanceof GameScene) {
scene = new GameOverScene();
}
else if (scene instanceof GameOverScene) {
scene = new TitleScene();
}
}
}
@Override
public void dispose()
{
scene.dispose();
}
}
interface Scene
{
void create();
boolean isInitialized();
boolean render();
void dispose();
}
class TitleScene
implements Scene
{
private boolean initialized = false;
private SpriteBatch batch;
private BitmapFont font;
@Override
public void create()
{
batch = new SpriteBatch();
font = new BitmapFont();
initialized = true;
}
@Override
public boolean isInitialized()
{
return initialized;
}
@Override
public boolean render()
{
Gdx.gl.glClearColor(128f / 256, 208f / 256, 48f / 256, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
font.draw(
batch,
"Diep.io like Game\n-- Please press space key --",
Gdx.graphics.getWidth() / 2 - 50,
Gdx.graphics.getHeight() / 2);
batch.end();
return Gdx.input.isKeyJustPressed(Input.Keys.SPACE);
}
@Override
public void dispose()
{
batch.dispose();
font.dispose();
}
}
class GameScene
implements Scene
{
private static final int SPEED = 5;
private static final int WINDOW_WIDTH = 800;
private static final int WINDOW_HEIGHT = 640;
private boolean initialized = false;
interface Damagable
{
enum Result {
HIT, DESTROIED, NONE
}
Result onShot(int damage, Rectangle rect);
}
static class Player
{
private static final Texture TEXTURE = new Texture("tank0.png");
private static final long FIRE_INTERVAL_MS = 100;
private final Image image;
private final List<Bullet> bullets = new CopyOnWriteArrayList<Bullet>();
private final Camera camera;
private long lastBulletFire = 0;
private int hp = 100;
Player(Camera camera, float x, float y)
{
// プレイヤーとカメラは一緒に動くので、簡単のためにプレイヤーにカメラを持たせてしまう
// OnMovedCallbackとか経由でカメラを更新しても良いかも
this.camera = camera;
image = new Image(TEXTURE);
image.setPosition(x, y);
}
private void rotate(float moveX, float moveY)
{
// float diffX = moveX;
// float diffY = moveY;
float diffX = Gdx.input.getX() - camera.viewportWidth / 2;
float diffY = -(Gdx.input.getY() - camera.viewportHeight / 2);
// 縦・横の移動量から回転する角度を計算
double d = Math.toDegrees(Math.atan2(diffY, diffX));
// 回転する軸を画像の中心に設定
image.setOrigin(
image.getImageWidth() / 2,
image.getImageHeight() / 2);
// 回転
image.setRotation((float) d);
}
private void updateCamera(float moveX, float moveY)
{
// カメラの更新
// カメラの位置をプレイヤーと同じ分だけ移動させる
camera.translate(moveX, moveY, 0);
// カメラの状態を更新
camera.update();
}
private Rectangle getRect()
{
return new Rectangle(image.getX(), image.getY(), image.getWidth(), image.getHeight());
}
void move(float moveX, float moveY, Blocks blocks)
{
// 発射した弾の更新
moveBullets(blocks);
// ブロックと衝突してたら反発
if (blocks.conflicted(getRect())) {
moveX = -(moveX * 2);
moveY = -(moveY * 2);
// ダメージも受ける
hp -= 5;
}
// 画像の回転
rotate(moveX, moveY);
// 場所を移動距離分更新
image.setPosition(
image.getX() + moveX,
image.getY() + moveY);
// カメラの更新
updateCamera(moveX, moveY);
}
private void moveBullets(Blocks blocks)
{
for (Bullet bullet : bullets) {
bullet.move();
bullet.hit(blocks);
if (bullet.finished()) {
bullets.remove(bullet);
}
}
}
void fire()
{
long now = System.currentTimeMillis();
// 前回の発射から一定時間経過していれば、発射可能
if (lastBulletFire + FIRE_INTERVAL_MS < now) {
bullets.add(
new Bullet(
image.getX() + image.getOriginX(),
image.getY() + image.getOriginY(),
image.getRotation(),
0)
);
lastBulletFire = now;
}
}
void draw(SpriteBatch batch)
{
image.draw(batch, 1);
for (Bullet bullet : bullets) {
bullet.draw(batch);
}
}
boolean isAlive()
{
return hp > 0;
}
int getHp()
{
return hp;
}
float getX()
{
return image.getX();
}
float getY()
{
return image.getY();
}
}
static class Bullet
{
private static final Texture TEXTURE = new Texture("bullet.png");
private static final Texture TEXTURE_CRASHING = new Texture("bullet_crashing.png");
private static final int WIDTH = 32;
private static final int HEIGHT = 32;
private final Image image;
private final float angle;
private int distance;
private int crashingCount;
private boolean broken;
Bullet(float x, float y, float angle, int distance)
{
image = new Image(TEXTURE);
image.setPosition(x - WIDTH / 2, y - HEIGHT / 2);
image.setWidth(WIDTH);
image.setHeight(HEIGHT);
this.angle = angle;
this.distance = distance;
}
void move()
{
if (crashingCount > 0) {
crashingCount -= 1;
if (crashingCount == 0) {
broken = true;
return;
}
}
float moveX = (float) (10 * Math.cos(Math.toRadians(angle)));
float moveY = (float) (10 * Math.sin(Math.toRadians(angle)));
image.setPosition(image.getX() + moveX, image.getY() + moveY);
distance += 10;
}
boolean finished()
{
// 一定距離を飛んだら終了
return distance >= 800 || broken;
}
void draw(SpriteBatch batch)
{
if (crashingCount > 0) {
batch.draw(TEXTURE_CRASHING, image.getX(), image.getY(), WIDTH, HEIGHT);
}
else {
image.draw(batch, 1);
}
}
void hit(Damagable damagable)
{
if (crashingCount > 0 || broken) {
return;
}
Rectangle myRect = getRect();
if (damagable.onShot(10, myRect) != Damagable.Result.NONE) {
crashingCount = 5;
}
}
private Rectangle getRect()
{
return new Rectangle(
(int) image.getX(),
(int) image.getY(),
(int) image.getWidth(),
(int) image.getHeight());
}
}
static class Block
implements Damagable
{
private static final int SPEED = 3;
private static final List<Texture> TEXTURES = new ArrayList<Texture>(12);
private static final Texture TEXTURE_DAMAGED = new Texture("seiza_damaged.png");
private int hp = 100;
private int inDamagedCount = 0;
private final Image image;
private final Image imageDamaged;
@Override
public Result onShot(int damage, Rectangle rect)
{
if (getRect().overlaps(rect)) {
if (hp > 0) {
hp -= damage;
}
}
else {
return Result.NONE;
}
inDamagedCount = 10;
if (hp <= 0) {
return Result.DESTROIED;
}
else {
return Result.HIT;
}
}
static {
// 12個作成したいので12回ループ
for (int i = 0; i < 12; i++) {
// たまたま画像がseiza1.pngからseiza12.pngなので、ループカウントを画像ファイル名の指定に利用
TEXTURES.add(new Texture("seiza" + (i + 1) + ".png"));
}
}
Block(int type, float x, float y)
{
image = new Image(TEXTURES.get(type % 12));
imageDamaged = new Image(TEXTURE_DAMAGED);
imageDamaged.setVisible(false);
image.setPosition(x, y);
}
void move(Player player)
{
// 回転する軸を画像の中心に設定
image.setOrigin(image.getImageWidth() / 2, image.getImageHeight() / 2);
// ちょっとずつ回転
image.rotateBy(1);
if (player.getX() > image.getX()) {
image.setX(image.getX() + SPEED);
}
else if (player.getX() < image.getX()) {
image.setX(image.getX() - SPEED);
}
if (player.getY() > image.getY()) {
image.setY(image.getY() + SPEED);
}
else if (player.getY() < image.getY()) {
image.setY(image.getY() - SPEED);
}
// 回転する軸を画像の中心に設定
image.setOrigin(image.getImageWidth() / 2, image.getImageHeight() / 2);
// ちょっとずつ回転
image.rotateBy(1);
}
void draw(SpriteBatch batch)
{
if (inDamagedCount > 0) {
image.setVisible(false);
imageDamaged.setX(image.getX());
imageDamaged.setY(image.getY());
imageDamaged.setOrigin(image.getOriginX(), image.getOriginY());
imageDamaged.setRotation(image.getRotation());
imageDamaged.setVisible(true);
imageDamaged.draw(batch, 1);
inDamagedCount -= 1;
}
else {
image.setVisible(true);
imageDamaged.setVisible(false);
image.draw(batch, 1);
}
}
Rectangle getRect()
{
return new Rectangle(
(int) image.getX(),
(int) image.getY(),
(int) image.getWidth(),
(int) image.getHeight());
}
}
static class Blocks
implements Damagable
{
private final List<Block> blocks = new CopyOnWriteArrayList<Block>();
Blocks(int count, float width, float height)
{
// 適当なキャラクラーたちを複数作成
Random random = new Random();
for (int i = 0; i < count; i++) {
// 適当な位置に配置
blocks.add(new Block(i,
random.nextInt((int) width) - width / 2,
random.nextInt((int) height) - height / 2));
}
}
void move(Player player)
{
for (Block block : blocks) {
block.move(player);
}
}
void draw(SpriteBatch batch)
{
for (Block block : blocks) {
block.draw(batch);
}
}
boolean conflicted(Rectangle playerRect)
{
// 全てのブロックの領域との衝突をチェック
for (Block block : blocks) {
Rectangle blockRect = block.getRect();
if (playerRect.overlaps(blockRect)) {
return true;
}
}
return false;
}
@Override
public Result onShot(int damage, Rectangle rect)
{
for (Block block : blocks) {
switch (block.onShot(damage, rect)) {
case HIT:
return Result.HIT;
case DESTROIED:
blocks.remove(block);
return Result.DESTROIED;
case NONE:
break;
}
}
return Result.NONE;
}
}
static class Background
{
private static final Texture TEXTURE = new Texture("bg.jpeg");
void draw(SpriteBatch batch, float centerX, float centerY, float width, float height)
{
// プレイヤーがいる画面を中心に、上下左右 3 x 3 画面の背景画像を描画
// 背景画像はプレイヤーの細かい動きによらず、原点から画面幅・高さの倍数ごとの固定表示で
// プレイヤーが移動している感じを出している
for (int iY = 0; iY < 3; iY++) {
for (int iX = 0; iX < 3; iX++) {
batch.draw(TEXTURE,
(float) Math.ceil((((centerX - width / 2) + width * (iX - 1)) / width)) * width,
(float) Math.ceil((((centerY - height / 2) + height * (iY - 1)) / height)) * height,
width, height);
}
}
}
}
static class HitPoint
{
private final BitmapFont font;
HitPoint(BitmapFont font)
{
this.font = font;
}
void draw(SpriteBatch batch, Player player)
{
font.draw(
batch,
String.format("HP: %d", player.getHp()),
player.getX() - 300,
player.getY() - 200);
}
}
private SpriteBatch batch;
private Player player;
private Blocks blocks;
private BitmapFont font;
private Background bg;
private HitPoint hp;
private Camera camera;
private Camera setupCamera(float width, float height)
{
// カメラの設定
OrthographicCamera camera = new OrthographicCamera();
// 画面全体を表示
camera.setToOrtho(false, width, height);
// 画面の中心にカメラを配置
camera.position.set(camera.viewportWidth / 2f, camera.viewportHeight / 2f, 0);
// カメラの更新
camera.update();
return camera;
}
@Override
public void create()
{
batch = new SpriteBatch();
font = new BitmapFont();
// 画面の縦と横
float width = Gdx.graphics.getWidth();
float height = Gdx.graphics.getHeight();
// カメラの設定
camera = setupCamera(width, height);
// プレイヤーを作成。開始位置を画面の中心にする
player = new Player(camera, width / 2, height / 2);
// 複数のブロックを作成
blocks = new Blocks(36, width * 16, height * 16);
// 背景画像
bg = new Background();
// HP表示
hp = new HitPoint(font);
initialized = true;
}
@Override
public boolean isInitialized()
{
return initialized;
}
private void movePlayerWithInput()
{
// プレイヤーの移動
// 今回の描画でプレイヤーをどれだけ移動するかの値
int moveX = 0;
int moveY = 0;
// キー入力に応じてプレイヤーの移動量を設定
if (Gdx.input.isKeyPressed(Input.Keys.W)) {
moveY = SPEED;
}
if (Gdx.input.isKeyPressed(Input.Keys.S)) {
moveY = -SPEED;
}
if (Gdx.input.isKeyPressed(Input.Keys.D)) {
moveX = SPEED;
}
if (Gdx.input.isKeyPressed(Input.Keys.A)) {
moveX = -SPEED;
}
if (Gdx.input.isKeyPressed(Input.Keys.SPACE) ||
Gdx.input.isButtonPressed(Input.Buttons.LEFT)) {
player.fire();
}
// プレイヤーと関連するものを移動
player.move(moveX, moveY, blocks);
}
@Override
public boolean render()
{
// 背景色の指定
Gdx.gl.glClearColor(128f / 256, 208f / 256, 48f / 256, 1);
// 毎回全体をクリアしてる?
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// ゲームオーバーであれば何もしないで終わる
if (!player.isAlive()) {
return true;
}
movePlayerWithInput();
blocks.move(player);
// スプライト画像等の描画
// SpriteBatchとカメラを紐付け
batch.setProjectionMatrix(camera.combined);
// 描画開始
batch.begin();
bg.draw(batch, player.getX(), player.getY(), WINDOW_WIDTH, WINDOW_HEIGHT);
player.draw(batch);
blocks.draw(batch);
hp.draw(batch, player);
// 描画終了
batch.end();
return false;
}
@Override
public void dispose()
{
// 描画をするやつを破棄
batch.dispose();
font.dispose();
}
}
class GameOverScene
implements Scene
{
private boolean initialized = false;
private SpriteBatch batch;
private BitmapFont font;
@Override
public void create()
{
batch = new SpriteBatch();
font = new BitmapFont();
initialized = true;
}
@Override
public boolean isInitialized()
{
return initialized;
}
@Override
public boolean render()
{
Gdx.gl.glClearColor(128f / 256, 208f / 256, 48f / 256, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
font.draw(
batch,
"Game Over\n-- Please press space key --",
Gdx.graphics.getWidth() / 2 - 50,
Gdx.graphics.getHeight() / 2);
batch.end();
return Gdx.input.isKeyJustPressed(Input.Keys.SPACE);
}
@Override
public void dispose()
{
batch.dispose();
font.dispose();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment