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

Commit ba79775

Browse files
committed
feat(powerup): add Magnet & Gun power-ups and paddle attachment components
1 parent 7bda65f commit ba79775

17 files changed

Lines changed: 288 additions & 35 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.github.codestorm.bounceverse.components.behaviors;
2+
3+
import com.almasb.fxgl.dsl.FXGL;
4+
import com.almasb.fxgl.entity.Entity;
5+
import com.almasb.fxgl.entity.component.Component;
6+
import com.almasb.fxgl.physics.PhysicsComponent;
7+
import com.github.codestorm.bounceverse.typing.enums.EntityType;
8+
9+
import javafx.geometry.Point2D;
10+
11+
/**
12+
* Cho phép bóng dính paddle và di chuyển cùng paddle.
13+
*/
14+
public class BaseAttachmentComponent extends Component {
15+
16+
protected Entity paddle;
17+
protected PhysicsComponent physics;
18+
protected boolean attached = false;
19+
20+
private double offsetX;
21+
private double offsetY;
22+
private double lastPaddleX;
23+
24+
@Override
25+
public void onAdded() {
26+
paddle = FXGL.getGameWorld().getSingleton(EntityType.PADDLE);
27+
physics = getEntity().getComponent(PhysicsComponent.class);
28+
29+
// FIX: Dừng hoàn toàn chuyển động khi mới spawn
30+
physics.setLinearVelocity(Point2D.ZERO);
31+
physics.getBody().setAwake(false); // tắt vật lý để không bị nảy
32+
33+
// FIX: Cho biết đang gắn paddle
34+
attached = true;
35+
36+
lastPaddleX = paddle.getX();
37+
offsetX = getEntity().getX() - paddle.getX();
38+
offsetY = getEntity().getY() - paddle.getY();
39+
}
40+
41+
@Override
42+
public void onUpdate(double tpf) {
43+
if (!attached || paddle == null)
44+
return;
45+
46+
double deltaX = paddle.getX() - lastPaddleX;
47+
lastPaddleX = paddle.getX();
48+
49+
double newX = getEntity().getX() + deltaX;
50+
double newY = paddle.getY() + offsetY;
51+
52+
physics.overwritePosition(new Point2D(newX, newY));
53+
physics.setLinearVelocity(Point2D.ZERO);
54+
55+
// FIX: đảm bảo physics không bị wake lại bởi va chạm
56+
physics.getBody().setAwake(false);
57+
}
58+
59+
/** Khi bóng vừa dính paddle – đặt lại offset chuẩn. */
60+
public void snapToPaddle(Entity ball) {
61+
if (paddle == null)
62+
return;
63+
64+
attached = true; // FIX: gắn lại
65+
physics.setLinearVelocity(Point2D.ZERO);
66+
physics.getBody().setAwake(false);
67+
68+
lastPaddleX = paddle.getX();
69+
offsetX = ball.getX() - paddle.getX();
70+
offsetY = ball.getY() - paddle.getY();
71+
72+
physics.overwritePosition(ball.getPosition());
73+
}
74+
75+
/** Thả bóng ra khỏi paddle với góc và tốc độ nhất định. */
76+
public void releaseBall(double angleDeg, double speed) {
77+
if (!attached)
78+
return;
79+
80+
attached = false;
81+
physics.getBody().setAwake(true); // kích hoạt lại vật lý
82+
83+
double angle = Math.toRadians(angleDeg);
84+
double dir = (getEntity().getCenter().getX() >= paddle.getCenter().getX()) ? 1 : -1;
85+
double vx = speed * Math.sin(angle) * dir;
86+
double vy = -speed * Math.cos(angle);
87+
88+
physics.setLinearVelocity(new Point2D(vx, vy));
89+
}
90+
91+
public void setAttached(boolean value) {
92+
this.attached = value;
93+
94+
if (physics != null) {
95+
if (value) {
96+
physics.setLinearVelocity(Point2D.ZERO);
97+
physics.getBody().setAwake(false);
98+
} else {
99+
physics.getBody().setAwake(true);
100+
}
101+
}
102+
}
103+
104+
public boolean isAttached() {
105+
return attached;
106+
}
107+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.github.codestorm.bounceverse.components.behaviors.paddle;
2+
3+
import java.util.Optional;
4+
5+
import com.almasb.fxgl.entity.Entity;
6+
import com.github.codestorm.bounceverse.components.behaviors.BaseAttachmentComponent;
7+
8+
/**
9+
* Power-Up Magnet: Paddle bắt bóng và giữ theo paddle.
10+
*/
11+
public final class MagnetComponent extends BaseAttachmentComponent {
12+
13+
private Optional<Entity> attachedBall = Optional.empty();
14+
private boolean waitingToRelease = false;
15+
16+
/** Khi paddle chạm bóng – thử bắt bóng lại. */
17+
public void tryAttachBall(Entity ball) {
18+
if (attachedBall.isPresent())
19+
return;
20+
21+
attachedBall = Optional.of(ball);
22+
23+
ball.getComponentOptional(BaseAttachmentComponent.class)
24+
.ifPresent(ballAttach -> {
25+
ballAttach.setAttached(true);
26+
ballAttach.snapToPaddle(ball); // gắn đúng vị trí va chạm
27+
});
28+
29+
waitingToRelease = true;
30+
}
31+
32+
/** Thả bóng ra khi người chơi nhấn SPACE. */
33+
public void releaseBallExternal() {
34+
if (attachedBall.isEmpty())
35+
return;
36+
37+
var ball = attachedBall.get();
38+
ball.getComponentOptional(BaseAttachmentComponent.class)
39+
.ifPresent(c -> c.releaseBall(45, 300));
40+
41+
attachedBall = Optional.empty();
42+
waitingToRelease = false;
43+
}
44+
45+
public boolean hasBallAttached() {
46+
return attachedBall.isPresent() && waitingToRelease;
47+
}
48+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.github.codestorm.bounceverse.components.properties.powerup.types.paddle;
2+
3+
import com.almasb.fxgl.dsl.FXGL;
4+
import com.almasb.fxgl.entity.Entity;
5+
import com.almasb.fxgl.entity.components.TransformComponent;
6+
import com.github.codestorm.bounceverse.components.behaviors.paddle.PaddleShooting;
7+
import com.github.codestorm.bounceverse.components.properties.powerup.types.PowerUp;
8+
import javafx.scene.paint.Color;
9+
import javafx.scene.shape.Rectangle;
10+
import javafx.util.Duration;
11+
12+
/**
13+
* PowerUp: tạo 2 nòng súng trên paddle và tự động bắn trong 10s.
14+
*/
15+
public final class GunPowerUp extends PowerUp {
16+
17+
private static final Duration DURATION = Duration.seconds(10);
18+
private static final double GUN_WIDTH = 6;
19+
private static final double GUN_HEIGHT = 10;
20+
private static final double OFFSET_Y = -10;
21+
22+
public GunPowerUp() {
23+
super("Gun");
24+
}
25+
26+
@Override
27+
public void apply(Entity paddle) {
28+
29+
if (paddle.getComponentOptional(PaddleShooting.class).isEmpty()) {
30+
var shooting = new PaddleShooting(0.25);
31+
paddle.addComponent(shooting);
32+
}
33+
34+
// tạo 2 nòng súng (rect nhỏ màu đỏ)
35+
var leftGun = FXGL.entityBuilder()
36+
.view(new Rectangle(GUN_WIDTH, GUN_HEIGHT, Color.DARKRED))
37+
.zIndex(paddle.getZIndex() + 1)
38+
.build();
39+
40+
var rightGun = FXGL.entityBuilder()
41+
.view(new Rectangle(GUN_WIDTH, GUN_HEIGHT, Color.DARKRED))
42+
.zIndex(paddle.getZIndex() + 1)
43+
.build();
44+
45+
FXGL.getGameWorld().addEntities(leftGun, rightGun);
46+
47+
// gắn theo paddle
48+
TransformComponent pT = paddle.getTransformComponent();
49+
TransformComponent lT = leftGun.getTransformComponent();
50+
TransformComponent rT = rightGun.getTransformComponent();
51+
52+
double rightOffsetX = paddle.getWidth() - GUN_WIDTH - 4;
53+
54+
lT.xProperty().bind(pT.xProperty().add(4));
55+
lT.yProperty().bind(pT.yProperty().add(OFFSET_Y));
56+
57+
rT.xProperty().bind(pT.xProperty().add(rightOffsetX));
58+
rT.yProperty().bind(pT.yProperty().add(OFFSET_Y));
59+
60+
var shooting = paddle.getComponent(PaddleShooting.class);
61+
var task = FXGL.run(() -> shooting.execute(null), Duration.seconds(0.25));
62+
63+
FXGL.runOnce(() -> {
64+
task.expire();
65+
lT.xProperty().unbind();
66+
lT.yProperty().unbind();
67+
rT.xProperty().unbind();
68+
rT.yProperty().unbind();
69+
leftGun.removeFromWorld();
70+
rightGun.removeFromWorld();
71+
paddle.removeComponent(PaddleShooting.class);
72+
}, DURATION);
73+
}
74+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.github.codestorm.bounceverse.components.properties.powerup.types.paddle;
2+
3+
import com.almasb.fxgl.dsl.FXGL;
4+
import com.almasb.fxgl.entity.Entity;
5+
import com.github.codestorm.bounceverse.components.behaviors.paddle.MagnetComponent;
6+
import com.github.codestorm.bounceverse.components.properties.powerup.types.PowerUp;
7+
8+
import javafx.util.Duration;
9+
10+
/**
11+
* Power-Up: Paddle có thể "bắt dính" bóng (Magnet effect).
12+
*/
13+
public final class MagnetPowerUp extends PowerUp {
14+
15+
private static final Duration DURATION = Duration.seconds(15);
16+
17+
public MagnetPowerUp() {
18+
super("Magnet");
19+
}
20+
21+
@Override
22+
public void apply(Entity paddle) {
23+
if (paddle.getComponentOptional(MagnetComponent.class).isEmpty()) {
24+
paddle.addComponent(new MagnetComponent());
25+
}
26+
27+
// Sau DURATION thì remove component
28+
FXGL.runOnce(() -> {
29+
paddle.removeComponent(MagnetComponent.class);
30+
}, DURATION);
31+
}
32+
}

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

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,67 +4,59 @@
44
import com.almasb.fxgl.dsl.FXGL;
55
import com.almasb.fxgl.entity.Entity;
66
import com.almasb.fxgl.entity.SpawnData;
7-
import com.almasb.fxgl.entity.component.Component;
7+
import com.almasb.fxgl.entity.Spawns;
88
import com.almasb.fxgl.physics.BoundingShape;
99
import com.almasb.fxgl.physics.HitBox;
10-
import com.almasb.fxgl.physics.PhysicsComponent;
1110
import com.almasb.fxgl.texture.Texture;
1211
import com.github.codestorm.bounceverse.Utilities;
12+
import com.github.codestorm.bounceverse.components.properties.powerup.FallingComponent;
1313
import com.github.codestorm.bounceverse.components.properties.powerup.PowerUpContainer;
14-
import com.github.codestorm.bounceverse.typing.enums.DirectionUnit;
14+
import com.github.codestorm.bounceverse.components.properties.powerup.types.PowerUp;
1515
import com.github.codestorm.bounceverse.typing.enums.EntityType;
1616

1717
import javafx.geometry.Point2D;
1818

1919
/**
20-
*
21-
*
22-
* <h1>{@link PowerUpFactory}</h1>
23-
*
24-
* Factory để tạo các entity loại {@link EntityType#POWER_UP} trong trò chơi.
25-
*
26-
* @see EntityFactory
20+
* Factory tạo Power-Up entity (rơi thẳng xuống, không dùng physics).
2721
*/
2822
public final class PowerUpFactory extends EntityFactory {
23+
2924
public static final double DEFAULT_RADIUS = 10;
30-
public static final double DEFAULT_SPEED = 10;
3125

3226
@Override
3327
protected EntityBuilder getBuilder(SpawnData data) {
34-
final var velocity = DirectionUnit.DOWN.getVector().mul(DEFAULT_SPEED);
35-
36-
final var physics = new PhysicsComponent();
37-
physics.setOnPhysicsInitialized(
38-
() -> {
39-
physics.setLinearVelocity(velocity.toPoint2D());
40-
physics.setAngularVelocity(0);
41-
physics.getBody().setFixedRotation(true);
42-
physics.getBody().setLinearDamping(0f);
43-
physics.getBody().setAngularDamping(0f);
44-
});
45-
46-
return FXGL.entityBuilder().type(EntityType.POWER_UP).collidable().with(physics);
28+
return FXGL.entityBuilder()
29+
.type(EntityType.POWER_UP)
30+
.collidable();
4731
}
4832

49-
/**
50-
* Tạo mới một PowerUp. Đây là một "abstract" method.
51-
*
52-
* @param data Dữ liệu Spawn
53-
* @return Entity PowerUp
54-
*/
55-
private Entity newPowerUp(SpawnData data) {
33+
@Spawns("powerUp")
34+
public Entity newPowerUp(SpawnData data) {
5635
final double radius = Utilities.Typing.getOr(data, "radius", DEFAULT_RADIUS);
57-
final var contains = Utilities.Typing.getOr(data, "contains", new Component[0]);
58-
final Point2D pos = data.get("pos");
59-
final Texture texture = data.get("texture");
36+
final var contains = Utilities.Typing.getOr(data, "contains", new PowerUp[0]);
37+
final Point2D pos = data.hasKey("pos")
38+
? data.get("pos")
39+
: new Point2D(data.getX(), data.getY());
40+
final Texture texture = data.hasKey("texture")
41+
? data.get("texture")
42+
: FXGL.texture("power/paddle/Expand Paddle.png");
43+
44+
// 🔹 Giới hạn kích thước hiển thị
45+
texture.setFitWidth(42);
46+
texture.setFitHeight(42);
47+
texture.setPreserveRatio(true);
48+
49+
// 🔹 Căn tâm ảnh để khi xoay không bị lệch
50+
texture.setTranslateX(-texture.getFitWidth() / 2);
51+
texture.setTranslateY(-texture.getFitHeight() / 2);
6052

6153
final var hitbox = new HitBox(BoundingShape.circle(radius));
6254

6355
return getBuilder(data)
6456
.bbox(hitbox)
6557
.at(pos)
6658
.view(texture)
67-
.with(new PowerUpContainer(contains))
59+
.with(new FallingComponent(), new PowerUpContainer(contains))
6860
.buildAndAttach();
6961
}
7062
}
295 KB
Loading
243 KB
Loading
188 KB
Loading
235 KB
Loading
249 KB
Loading

0 commit comments

Comments
 (0)