JScratch
Loading...
Searching...
No Matches
Sprite.java
Go to the documentation of this file.
1package com.jscratch;
2
3import java.awt.AlphaComposite;
4import java.awt.Color;
5import java.awt.Graphics2D;
6import java.awt.Rectangle;
7import java.awt.geom.AffineTransform;
8import java.awt.image.BufferedImage;
9import java.util.ArrayList;
10import java.util.List;
11
12public class Sprite extends ScratchObject {
14
15 // Motion
16 private double x = 0;
17 private double y = 0;
18 private double direction = 90;
20
21 // Looks
22 private boolean visible = true;
23 private double size = 100;
24 private double scaleX = 1.0;
25 private double scaleY = 1.0;
26 private List<Costume> costumes = new ArrayList<>();
27 private int costumeIndex = 0;
28
29 // Effects
30 private double ghostEffect = 0;
31 private double brightnessEffect = 0;
32 private double colorEffect = 0;
33
34 private Stage stage;
35 private List<Runnable> clickListeners = new java.util.concurrent.CopyOnWriteArrayList<>();
36 private List<java.util.function.BiConsumer<Double, Double>> scaleListeners = new java.util.concurrent.CopyOnWriteArrayList<>();
37
38 public Sprite(String name) { super(name); }
39 public static BufferedImage createPlaceholder(Color c, int w, int h) {
40 BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
41 Graphics2D g = img.createGraphics();
42 g.setColor(c);
43 g.fillRect(0, 0, w, h);
44 g.setColor(Color.BLACK);
45 g.drawRect(0, 0, w - 1, h - 1);
46 g.dispose();
47 return img;
48 }
49
50 public void setStage(Stage stage) {
51 this.stage = stage;
52 }
53
54 public double getX() { return x; }
55 public double getY() { return y; }
56 public double getScaleX() { return scaleX; }
57 public double getScaleY() { return scaleY; }
58 public double getWidth() { return width(); }
59 public double getHeight() { return height(); }
60
61 public void whenClicked(Runnable action) {
62 clickListeners.add(action);
63 }
64
65 public void whenScaleChanged(java.util.function.BiConsumer<Double, Double> action) {
66 scaleListeners.add(action);
67 }
68
69 public boolean containsPoint(int mx, int my) {
70 if (!visible || costumes.isEmpty()) return false;
72 BufferedImage img = c.getImage();
73 if (img == null) return false;
74
75 try {
76 java.awt.geom.AffineTransform tx = new java.awt.geom.AffineTransform();
77 double screenX = x + stage.getWidth() / 2.0;
78 double screenY = stage.getHeight() / 2.0 - y;
79
80 tx.translate(screenX, screenY);
81 tx.scale((size / 100.0) * scaleX, (size / 100.0) * scaleY);
82
84 tx.rotate(Math.toRadians(direction - 90));
85 } else if (rotationStyle == RotationStyle.LEFT_RIGHT && (direction < 0 || (direction > 180 && direction < 360))) {
86 tx.scale(-1, 1);
87 }
88 tx.translate(-c.getRotationCenterX(), -c.getRotationCenterY());
89
90 java.awt.geom.AffineTransform inv = tx.createInverse();
91 java.awt.geom.Point2D local = inv.transform(new java.awt.geom.Point2D.Double(mx, my), null);
92
93 int ix = (int) local.getX();
94 int iy = (int) local.getY();
95
96 if (ix >= 0 && ix < img.getWidth() && iy >= 0 && iy < img.getHeight()) {
97 int argb = img.getRGB(ix, iy);
98 return ((argb >> 24) & 0xff) > 0;
99 }
100 } catch (Exception e) {
101 return false;
102 }
103 return false;
104 }
105
106 public void handleMouseClick(int mx, int my) {
107 // containsPoint check is now expected to be done by the caller (Stage)
108 // to handle layering, but we keep it here for safety if called directly.
109 if (visible) {
110 for (Runnable r : clickListeners) {
111 startScript(r);
112 }
113 }
114 }
115
116 public void handleScaleChange(double oldScale, double newScale) {
117 for (java.util.function.BiConsumer<Double, Double> listener : scaleListeners) {
118 startScript(() -> listener.accept(oldScale, newScale));
119 }
120 }
121
122 // --- Drawing ---
123 public void draw(Graphics2D g) {
124 if (!visible || costumes.isEmpty()) return;
126 BufferedImage img = c.getImage();
127 if (img == null) return;
128
129 AffineTransform old = g.getTransform();
130 double screenX = x + stage.getWidth() / 2.0;
131 double screenY = stage.getHeight() / 2.0 - y;
132
133 g.translate(screenX, screenY);
134 g.scale((size / 100.0) * scaleX, (size / 100.0) * scaleY);
135
137 g.rotate(Math.toRadians(direction - 90));
138 } else if (rotationStyle == RotationStyle.LEFT_RIGHT && (direction < 0 || (direction > 180 && direction < 360))) {
139 g.scale(-1, 1);
140 }
141
142 if (ghostEffect > 0) {
143 float alpha = (float) Math.max(0, Math.min(1, 1.0 - (ghostEffect / 100.0)));
144 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
145 }
146
147 if (brightnessEffect != 0) {
148 g.drawImage(img, -c.getRotationCenterX(), -c.getRotationCenterY(), null);
149 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, (float)(Math.abs(brightnessEffect)/200.0)));
150 g.setColor(brightnessEffect > 0 ? Color.WHITE : Color.BLACK);
151 g.fillRect(-c.getRotationCenterX(), -c.getRotationCenterY(), img.getWidth(), img.getHeight());
152 } else {
153 g.drawImage(img, -c.getRotationCenterX(), -c.getRotationCenterY(), null);
154 }
155
156 g.setTransform(old);
157 g.setComposite(AlphaComposite.SrcOver);
158 }
159
160 public double width() { return costumes.isEmpty() ? 0 : costumes.get(costumeIndex).getImage().getWidth() * (size/100.0) * Math.abs(scaleX); }
161 public double height() { return costumes.isEmpty() ? 0 : costumes.get(costumeIndex).getImage().getHeight() * (size/100.0) * Math.abs(scaleY); }
162 public void setScaleX(double sx) { this.scaleX = sx; }
163 public void setScaleY(double sy) { this.scaleY = sy; }
164
165 public Rectangle getBounds() {
166 int w = (int)width(), h = (int)height();
167 return new Rectangle((int)(x + stage.getWidth()/2.0 - w/2), (int)(stage.getHeight()/2.0 - y - h/2), w, h);
168 }
169
170 // --- Blocks ---
171 public void move(double steps) {
172 double rad = Math.toRadians(90 - direction);
173 x += steps * Math.cos(rad);
174 y += steps * Math.sin(rad);
175 }
176 public void turnRight(double deg) { direction += deg; normalize(); }
177 public void turnLeft(double deg) { direction -= deg; normalize(); }
178 public void pointInDirection(double dir) { direction = dir; normalize(); }
179 public void goTo(double x, double y) { this.x = x; this.y = y; }
180 public void changeXBy(double dx) { x += dx; }
181 public void changeYBy(double dy) { y += dy; }
182
183 public void glide(double sec, double tx, double ty) {
184 long dur = (long)(sec * 1000), start = System.currentTimeMillis();
185 double sx = x, sy = y;
186 while (System.currentTimeMillis() - start < dur) {
187 double p = (System.currentTimeMillis() - start) / (double)dur;
188 x = sx + (tx - sx) * p; y = sy + (ty - sy) * p;
190 }
191 x = tx; y = ty;
192 }
193
194 public void goToRandomPosition() {
195 int w = stage.getWidth()/2, h = stage.getHeight()/2;
196 x = (Math.random()*w*2)-w; y = (Math.random()*h*2)-h;
197 }
198
199 public void ifOnEdgeBounce() {
200 int w = stage.getWidth()/2, h = stage.getHeight()/2;
201 if (x > w) { x = w; direction = -direction; }
202 if (x < -w) { x = -w; direction = -direction; }
203 if (y > h) { y = h; direction = 180 - direction; }
204 if (y < -h) { y = -h; direction = 180 - direction; }
205 normalize();
206 }
207
208 public void setEffect(String effect, double value) {
209 if (effect.equalsIgnoreCase("ghost")) ghostEffect = value;
210 else if (effect.equalsIgnoreCase("brightness")) brightnessEffect = value;
211 }
212 public void changeEffectBy(String effect, double amount) {
213 if (effect.equalsIgnoreCase("ghost")) ghostEffect += amount;
214 else if (effect.equalsIgnoreCase("brightness")) brightnessEffect += amount;
215 }
217
218 public void say(String text) { System.out.println(name + ": " + text); }
219 public void say(String text, double sec) { say(text); wait(sec); say(""); }
220
221 public void switchCostume(String n) { for(int i=0; i<costumes.size(); i++) if(costumes.get(i).getName().equals(n)) costumeIndex = i; }
222 public void nextCostume() { if(!costumes.isEmpty()) costumeIndex = (costumeIndex+1)%costumes.size(); }
223 public void setSize(double s) { size = s; }
224 public void changeSizeBy(double a) { size += a; }
225 public void show() { visible = true; }
226 public void hide() { visible = false; }
227 public boolean isVisible() { return visible; }
228 public void addCostume(Costume c) { costumes.add(c); }
229
230 public boolean isTouching(Sprite o) { return getBounds().intersects(o.getBounds()); }
231 public boolean isTouchingColor(Color c) {
232 Costume b = stage.getBackdrop();
233 if (b == null) return false;
234 int sx = (int)(x + stage.getWidth()/2.0), sy = (int)(stage.getHeight()/2.0 - y);
235 if (sx < 0 || sx >= b.getImage().getWidth() || sy < 0 || sy >= b.getImage().getHeight()) return false;
236 return new Color(b.getImage().getRGB(sx, sy)).equals(c);
237 }
238
239 private void normalize() { while(direction > 180) direction -= 360; while(direction <= -180) direction += 360; }
241
242 @Override
243 public Object getVariable(String name) {
244 switch(name.toLowerCase()) {
245 case "ghost": return ghostEffect;
246 case "brightness": return brightnessEffect;
247 case "x": return x;
248 case "y": return y;
249 case "direction": return direction;
250 case "size": return size;
251 }
252 return super.getVariable(name);
253 }
254}
BufferedImage getImage()
Definition Costume.java:64
void startScript(Runnable script)
List< Runnable > clickListeners
Definition Sprite.java:35
List< Costume > costumes
Definition Sprite.java:26
RotationStyle rotationStyle
Definition Sprite.java:19
double getWidth()
Definition Sprite.java:58
void turnRight(double deg)
Definition Sprite.java:176
void goToRandomPosition()
Definition Sprite.java:194
double getScaleY()
Definition Sprite.java:57
double getScaleX()
Definition Sprite.java:56
void clearGraphicsEffects()
Definition Sprite.java:216
boolean isTouching(Sprite o)
Definition Sprite.java:230
void whenClicked(Runnable action)
Definition Sprite.java:61
Sprite(String name)
Definition Sprite.java:38
static BufferedImage createPlaceholder(Color c, int w, int h)
Definition Sprite.java:39
void setEffect(String effect, double value)
Definition Sprite.java:208
void handleScaleChange(double oldScale, double newScale)
Definition Sprite.java:116
Rectangle getBounds()
Definition Sprite.java:165
void glide(double sec, double tx, double ty)
Definition Sprite.java:183
void handleMouseClick(int mx, int my)
Definition Sprite.java:106
boolean containsPoint(int mx, int my)
Definition Sprite.java:69
void setScaleY(double sy)
Definition Sprite.java:163
void changeEffectBy(String effect, double amount)
Definition Sprite.java:212
List< java.util.function.BiConsumer< Double, Double > > scaleListeners
Definition Sprite.java:36
void setStage(Stage stage)
Definition Sprite.java:50
void whenScaleChanged(java.util.function.BiConsumer< Double, Double > action)
Definition Sprite.java:65
void setScaleX(double sx)
Definition Sprite.java:162
void setRotationStyle(RotationStyle s)
Definition Sprite.java:240
void changeXBy(double dx)
Definition Sprite.java:180
boolean isVisible()
Definition Sprite.java:227
boolean isTouchingColor(Color c)
Definition Sprite.java:231
void setSize(double s)
Definition Sprite.java:223
Object getVariable(String name)
Definition Sprite.java:243
void move(double steps)
Definition Sprite.java:171
double brightnessEffect
Definition Sprite.java:31
void switchCostume(String n)
Definition Sprite.java:221
void say(String text, double sec)
Definition Sprite.java:219
void changeYBy(double dy)
Definition Sprite.java:181
void say(String text)
Definition Sprite.java:218
double getHeight()
Definition Sprite.java:59
void draw(Graphics2D g)
Definition Sprite.java:123
void changeSizeBy(double a)
Definition Sprite.java:224
void pointInDirection(double dir)
Definition Sprite.java:178
void goTo(double x, double y)
Definition Sprite.java:179
void turnLeft(double deg)
Definition Sprite.java:177
void addCostume(Costume c)
Definition Sprite.java:228