Libgdx – Lập trình Game Sokoban

1.Cấu trúc game

Khởi tạo Project Libgdx:

  • Tạo một project Libgdx mới.
  • Đảm bảo rằng bạn đã cài đặt Libgdx trên máy của mình và tạo project với gdx-setup-ui.

Assets:

  • Chuẩn bị tất cả các tài nguyên cần thiết như hình ảnh, âm thanh, và font chữ.
  • Sao chép các tài nguyên này vào thư mục assets của project.

Cấu trúc Package:

  • Tạo các package cho mã nguồn của game. Ví dụ: com.yourname.sokobangame.

Main Game Class:

  • Tạo class chính cho game, ví dụ: GameScreen.
  • Kế thừa Game class từ Libgdx.
package com.nopalsoft.sokoban.game;

import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.viewport.StretchViewport;
import com.nopalsoft.sokoban.Assets;
import com.nopalsoft.sokoban.MainSokoban;
import com.nopalsoft.sokoban.Settings;
import com.nopalsoft.sokoban.scene2d.ContadorBar;
import com.nopalsoft.sokoban.scene2d.ControlesNoPad;
import com.nopalsoft.sokoban.scene2d.VentanaPause;
import com.nopalsoft.sokoban.screens.MainMenuScreen;
import com.nopalsoft.sokoban.screens.Screens;

public class GameScreen extends Screens {
	static final int STATE_RUNNING = 0;
	static final int STATE_PAUSED = 1;
	static final int STATE_GAME_OVER = 2;
	public int state;

	TableroRenderer renderer;
	Tablero oTablero;

	ControlesNoPad oControl;
	Button btUndo;
	Button btPausa;

	ContadorBar barTime;
	ContadorBar barMoves;

	private Stage stageGame;

	VentanaPause vtPause;

	public int level;

	public GameScreen(final MainSokoban game, int level) {
		super(game);
		this.level = level;

		stageGame = new Stage(new StretchViewport(SCREEN_WIDTH, SCREEN_HEIGHT));
		oTablero = new Tablero();

		renderer = new TableroRenderer(batcher);

		oControl = new ControlesNoPad(this);

		barTime = new ContadorBar(Assets.backgroundTime, 5, 430);
		barMoves = new ContadorBar(Assets.backgroundMoves, 5, 380);

		vtPause = new VentanaPause(this);

		Label lbNivel = new Label("Level " + (level + 1), new LabelStyle(Assets.fontRed, Color.WHITE));
		lbNivel.setWidth(barTime.getWidth());
		lbNivel.setPosition(5, 330);
		lbNivel.setAlignment(Align.center);

		btUndo = new Button(Assets.btRefresh, Assets.btRefreshPress);
		btUndo.setSize(80, 80);
		btUndo.setPosition(700, 20);
		btUndo.getColor().a = oControl.getColor().a;// Que tengan el mismo color de alpha
		btUndo.addListener(new ClickListener() {
			@Override
			public void clicked(InputEvent event, float x, float y) {
				oTablero.undo = true;
			}
		});

		btPausa = new Button(Assets.btPausa, Assets.btPausaPress);
		btPausa.setSize(60, 60);
		btPausa.setPosition(730, 410);
		// btPausa.getColor().a = oControl.getColor().a;// Que tengan el mismo color de alpha
		btPausa.addListener(new ClickListener() {
			@Override
			public void clicked(InputEvent event, float x, float y) {
				setPause();
			}

		});

		stageGame.addActor(oTablero);
		stageGame.addActor(barTime);
		stageGame.addActor(barMoves);
		stage.addActor(lbNivel);
		stage.addActor(oControl);
		stage.addActor(btUndo);
		stage.addActor(btPausa);

		setRunning();
	}

	@Override
	public void draw(float delta) {
		Assets.background.render(delta);

		// Render el tileMap
		renderer.render(delta);

		// Render el tablero
		stageGame.draw();

	}

	@Override
	public void update(float delta) {

		if (state != STATE_PAUSED) {
			stageGame.act(delta);
			barMoves.updateActualNum(oTablero.moves);
			barTime.updateActualNum((int) oTablero.time);

			if (state == STATE_RUNNING && oTablero.state == Tablero.STATE_GAMEOVER) {
				setGameover();
			}
		}

	}

	private void setGameover() {
		state = STATE_GAME_OVER;
		Settings.levelCompeted(level, oTablero.moves, (int) oTablero.time);
		stage.addAction(Actions.sequence(Actions.delay(.35f), Actions.run(new Runnable() {
			@Override
			public void run() {
				level += 1;
				if (level >= Settings.NUM_MAPS)
					changeScreenWithFadeOut(MainMenuScreen.class, game);
				else
					changeScreenWithFadeOut(GameScreen.class, level, game);

			}
		})));
	}

	public void setRunning() {
		if (state != STATE_GAME_OVER) {
			state = STATE_RUNNING;
		}
	}

	private void setPause() {
		if (state == STATE_RUNNING) {
			state = STATE_PAUSED;
			vtPause.show(stage);
		}
	}

	@Override
	public void up() {
		oTablero.moveUp = true;
		super.up();
	}

	@Override
	public void down() {
		oTablero.moveDown = true;
		super.down();
	}

	@Override
	public void right() {
		oTablero.moveRight = true;
		super.right();
	}

	@Override
	public void left() {
		oTablero.moveLeft = true;
		super.left();
	}

	@Override
	public boolean keyDown(int keycode) {
		if (state == STATE_RUNNING) {
			if (keycode == Keys.LEFT || keycode == Keys.A) {
				oTablero.moveLeft = true;

			}
			else if (keycode == Keys.RIGHT || keycode == Keys.D) {
				oTablero.moveRight = true;

			}
			else if (keycode == Keys.UP || keycode == Keys.W) {
				oTablero.moveUp = true;

			}
			else if (keycode == Keys.DOWN || keycode == Keys.S) {
				oTablero.moveDown = true;

			}
			else if (keycode == Keys.Z) {
				oTablero.undo = true;

			}
			else if (keycode == Keys.ESCAPE || keycode == Keys.BACK) {
				setPause();
			}
		}
		else if (keycode == Keys.ESCAPE || keycode == Keys.BACK) {
			if (vtPause.isShown())
				vtPause.hide();
		}

		return true;
	}

	@Override
	public void pinchStop() {

	}
}

Screen:

  • Tạo các màn hình cho game, chẳng hạn MainMenuScreenGameScreenGameOverScreen, vv.
  • Mỗi màn hình kế thừa từ ScreenAdapter.
package com.nopalsoft.sokoban.screens;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.nopalsoft.sokoban.Assets;
import com.nopalsoft.sokoban.MainSokoban;
import com.nopalsoft.sokoban.scene2d.LevelSelector;

public class MainMenuScreen extends Screens {

	LevelSelector lvlSelector;

	Table tbMenu;
	Button btLeaderboard, btAchievements, btFacebook, btSettings, btMore;
	Button btNextPage, btPreviousPage;

	public MainMenuScreen(final MainSokoban game) {
		super(game);

		lvlSelector = new LevelSelector(this);

		btPreviousPage = new Button(Assets.btIzq, Assets.btIzqPress);
		btPreviousPage.setSize(75, 75);
		btPreviousPage.setPosition(65, 220);
		btPreviousPage.addListener(new ClickListener() {
			@Override
			public void clicked(InputEvent event, float x, float y) {
				right();
			}

		});
		btNextPage = new Button(Assets.btDer, Assets.btDerPress);
		btNextPage.setSize(75, 75);
		btNextPage.setPosition(660, 220);
		btNextPage.addListener(new ClickListener() {
			@Override
			public void clicked(InputEvent event, float x, float y) {
				left();
			}
		});

		btLeaderboard = new Button(Assets.btLeaderboard, Assets.btLeaderboardPress);
		btLeaderboard.addListener(new ClickListener() {

		});

		btAchievements = new Button(Assets.btAchievement, Assets.btAchievementPress);
		btAchievements.addListener(new ClickListener() {

		});

		btFacebook = new Button(Assets.btFacebook, Assets.btFacebookPress);
		btFacebook.addListener(new ClickListener() {

		});

		btSettings = new Button(Assets.btSettings, Assets.btSettingsPress);
		btSettings.addListener(new ClickListener() {

		});

		btMore = new Button(Assets.btMas, Assets.btMasPress);
		btMore.addListener(new ClickListener() {

		});

		tbMenu = new Table();
		tbMenu.defaults().size(80).pad(7.5f);

		// tbMenu.add(btLeaderboard);
		tbMenu.add(btAchievements);
		tbMenu.add(btFacebook);
		tbMenu.add(btSettings);
		tbMenu.add(btMore);

		tbMenu.pack();
		tbMenu.setPosition(SCREEN_WIDTH / 2f - tbMenu.getWidth() / 2f, 20);

		stage.addActor(lvlSelector);
		stage.addActor(tbMenu);
		stage.addActor(btPreviousPage);
		stage.addActor(btNextPage);
	}

	@Override
	public void update(float delta) {

	}

	@Override
	public void draw(float delta) {
		Assets.background.render(delta);
	}

	@Override
	public void right() {
		lvlSelector.previousPage();
	}

	@Override
	public void left() {
		lvlSelector.nextPage();

	}

	@Override
	public boolean keyDown(int keycode) {

		if (keycode == Keys.LEFT || keycode == Keys.A) {
			right();
		}
		else if (keycode == Keys.RIGHT || keycode == Keys.D) {
			left();
		}
		else if (keycode == Keys.ESCAPE || keycode == Keys.BACK) {
			Gdx.app.exit();
		}

		return true;
	}


}

WordRender và WordController:

  • Tạo các class WordRender và WordController để quản lý hiển thị và điều khiển các “word” (tức là các vật thể như người chơi, các platform, vv).
  • WordRender sẽ chịu trách nhiệm vẽ các word lên màn hình sử dụng SpriteBatch.
  • WordController sẽ xử lý logic và vị trí của các word, cũng như các tương tác giữa chúng.
package com.nopalsoft.sokoban.game;

import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.nopalsoft.sokoban.Assets;
import com.nopalsoft.sokoban.screens.Screens;

public class TableroRenderer {

	SpriteBatch batcher;
	OrthogonalTiledMapRenderer tiledRender;
	TiledMapTileLayer mapStaticLayer;
	OrthographicCamera oCam;

	public TableroRenderer(SpriteBatch batch) {
		batcher = batch;
		oCam = new OrthographicCamera(Screens.SCREEN_WIDTH, Screens.SCREEN_HEIGHT);
		oCam.position.set(Screens.SCREEN_WIDTH / 2f, Screens.SCREEN_HEIGHT / 2f, 0);
		tiledRender = new OrthogonalTiledMapRenderer(Assets.map, Tablero.UNIT_SCALE);
		mapStaticLayer = (TiledMapTileLayer) tiledRender.getMap().getLayers().get("StaticMap");

	}

	public void render(float delta) {
		
		
		
		oCam.update();
		tiledRender.setView(oCam);
		tiledRender.getBatch().begin();
		tiledRender.renderTileLayer(mapStaticLayer);
		tiledRender.getBatch().end();
	}

}
package com.nopalsoft.sokoban.scene2d;

import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.nopalsoft.sokoban.Assets;
import com.nopalsoft.sokoban.game.GameScreen;

public class ControlesNoPad extends Table {

	GameScreen gameScreen;

	Button btUp, btDown, btLeft, btRight;

	public ControlesNoPad(GameScreen oScreen) {
		gameScreen = oScreen;

		getColor().a = .4f;
		init();

		int buttonSize = 75;
		defaults().size(buttonSize);

		add(btUp).colspan(2).center();
		row();
		add(btLeft).left();
		add(btRight).right().padLeft(buttonSize / 1.15f);
		row();
		add(btDown).colspan(2).center();
		pack();
	}

	private void init() {
		btUp = new Button(Assets.btUp, Assets.btUpPress);
		btUp.addListener(new ClickListener() {
			@Override
			public void clicked(InputEvent event, float x, float y) {
				gameScreen.up();
			}
		});

		btDown = new Button(Assets.btDown, Assets.btDownPress);
		btDown.addListener(new ClickListener() {
			@Override
			public void clicked(InputEvent event, float x, float y) {
				gameScreen.down();
			}
		});

		btLeft = new Button(Assets.btIzq, Assets.btIzqPress);
		btLeft.addListener(new ClickListener() {
			@Override
			public void clicked(InputEvent event, float x, float y) {
				gameScreen.left();
			}
		});

		btRight = new Button(Assets.btDer, Assets.btDerPress);
		btRight.addListener(new ClickListener() {
			@Override
			public void clicked(InputEvent event, float x, float y) {
				gameScreen.right();
			}
		});

	}

}

Entities và World

  • Xây dựng các entity như Player, Platform, Coin, vv.
  • Quản lý vật lý và sự tương tác giữa chúng trong World, có thể sử dụng Box2D cho phần vật lý.

Input Handling:

  • Xử lý sự kiện nhấn nút và cử chỉ của người chơi.
  • Sử dụng InputProcessor để lắng nghe và xử lý các sự kiện input.

Game Logic:

  • Viết logic để điều khiển trò chơi như điểm số, trạng thái game (bắt đầu, kết thúc), vv.

Rendering:

  • Sử dụng WordRender để vẽ các word lên màn hình.
  • Quản lý việc hiển thị các entity và background.

Sound và Music:

  • Sử dụng Sound và Music để thêm hiệu ứng âm thanh và nhạc vào game.

UI và HUD:

  • Tạo các UI elements như score display, buttons, vv.
  • Sử dụng Stage và Actor để quản lý UI.

Collision Detection:

  • Xác định và xử lý va chạm giữa các đối tượng trong game.

Game States:

  • Quản lý các trạng thái khác nhau của trò chơi như MenuPlayingPausedGameOver, vv.

Game Loop:

  • Thực hiện vòng lặp game để cập nhật và vẽ các đối tượng.

Testing và Debugging:

  • Kiểm tra, sửa lỗi và điều chỉnh trò chơi để đảm bảo hoạt động mượt mà và không có lỗi.

2.Source code

https://github.com/VANDINHHOAI/Sokoban-game

Nhóm: Văn Đình Hoài, Trương Trung Tín

Lớp: 22CDTH11