-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathJGameEngine.java
More file actions
1023 lines (1000 loc) · 51.1 KB
/
JGameEngine.java
File metadata and controls
1023 lines (1000 loc) · 51.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* @author ammaraslam10
*/
public class JGameEngine {
/** Get how much time passed since last frame finished. This will be useful to write frame independent code by multiplication with this variable. */
public volatile double deltaTime;
private volatile int frameDelay;
private JGameEngine.Window window;
private JGameEngine.Key keyboard;
private JGameEngine.Mouse mouse;
private JGameEngine.Camera camera;
private JGameEngine.Quadtree collisionTree;
private JGameEngine.Audios audios;
private JGameEngine.Fonts fonts;
// Initialize JGameEngine
public JGameEngine() {
window = null;
keyboard = new JGameEngine.Key();
mouse = new JGameEngine.Mouse();
camera = new JGameEngine.Camera();
audios = new JGameEngine.Audios();
fonts = new JGameEngine.Fonts();
collisionTree = null;
deltaTime = 0;
frameDelay = 0;
}
/** Get the delay between each each frame */
int frameDelay() { return frameDelay; }
/** Set the delay between each each frame */
void frameDelay(int delay) { frameDelay = delay; }
/** Set a game space. Clears out all objects and sprites and prepares a room with the given size for collisions. It is the logical boundary of the Game Area */
void setGameSpace(int room_width, int room_height) {
if(window != null) {
window.game_loop_can_run = false;
while(window.game_loop_running) {}
}
if(collisionTree != null) collisionTree.clear();
collisionTree = new JGameEngine.Quadtree(0, new Rectangle(room_width, room_height));
if(window != null) {
window.objects.clear(); window.object_queue.clear(); window.object_queue_r.clear();
window.sprites.clear(); window.sprite_queue.clear(); window.sprite_queue_r.clear();
audios = new JGameEngine.Audios();
fonts = new JGameEngine.Fonts();
window.game_loop_can_run = true;
}
}
/** Actual window on the screen with positions x, y and dimensions width, height */
void setWindow(String title, int x, int y, int width, int height) {
if(window != null) { window.dispose(); window.onWindowClosing(); }
window = new JGameEngine.Window(title, x, y, width, height);
window.addWindowListener( new WindowAdapter() {
@Override
public void windowClosing( WindowEvent e ) {
window.onWindowClosing();
try {
window.gameThread.join();
} catch( InterruptedException ex ) { ex.printStackTrace(); }
System.exit( 0 );
}
});
}
/** Actual window on the screen with positions at the centre of the screen and dimensions width, height */
void setWindow(String title, int width, int height) { setWindow(title, (screenWidth() - width) / 2, (screenHeight() - height) / 2, 16 * 50, 9 * 50); }
/** Actual window on the screen with positions at the centre of the screen and dimensions 800, 450 with a default Game Space of 4000, 2250 (if not set) */
void setWindow(String title) { if(collisionTree == null) setGameSpace(4000, 2250); setWindow(title, 16 * 50, 9 * 50); }
/** Set a window with default settings 800, 450 with a default Game Space of 4000, 2250 (if not set) */
void setWindow() { if(collisionTree == null) setGameSpace(4000, 2250); setWindow("JGameEngine :)"); }
/** Get the screen width. */
public int screenWidth() { return Toolkit.getDefaultToolkit().getScreenSize().width; }
/** Get the screen height. */
public int screenHeight() { return Toolkit.getDefaultToolkit().getScreenSize().height; }
/** Get the window . This is a raw value, See cameraWidth() for a more useful value. */
public int windowWidth() { return window.canvas.getSize().width; }
/** Get the window height. This is a raw value, See cameraHeight() for a more useful value. */
public int windowHeight() { return window.canvas.getSize().height; }
/** Set the background colour of the window */
void windowBackground(Color c) { window.canvas.setBackground(c); }
/** Set the icon of the window */
public void windowIcon(String file) { ImageIcon img = new ImageIcon(file); window.setIconImage(img.getImage()); }
/** Set if the window is resizable by user */
public void windowResizable(boolean stance) { window.setResizable(stance); }
/** Set window to full screen or make it windowed */
public void windowFullScreen(boolean stance) {
window.dispose();
if(stance) {
window.setUndecorated(true);
window.setExtendedState(JFrame.MAXIMIZED_BOTH);
} else {
window.setUndecorated(false);
window.setExtendedState(JFrame.NORMAL);
window.setLocation(window.x, window.y);
window.setSize(window.width, window.height);
}
window.setVisible(true);
}
//~~~~~~~~~~ Rendering
private class Window extends JFrame implements Runnable {
private ArrayList<JGameEngine.Object> objects, object_queue, object_queue_r;
private ArrayList<JGameEngine.Sprite> sprites, sprite_queue, sprite_queue_r;
private BufferStrategy bs;
private volatile boolean running; volatile boolean game_loop_can_run = true; volatile boolean game_loop_running;
private Thread gameThread;
private Graphics g = null;
private long last_time = System.currentTimeMillis();
private Canvas canvas;
private double garbage_time;
int x, y, width, height;
public Window(String title, int x, int y, int width, int height) {
canvas = new Canvas();
objects = new ArrayList<>(); object_queue = new ArrayList<>(); object_queue_r = new ArrayList<>();
sprites = new ArrayList<>(); sprite_queue = new ArrayList<>(); sprite_queue_r = new ArrayList<>();
this.x = x; this.y = y; this.width = width; this.height = height;
init(title, x, y, width, height);
}
private void init(String title, int x, int y, int width, int height) {
canvas.setSize( width, height );
canvas.setBackground( Color.WHITE );
canvas.setIgnoreRepaint( true );
setLocation(x, y);
getContentPane().add( canvas );
setTitle( title );
setIgnoreRepaint( true );
addKeyListener(keyboard);
canvas.addKeyListener(keyboard);
this.setResizable(false);
pack();
setVisible( true );
canvas.createBufferStrategy( 2 );
bs = canvas.getBufferStrategy();
canvas.addMouseListener(mouse);
requestFocus();
addComponentListener(new ComponentAdapter() { // Update canvas size on window resize
@Override public void componentResized(ComponentEvent evt) {
canvas.setSize(getWidth(), getHeight());
}
});
gameThread = new Thread( this );
gameThread.start();
}
@Override
public void run() {
running = true;
if(collisionTree == null) { String err = "JGameEngine::Window No Game Space is set! use setGameSpace()"; try { throw new Exception(err); } catch(Exception e) { System.out.println(err); } }
while( running ) {
// Calculate time since last thread run as deltaTime => deltaTime can be used to
// implement framerate independant code
long current_time = System.currentTimeMillis();
deltaTime = (current_time - last_time)/100f;
last_time = current_time;
// A different thread might be trying to safely clear objects
if(game_loop_can_run) {
game_loop_running = true;
gameLoop();
}
game_loop_running = false;
// After ~10 seconds, call the garbage collector
garbage_time += deltaTime;
if(garbage_time > 100) {
System.gc();
garbage_time = 0;
}
}
}
private void gameLoop() {
do {
do {
Graphics g = null;
try {
g = bs.getDrawGraphics();
g.clearRect( 0, 0, getWidth(), getHeight() );
keyboard.allow_remove_pressed = true; keyboard.allow_remove_released = true;
mouse.allow_remove_clicked = true; mouse.allow_remove_released = true;
render(g);
if(keyboard.allow_remove_released) { keyboard.removeReleased(); keyboard.allow_remove_released = false; }
if(keyboard.allow_remove_pressed) { keyboard.removePressed(); keyboard.allow_remove_pressed = false; }
if(mouse.allow_remove_clicked) { mouse.removeClicked(); mouse.allow_remove_clicked = false; }
if(mouse.allow_remove_released) { mouse.removeReleased(); mouse.allow_remove_released = false; }
if(frameDelay > 0) Thread.sleep(frameDelay);
} catch (InterruptedException ex) { ex.printStackTrace(); }
finally { if( g != null ) g.dispose(); }
} while( bs.contentsRestored() );
bs.show();
} while( bs.contentsLost() );
}
// Run update code for each game object
private void render(Graphics ga) {
g = ga;
// While running object loops, the objects may have requested to add/remove other objects, handle queue
while(!object_queue.isEmpty()) { objects.add(object_queue.remove(0)); } while(!object_queue_r.isEmpty()) { objects.remove(object_queue_r.remove(0)); }
while(!sprite_queue.isEmpty()) { sprites.add(sprite_queue.remove(0)); } while(!sprite_queue_r.isEmpty()) { sprites.remove(sprite_queue_r.remove(0)); }
collisionTree.runCollisions();
for(JGameEngine.Object o : objects) {
//o.preUpdate(); Might have a use later
o.update();
}
for(JGameEngine.Sprite s : sprites) {
s.draw(s, g);
}
revalidate();
}
private void onWindowClosing() {
running = false;
System.gc();
}
}
//~~~~~~~~~~ Rendering
//~~~~~~~~~~ Game Object Managment
private static interface WrapCall { public abstract void call(); }
/** The Object class represents a Game Object. Every Game Object has an x and y position, a start() and update() method */
public static abstract class Object {
/** The x position of the Game Object */ public double x = 0;
/** The y position of the Game Object */ public double y = 0;
/** The name of the Game Object */ public String name = "Default";
//private ArrayList<WrapCall> calls = new ArrayList<>();
/** This function is called once when the object is added to the Game Space */
public abstract void start();
/** This function is called on every update cycle of the game. Also see deltaTime() */
public abstract void update();
/*private void preUpdate() {
// Current implementation never supported a preUpdate() so keeping this felt like a waste
// If there are additional object properties to invoke, do that before update (colliders etc)
for(WrapCall c : calls) {
c.call();
}
}*/
}
/** Place a game object in current space, start() is called immediately. Object is added in next cycle */
void objectAdd(JGameEngine.Object obj) {
obj.start(); // Initialize object
window.object_queue.add(obj);
}
/** Remove a game object from current space, Object is removed in next cycle */
void objectRemove(JGameEngine.Object obj) {
window.object_queue_r.add(obj);
}
/** Find a game object from the current space by reference */
List<JGameEngine.Object> objectList(JGameEngine.Object obj) {
List<JGameEngine.Object> l = new ArrayList<>();
for(int i = 0; i < window.objects.size(); i++)
if(window.objects.get(i) == obj)
l.add(window.objects.get(i));
return l;
}
/** Find a game object from the current space by name */
List<JGameEngine.Object> objectsFind(String name) {
List<JGameEngine.Object> l = new ArrayList<>();
for(int i = 0; i < window.objects.size(); i++)
if(window.objects.get(i).name.equals(name))
l.add(window.objects.get(i));
return l;
}
//~~~~~~~~~~ Game Object Managment Ends
//~~~~~~~~~~ Sprite Managment
/** The Sprite class contains information about a sprite. A sprite is an image or a collection of images (for animation) */
public class Sprite {
/** The x-position in the game space */ double x = 0;
/** The y-position in the game space */ double y = 0;
/** The width of the sprite */ double width = 0;
/** The height of the sprite */ double height = 0;
/** The speed at which the animation cycles */ double image_speed = 0;
/** The frame of the sprite animation */ int image_index = 0;
private JGameEngine.Object obj = null;
private BufferedImage[] img;
private int subimages_x = 0, subimages_width = 0;
private int subimages_y = 0, subimages_height = 0;
private double current_count_speed;
public Sprite(String image) {
try {
img = new BufferedImage[1];
img[0] = ImageIO.read(new File(image));
width = img[0].getWidth(); height = img[0].getHeight();
x = 0; y = 0;
} catch (IOException ex) { System.out.println("JGameEngine::Sprite() Unable to open image (" + image + ")"); }
}
public Sprite(Object ob, String image) { this(image); obj = ob; x = 0; y = 0; }
public Sprite(String image, int subimages_x, int subimages_width, int subimages_y, int subimages_height) {
this.subimages_x = subimages_x; this.subimages_y = subimages_y;
this.subimages_width = subimages_width; this.subimages_height = subimages_height;
BufferedImage tmp = null;
try { tmp = ImageIO.read(new File(image)); } catch (IOException ex) { System.out.println("JGameEngine::Sprite() Unable to open image (" + image + ")"); }
if(subimages_x > 0 && subimages_y > 0) {
img = new BufferedImage[subimages_x * subimages_y];
for(int i = 0; i < subimages_x; i++) {
for(int j = 0; j < subimages_y; j++) {
img[i * subimages_y + j] = tmp.getSubimage(j * subimages_width, i * subimages_height, subimages_width, subimages_height);
}
}
} else {
img = new BufferedImage[1];
img[0] = tmp;
}
width = img[0].getWidth(); height = img[0].getHeight();
}
public Sprite(Object ob, String image, int subimages_x, int subimages_width, int subimages_y, int subimages_height) { this(image, subimages_x, subimages_width, subimages_y, subimages_height); obj = ob; x = 0; y = 0; }
private void draw(Sprite sprite, Graphics g) {
double draw_x = x, draw_y = y, check_x = x, check_y = y;
if(obj != null) {
draw_x += obj.x; draw_y += obj.y;
check_x += obj.x; check_y += obj.y;
}
draw_x = cameraCoordX(draw_x); draw_y = cameraCoordY(draw_y);
double w = width; double h = height;
if(sprite.subimages_x == 0 || sprite.subimages_y == 0) {
sprite.image_index = 0;
if(!cameraBounded(check_x, check_y, w, h)) return;
g.drawImage(sprite.img[sprite.image_index], (int) Math.round(draw_x * camera.d), (int) Math.round(draw_y * camera.d), (int) Math.round(w * camera.d), (int) Math.round(h * camera.d), null);
return;
}
if(!cameraBounded(check_x, check_y, w, h)) return;
g.drawImage(sprite.img[sprite.image_index], (int) Math.round(draw_x * camera.d), (int) Math.round(draw_y * camera.d), (int) Math.round(w * camera.d), (int) Math.round(h * camera.d), null);
// Runs after about 1ms under frameDelay of 128. Can't keep up well afterwards.
current_count_speed += image_speed * deltaTime;
if(current_count_speed >= 1) {
sprite.image_index = (sprite.image_index + 1) % (sprite.subimages_x * sprite.subimages_y);
current_count_speed -= 1;
}
}
}
/** Create a Sprite from the path to an image.
* @param path A String containing the path to the image
* @return A Sprite */
public Sprite sprite(String path) { return new Sprite(path); }
/** Create a Sprite whose coordinates are relative to object. Moving the object by X amount will move the sprite by X amount.
* @param obj The Game Object to follow
* @param image A String containing the path to the image
* @return A Sprite */
public Sprite sprite(Object obj, String image) { return new Sprite(obj, image); }
/** Create an Animated Sprite, The images will cycle according to the image_speed set. The current image can be obtained or changed through image_index.
* @param image A String containing the path to the image
* @param subimages_x The number of images that exist in the x-axis
* @param subimages_width The width of each image in the x-axis
* @param subimages_y The number of images that exist in the y-axis
* @param subimages_height The width of each image in the y-axis
* @return A Sprite */
public Sprite sprite(String image, int subimages_x, int subimages_width, int subimages_y, int subimages_height) { return new Sprite(image, subimages_x, subimages_width, subimages_y, subimages_height); }
/** Create an Animated Sprite whose coordinates are relative to object. The images will cycle according to the image_speed set. The current image can be obtained or changed through image_index. Moving the object by X amount will move the sprite by X amount.
* @param obj The game object to follow
* @param image A String containing the path to the image
* @param subimages_x The number of images that exist in the x-axis
* @param subimages_width The width of each image in the x-axis
* @param subimages_y The number of images that exist in the y-axis
* @param subimages_height The width of each image in the y-axis
* @return A Sprite */
public Sprite sprite(Object obj, String image, int subimages_x, int subimages_width, int subimages_y, int subimages_height) { return new Sprite(obj, image, subimages_x, subimages_width, subimages_y, subimages_height); }
/** Draw a sprite */
public void spriteDraw(Sprite sprite) { sprite.draw(sprite, window.g); }
/** Add a sprite to the game space. This may be done once in start() of your game object. Once a sprite is added it will keep being drawn until its removed */
void spriteAdd(JGameEngine.Sprite spr) { window.sprite_queue.add(spr); }
/** Remove a sprite from the game space. This may be done once before deletion. Once a sprite is deleted it will no longer be drawn. See addSprite() */
void spriteRemove(JGameEngine.Sprite spr) { window.sprite_queue_r.add(spr); }
/** Update sprite width but respect the aspect ratio */
void spriteWidthRelative(Sprite sprite, double width) { sprite.height *= width/sprite.width; sprite.width = width; }
/** Update sprite height but respect the aspect ratio */
void spriteHeightRelative(Sprite sprite, double height) { sprite.width *= height/sprite.height; sprite.height = height; }
//~~~~~~~~~~ Sprite Managment Ends
//~~~~~~~~~~ Keyboard Input Managment
// Keylistener for the window
private class Key implements KeyListener {
// Single press, Single Release, Button held down
private volatile boolean[] pressed, released, pressing, pressed_act, released_act;
private volatile boolean allow_remove_pressed = false, allow_remove_released = false;
Key() {
pressed = new boolean[118];
released = new boolean[118];
//pressed_act = new boolean[118]; TODO: Fix multi key registeration
//released_act = new boolean[118];
pressing = new boolean[118];
for(int i = 0; i < 118; i++) {
pressed[i] = false;
released[i] = false;
pressing[i] = false;
}
}
@Override public void keyTyped(KeyEvent e) { }
@Override public void keyPressed(KeyEvent e) { allow_remove_pressed = false; if(!pressing[e.getKeyCode()]) pressed[e.getKeyCode()] = true; pressing[e.getKeyCode()] = true; }
@Override public void keyReleased(KeyEvent e) { allow_remove_released = false; released[e.getKeyCode()] = true; pressing[e.getKeyCode()] = false; }
private synchronized void removePressed() {
for(int i = 0; i < 118; i++) { /*pressed_act[i] = pressed[i];*/ pressed[i] = false; }
}
private synchronized void removeReleased() {
for(int i = 0; i < 118; i++) { /*released_act[i] = released[i];*/ released[i] = false; }
}
}
// Helper to convert english to key codes
private int key_code(String key) {
if(key.length() == 1) {
return KeyStroke.getKeyStroke(key.toUpperCase().charAt(0), 0).getKeyCode();
}
else if("up".equals(key)) return 38; else if("down".equals(key)) return 40; else if ("left".equals(key)) return 37;
else if("right".equals(key)) return 39; else if("space".equals(key)) return 32; else if("tab".equals(key)) return 9;
else if("enter".equals(key)) return 13; else if("ctrl".equals(key)) return 17; else if("alt".equals(key)) return 18;
else if("right_click".equals(key)) return 93; else if("esc".equals(key)) return 27;
return -1;
}
/** Will return true once when the key is first held down
* @param key A string representation of the key. May be one of the following: up, down, left, right, space, tab, enter, ctrl, alt, right_click, esc, [A-Z], [0-9], [Special Characters]*/
public boolean keyPressed(String key) {
int code = key_code(key);
return keyboard.allow_remove_pressed && keyboard.pressed[code];
}
/** Will return true as long as the key is being held down
* @param key A string representation of the key. May be one of the following: up, down, left, right, space, tab, enter, ctrl, alt, right_click, esc, [A-Z], [0-9], [Special Characters]*/
public boolean keyPressing(String key) {
int code = key_code(key);
return keyboard.pressing[code];
}
/** Will return true once when the key stops being held down
* @param key A string representation of the key. May be one of the following: up, down, left, right, space, tab, enter, ctrl, alt, right_click, esc, [A-Z], [0-9], [Special Characters]*/
public boolean keyReleased(String key) {
int code = key_code(key);
return keyboard.allow_remove_released && keyboard.released[code];
}
//~~~~~~~~~~ Keyboard Input Managment Ends
//~~~~~~~~~~ Mouse Input Managment
private class Mouse implements MouseListener {
private boolean mouseClicked = false, mouseRightClicked = false;
private boolean mouseClicking = false, mouseReleased = false;
private boolean windowFocus, allow_remove_clicked = false, allow_remove_released = false;
@Override public void mouseClicked(MouseEvent e) { }
@Override public void mousePressed(MouseEvent e) {
allow_remove_clicked = false;
if(!SwingUtilities.isRightMouseButton(e)) {
if(mouseClicked == true) mouseClicked = false; else mouseClicked = true; mouseClicking = true;
} else {
if(mouseRightClicked == true) mouseRightClicked = false; else mouseRightClicked = true;
}
}
@Override public void mouseReleased(MouseEvent e) {
if(SwingUtilities.isRightMouseButton(e)) return;
allow_remove_released = false; mouseClicking = false; mouseReleased = true;
}
@Override public void mouseEntered(MouseEvent e) { windowFocus = true; }
@Override public void mouseExited(MouseEvent e) { windowFocus = false; }
private void removeClicked() { mouseClicked = false; mouseRightClicked = false; }
private void removeReleased() { mouseReleased = false; }
}
/** Get the x-position of the mouse inside the window, if the mouse is outside the window it will assume that the mouse is at the corner. You can use mouseFocused() to see if mouse is inside the window */
public int mouseX() {
try { return window.getContentPane().getMousePosition(true).x; }
catch(Exception e) {
if(MouseInfo.getPointerInfo().getLocation().x > window.getContentPane().getX() + window.getX() + window.getContentPane().getWidth())
return window.getContentPane().getWidth() - 32;
else if(MouseInfo.getPointerInfo().getLocation().y > window.getContentPane().getY() + window.getY() + window.getContentPane().getHeight())
if(MouseInfo.getPointerInfo().getLocation().x <= window.getContentPane().getX() + window.getX())
return 0;
else return MouseInfo.getPointerInfo().getLocation().x - window.getContentPane().getX() - window.getX();
else if(MouseInfo.getPointerInfo().getLocation().x > window.getContentPane().getX() + window.getX())
return MouseInfo.getPointerInfo().getLocation().x - window.getContentPane().getX() - window.getX();
else return 0;
}
}
/** Get the y-position of the mouse inside the window, if the mouse is outside the window it will assume that the mouse is at the corner. You can use mouseFocused() to see if mouse is inside the window */
public int mouseY() {
try { return window.getContentPane().getMousePosition(true).y; }
catch(Exception e) {
if(MouseInfo.getPointerInfo().getLocation().y > window.getContentPane().getY() + window.getY() + window.getContentPane().getHeight())
if(MouseInfo.getPointerInfo().getLocation().x < window.getContentPane().getX() + window.getX() + window.getContentPane().getWidth())
return window.getContentPane().getHeight() - 32;
else return window.getContentPane().getHeight() - 32;
else if(MouseInfo.getPointerInfo().getLocation().x > window.getContentPane().getX() + window.getX() + window.getContentPane().getWidth() && MouseInfo.getPointerInfo().getLocation().y < window.getContentPane().getY() + window.getY())
return 0;
else if(MouseInfo.getPointerInfo().getLocation().x < window.getContentPane().getX() + window.getX() && MouseInfo.getPointerInfo().getLocation().y < window.getContentPane().getY() + window.getY())
return 0;
else if(MouseInfo.getPointerInfo().getLocation().x > window.getContentPane().getX() + window.getX() && MouseInfo.getPointerInfo().getLocation().x < window.getContentPane().getX() + window.getX() + window.getContentPane().getWidth())
return 0;
else return MouseInfo.getPointerInfo().getLocation().y - window.getContentPane().getY() - window.getY() - 16;
}
}
/** Remove the cursor of the mouse. It can not be added back once it has been removed. */
public void mouseDisableCursor() {
BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(0, 0), "blank cursor");
window.getContentPane().setCursor(blankCursor);
}
/** Check if the mouse was left clicked. Occurs once the first click. */
public boolean mouseClicked() { return mouse.allow_remove_clicked && mouse.mouseClicked; }
/** Check if the mouse was right clicked. Occurs once the first click. */
public boolean mouseRightClicked() { return mouse.allow_remove_clicked && mouse.mouseRightClicked; }
/** Check if the mouse stopped being clicked. Occurs once when finger is lifted off. */
public boolean mouseReleased() { return mouse.allow_remove_released && mouse.mouseReleased; }
/** Check if the mouse being left clicked. Occurs as long as the finger isn't lifted off. */
public boolean mouseClicking() { return mouse.mouseClicking; }
/** Check if the mouse is inside the window */
public boolean mouseFocused() { return mouse.windowFocus; }
//~~~~~~~~~~ Mouse Input Managment Ends
//~~~~~~~~~~ Camera
/** The Camera is part of the game space that is displayed on the window, The actual game space may be much much larger.
The coordinates of everything in the Game Space is translated into the Camera Space. A camera can be set to follow a Game Object.
The camera may also be zoomed in and out as required. The camera is a game object which is added to the Game Space when it is set to follow a Game Object. */
public class Camera extends Object {
private Object following;
private double offset_x, offset_y;
private double d; /* distance */
public Camera() {
d = 1;
following = null;
}
@Override public void start() { }
@Override public void update() {
if(following != null) {
x = following.x + offset_x;
y = following.y + offset_y;
}
}
}
/** Set the camera to follow a Game Object */
void cameraFollow(JGameEngine.Object obj) {
camera.following = obj;
if(obj != null)
objectAdd(camera);
else
objectRemove(camera);
}
/** Set the camera to follow a Game Object at an offset of x, y from the object */
void cameraFollow(JGameEngine.Object obj, double x, double y) {
cameraFollow(obj); camera.offset_x = x; camera.offset_y = y;
}
/** Get the camera's x-coordinate in the Game Space. It may be useful to position UI elements at the position of the camera so that the UI follows the camera */
public double cameraX() { return camera.x; }
/** Get the camera's y-coordinate in the Game Space. It may be useful to position UI elements at the position of the camera so that the UI follows the camera */
public double cameraY() { return camera.y; }
/** Set the camera's x-coordinate in the Game Space */
public void cameraX(double x) { camera.x = x; }
/** Set the camera's y-coordinate in the Game Space */
public void cameraY(double y) { camera.y = y; }
/** Get how much the camera sees in the x-direction. When camera distance is 1 this value is same as windowWidth() otherwise the value scales with the cameraDistance */
public double cameraWidth() { return window.width * 1 / camera.d; }
/** Get how much the camera sees in the y-direction. When camera distance is 1 this value is same as windowHeight() otherwise the value scales with the cameraDistance */
public double cameraHeight() { return window.height * 1 / camera.d; }
/** Set the camera distance */
public void cameraDistance(double distance) { camera.d = 1/distance; }
/** Get the camera distance */
public double cameraDistance() { return 1/camera.d; }
// Get X, Y position relative to camera position to translate position to Window Space
private double cameraCoordX(double x) { return x - camera.x; }
private double cameraCoordY(double y) { return y - camera.y; }
/** Check if a box lies inside the Camera Space (If it is currently visible to the player) */
public boolean cameraBounded(double x, double y, double width, double height) {
x = cameraCoordX(x); y = cameraCoordY(y);
x *= camera.d; y *= camera.d; width *= camera.d; height *= camera.d;
if(x + width < 0 && y + width < 0) return false;
if(x > windowWidth() && y > windowHeight()) return false;
return true;
}
//~~~~~~~~~~ Camera Ends
//~~~~~~~~~~ Fonts
private class Fonts {
ArrayList<String> name;
ArrayList<Font> font;
public Fonts() {
name = new ArrayList<>();
font = new ArrayList<>();
}
private void addFont(String name, Font font) {
if(getFont(name) != -1) return;
this.name.add(name);
this.font.add(font);
}
private void addFont(Font f) {
if(getFont(f) != -1) return;
this.name.add(f.getFontName()+"~"+((float)f.getSize()));
this.font.add(f);
}
private int getFont(String name) {
for(int i = 0; i < this.name.size(); i++) if(this.name.get(i).equals(name)) return i;
return -1;
}
private int getFont(Font ft) {
for(int i = 0; i < this.font.size(); i++) if(this.font.get(i).equals(ft)) return i;
return -1;
}
}
//~~~~~~~~~~ Fonts Ends
//~~~~~~~~~~ Draw
/** Directly access the Graphics Object. This is not recommended as objects drawn from this will always be in the Camera Space (May be avoided if CameraX, CameraY, CameraDistance is used) */
public Graphics draw() { return window.g; }
/** Get the colour that the Graphics Object will use */
public Color drawColor() { return draw().getColor(); }
/** Set the colour that the Graphics Object will use */
public void drawColor(Color c) { draw().setColor(c); }
/** Draw a line */
public void drawLine(double x1, double y1, double x2, double y2) {
if(x2>x1 && y2>y1 && cameraBounded(x1,y1,x2-x1,y2-y1)) { x1 = cameraCoordX(x1); y1 = cameraCoordY(y1); x2 = cameraCoordX(x2); y2 = cameraCoordY(y2);
draw().drawLine((int) (x1 * camera.d), (int) (y1 * camera.d), (int) (x2 * camera.d), (int) (y2 * camera.d)); }
}
/** Draw an oval */
public void drawOval(double x, double y, double w, double h) {
if(cameraBounded(x,y,w,h)) { x = cameraCoordX(x); y = cameraCoordY(y);
draw().drawOval((int) (x * camera.d), (int) (y * camera.d), (int) (w * camera.d), (int) (h * camera.d)); }
}
/** Draw a rectangle */
public void drawRect(double x, double y, double w, double h) {
if(cameraBounded(x,y,w,h)) { x = cameraCoordX(x); y = cameraCoordY(y);
draw().drawRect((int) (x * camera.d), (int) (y * camera.d), (int) (w * camera.d), (int) (h * camera.d)); }
}
/** Draw text */
public void drawText(String s, double x, double y) {
if(cameraBounded(x,y,textWidth(s),textHeight(s))) { x = cameraCoordX(x); y = cameraCoordY(y);
draw().drawString(s, (int) (x * camera.d), (int) (y * camera.d)); }
}
/** Draw a line */
public void drawLine(double x1, double y1, double x2, double y2, Color c) { Color t = draw().getColor(); drawLine(x1, y1, x2, y2); draw().setColor(t); }
/** Draw an oval */
public void drawOval(double x, double y, double w, double h, Color c) { drawOval(x, y, w, h, c, false); }
/** Draw a rectangle */
public void drawRect(double x, double y, double w, double h, Color c) { drawRect(x, y, w, h, c, false); }
/** Draw text */
public void drawText(String s, double x, double y, Color c) { Color t = draw().getColor(); draw().setColor(c); drawText(s, x, y); draw().setColor(t); }
/** Get the width of a text */
public double textWidth(String s) { return draw().getFontMetrics().stringWidth(s); }
/** Get the height of a text */
public double textHeight(String s) { return draw().getFontMetrics().getStringBounds(s, draw()).getHeight(); }
/** Change the font of text to a system font. Type can be changed to "italic" and "bold", otherwise it will be normal. 3rd parameter is font size */
public void textFontSystem(String name, String type, int size) {
type = type.toLowerCase();
int i = fonts.getFont(name+type+"~"+size); if(i != -1) { draw().setFont(fonts.font.get(i)); return; }
int t = Font.PLAIN; if(type.equals("bold")) t = Font.BOLD; else if(type.equals("italic")) t = Font.ITALIC;
draw().setFont(new Font(name, t, size * (int) camera.d));
fonts.addFont(name+type+"~"+size, draw().getFont()); }
/** Create a Font Object from the given path, ttf format supported only. */
public Font textFontCreate(String path, float size) {
int i = fonts.getFont(path+"~"+size); if(i != -1) return fonts.font.get(i); Font font = null;
try { font = Font.createFont(Font.TRUETYPE_FONT, new File(path)); font = font.deriveFont(size * (float) camera.d); }
catch(Exception e) { System.out.println("JGameEngine::textFont() font " + path + " can't be set. details: " + e.toString()); }
if(font != null) fonts.addFont(path+"~"+size, font);
return font;
}
/** Change the current font to a Font Object */
public void textFont(Font font) { fonts.addFont(font); draw().setFont(font); }
/** Change the current font from the given path, ttf format supported only. */
public void textFont(String path, float size) { draw().setFont(textFontCreate(path, size)); }
/** Change the font size */
public void textSize(float size) { int i = fonts.getFont(draw().getFont().getName()+"~"+size);
if(i != -1) { draw().setFont(fonts.font.get(i)); return; } Font f = draw().getFont().deriveFont(size);
fonts.addFont(f); draw().setFont(f);
}
/** Draw an filled oval of colour c */
public void drawOval(double x, double y, double w, double h, Color c, Boolean fill) { Color t = draw().getColor(); draw().setColor(c);
if(fill && cameraBounded(x,y,w,h)) { x = cameraCoordX(x); y = cameraCoordY(y);
draw().fillOval((int) (x * camera.d), (int) (y * camera.d), (int) (w * camera.d), (int) (h * camera.d));
} else drawOval(x, y, w, h); draw().setColor(t);
}
/** Draw a filled rectangle of colour c*/
public void drawRect(double x, double y, double w, double h, Color c, Boolean fill) { Color t = draw().getColor(); draw().setColor(c);
if(fill && cameraBounded(x,y,w,h)) { x = cameraCoordX(x); y = cameraCoordY(y);
draw().fillRect((int) (x * camera.d), (int) (y * camera.d), (int) (w * camera.d), (int) (h * camera.d));
} else drawRect(x, y, w, h); draw().setColor(t);
}
//~~~~~~~~~~ Draw Ends
//~~~~~~~~~~ Collisions https://gamedevelopment.tutsplus.com/tutorials/quick-tip-use-quadtrees-to-detect-likely-collisions-in-2d-space--gamedev-374
private class Quadtree {
private int MAX_OBJECTS = 10, MAX_LEVELS = 5;
private int level;
ArrayList<CollisionMask> objects, objects_a, objects_r;
private Rectangle bounds;
private Quadtree[] nodes;
public Quadtree(int pLevel, Rectangle pBounds) {
level = pLevel; objects = new ArrayList(); objects_r = new ArrayList();
bounds = pBounds; nodes = new Quadtree[4]; objects_a = new ArrayList();
}
private void clear() {
objects.clear(); objects_a.clear(); objects_r.clear();
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] != null) nodes[i].clear();
nodes[i] = null;
}
}
private void split() {
int subWidth = (int)(bounds.getWidth() / 2), subHeight = (int)(bounds.getHeight() / 2);
int x = (int) bounds.getX(), y = (int) bounds.getY();
nodes[0] = new Quadtree(level+1, new Rectangle(x + subWidth, y, subWidth, subHeight));
nodes[1] = new Quadtree(level+1, new Rectangle(x, y, subWidth, subHeight));
nodes[2] = new Quadtree(level+1, new Rectangle(x, y + subHeight, subWidth, subHeight));
nodes[3] = new Quadtree(level+1, new Rectangle(x + subWidth, y + subHeight, subWidth, subHeight));
}
private int getIndex(Rectangle pRect) {
int index = -1;
double verticalMidpoint = bounds.getX() + (bounds.getWidth() / 2), horizontalMidpoint = bounds.getY() + (bounds.getHeight() / 2);
// Object can completely fit within the top/bottom quadrants
boolean topQuadrant = (pRect.getY() < horizontalMidpoint && pRect.getY() + pRect.getHeight() < horizontalMidpoint);
boolean bottomQuadrant = (pRect.getY() > horizontalMidpoint);
// Object can completely fit within the left/right quadrants
if (pRect.getX() < verticalMidpoint && pRect.getX() + pRect.getWidth() < verticalMidpoint) {
if (topQuadrant) index = 1;
else if (bottomQuadrant) index = 2;
}
else if (pRect.getX() > verticalMidpoint) {
if (topQuadrant) index = 0;
else if (bottomQuadrant) index = 3;
}
return index;
}
private void insert(CollisionMask m) {
if (nodes[0] != null) {
int index = getIndex(m.realBoundsRect());
if (index != -1) {
nodes[index].insert(m); return;
}
}
objects.add(m);
if (objects.size() > MAX_OBJECTS && level < MAX_LEVELS) {
if (nodes[0] == null)
split();
int i = 0;
while (i < objects.size()) {
int index = getIndex(objects.get(i).realBoundsRect());
if (index != -1) nodes[index].insert(objects.remove(i));
else i++;
}
}
}
public ArrayList<CollisionMask> retrieve(ArrayList<CollisionMask> returnObjects, Rectangle pRect) {
int index = getIndex(pRect);
if (index != -1 && nodes[0] != null)
nodes[index].retrieve(returnObjects, pRect);
returnObjects.addAll(objects);
return returnObjects;
}
public void remake() {
ArrayList<CollisionMask> tmp = new ArrayList<>(objects);
clear();
for(CollisionMask t : tmp) {
insert(t);
}
}
public void remove(CollisionMask m) { objects_r.add(m); }
public void add(CollisionMask m) { objects_a.add(m); }
public void runCollisions() {
while(!objects_a.isEmpty()) { objects.add(objects_a.remove(0)); } while(!objects_r.isEmpty()) { objects.remove(objects_r.remove(0)); }
ArrayList<CollisionMask> returnObjects = new ArrayList();
for (int i = 0; i < objects.size(); i++) {
returnObjects.clear(); retrieve(returnObjects, objects.get(i).realBoundsRect());
for (int x = 0; x < returnObjects.size(); x++) {
if(objects.get(i).check(objects.get(i).realBounds(), returnObjects.get(x).realBounds()) && objects.get(i) != returnObjects.get(x)) {
objects.get(i).c.collision(returnObjects.get(x).o);
}
}
}
}
}
/** A CollisionMask is the area in which the Collision is effectively applied. A class that implements
Collision can use the CollisionMaskAdd() method of JGameEngine to add a collision mask */
public class CollisionMask {
private char type;
private double x, y, w, h;
/** Object to which the collision mask is attached */
Object o = null;
private Collision c = null;
private CollisionMask(Object obj, Collision col, double x, double y, double r) {
o = obj; c = col; this.x = x; this.y = y; this.w = r; this.h = r; this.type = 1;
}
private CollisionMask(Object obj, Collision col, double x, double y, double w, double h) {
o = obj; c = col; this.x = x; this.y = y; this.w = w; this.h = h; this.type = 0;
}
/** Get the X position of mask */
public double getX() { if(o != null) return x + o.x; return x; }
/** Get the Y position of mask */
public double getY() { if(o != null) return y + o.y; return y; }
/** Get the width of mask */
public double getWidth() { return w; }
/** Get the height of mask */
public double getHeight() { return h; }
/** Draw the collision mask for debugging purposes */
public void debug() {
CollisionMask m = this.realBounds();
if(m.type == 0) { drawRect(m.x, m.y, m.w, m.h); }
else if(m.type == 1) { drawOval(m.x, m.y, m.w * 2, m.w * 2); }
}
private boolean check(CollisionMask c1, CollisionMask c2) {
// https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection & https://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersection
if(c1.type == 0 && c2.type == 0) {
if (c1.x < c2.x + c2.w &&
c1.x + c1.w > c2.x &&
c1.y < c2.y + c2.h &&
c1.y + c1.h > c2.y) return true;
} else if(c1.type == 1 && c2.type == 1) {
double dx = c1.x - c2.x + c1.w - c2.w,
dy = c1.y - c2.y + c1.w - c2.w;
double distance = Math.sqrt(dx * dx + dy * dy);
if (distance < c1.w + c2.w) return true;
} else {
double c1x = c1.x, c1y = c1.y, c1w = c1.w, c1h = c1.h,
c2x = c2.x + c2.w, c2y = c2.y + c2.w, c2r = c2.w;
if(c1.type == 1) {
c1x = c2.x; c1y = c2.y; c1w = c2.w; c1h = c2.h;
c2x = c1.x + c1.w; c2y = c1.y + c1.w; c2r = c1.w;
}
double DeltaX = c2x - Math.max(c1x, Math.min(c2x, c1x + c1w)),
DeltaY = c2y - Math.max(c1y, Math.min(c2y, c1y + c1h));
return (DeltaX * DeltaX + DeltaY * DeltaY) < (c2r * c2r);
}
return false;
}
/** Get real bounds in rectangle form */
private Rectangle realBoundsRect() { CollisionMask tmp = realBounds(); return new Rectangle((int) tmp.x, (int) tmp.y, (int) tmp.w, (int) tmp.h); }
/** Get real bounds (relative to position of object) */
private CollisionMask realBounds() {
if(o == null) return this;
CollisionMask tmp = new CollisionMask(o, c, x, y, w, h); tmp.type = type;
tmp.x += o.x; tmp.y += o.y;
return tmp;
}
}
/** Need to be implemented by Objects that need collisions, also see collisionMaskAdd() */
public interface Collision { public abstract void collision(Object with); }
/** Add a collision mask to Object. Object must implement Collision class, Note that r is radius (not width/diameter). The collision box will be relative to Object x, y (So x=0,y=0 is the x,y position of Object), Need to be done once in Start() */
CollisionMask collisionMaskAdd(Object obj, double x, double y, double r) {
CollisionMask m = null;
if(Collision.class.isInstance(obj)) {
m = new CollisionMask(obj, (Collision) obj, x, y, r);
collisionTree.add(m);
} else {
String err = "JGameEngine::CollisionMaskAdd() Trying to add a Collision Mask to an Object that doesn't implement Collsions. (Class "+obj.getClass().getName()+" must implement JGameEngine.Collision)"; try{ throw new Exception(err); }
catch(Exception e) { System.out.println(err); }
}
return m;
}
/** Add a collision mask to Object. Object must implement Collision class, the collision box will be relative to Object x, y (So x=0,y=0 is the x,y position of Object), Need to be done once in Start() */
CollisionMask collisionMaskAdd(Object obj, double x, double y, double w, double h) {
CollisionMask m = null;
if(Collision.class.isInstance(obj)) {
m = new CollisionMask(obj, (Collision) obj, x, y, w, h);
collisionTree.add(m);
} else {
String err = "JGameEngine::CollisionMaskAdd() Trying to add a Collision Mask to an Object that doesn't implement Collsions. (Class "+obj.getClass().getName()+" must implement JGameEngine.Collision)"; try{ throw new Exception(err); }
catch(Exception e) { System.out.println(err); }
}
return m;
}
/** Search through the list of all collision masks in the room to find masks that belong to the object */
List<CollisionMask> collisionMaskList(Object obj) {
ArrayList<CollisionMask> ret = new ArrayList<>();
for(int i = 0; i < collisionTree.objects.size(); i++) {
if(collisionTree.objects.get(i).o == obj) ret.add(collisionTree.objects.get(i));
}
for(int i = 0; i < collisionTree.objects_a.size(); i++) {
if(collisionTree.objects_a.get(i).o == obj) ret.add(collisionTree.objects_a.get(i));
}
return ret;
}
/** Remove the collision mask */
void collisionMaskRemove(CollisionMask m) {
collisionTree.remove(m);
}
/** Remove all of the collision mask of an object */
void collisionMaskRemove(JGameEngine.Object o) {
ArrayList<CollisionMask> m = (ArrayList) collisionMaskList(o);
for(int i = 0; i < m.size(); i++)
collisionTree.remove(m.get(i));
}
/** Draw all collision masks */
void collisionMaskDebug() {
for(int i = 0; i < collisionTree.objects.size(); i++) {
CollisionMask m = collisionTree.objects.get(i).realBounds();
if(m.type == 0) { drawRect(m.x, m.y, m.w, m.h); }
else if(m.type == 1) { this.drawOval(m.x, m.y, m.w * 2, m.w * 2); }
}
}
/** Get the list of objects that are collide to the given point */
List<CollisionMask> collisionPointTest(double x, double y) {
return collisionBoxTest(x,y,1,1);
}
/** Get the list of objects that are collide to the given box */
List<CollisionMask> collisionBoxTest(double x, double y, double w, double h) {
ArrayList<CollisionMask> m = new ArrayList<>();
collisionTree.retrieve(m, new Rectangle((int) x, (int) y, (int) w, (int) h));
CollisionMask tmp = new CollisionMask(null, null, x, y, w, h);
for (int i = 0; i < m.size(); i++) {
if(!tmp.check(m.get(i).realBounds(), tmp)) {
m.remove(i); i--;
}
}
return m;
}
//~~~~~~~~~~ Collisions End
//~~~~~~~~~~ Audio https://www.geeksforgeeks.org/play-audio-file-using-java/
private class Audio {
long current;
String status, file;
Clip clip;
Boolean loop;
Audio(String file, boolean loop) { this.file = file; this.loop = loop; makeStream(file, loop); }
Audio(String file, boolean loop, float gain) { this.file = file; this.loop = loop; makeStream(file, loop); setVolume(gain); }
void makeStream(String file, boolean loop) {
AudioInputStream audioInputStream = null;
try {
audioInputStream = AudioSystem.getAudioInputStream(new File(file).getAbsoluteFile());
clip = AudioSystem.getClip();
clip.open(audioInputStream);
} catch(Exception e) {
System.out.println("JGameEngine::Audio Sound clip (" + file + ") doesn't exist or line unavailable. details: " + e.toString());
}
if(loop)
clip.loop(Clip.LOOP_CONTINUOUSLY);
else
clip.start();
status = "playing";
}
private void play(float gain) {
if(status.equals("none")) {
clip.start();
status = "playing";
} else if(status.equals("paused")) {
clip.close();
makeStream(file, loop);
clip.setMicrosecondPosition(current);
this.play(gain);
}
if(gain != -1) setVolume(gain);
}
private void setVolume(float gain) {
FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float dB = (float) (Math.log(gain) / Math.log(10.0) * 20.0);
gainControl.setValue(dB);
}
private void pause() {
if(status.equals("none") || status.equals("paused")) return;
clip.stop();
current = clip.getMicrosecondPosition();
status = "paused";
}
public void stop() {
clip.stop();
clip.close();
}
}
private class Audios {
ArrayList<Audio> audios;
public Audios() { audios = new ArrayList<>(); }
void add(String path, Boolean loop, float gain) {
// remove inactive instances of this audio clip
for(int i = 0; i < audios.size(); i++)
if(audios.get(i).file.equalsIgnoreCase(path))
if(!audios.get(i).clip.isRunning() && !audios.get(i).status.equals("paused")) { audios.remove(i); i--; }
audios.add(new Audio(path, loop, gain));
}
Audio find(String path) {
for(int i = 0; i < audios.size(); i++) if(audios.get(i).file.equalsIgnoreCase(path)) return audios.get(i);
return null;
}