Skip to content
This repository was archived by the owner on Dec 24, 2025. It is now read-only.

Commit 2d10408

Browse files
committed
fix(brick): prevent NPE when hpPercent=0 and update strong brick textures
1 parent 997c3d5 commit 2d10408

29 files changed

Lines changed: 146 additions & 30 deletions

File tree

src/main/java/com/github/codestorm/bounceverse/AssetsPath.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ public static final class ColorAssets {
105105
private static final NavigableMap<Double, String> SHIELD = new TreeMap<>();
106106
private static final NavigableMap<Double, String> STRONG = new TreeMap<>();
107107
private static final NavigableMap<Double, String> KEY = new TreeMap<>();
108+
private static final NavigableMap<Double, String> EXPLODING = new TreeMap<>();
108109

109110
private final Color color;
110111

@@ -124,12 +125,17 @@ private ColorAssets(Color color) {
124125
SHIELD.put(0.0, "/shield.png");
125126

126127
// Strong
127-
STRONG.put(2.0 / 3, "/strong.png");
128-
STRONG.put(1.0 / 3, "/strongFirstHit.png");
129-
STRONG.put(0.0, "/strongSecondHit.png");
128+
// Strong
129+
STRONG.put(1.0, "/strong.png");
130+
STRONG.put(2.0 / 3, "/strongFirstHit.png");
131+
STRONG.put(1.0 / 3, "/strongSecondHit.png");
132+
STRONG.put(0.0, "/strongThirdHit.png");
130133

131134
// Key Brick
132135
KEY.put(0.0, "/keybrick.png");
136+
137+
// Exploding
138+
EXPLODING.put(0.0, "/explode.png");
133139
}
134140

135141
public String getColorName() {
@@ -165,7 +171,7 @@ public String getTexture(BrickType brickType, double hpPercent) {
165171
case SHIELD -> SHIELD;
166172
case STRONG -> STRONG;
167173
case KEY -> KEY;
168-
case EXPLODING -> NORMAL; // fallback dùng texture thường
174+
case EXPLODING -> EXPLODING; // fallback dùng texture thường
169175
};
170176
return getRoot() + map.floorEntry(hpPercent).getValue();
171177
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.github.codestorm.bounceverse.components.behaviors.brick;
2+
3+
import com.almasb.fxgl.dsl.FXGL;
4+
import com.almasb.fxgl.dsl.components.HealthIntComponent;
5+
import com.almasb.fxgl.entity.component.Component;
6+
import com.almasb.fxgl.texture.Texture;
7+
import com.github.codestorm.bounceverse.AssetsPath;
8+
import com.github.codestorm.bounceverse.typing.enums.BrickType;
9+
import javafx.scene.paint.Color;
10+
11+
/**
12+
* Tự động đổi texture của Strong Brick dựa theo phần trăm HP còn lại.
13+
*/
14+
public class StrongBrickTextureUpdater extends Component {
15+
16+
private BrickType brickType = BrickType.STRONG;
17+
private Color color = Color.BLUE;
18+
private Texture currentTexture;
19+
20+
@Override
21+
public void onAdded() {
22+
var health = getEntity().getComponent(HealthIntComponent.class);
23+
health.valueProperty().addListener((obs, oldVal, newVal) -> updateTexture());
24+
updateTexture();
25+
}
26+
27+
private void updateTexture() {
28+
var health = getEntity().getComponent(HealthIntComponent.class);
29+
double hpPercent = Math.max(0.0, health.getValue() / (double) health.getMaxValue());
30+
31+
// Lấy texture tương ứng với HP còn lại
32+
var colorAssets = AssetsPath.Textures.Bricks.COLORS.get(getColorName());
33+
String texPath = colorAssets.getTexture(brickType, hpPercent);
34+
35+
Texture tex = FXGL.texture(texPath);
36+
37+
// ✅ Giữ nguyên kích thước ban đầu của brick
38+
double width = getEntity().getWidth();
39+
double height = getEntity().getHeight();
40+
tex.setFitWidth(width);
41+
tex.setFitHeight(height);
42+
43+
getEntity().getViewComponent().clearChildren();
44+
getEntity().getViewComponent().addChild(tex);
45+
currentTexture = tex;
46+
}
47+
48+
private String getColorName() {
49+
if (color.equals(Color.BLUE))
50+
return "blue";
51+
if (color.equals(Color.GREEN))
52+
return "green";
53+
if (color.equals(Color.ORANGE))
54+
return "orange";
55+
if (color.equals(Color.PINK))
56+
return "pink";
57+
if (color.equals(Color.RED))
58+
return "red";
59+
if (color.equals(Color.YELLOW))
60+
return "yellow";
61+
return "blue";
62+
}
63+
64+
public StrongBrickTextureUpdater withColor(Color color) {
65+
this.color = color;
66+
return this;
67+
}
68+
}
Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.github.codestorm.bounceverse.components.properties.powerup.types.ball;
22

3+
import java.util.List;
4+
35
import com.almasb.fxgl.dsl.FXGL;
46
import com.almasb.fxgl.entity.Entity;
57
import com.almasb.fxgl.physics.PhysicsComponent;
@@ -9,7 +11,7 @@
911

1012
/**
1113
* PowerUp nhân đôi toàn bộ bóng hiện có.
12-
* Mỗi bóng sinh thêm 1 bóng mới lệch góc nhẹ để bay tách ra.
14+
* Mỗi bóng sinh thêm 1 bóng mới tách từ vị trí của nó và bay lệch nhẹ sang 2 hướng.
1315
*/
1416
public final class MultipleBallPowerUp extends PowerUp {
1517

@@ -21,31 +23,48 @@ public MultipleBallPowerUp() {
2123
public void apply(Entity paddle) {
2224
var balls = FXGL.getGameWorld().getEntitiesByType(EntityType.BALL);
2325

24-
for (Entity ball : balls) {
25-
ball.getComponentOptional(PhysicsComponent.class).ifPresent(physics -> {
26-
Point2D pos = ball.getCenter();
27-
Point2D velocity = physics.getLinearVelocity();
28-
29-
// Offset nhẹ theo hướng bay hiện tại
30-
Point2D spawnPos = pos.add(velocity.normalize().multiply(15));
26+
// copy list để tránh ConcurrentModificationException khi spawn trong loop
27+
var currentBalls = List.copyOf(balls);
3128

32-
Entity newBall = FXGL.spawn("ball", spawnPos);
29+
for (Entity ball : currentBalls) {
30+
ball.getComponentOptional(PhysicsComponent.class).ifPresent(phys -> {
31+
Point2D pos = ball.getCenter();
32+
Point2D velocity = phys.getLinearVelocity();
3333

34-
// Lệch góc ±30 độ
35-
double angle = Math.toRadians(FXGL.random(-30, 30));
36-
Point2D rotatedVelocity = rotateVector(velocity, angle);
34+
if (velocity.magnitude() < 1e-3)
35+
return;
3736

38-
newBall.getComponent(PhysicsComponent.class).setLinearVelocity(rotatedVelocity);
37+
// tạo 2 bóng lệch trái/phải
38+
spawnSplitBall(pos, velocity, 20);
39+
spawnSplitBall(pos, velocity, -20);
3940
});
4041
}
4142
}
4243

43-
/** Xoay vector một góc nhất định (đơn vị radian). */
44+
private void spawnSplitBall(Point2D pos, Point2D velocity, double deg) {
45+
double angle = Math.toRadians(deg);
46+
Point2D newVelocity = rotateVector(velocity, angle);
47+
48+
// spawn tại vị trí bóng cũ, nhưng dịch nhẹ theo hướng mới để tránh overlap
49+
Point2D spawnPos = pos.add(newVelocity.normalize().multiply(8));
50+
51+
Entity newBall = FXGL.spawn("ball", spawnPos);
52+
53+
// ép đặt velocity sau khi spawn (ghi đè mọi mặc định trong BallFactory)
54+
newBall.getComponentOptional(PhysicsComponent.class).ifPresent(newPhys -> {
55+
newPhys.setLinearVelocity(newVelocity);
56+
57+
// đảm bảo ngay frame sau velocity vẫn giữ nguyên
58+
FXGL.runOnce(() -> newPhys.setLinearVelocity(newVelocity), javafx.util.Duration.millis(20));
59+
});
60+
}
61+
4462
private static Point2D rotateVector(Point2D v, double angleRad) {
4563
double cos = Math.cos(angleRad);
4664
double sin = Math.sin(angleRad);
4765
return new Point2D(
4866
v.getX() * cos - v.getY() * sin,
49-
v.getX() * sin + v.getY() * cos);
67+
v.getX() * sin + v.getY() * cos
68+
);
5069
}
5170
}

src/main/java/com/github/codestorm/bounceverse/factory/entities/BallFactory.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,26 @@ protected EntityBuilder getBuilder(SpawnData data) {
6767
public Entity spawnBall(SpawnData data) {
6868
final boolean attached = data.hasKey("attached") && Boolean.TRUE.equals(data.get("attached"));
6969

70-
double x = data.hasKey("x") ? data.get("x") : 0;
71-
double y = data.hasKey("y") ? data.get("y") : 0;
70+
// ✅ Ưu tiên dùng vị trí từ SpawnData nếu có
71+
Point2D pos;
72+
if (data.hasKey("x") && data.hasKey("y")) {
73+
pos = new Point2D(data.getX(), data.getY());
74+
} else if (data.hasKey("position")) {
75+
pos = (Point2D) data.get("position");
76+
} else {
77+
pos = null;
78+
}
7279

73-
if (x == 0 && y == 0) {
80+
if (pos == null || (pos.getX() == 0 && pos.getY() == 0)) {
7481
var paddleOpt = FXGL.getGameWorld().getEntitiesByType(EntityType.PADDLE).stream().findFirst();
7582
if (paddleOpt.isPresent()) {
7683
var paddle = paddleOpt.get();
77-
x = paddle.getCenter().getX() - DEFAULT_RADIUS;
78-
y = paddle.getY() - DEFAULT_RADIUS * 2;
84+
pos = new Point2D(
85+
paddle.getCenter().getX() - DEFAULT_RADIUS,
86+
paddle.getY() - DEFAULT_RADIUS * 2);
7987
}
8088
}
8189

82-
System.out.println("[BallFactory] Spawn ball at (" + x + ", " + y + "), attached=" + attached);
83-
return getBuilder(new SpawnData(x, y).put("attached", attached)).buildAndAttach();
90+
return getBuilder(new SpawnData(pos.getX(), pos.getY()).put("attached", attached)).buildAndAttach();
8491
}
8592
}

src/main/java/com/github/codestorm/bounceverse/factory/entities/BrickFactory.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
import com.github.codestorm.bounceverse.components.behaviors.Explosion;
1616
import com.github.codestorm.bounceverse.components.behaviors.HealthDeath;
1717
import com.github.codestorm.bounceverse.components.behaviors.Special;
18+
import com.github.codestorm.bounceverse.components.behaviors.brick.StrongBrickTextureUpdater;
1819
import com.github.codestorm.bounceverse.components.properties.Attributes;
1920
import com.github.codestorm.bounceverse.components.properties.Shield;
2021
import com.github.codestorm.bounceverse.typing.enums.BrickType;
2122
import com.github.codestorm.bounceverse.typing.enums.EntityType;
2223
import javafx.geometry.Point2D;
2324
import javafx.geometry.Side;
25+
import javafx.scene.paint.Color;
2426

2527
import java.util.List;
2628
import java.util.Random;
@@ -93,8 +95,22 @@ public Entity newNormalBrick(SpawnData data) {
9395
@Spawns("strongBrick")
9496
public Entity newStrongBrick(SpawnData data) {
9597
data.put("type", BrickType.STRONG);
96-
data.put("hp", (double) (DEFAULT_HP + 2));
97-
return getBuilder(data).buildAndAttach();
98+
data.put("hp", DEFAULT_HP + 2);
99+
100+
// Xác định màu
101+
String colorKey = Utilities.Typing.getOr(data, "color", COLORS.get(RANDOM.nextInt(COLORS.size())));
102+
var color = switch (colorKey) {
103+
case "green" -> Color.GREEN;
104+
case "orange" -> Color.ORANGE;
105+
case "pink" -> Color.PINK;
106+
case "red" -> Color.RED;
107+
case "yellow" -> Color.YELLOW;
108+
default -> Color.BLUE;
109+
};
110+
111+
return getBuilder(data)
112+
.with(new StrongBrickTextureUpdater().withColor(color))
113+
.buildAndAttach();
98114
}
99115

100116
/** Gạch có khiên bảo vệ 3 phía (chỉ phá từ trên xuống) */
@@ -123,7 +139,7 @@ public Entity newKeyBrick(SpawnData data) {
123139
data.put("type", BrickType.KEY);
124140
data.put("hp", (double) DEFAULT_HP);
125141

126-
// 🔹 Gắn component Special để rơi power-up khi bị phá
142+
// Gắn component Special để rơi power-up khi bị phá
127143
var special = new Special();
128144

129145
return getBuilder(data)

src/main/java/com/github/codestorm/bounceverse/systems/init/GameSystem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ else if (y == 3) {
124124
}
125125
// 💣 Hàng cuối (index = 5) là explodingBrick để test nổ lan
126126
else if (y == 5) {
127-
type = "explodingBrick";
127+
type = "strongBrick";
128128
}
129129
// 💎 Các hàng còn lại là gạch thường
130130
else {
3.07 KB
Loading
200 Bytes
Loading
679 Bytes
Loading
726 Bytes
Loading

0 commit comments

Comments
 (0)