ภาพรวมโปรเจกต์
เอกสารนี้เป็น HTML single file ที่รวบรวมทั้ง สถาปัตยกรรม และ Java skeleton code สำหรับเกม Container Truck Fleet Simulator ด้วย LibGDX โดยเน้นสองมิติหลักคือ
- Realistic Logistics Simulation – จำลอง flow รับตู้เปล่า → โรงงาน → ท่าเรือ → กลับลานจอด พร้อมเวลา, ระยะทาง, ค่าน้ำมัน, ต้นทุน
- Gamification – mission, level, score, ปลดล็อกรถ และวิเคราะห์ performance ของทั้ง fleet
ด้านล่างนี้เป็นโครงสร้าง class หลักของโปรเจกต์ และ skeleton code สำหรับแต่ละ class พร้อมคำอธิบายภาษาไทยแบบละเอียด เพื่อให้สามารถนำไปต่อยอดจริงใน LibGDX ได้
รายการคลาสหลัก
- Entry & Core
DesktopLauncherGwtLauncherTruckSimGame
- Screens
MainMenuScreenWorldMapScreenResultScreen
- World & Map
WorldMapManagerLocation
ต่อเนื่อง
- Fleet & Jobs
TruckUnitFleetManagerJobOrder
- Simulation & Metrics
TripStatsCostCalculatorScoreSystem
- UI & HUD
HudOverlay
หมายเหตุ: โค้ดด้านล่างเป็น skeleton ไม่ครบทุกรายละเอียด แต่จัดโครงให้พร้อมต่อยอดในโปรเจกต์ LibGDX จริง
Entry & Core Classes
คลาสนี้ใช้สำหรับรันเกมบน Desktop (ช่วยทดสอบ logic ก่อน export ไป HTML5) โครงสร้างมาตรฐานของ LibGDX
package com.trucksim.desktop;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.trucksim.TruckSimGame;
/**
* จุดเริ่มต้นสำหรับรันเกมบน Desktop
* ใช้ทดสอบเกม logic, rendering, input ก่อน export ไป HTML5
*/
public class DesktopLauncher {
public static void main (String[] arg) {
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.title = "Container Truck Fleet Simulator";
config.width = 1280;
config.height = 720;
config.resizable = true;
new LwjglApplication(new TruckSimGame(), config);
}
}
สำหรับ deployment แบบ Web/HTML5 LibGDX จะใช้ GWT backend คลาสนี้เป็นตัวกำหนดการตั้งค่าและ bootstrap เกมบน Browser
package com.trucksim.client;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.backends.gwt.GwtApplication;
import com.badlogic.gdx.backends.gwt.GwtApplicationConfiguration;
import com.trucksim.TruckSimGame;
/**
* Entry point สำหรับรันเกมบน HTML5 ผ่าน GWT
*/
public class GwtLauncher extends GwtApplication {
@Override
public GwtApplicationConfiguration getConfig () {
// กำหนดขนาด canvas บน browser
return new GwtApplicationConfiguration(1280, 720);
}
@Override
public ApplicationListener createApplicationListener () {
// ใช้ core game class เดียวกับ Desktop
return new TruckSimGame();
}
}
คลาสหลักของเกม ทำหน้าที่จัดการ Screen ต่าง ๆ (เมนู, แผนที่, หน้าสรุป) และเป็นศูนย์กลางของ resource/shared systems
package com.trucksim;
import com.badlogic.gdx.Game;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.trucksim.screens.MainMenuScreen;
/**
* Core Game class ของ LibGDX
* จัดการ SpriteBatch, Font, และ Screen หลัก ๆ
*/
public class TruckSimGame extends Game {
public SpriteBatch batch;
public BitmapFont font;
@Override
public void create() {
batch = new SpriteBatch();
font = new BitmapFont();
// เริ่มแสดงจาก MainMenuScreen
this.setScreen(new MainMenuScreen(this));
}
@Override
public void render() {
super.render();
}
@Override
public void dispose() {
if (batch != null) batch.dispose();
if (font != null) font.dispose();
if (getScreen() != null) getScreen().dispose();
}
}
» จากคลาสนี้ เราจะสลับไปมาระหว่าง Screen เช่น MainMenuScreen, WorldMapScreen, ResultScreen ได้อย่างเป็นระบบ
Screen ต่าง ๆ ของเกม
หน้าจอเมนูหลัก ใช้ Scene2D UI สร้างปุ่มเริ่มเกม, เลือก mission, ดูสถิติ เบื้องต้นแสดงปุ่มเริ่ม simulation
package com.trucksim.screens;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
import com.trucksim.TruckSimGame;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
/**
* หน้าจอเมนูหลักของเกม
*/
public class MainMenuScreen implements Screen {
private final TruckSimGame game;
private Stage stage;
private Skin skin;
public MainMenuScreen(TruckSimGame game) {
this.game = game;
}
@Override
public void show() {
stage = new Stage(new ScreenViewport());
Gdx.input.setInputProcessor(stage);
// หมายเหตุ: ในโปรเจกต์จริงควรมี skin ไฟล์ .json/.atlas
skin = new Skin(Gdx.files.internal("uiskin.json"));
Table table = new Table();
table.setFillParent(true);
stage.addActor(table);
TextButton startButton = new TextButton("Start Simulation", skin);
startButton.addListener(new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
game.setScreen(new WorldMapScreen(game));
}
});
table.add(startButton).width(240).height(60);
}
@Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0.1f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(delta);
stage.draw();
}
@Override
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
}
@Override
public void pause() {}
@Override
public void resume() {}
@Override
public void hide() {
dispose();
}
@Override
public void dispose() {
if (stage != null) stage.dispose();
if (skin != null) skin.dispose();
}
}
หน้าจอนี้คือหัวใจของเกม แสดงแผนที่ 2D, รถหลายคัน (fleet), HUD และคำนวณค่าเวลาจำลอง, ระยะทาง, ค่าน้ำมัน และคะแนน
package com.trucksim.screens;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
import com.trucksim.TruckSimGame;
import com.trucksim.world.WorldMapManager;
import com.trucksim.fleet.FleetManager;
import com.trucksim.ui.HudOverlay;
/**
* หน้าจอหลักที่ใช้ render แผนที่ + fleet trucks + HUD
*/
public class WorldMapScreen implements Screen {
private final TruckSimGame game;
private OrthographicCamera camera;
private Viewport viewport;
private TiledMap map;
private OrthogonalTiledMapRenderer mapRenderer;
private WorldMapManager worldMapManager;
private FleetManager fleetManager;
private HudOverlay hud;
// เวลาใน simulation (สมมุติว่า 1 วินาทีจริง = 1 นาทีในเกม เป็นต้น)
private float simulationTimeMinutes = 0f;
private float timeScale = 1f; // เอาไว้ปรับเร่ง/ช้า simulation
public WorldMapScreen(TruckSimGame game) {
this.game = game;
}
@Override
public void show() {
camera = new OrthographicCamera();
viewport = new FitViewport(40, 22.5f, camera); // world units
// โหลดแผนที่ TMX (ต้องมีไฟล์จริงใน assets)
map = new TmxMapLoader().load("maps/port_area.tmx");
mapRenderer = new OrthogonalTiledMapRenderer(map, 1f);
worldMapManager = new WorldMapManager(map);
fleetManager = new FleetManager(worldMapManager);
fleetManager.initializeSampleFleet(); // สร้างรถตัวอย่างหลายคัน + job
hud = new HudOverlay(game.batch, fleetManager);
}
@Override
public void render(float delta) {
// อัปเดตเวลา simulation (สามารถยืด/หดเวลาได้ผ่าน timeScale)
float simDeltaMinutes = delta * 60f * timeScale;
simulationTimeMinutes += simDeltaMinutes;
// อัปเดต logic ของ fleet (เคลื่อนที่, เปลี่ยน state, อัปเดตสถิติ)
fleetManager.update(simDeltaMinutes);
// Clear screen
Gdx.gl.glClearColor(0.02f, 0.05f, 0.1f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// กำหนดตำแหน่งกล้อง (ในเวอร์ชันง่าย ๆ fix ไว้กลางแผนที่)
camera.update();
mapRenderer.setView(camera);
// Render แผนที่ก่อน
mapRenderer.render();
// Render fleet (เราจะวาด sprite รถใน worldMapManager หรือ fleetManager)
game.batch.setProjectionMatrix(camera.combined);
game.batch.begin();
fleetManager.render(game.batch);
game.batch.end();
// Render HUD ทับด้านบน
hud.update(simulationTimeMinutes);
hud.render();
}
@Override
public void resize(int width, int height) {
viewport.update(width, height);
hud.resize(width, height);
}
@Override
public void pause() {}
@Override
public void resume() {}
@Override
public void hide() {
dispose();
}
@Override
public void dispose() {
if (mapRenderer != null) mapRenderer.dispose();
if (map != null) map.dispose();
if (hud != null) hud.dispose();
}
}
ใช้แสดงสรุป performance หลังจากจบ mission เช่น รวมระยะทาง, เวลารวม, ค่าน้ำมันรวม, ต้นทุนรวม และคะแนน
package com.trucksim.screens;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
import com.trucksim.TruckSimGame;
import com.trucksim.sim.ScoreSystem;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
/**
* หน้าจอสรุปผลหลังจบ mission/level
*/
public class ResultScreen implements Screen {
private final TruckSimGame game;
private final ScoreSystem.ScoreSummary summary;
private Stage stage;
private Skin skin;
public ResultScreen(TruckSimGame game, ScoreSystem.ScoreSummary summary) {
this.game = game;
this.summary = summary;
}
@Override
public void show() {
stage = new Stage(new ScreenViewport());
Gdx.input.setInputProcessor(stage);
skin = new Skin(Gdx.files.internal("uiskin.json"));
Table table = new Table();
table.setFillParent(true);
stage.addActor(table);
Label title = new Label("Mission Result", skin);
Label stats = new Label(
"Total Distance: " + summary.totalDistanceKm + " km\n" +
"Total Fuel: " + summary.totalFuelLiters + " L\n" +
"Total Cost: " + summary.totalCost + " THB\n" +
"Score: " + summary.finalScore,
skin
);
TextButton backButton = new TextButton("Back to Menu", skin);
backButton.addListener(new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
game.setScreen(new MainMenuScreen(game));
}
});
table.add(title).padBottom(20).row();
table.add(stats).padBottom(20).row();
table.add(backButton).width(220).height(50);
}
@Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(delta);
stage.draw();
}
@Override
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
}
@Override
public void pause() {}
@Override
public void resume() {}
@Override
public void hide() {
dispose();
}
@Override
public void dispose() {
if (stage != null) stage.dispose();
if (skin != null) skin.dispose();
}
}
World & Map Management
คลาสสำหรับเก็บข้อมูลจุดสำคัญ (POI) บนแผนที่ เช่น ลานตู้, โรงงาน, ท่าเรือ, ลานจอด โดยเก็บชื่อ ประเภท และพิกัดใน world units
package com.trucksim.world;
/**
* แทนตำแหน่งสำคัญบนแผนที่ เช่น Depot, Factory, Port, Yard
*/
public class Location {
public enum Type {
DEPOT,
FACTORY,
PORT,
YARD
}
private final String id;
private final String name;
private final Type type;
// พิกัด world (เช่น หน่วย tile หรือ meters-scaled)
private final float x;
private final float y;
public Location(String id, String name, Type type, float x, float y) {
this.id = id;
this.name = name;
this.type = type;
this.x = x;
this.y = y;
}
public String getId() { return id; }
public String getName() { return name; }
public Type getType() { return type; }
public float getX() { return x; }
public float getY() { return y; }
}
ใช้บริหารจัดการแผนที่ TMX และตำแหน่งต่าง ๆ รวมถึง utility function สำหรับแปลงตำแหน่ง, คำนวณระยะทาง เป็นต้น
package com.trucksim.world;
import com.badlogic.gdx.maps.tiled.TiledMap;
import java.util.ArrayList;
import java.util.List;
/**
* จัดการข้อมูลเกี่ยวกับแผนที่ เช่น Location ต่าง ๆ และ helper method
*/
public class WorldMapManager {
private final TiledMap map;
private final List<Location> locations = new ArrayList<>();
public WorldMapManager(TiledMap map) {
this.map = map;
loadDefaultLocations();
}
/**
* ในโปรเจกต์จริง อาจโหลดจาก Object Layer ใน TMX
* ใน skeleton นี้จะสร้างตัวอย่างด้วยมือ
*/
private void loadDefaultLocations() {
locations.add(new Location("depot1", "Depot A", Location.Type.DEPOT, 5f, 10f));
locations.add(new Location("factory1", "Factory A", Location.Type.FACTORY, 18f, 12f));
locations.add(new Location("port1", "Port A", Location.Type.PORT, 30f, 8f));
locations.add(new Location("yard1", "Truck Yard", Location.Type.YARD, 3f, 4f));
}
public List<Location> getLocations() {
return locations;
}
public Location findById(String id) {
for (Location loc : locations) {
if (loc.getId().equals(id)) return loc;
}
return null;
}
/**
* คำนวณระยะทาง (Euclidean) ระหว่างจุดสองจุดบนแผนที่
*/
public float distance(Location a, Location b) {
float dx = a.getX() - b.getX();
float dy = a.getY() - b.getY();
return (float) Math.sqrt(dx * dx + dy * dy);
}
}
Fleet, Truck Unit & Job Orders
งาน 1 เที่ยว: อาจประกอบด้วยเส้นทางย่อยหลายช่วง เช่น Yard → Depot → Factory → Port โดยกำหนด origin/destination เป็น Location
package com.trucksim.fleet;
import com.trucksim.world.Location;
/**
* แทนงานขนส่ง 1 เที่ยวของรถ 1 คัน
*/
public class JobOrder {
private final String id;
private final Location origin;
private final Location destination;
// ตัวอย่าง: Job ประเภท "รับตู้เปล่าจาก Depot ไปโรงงาน" หรือ "โรงงานไปท่าเรือ"
private final String description;
public enum Status {
PENDING,
IN_PROGRESS,
COMPLETED
}
private Status status = Status.PENDING;
public JobOrder(String id, Location origin, Location destination, String description) {
this.id = id;
this.origin = origin;
this.destination = destination;
this.description = description;
}
public String getId() { return id; }
public Location getOrigin() { return origin; }
public Location getDestination() { return destination; }
public String getDescription() { return description; }
public Status getStatus() { return status; }
public void setStatus(Status status) { this.status = status; }
}
แทนรถหัวลาก–หางพ่วง 1 คันในเกม เก็บตำแหน่ง, ความเร็ว, state ของงาน และ JobOrder ปัจจุบันที่กำลังทำ
package com.trucksim.fleet;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.trucksim.world.Location;
/**
* แทนรถหัวลาก+หางพ่วง 1 คันในเกม
*/
public class TruckUnit {
public enum State {
IDLE,
MOVING_TO_ORIGIN,
LOADING,
MOVING_TO_DESTINATION,
UNLOADING
}
private final String id;
private float x;
private float y;
private float speed = 12f; // หน่วยต่อชั่วโมง (หรือ world unit ต่อชั่วโมง)
private State state = State.IDLE;
private JobOrder currentJob;
// เป้าหมายตำแหน่งที่กำลังวิ่งไป
private Location targetLocation;
// เก็บสถิติระยะทางรวมของคันนี้
private float totalDistanceKm = 0f;
public TruckUnit(String id, float startX, float startY) {
this.id = id;
this.x = startX;
this.y = startY;
}
public void assignJob(JobOrder job) {
this.currentJob = job;
if (job != null) {
this.state = State.MOVING_TO_ORIGIN;
this.targetLocation = job.getOrigin();
job.setStatus(JobOrder.Status.IN_PROGRESS);
}
}
/**
* อัปเดตตำแหน่งรถตามเวลา (simDeltaMinutes)
*/
public void update(float simDeltaMinutes) {
if (currentJob == null || targetLocation == null) return;
float hours = simDeltaMinutes / 60f;
float distanceThisStep = speed * hours; // world units per hour (สมมุติ ~ km)
float dx = targetLocation.getX() - x;
float dy = targetLocation.getY() - y;
float distanceToTarget = (float) Math.sqrt(dx * dx + dy * dy);
if (distanceToTarget <= distanceThisStep) {
// ถึงเป้าหมาย
x = targetLocation.getX();
y = targetLocation.getY();
totalDistanceKm += distanceToTarget;
onReachTarget();
} else {
// เคลื่อนที่เข้าใกล้เป้าหมาย
float ratio = distanceThisStep / distanceToTarget;
x += dx * ratio;
y += dy * ratio;
totalDistanceKm += distanceThisStep;
}
}
private void onReachTarget() {
switch (state) {
case MOVING_TO_ORIGIN:
state = State.LOADING;
// ในเวอร์ชันจริงใส่เวลา loading ถ้าต้องการ
// จากนั้นตั้งเป้าไปที่ destination
targetLocation = currentJob.getDestination();
state = State.MOVING_TO_DESTINATION;
break;
case MOVING_TO_DESTINATION:
state = State.UNLOADING;
// หลัง UNLOADING เสร็จถือว่างานจบ
if (currentJob != null) {
currentJob.setStatus(JobOrder.Status.COMPLETED);
}
state = State.IDLE;
currentJob = null;
targetLocation = null;
break;
default:
break;
}
}
public void render(SpriteBatch batch) {
// ใน skeleton นี้ยังไม่วาด sprite จริง
// ในโปรเจกต์จริงควรใช้ Texture/TextureRegion วาดรูปหัวลาก+หางพ่วงที่ตำแหน่ง (x, y)
}
public String getId() { return id; }
public float getX() { return x; }
public float getY() { return y; }
public float getTotalDistanceKm() { return totalDistanceKm; }
public State getState() { return state; }
}
จัดการ list ของ TruckUnit, สร้างรถตัวอย่าง, สร้าง JobOrder และแจกจ่ายงานให้อัตโนมัติ รวมถึงรวบรวมสถิติของทั้ง fleet
package com.trucksim.fleet;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.trucksim.world.WorldMapManager;
import com.trucksim.world.Location;
import java.util.ArrayList;
import java.util.List;
/**
* บริหารจัดการรถหลายคันใน fleet
*/
public class FleetManager {
private final WorldMapManager worldMapManager;
private final List<TruckUnit> trucks = new ArrayList<>();
private final List<JobOrder> jobs = new ArrayList<>();
public FleetManager(WorldMapManager worldMapManager) {
this.worldMapManager = worldMapManager;
}
/**
* สร้างรถ + งานตัวอย่างสำหรับทดลอง simulation
*/
public void initializeSampleFleet() {
Location yard = worldMapManager.findById("yard1");
Location depot = worldMapManager.findById("depot1");
Location factory = worldMapManager.findById("factory1");
Location port = worldMapManager.findById("port1");
// รถสามคันออกจาก yard
trucks.add(new TruckUnit("TRUCK-01", yard.getX(), yard.getY()));
trucks.add(new TruckUnit("TRUCK-02", yard.getX() + 1, yard.getY()));
trucks.add(new TruckUnit("TRUCK-03", yard.getX() - 1, yard.getY()));
// Job ตัวอย่าง: Depot -> Factory, Factory -> Port
jobs.add(new JobOrder("JOB-1", depot, factory, "Empty container to factory"));
jobs.add(new JobOrder("JOB-2", factory, port, "Loaded container to port"));
jobs.add(new JobOrder("JOB-3", depot, factory, "Empty container to factory"));
// แจกจ่ายงานแบบง่าย ๆ: assign ตามลำดับ
assignJobsToIdleTrucks();
}
public void update(float simDeltaMinutes) {
// อัปเดตรถทุกคัน
for (TruckUnit truck : trucks) {
truck.update(simDeltaMinutes);
}
// ตรวจดูว่ามี truck ว่างแล้วหรือยัง ถ้ามี assign งานใหม่
assignJobsToIdleTrucks();
}
private void assignJobsToIdleTrucks() {
for (TruckUnit truck : trucks) {
if (truck.getState() == TruckUnit.State.IDLE) {
JobOrder pending = findNextPendingJob();
if (pending != null) {
truck.assignJob(pending);
}
}
}
}
private JobOrder findNextPendingJob() {
for (JobOrder job : jobs) {
if (job.getStatus() == JobOrder.Status.PENDING) {
return job;
}
}
return null;
}
public void render(SpriteBatch batch) {
for (TruckUnit truck : trucks) {
truck.render(batch);
}
}
public List<TruckUnit> getTrucks() {
return trucks;
}
public List<JobOrder> getJobs() {
return jobs;
}
}
TripStats, CostCalculator & ScoreSystem
ใช้เก็บข้อมูลสถิติ เช่น เวลารวม, ระยะทาง รวมถึงคำนวณจาก fleet ใน ResultScreen หรือ ScoreSystem
package com.trucksim.sim;
/**
* เก็บสถิติพื้นฐานของการขนส่ง (อาจต่อยอด scope เป็นรายคัน/ราย mission)
*/
public class TripStats {
public float totalDistanceKm;
public float totalFuelLiters;
public float totalCost;
public float totalSimMinutes;
public TripStats() {}
}
ใช้สมมุติสูตรง่าย ๆ: น้ำมัน (ลิตร) = ระยะทาง / (kmPerLiter) และ ต้นทุน = Liters × ราคา/ลิตร สามารถต่อยอดให้ละเอียดขึ้นได้
package com.trucksim.sim;
/**
* ฟังก์ชันช่วยคำนวณค่าน้ำมันและต้นทุนเบื้องต้น
*/
public class CostCalculator {
private final float kmPerLiter;
private final float fuelPricePerLiter;
public CostCalculator(float kmPerLiter, float fuelPricePerLiter) {
this.kmPerLiter = kmPerLiter;
this.fuelPricePerLiter = fuelPricePerLiter;
}
public float estimateFuelLiters(float distanceKm) {
if (kmPerLiter <= 0) return 0f;
return distanceKm / kmPerLiter;
}
public float estimateFuelCost(float distanceKm) {
float liters = estimateFuelLiters(distanceKm);
return liters * fuelPricePerLiter;
}
}
ระบบนี้รวบรวม TripStats ของทั้ง fleet แล้วคำนวณคะแนนตาม performance เช่น ระยะทางรวม, เวลา, ค่าน้ำมัน และให้ summary ใช้ใน ResultScreen
package com.trucksim.sim;
import com.trucksim.fleet.FleetManager;
import com.trucksim.fleet.TruckUnit;
/**
* ระบบคำนวณคะแนนจาก performance ของทั้ง fleet
*/
public class ScoreSystem {
public static class ScoreSummary {
public float totalDistanceKm;
public float totalFuelLiters;
public float totalCost;
public float totalSimMinutes;
public int finalScore;
}
private final FleetManager fleetManager;
private final CostCalculator costCalculator;
public ScoreSystem(FleetManager fleetManager, CostCalculator costCalculator) {
this.fleetManager = fleetManager;
this.costCalculator = costCalculator;
}
/**
* สร้าง summary จากข้อมูล fleet + simulation time
*/
public ScoreSummary buildSummary(float totalSimMinutes) {
ScoreSummary summary = new ScoreSummary();
// รวมระยะทางจากทุกคัน
float totalDistance = 0f;
for (TruckUnit truck : fleetManager.getTrucks()) {
totalDistance += truck.getTotalDistanceKm();
}
summary.totalDistanceKm = totalDistance;
summary.totalFuelLiters = costCalculator.estimateFuelLiters(totalDistance);
summary.totalCost = costCalculator.estimateFuelCost(totalDistance);
summary.totalSimMinutes = totalSimMinutes;
// ตัวอย่างสูตรคะแนนง่าย ๆ
// คะแนนเริ่มจาก 1000 แล้วลบตามเวลาและต้นทุน
int score = 1000;
score -= (int)(summary.totalSimMinutes / 10); // ยิ่งใช้นานยิ่งโดนลบ
score -= (int)(summary.totalCost / 100); // ต้นทุนสูงโดนหักคะแนน
if (score < 0) score = 0;
summary.finalScore = score;
return summary;
}
}
HudOverlay – ส่วนแสดงผลบนหน้าจอ
HUD แสดงข้อมูลสำคัญ เช่น เวลาจำลอง, จำนวนรถที่กำลังวิ่ง, ระยะทางรวม และค่าอื่น ๆ เพื่อให้ผู้เล่นมองเห็น performance ของ fleet แบบเรียลไทม์
package com.trucksim.ui;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
import com.trucksim.fleet.FleetManager;
import com.trucksim.fleet.TruckUnit;
/**
* HUD แสดงข้อมูลรวมของ simulation
*/
public class HudOverlay {
private final Stage stage;
private final Skin skin;
private final FleetManager fleetManager;
private Label timeLabel;
private Label trucksLabel;
private Label distanceLabel;
public HudOverlay(SpriteBatch batch, FleetManager fleetManager) {
this.fleetManager = fleetManager;
stage = new Stage(new ScreenViewport(), batch);
skin = new Skin(Gdx.files.internal("uiskin.json"));
Table root = new Table();
root.setFillParent(true);
root.top().left().pad(10);
stage.addActor(root);
timeLabel = new Label("Time: 0 min", skin);
trucksLabel = new Label("Trucks: 0", skin);
distanceLabel = new Label("Total Distance: 0 km", skin);
root.add(timeLabel).left().row();
root.add(trucksLabel).left().row();
root.add(distanceLabel).left().row();
}
public void update(float simulationTimeMinutes) {
timeLabel.setText(String.format("Time: %.1f min", simulationTimeMinutes));
int truckCount = fleetManager.getTrucks().size();
trucksLabel.setText("Trucks: " + truckCount);
float totalDistance = 0f;
for (TruckUnit truck : fleetManager.getTrucks()) {
totalDistance += truck.getTotalDistanceKm();
}
distanceLabel.setText(String.format("Total Distance: %.1f km", totalDistance));
}
public void render() {
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
}
public void dispose() {
stage.dispose();
skin.dispose();
}
}
» HUD นี้เป็นตัวอย่างเบื้องต้น สามารถต่อยอดให้แสดงสถานะของ Job แต่ละคัน, mission objectives, ปุ่มควบคุมความเร็ว simulation เป็นต้น
สรุป & แนวทางต่อยอด
HTML single file นี้ออกแบบให้เป็นเหมือน "Design Document + Code Skeleton" สำหรับเกม Container Truck Fleet Simulator บน LibGDX โดยมี class แยกตามหน้าที่ชัดเจน: Core Game, Screens, World/Map, Fleet/Jobs, Simulation Metrics, และ HUD
เพื่อให้กลายเป็นโปรเจกต์จริง คุณสามารถ:
- สร้าง LibGDX project ผ่าน Gradle setup แล้วนำโค้ด skeleton เหล่านี้ไปวางใน package ตามที่ระบุ
- เพิ่ม asset จริง: แผนที่ TMX, sprite รถ, skin UI, font ฯลฯ
- พัฒนา AI/logic เพิ่มเติม เช่น pathfinding (A*), traffic simulation, mission system แบบหลายระดับ
- เชื่อมค่าพารามิเตอร์ให้สะท้อนต้นทุนโลจิสติกส์จริง (ราคาน้ำมัน, เวลาโหลด/ขนถ่าย, SLA ของลูกค้า ฯลฯ)
จากจุดนี้ คุณสามารถใช้ skeleton นี้เป็นฐานเพื่อสร้าง "เกมสอนโลจิสติกส์" ที่ทั้งสมจริงและเล่นสนุกสำหรับพนักงานและนักศึกษาได้อย่างมีประสิทธิภาพ