View Javadoc
1   package com.jed.state;
2   
3   import com.jed.actor.AbstractEntity;
4   import com.jed.actor.Player;
5   import com.jed.core.Collision;
6   import com.jed.core.MotherBrainConstants;
7   import com.jed.core.QuadTree;
8   import com.jed.util.Rectangle;
9   import com.jed.util.Util;
10  import com.jed.util.Vector2f;
11  import org.lwjgl.opengl.GL11;
12  import org.newdawn.slick.Color;
13  import org.newdawn.slick.opengl.Texture;
14  
15  import javax.annotation.Nonnull;
16  import java.util.*;
17  import java.util.concurrent.CopyOnWriteArrayList;
18  
19  /**
20   * 
21   * @author jlinde, Peter Colapietro
22   *
23   * TODO Decouple from com.jed.core.MotherBrain / com.jed.core.MotherBrainConstants
24   *
25   */
26  public final class GameMap extends AbstractDisplayableState {
27  
28      /**
29       *
30       */
31      private int width;
32  
33      /**
34       *
35       */
36      private int height;
37  
38      /**
39       *
40       */
41      private int tileWidth;
42  
43      /**
44       *
45       */
46      private int tileHeight;
47  
48      /**
49       * 
50       */
51      private String tileSetPath;
52  
53      /**
54       * 
55       */
56      //TODO: this should be set when the map loads...
57      private static final Vector2f INITIAL_POSITION = new Vector2f(0, 0);
58  
59      /**
60       * 
61       */
62      private List<MapTile> tiles;
63  
64      /**
65       * 
66       */
67      @Nonnull
68      private Texture texture;
69  
70      /**
71       * 
72       */
73      private final Player player;
74      
75      /**
76       * 
77       */
78      private final Stack<AbstractEntity> scene;
79  
80      /**
81       * 
82       */
83      private QuadTree quadTree;
84  
85      /**
86       * 
87       */
88      private float gravity = 0.21875f;
89      
90      /**
91       * 
92       */
93      private boolean isDebugViewEnabled;
94  
95      /**
96       *
97       */
98      public GameMap() {
99          //TODO: initialize scene Stack by some data contained in the map i.e. start position or something like that...
100         scene = new GameEntityStack<>();
101         player = new Player(new Vector2f(50, 200), 256, 256, this);
102         scene.push(player);
103     }
104 
105     @Override
106     public void entered() {
107         texture = Util.loadTexture(tileSetPath);
108 
109         quadTree = new QuadTree(
110                 new Vector2f(0, 0), 0,
111                 new Rectangle(
112                         width * tileWidth,
113                         height * tileHeight),
114                 this);
115 
116         //TODO: the quad tree should come pre-populated with all map tiles
117         // and should only add and remove map entities...
118 
119         //Draw the map once prior to the first update to ensure the quad tree
120         //is populated with map tiles on the first frame
121         drawMap();
122     }
123 
124     @Override
125     public void update() {
126         tiles.forEach(each -> { each.setColliding(false); each.setEvaluating(false); });
127         scene.forEach(quadTree::insert);
128         detectCollisions();
129         scene.forEach(AbstractEntity::update);
130         scrollMap();
131     }
132 
133     /**
134      * FIXME.
135      */
136     private void scrollMap() {
137         final float playerHeightMinusInitialPosition = (player.getHeight() / 2) - INITIAL_POSITION.y;
138         if (player.getMovement().y > 0) {
139             if ((player.getPosition().y + playerHeightMinusInitialPosition) > MotherBrainConstants.HEIGHT / 2) {
140                 if (INITIAL_POSITION.y + player.getMovement().y > height * tileHeight - MotherBrainConstants.HEIGHT) {
141                     INITIAL_POSITION.y = height * tileHeight - MotherBrainConstants.HEIGHT;
142                 } else {
143                     INITIAL_POSITION.y += player.getMovement().y;
144                 }
145             }
146         } else if (player.getMovement().y < 0) {
147             if ((player.getPosition().y + playerHeightMinusInitialPosition) < MotherBrainConstants.HEIGHT / 2) {
148                 if (player.getMovement().y + INITIAL_POSITION.y < 0) {
149                     INITIAL_POSITION.y = 0;
150                 } else {
151                     INITIAL_POSITION.y += player.getMovement().y;
152                 }
153             }
154         }
155         final float playerWidthMinusInitialPosition = (player.getWidth() / 2) - INITIAL_POSITION.x;
156         if (player.getMovement().x > 0) {
157             if ((player.getPosition().x + playerWidthMinusInitialPosition) > MotherBrainConstants.WIDTH / 2) {
158                 if (INITIAL_POSITION.x + player.getMovement().x > width * tileWidth - MotherBrainConstants.WIDTH) {
159                     INITIAL_POSITION.x = width * tileWidth - MotherBrainConstants.WIDTH;
160                 } else {
161                     INITIAL_POSITION.x += player.getMovement().x;
162                 }
163             }
164         } else if (player.getMovement().x < 0) {
165             if ((player.getPosition().x + playerWidthMinusInitialPosition) < MotherBrainConstants.WIDTH / 2) {
166                 if (player.getMovement().x + INITIAL_POSITION.x < 0) {
167                     INITIAL_POSITION.x = 0;
168                 } else {
169                     INITIAL_POSITION.x += player.getMovement().x;
170                 }
171             }
172         }
173     }
174 
175     /**
176      * 
177      */
178     private void detectCollisions() {
179         final List<AbstractEntity> returnObjects = new ArrayList<>(scene.size());
180         final List<Collision> collisions = new CopyOnWriteArrayList<>();
181         for (final AbstractEntity entity : scene) {
182             quadTree.retrieve(returnObjects, entity);
183             //Detect all collisions that might occur this frame
184             returnObjects.stream().filter(returnObject -> !returnObject.equals(entity)).forEach(returnObject -> {
185                 final Collision collision = new Collision(entity, returnObject, isDebugViewEnabled());
186                 //Detect all collisions that might occur this frame
187                 if (collision.detectCollision()) {
188                     collisions.add(collision);
189                 }
190             });
191 
192             //Sort Collisions, resolve soonest depending on type in following order:
193             //    OVERLAPS
194             //    SWEPT Y
195             //    SWEPT X
196             final Iterator<Collision> it = collisions.iterator();
197             while (collisions.size() > 0) {
198                 Collections.sort(collisions);
199                 collisions.get(0).resolveCollision();
200                 collisions.remove(0);
201                 while (it.hasNext()) {
202                     final Collision each = it.next();
203                     if (!each.detectCollision()) {
204                         collisions.remove(each);
205                     }
206                 }
207             }
208         }
209 
210     }
211 
212 
213     @Override
214     public void render() {
215         quadTree.clear();
216         drawMap();
217         if(isDebugViewEnabled()) {
218             quadTree.render();
219         }
220         for (AbstractEntity each : scene) {
221             each.render();
222         }
223     }
224 
225     /**
226      * 
227      */
228     private void drawMap() {
229         Color.white.bind();
230         texture.bind();
231         GL11.glEnable(GL11.GL_TEXTURE_2D);
232 
233         final float tileOffsetY = INITIAL_POSITION.y / tileHeight;
234         final double pixelOffsetY = tileHeight * (tileOffsetY % 1);
235 
236         final float tileOffsetX = INITIAL_POSITION.x / tileWidth;
237         final double pixelOffsetX = tileWidth * (tileOffsetX % 1);
238 
239         int tileIndex = (int) (width * (Math.floor(tileOffsetY)) + tileOffsetX);
240 
241         final int rows = (MotherBrainConstants.HEIGHT / tileHeight + (pixelOffsetY == 0 ? 0 : 1));
242         final int columns = (MotherBrainConstants.WIDTH / tileWidth + (pixelOffsetX == 0 ? 0 : 1));
243         final int nextRow = width - columns;
244 
245         MapTile mapTile;
246         for (int rowIndex = 0; rowIndex < rows; rowIndex++) {
247             for (int columnIndex = 0; columnIndex < columns; columnIndex++) {
248                 mapTile = tiles.get(tileIndex);
249                 if (mapTile.getTileId() != 0) {
250                     mapTile.render();
251                     quadTree.insert(mapTile);
252                 }
253                 tileIndex++;
254             }
255             tileIndex += nextRow;
256         }
257 
258         GL11.glDisable(GL11.GL_TEXTURE_2D);
259     }
260     
261     /**
262      * @param x x
263      * @param y y
264      */
265     public void drawChildVertex2f(float x, float y) {
266         GL11.glVertex2f(x - INITIAL_POSITION.x, y - INITIAL_POSITION.y);
267     }
268 
269     /**
270      * @param tileSetPath path to tile set 
271      * 
272      */
273     public void setTileSetPath(String tileSetPath) {
274         this.tileSetPath = tileSetPath;
275     }
276 
277     /**
278      *
279      * @param width width
280      */
281     public void setWidth(int width) {
282         this.width = width;
283     }
284 
285     /**
286      *
287      * @return width
288      */
289     public int getWidth() {
290         return width;
291     }
292 
293     /**
294      *
295      * @param height height
296      */
297     public void setHeight(int height) {
298         this.height = height;
299     }
300 
301     /**
302      *
303      * @return height
304      */
305     public int getHeight() {
306         return height;
307     }
308 
309     /**
310      *
311      * @return gravity
312      */
313     public float getGravity() {
314         return gravity;
315     }
316 
317     /**
318      *
319      * @return tileWidth
320      */
321     public int getTileWidth() {
322         return tileWidth;
323     }
324 
325     /**
326      *
327      * @param tileWidth tileWidth
328      */
329     public void setTileWidth(int tileWidth) {
330         this.tileWidth = tileWidth;
331     }
332 
333     /**
334      *
335      * @return tileHeight
336      */
337     public int getTileHeight() {
338         return tileHeight;
339     }
340 
341     /**
342      *
343      * @param tileHeight tileHeight
344      */
345     public void setTileHeight(int tileHeight) {
346         this.tileHeight = tileHeight;
347     }
348 
349     /**
350      *
351      * @param gravity gravity
352      */
353     public void setGravity(Float gravity) {
354         this.gravity = gravity;
355     }
356 
357     /**
358      *
359      * @param tiles tiles
360      */
361     public void setTiles(final List<MapTile> tiles) {
362         this.tiles = tiles;
363     }
364 
365     /**
366      *
367      * @return tiles
368      */
369     public List<MapTile> getTiles() {
370         return tiles;
371     }
372     
373     /**
374      * 
375      * @param isDebugViewEnabled isDebugViewEnabled
376      */
377     public void setDebugViewEnabled(boolean isDebugViewEnabled) {
378         this.isDebugViewEnabled = isDebugViewEnabled;
379     }
380     
381     /**
382      * 
383      * @return isDebugViewEnabled
384      */
385     boolean isDebugViewEnabled() {
386         return isDebugViewEnabled;
387     }
388 
389     /**
390      *
391      * @return player
392      */
393     public Player getPlayer() {
394         return player;
395     }
396 }