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
MainMenuScreen
,GameScreen
,GameOverScreen
, 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ụngSpriteBatch
.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ư
Menu
,Playing
,Paused
,GameOver
, 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