diff --git a/docs/dev/guide.md b/docs/dev/guide.md
index a08b227..5eb6434 100644
--- a/docs/dev/guide.md
+++ b/docs/dev/guide.md
@@ -48,3 +48,6 @@ Chi tiết: [Spotless Gradle Plugin](https://github.com/diffplug/spotless/tree/m
* **Commits:** [Conventional Commits](https://www.conventionalcommits.org/) (và viết message bằng **Tiếng Anh**)
* **Files Structure:**
[Gradle project Structure](https://docs.gradle.org/current/userguide/organizing_gradle_projects.html)
+
+Ngoài ra:
+* [Hướng dẫn viết javadoc](javadoc.md)
diff --git a/docs/dev/javadoc.md b/docs/dev/javadoc.md
new file mode 100644
index 0000000..cdaaeac
--- /dev/null
+++ b/docs/dev/javadoc.md
@@ -0,0 +1,55 @@
+# 📄 Viết Javadoc
+
+### 💾 Format
+
+Javadoc được viết tuân theo các quy tắc javadoc cơ bản và viết trên format HTML.
+
+### 🏗️ Cấu trúc
+
+Trên IDE, khi bạn di chuyển lên trên đầu lớp/method và viết `/**` rồi Enter, IDE sẽ tự sinh ra cho bạn khung để bạn viết
+javadoc. Cấu trúc của dự án chúng ta sẽ là:
+
+- Đối với Lớp:
+
+```java
+/**
+ *
+ *
+ *
?{@link Tên Lớp}
+ *
+ * Mô tả lớp... Có thể sử dụng {@link Class nào đó#TP trong class} để liên kết
+ * Chú ý: Sử dụng thẻ br để xuống dòng
+ * Plot Twist: tui cũng ko bt viết gì ở đây nữa
+ *
+ * @see ...
+ */
+
+```
+
+trong đó `?` ở Tên lớp sẽ là:
+
+| Loại lớp | 📚 Class | 📱Interface | 🔢 Enum | ❗ Exception | 📍 Annotation | 📝 Record |
+|----------|----------|-------------|---------|-------------|---------------|-----------|
+| Kí hiệu | | % | # | ! | @ | $ |
+
+**VD:** `ThisIsClass`, `%Interface`, `!StackoverflowException`, `#EntityType`
+
+- Đối với method:
+
+```java
+/**
+ * Hàm này thực hiện chức năng gì... .
+ *
+ * @params Input1 Đầu vào 1
+ * @params Input2 Đầu vào 2
+ * @return Kết quả của hàm
+ * @throws Exception nếu có lỗi xảy ra...
+ */
+
+```
+
+### ❗Chú ý
+
+- Formating docs sau khi viết code xong. (Phím tắt thường là `Alt` + `Shift` + `F`)
+
+### Bạn có thể xem source code trong dự án để xem ví dụ.
\ No newline at end of file
diff --git a/src/main/java/com/github/codestorm/bounceverse/Utils.java b/src/main/java/com/github/codestorm/bounceverse/Utils.java
new file mode 100644
index 0000000..93f75f8
--- /dev/null
+++ b/src/main/java/com/github/codestorm/bounceverse/Utils.java
@@ -0,0 +1,279 @@
+package com.github.codestorm.bounceverse;
+
+import com.almasb.fxgl.dsl.FXGL;
+import com.almasb.fxgl.entity.Entity;
+import com.almasb.fxgl.time.TimerAction;
+import com.github.codestorm.bounceverse.data.types.DirectionUnit;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.shape.Circle;
+import javafx.util.Duration;
+
+/** Utilities. */
+public final class Utils {
+ private Utils() {}
+
+ /** Input/Output utilities. */
+ public static final class IO {
+ private IO() {}
+
+ /**
+ * Load .properties file.
+ *
+ * @param path Relative path
+ * @return Parsed properties
+ * @throws IOException if an error occurred when reading from the input stream.
+ */
+ public static Properties loadProperties(String path) throws IOException {
+ InputStream fileStream = IO.class.getResourceAsStream(path);
+ if (fileStream == null) {
+ throw new IOException("Cannot open InputStream on " + path);
+ }
+
+ Properties prop = new Properties();
+ prop.load(fileStream);
+ fileStream.close();
+ return prop;
+ }
+
+ /**
+ * Convert an array of key=value pairs into a hashmap. The string "key=" maps key onto "",
+ * while just "key" maps key onto null. The value may contain '=' characters, only the first
+ * "=" is a delimiter.
+ * Source code from here.
+ *
+ * @param args command-line arguments in the key=value format (or just key= or key)
+ * @param defaults a map of default values, may be null. Mappings to null are not copied to
+ * the resulting map.
+ * @param whiteList if not null, the keys not present in this map cause an exception (and
+ * keys mapped to null are ok)
+ * @return a map that maps these keys onto the corresponding values.
+ */
+ public static HashMap parseArgs(
+ String[] args,
+ HashMap defaults,
+ HashMap whiteList) {
+ // HashMap allows null values
+ HashMap res = new HashMap<>();
+ if (defaults != null) {
+ for (Map.Entry e : defaults.entrySet()) {
+ if (e.getValue() != null) {
+ res.put(e.getKey(), e.getValue());
+ }
+ }
+ }
+ for (String s : args) {
+ String[] kv = s.split("=", 2);
+ if (whiteList != null && !whiteList.containsKey(kv[0])) {
+ continue;
+ }
+ res.put(kv[0], kv.length < 2 ? null : kv[1]);
+ }
+ return res;
+ }
+
+ /**
+ * Read text file (txt) and put all lines into {@link List}.
+ *
+ * @param path File path
+ * @return All lines in text file
+ */
+ public static List readTextFile(String path) {
+ var res = new ArrayList();
+ var scanner = new Scanner(path);
+ while (scanner.hasNext()) {
+ res.add(scanner.next());
+ }
+ scanner.close();
+ return res;
+ }
+ }
+
+ public static final class Time {
+ /**
+ * Thời gian hồi để thực hiện lại gì đó. Thực hiện thông qua {@link #current}
+ *
+ * @see ActiveCooldown
+ */
+ public static final class Cooldown {
+ private final ActiveCooldown current = new ActiveCooldown();
+ private Duration duration = Duration.INDEFINITE;
+
+ public Duration getDuration() {
+ return duration;
+ }
+
+ /**
+ * Đặt thời lượng cooldown mới.
+ * Lưu ý: Chỉ áp dụng cho cooldown mới.
+ *
+ * @param duration Thời lượng mới
+ */
+ public void setDuration(Duration duration) {
+ this.duration = duration;
+ }
+
+ public ActiveCooldown getCurrent() {
+ return current;
+ }
+
+ public Cooldown() {}
+
+ public Cooldown(Duration duration) {
+ this.duration = duration;
+ }
+
+ /** Cooldown thời điểm hiện tại. Giống như một wrapper của {@link TimerAction}. */
+ public final class ActiveCooldown {
+ private TimerAction waiter = null;
+ private double timestamp = Double.NaN;
+ private Runnable onExpiredCallback = null;
+
+ /** Hành động khi cooldown hết. */
+ private void onExpired() {
+ timestamp = Double.NaN;
+ if (onExpiredCallback != null) {
+ onExpiredCallback.run();
+ }
+ }
+
+ /**
+ * Callback thực thi khi cooldown hết hạn.
+ *
+ * @param callback Callback sẽ thực thi
+ */
+ public void setOnExpired(Runnable callback) {
+ this.onExpiredCallback = callback;
+ }
+
+ /**
+ * Kiểm tra Cooldown hiện tại hết hạn chưa.
+ *
+ * @return {@code true} nếu hết hạn, ngược lại {@code false}.
+ */
+ public boolean expired() {
+ return (waiter == null) || waiter.isExpired();
+ }
+
+ /** Khiến cooldown hết hạn ngay (nếu có). */
+ public void expire() {
+ if (!expired()) {
+ waiter.expire();
+ }
+ }
+
+ /** Set một cooldown mới. */
+ public void makeNew() {
+ expire();
+
+ final var gameTimer = FXGL.getGameTimer();
+ waiter = gameTimer.runOnceAfter(this::onExpired, duration);
+ timestamp = gameTimer.getNow();
+ }
+
+ /** Tạm dừng cooldown. */
+ public void pause() {
+ if (!expired()) {
+ waiter.pause();
+ }
+ }
+
+ /** Tiếp tục cooldown. */
+ public void resume() {
+ if (!expired()) {
+ waiter.resume();
+ }
+ }
+
+ public boolean isPaused() {
+ return !expired() && waiter.isPaused();
+ }
+
+ /**
+ * Lấy thời gian còn lại của cooldown.
+ *
+ * @return Thời gian còn lại
+ */
+ public Duration getTimeLeft() {
+ if (expired()) {
+ return Duration.ZERO;
+ }
+ final var elapsed = Duration.millis(FXGL.getGameTimer().getNow() - timestamp);
+ return duration.subtract(elapsed);
+ }
+
+ /**
+ * Giảm thời gian hồi đi một lượng thời gian.
+ *
+ * @param duration Thời lượng giảm.
+ */
+ public void reduce(Duration duration) {
+ if (!expired()) {
+ waiter.update(duration.toMillis());
+ }
+ }
+
+ private ActiveCooldown() {}
+ }
+ }
+ }
+
+ public static final class Geometric {
+ /**
+ * Lọc các Entity trong phạm vi Hình tròn.
+ *
+ * @param circle Hình tròn
+ * @return Các entity
+ */
+ public static List getEntityInCircle(Circle circle) {
+ final var cx = circle.getCenterX();
+ final var cy = circle.getCenterY();
+ final var radius = circle.getRadius();
+
+ return getEntityInCircle(cx, cy, radius);
+ }
+
+ /**
+ * Lọc các Entity trong phạm vi Hình tròn.
+ *
+ * @param cx Tâm X
+ * @param cy Tâm Y
+ * @param radius Bán kính
+ * @return Các entity
+ */
+ public static List getEntityInCircle(double cx, double cy, double radius) {
+ final Rectangle2D outRect =
+ new Rectangle2D(cx - radius, cy - radius, 2 * radius, 2 * radius);
+ return FXGL.getGameWorld().getEntitiesInRange(outRect).stream()
+ .filter(
+ e -> {
+ double nearestX =
+ Math.max(e.getX(), Math.min(cx, e.getX() + e.getWidth()));
+ double nearestY =
+ Math.max(e.getY(), Math.min(cy, e.getY() + e.getHeight()));
+ double dx = cx - nearestX;
+ double dy = cy - nearestY;
+ return (dx * dx + dy * dy) <= radius * radius;
+ })
+ .toList();
+ }
+ }
+
+ public static final class Collision {
+ public static DirectionUnit getCollisionDirection(Entity source, Entity target) {
+ var fromBox = source.getBoundingBoxComponent();
+ var toBox = target.getBoundingBoxComponent();
+
+ var fCenter = fromBox.getCenterWorld();
+ var tCenter = toBox.getCenterWorld();
+
+ var direction = tCenter.subtract(fCenter);
+
+ return Math.abs(direction.getX()) > Math.abs(direction.getY())
+ ? direction.getX() > 0 ? DirectionUnit.RIGHT : DirectionUnit.LEFT
+ : direction.getY() > 0 ? DirectionUnit.DOWN : DirectionUnit.UP;
+ }
+ }
+}
diff --git a/src/main/java/com/github/codestorm/bounceverse/data/contracts/CanExecute.java b/src/main/java/com/github/codestorm/bounceverse/data/contracts/CanExecute.java
new file mode 100644
index 0000000..3951ea1
--- /dev/null
+++ b/src/main/java/com/github/codestorm/bounceverse/data/contracts/CanExecute.java
@@ -0,0 +1,19 @@
+package com.github.codestorm.bounceverse.data.contracts;
+
+import java.util.List;
+
+/**
+ *
+ *
+ *
{@link CanExecute}
+ *
+ * Có thể thực thi hành động nào đó.
+ */
+public interface CanExecute {
+ /**
+ * Thực thi hành động.
+ *
+ * @param data Dữ liệu truyền vào
+ */
+ void execute(List