瀏覽代碼

refactored complete renderer, better design, render interface

Kajetan Johannes Hammerle 7 年之前
父節點
當前提交
e5f2711c4c

+ 62 - 53
src/me/hammerle/supersnuvi/entity/Entity.java

@@ -6,20 +6,20 @@ import javafx.scene.image.PixelWriter;
 import javafx.scene.image.WritableImage;
 import javafx.scene.paint.Color;
 import me.hammerle.supersnuvi.input.IKeyHandler;
-import me.hammerle.supersnuvi.rendering.ITick;
-import me.hammerle.supersnuvi.rendering.MapRenderer;
+import me.hammerle.supersnuvi.rendering.GameRenderer;
+import me.hammerle.supersnuvi.rendering.WorldRenderer;
 import me.hammerle.supersnuvi.tiles.Tile;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.Utils;
 
-public class Entity implements ITick
+public class Entity
 {
     // 1 Tile = 1 m
     // Pixel    m       s      Pixel
     // ----- * --- * ------ = -------
     //   m      s     Tick     Tick
     // added correction factor, real life gravity seems to strong
-    public static double GRAVITY = Tile.TILE_SIZE * 9.81 / 60 * 0.1;
+    public static double GRAVITY = GameRenderer.TILE_SIZE * 9.81 / 60 * 0.1;
     
     private double prevX;
     private double prevY;
@@ -28,15 +28,17 @@ public class Entity implements ITick
     
     protected double motionX;
     protected double motionY;
-    private boolean onGround;
+    public boolean onGround;
     
     private CollisionBox collisionBox;
+    private double width;
+    private double height;
     
     private Image image;
     
-    private MapRenderer map;
+    private WorldRenderer map;
     
-    public Entity(MapRenderer map, double x, double y, double width, double height)
+    public Entity(WorldRenderer map, double x, double y, double width, double height)
     {
         this.map = map;
         this.posX = x;
@@ -46,7 +48,9 @@ public class Entity implements ITick
         this.motionX = 0;
         this.motionY = 0;
         this.onGround = false;
-        this.collisionBox = new CollisionBox(-width / 2, -height / 2, width / 2, height / 2);
+        this.collisionBox = new CollisionBox(0, 0, width, height);
+        this.width = width;
+        this.height = height;
         
         image = new WritableImage((int) width, (int) height);
         PixelWriter writer = ((WritableImage) image).getPixelWriter();
@@ -68,12 +72,11 @@ public class Entity implements ITick
     {
     }
     
-    @Override
     public final void tick(long delta) 
     {
         if(isAffectedByGravity())
         {
-            motionY += GRAVITY;
+            motionY -= GRAVITY;
         }
         
         prevX = posX;
@@ -99,7 +102,7 @@ public class Entity implements ITick
         if(onGround)
         {
             onGround = false;
-            motionY -= getJumpPower();
+            motionY += getJumpPower();
         }
     }
     
@@ -128,29 +131,34 @@ public class Entity implements ITick
         
         
         CollisionBox eBox = getBox();
-        double fixedStartX = eBox.getFixedStartX();
-        double fixedStartY = eBox.getFixedStartY();
+        double halfWidth = eBox.getWidth() / 2;
+        double halfHeight = eBox.getHeight() / 2;
         eBox = eBox.copy();
         
+        startX += halfWidth;
+        startY += halfHeight;
+        endX += halfWidth;
+        endY += halfHeight;
+        
         // expanding area by the size of the entity
-        CollisionBox expandedBox = eBox.copy().expand(motionX, motionY).grow(fixedStartX, fixedStartY);
+        CollisionBox expandedBox = eBox.copy().expand(motionX, motionY);
         
         LinkedList<CollisionBox> allBoxes = map.getCollisionBoxesAt(this, expandedBox);
         if(allBoxes.isEmpty())
         {
             return;
-        }         
+        }        
         
         double x;
         double y;
                 
         if(dirX >= 0)
         {
-            if(dirY <= 0) // Right - Up
+            if(dirY <= 0) // Right - Down
             {
                 for(CollisionBox box : allBoxes)
                 {
-                    box.grow(fixedStartX, fixedStartY);
+                    box.grow(halfWidth, halfHeight);
                     if(endX <= box.getMinX() || endY >= box.getMaxY() ||
                         startX >= box.getMaxX() || startY <= box.getMinY())
                     {
@@ -158,23 +166,21 @@ public class Entity implements ITick
                     }
                     x = Utils.interpolateX(startX, startY, endX, endY, box.getMaxY());
                     y = Utils.interpolateY(startX, startY, endX, endY, box.getMinX());
-                    if(x >= box.getMinX() && x <= box.getMaxX())
+                    if(y >= box.getMinY() && y <= box.getMaxY())
                     {
-                        //endX = x;
-                        endY = box.getMaxY();
+                        endX = box.getMinX();
                     }
-                    else if(y >= box.getMinY() && y <= box.getMaxY())
+                    else if(x >= box.getMinX() && x <= box.getMaxX())
                     {
-                        endX = box.getMinX();
-                        //endY = y;
+                        endY = box.getMaxY();
                     }
                 }
             }
-            else // Right - Down
+            else // Right - Up
             {
                 for(CollisionBox box : allBoxes)
                 {
-                    box.grow(fixedStartX, fixedStartY);
+                    box.grow(halfWidth, halfHeight);
                     if(endX <= box.getMinX() || endY <= box.getMinY() ||
                         startX >= box.getMaxX() || startY >= box.getMaxY())
                     {
@@ -182,26 +188,24 @@ public class Entity implements ITick
                     }
                     x = Utils.interpolateX(startX, startY, endX, endY, box.getMinY());
                     y = Utils.interpolateY(startX, startY, endX, endY, box.getMinX());
-                    if(x >= box.getMinX() && x <= box.getMaxX())
+                    if(y >= box.getMinY() && y <= box.getMaxY())
                     {
-                        //endX = x;
-                        endY = box.getMinY();
+                        endX = box.getMinX();
                     }
-                    else if(y >= box.getMinY() && y <= box.getMaxY())
+                    else if(x >= box.getMinX() && x <= box.getMaxX())
                     {
-                        endX = box.getMinX();
-                        //endY = y;
+                        endY = box.getMinY();
                     }
                 }
             }
         }
         else
         {
-            if(dirY <= 0) // Left - Up
+            if(dirY <= 0) // Left - Down
             {
                 for(CollisionBox box : allBoxes)
                 {
-                    box.grow(fixedStartX, fixedStartY);
+                    box.grow(halfWidth, halfHeight);
                     if(endX >= box.getMaxX() || endY >= box.getMaxY() ||
                         startX <= box.getMinX() || startY <= box.getMinY())
                     {
@@ -209,21 +213,21 @@ public class Entity implements ITick
                     }
                     x = Utils.interpolateX(startX, startY, endX, endY, box.getMaxY());
                     y = Utils.interpolateY(startX, startY, endX, endY, box.getMaxX());
-                    if(x >= box.getMinX() && x <= box.getMaxX())
+                    if(y >= box.getMinY() && y <= box.getMaxY())
                     {
-                        endY = box.getMaxY();
+                        endX = box.getMaxX();
                     }
-                    else if(y >= box.getMinY() && y <= box.getMaxY())
+                    else if(x >= box.getMinX() && x <= box.getMaxX())
                     {
-                        endX = box.getMaxX();
+                        endY = box.getMaxY();
                     }
                 }
             }
-            else  // Left - Down
+            else  // Left - Up
             {
                 for(CollisionBox box : allBoxes)
                 {
-                    box.grow(fixedStartX, fixedStartY);
+                    box.grow(halfWidth, halfHeight);
                     if(endX >= box.getMaxX() || endY <= box.getMinY() ||
                         startX <= box.getMinX() || startY >= box.getMaxY())
                     {
@@ -231,13 +235,13 @@ public class Entity implements ITick
                     }
                     x = Utils.interpolateX(startX, startY, endX, endY, box.getMinY());
                     y = Utils.interpolateY(startX, startY, endX, endY, box.getMaxX());
-                    if(x >= box.getMinX() && x <= box.getMaxX())
+                    if(y >= box.getMinY() && y <= box.getMaxY())
                     {
-                        endY = box.getMinY();
+                        endX = box.getMaxX();
                     }
-                    else if(y >= box.getMinY() && y <= box.getMaxY())
+                    else if(x >= box.getMinX() && x <= box.getMaxX())
                     {
-                        endX = box.getMaxX();
+                        endY = box.getMinY();
                     }
                 }
             }
@@ -245,7 +249,8 @@ public class Entity implements ITick
         
         motionX = endX - startX;
         motionY = endY - startY;
-        if(dirY > 0 && motionY == 0)
+        
+        if(dirY < 0 && motionY == 0)
         {
             onGround = true;
         }
@@ -255,6 +260,10 @@ public class Entity implements ITick
             System.err.println("illegal movement " + motionX + " " + motionY);
             System.exit(0);
         }
+        
+        System.out.println("VORHER " + dirX + " " + dirY);
+        System.out.println("NACHHER " + motionX + " " + motionY);
+        //System.exit(0);
     }
     
     public final Image getImage()
@@ -262,28 +271,28 @@ public class Entity implements ITick
         return image;
     }
     
-    public final double getRenderX()
+    public boolean isAffectedByGravity()
     {
-        return getX() + collisionBox.getFixedStartX();
+        return true;
     }
     
-    public final double getRenderY()
+    public final double getX()
     {
-        return getY() + collisionBox.getFixedStartY();
+        return posX;
     }
     
-    public boolean isAffectedByGravity()
+    public final double getY()
     {
-        return true;
+        return posY;
     }
     
-    public final double getX()
+    public final double getRenderX()
     {
         return posX;
     }
     
-    public final double getY()
+    public final double getRenderY()
     {
-        return posY;
+        return posY + height;
     }
 }

+ 2 - 2
src/me/hammerle/supersnuvi/entity/Hero.java

@@ -2,11 +2,11 @@ package me.hammerle.supersnuvi.entity;
 
 import javafx.scene.input.KeyCode;
 import me.hammerle.supersnuvi.input.IKeyHandler;
-import me.hammerle.supersnuvi.rendering.MapRenderer;
+import me.hammerle.supersnuvi.rendering.WorldRenderer;
 
 public class Hero extends Entity
 {
-    public Hero(MapRenderer map, double x, double y, double width, double height) 
+    public Hero(WorldRenderer map, double x, double y, double width, double height) 
     {
         super(map, x, y, width, height);
     }

+ 1 - 3
src/me/hammerle/supersnuvi/input/KeyHandler.java

@@ -3,9 +3,8 @@ package me.hammerle.supersnuvi.input;
 import java.util.EnumMap;
 import java.util.LinkedList;
 import javafx.scene.input.KeyCode;
-import me.hammerle.supersnuvi.rendering.ITick;
 
-public class KeyHandler implements ITick, IKeyHandler
+public class KeyHandler implements IKeyHandler
 {
     private LinkedList<KeyCode> removeQueue;
     private EnumMap<KeyCode, Integer> keys;
@@ -48,7 +47,6 @@ public class KeyHandler implements ITick, IKeyHandler
         return keys.containsKey(key);
     }
 
-    @Override
     public void tick(long delta) 
     {
         keys.replaceAll((k, v) -> v + 1);

+ 154 - 0
src/me/hammerle/supersnuvi/rendering/GameRenderer.java

@@ -0,0 +1,154 @@
+package me.hammerle.supersnuvi.rendering;
+
+import javafx.animation.AnimationTimer;
+import javafx.scene.Scene;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.image.Image;
+import javafx.scene.input.KeyEvent;
+import me.hammerle.supersnuvi.input.KeyHandler;
+
+public class GameRenderer extends AnimationTimer implements IGameRenderer
+{
+    // constants
+    public final static int TILE_SIZE = 32;
+    
+    // basic graphic stuff
+    private Scene scene;
+    private Canvas canvas;
+    
+    // input handler
+    private KeyHandler keys;
+    
+    // timing
+    private long lastHandle;
+    
+    // rendering
+    private double cameraX;
+    private double cameraY;
+    private double offsetX;
+    private double offsetY;
+    private GraphicsContext context;
+    
+    // world rendering
+    private WorldRenderer world;
+    
+    public GameRenderer(Scene scene, Canvas canvas)
+    {
+        // basic graphic stuff
+        this.scene = scene;
+        this.canvas = canvas;
+        // input handler
+        this.keys = new KeyHandler();
+        //timing
+        this.lastHandle = System.nanoTime();
+        // rendering
+        this.cameraX = 0;
+        this.cameraY = 0;
+        this.offsetX = 0;
+        this.offsetY = 0;
+        this.context = null;
+        
+        // events
+        registerEvents();
+        
+        this.world = new WorldRenderer(this);
+        world.loadWorld("test");
+    }
+    
+    private void registerEvents()
+    {
+        // basic input handling
+        scene.setOnKeyPressed((KeyEvent e) -> 
+        {
+            keys.pressKey(e.getCode());
+        });
+        scene.setOnKeyReleased((KeyEvent e) -> 
+        {
+            keys.releaseKey(e.getCode());
+        });
+        // scene rescaling event
+        scene.widthProperty().addListener((observable, oldValue, newValue) -> 
+        {
+            if(oldValue.intValue() != 0)
+            {
+                canvas.setWidth(scene.getWidth());
+            }
+        });
+        scene.heightProperty().addListener((observable, oldValue, newValue) -> 
+        {
+            if(oldValue.intValue() != 0)
+            {
+                canvas.setHeight(scene.getHeight());
+            }
+        });
+    }
+    
+    @Override
+    public void handle(long now) 
+    {
+        long delta = (now - lastHandle) / 1000;
+        lastHandle = now;
+        
+        keys.tick(delta);
+        
+        prepareRendering();
+        world.tick(delta, keys);
+    }
+    
+    private void prepareRendering()
+    {
+        offsetX = -cameraX;
+        offsetY = canvas.getHeight() + cameraY;
+        context = canvas.getGraphicsContext2D();
+        context.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
+    }
+    
+    @Override
+    public void drawImage(Image image, double x, double y)
+    {
+        context.drawImage(image, offsetX + x, offsetY - y);
+    }
+    
+    @Override
+    public void drawBlockImage(Image image, int x, int y)
+    {
+        context.drawImage(image, offsetX + x * TILE_SIZE, offsetY - (y + 1) * TILE_SIZE);
+    }
+    
+    @Override
+    public int getFirstVisibleBlockX()
+    {
+        return Math.max(0, (int) (cameraX / TILE_SIZE));
+    }
+    
+    @Override
+    public int getFirstVisibleBlockY()
+    {
+        return Math.max(0, (int) (cameraY / TILE_SIZE));
+    }
+    
+    @Override
+    public int getLastVisibleBlockX()
+    {
+        return getFirstVisibleBlockX() + (int) (canvas.getWidth() / TILE_SIZE);
+    }
+    
+    @Override
+    public int getLastVisibleBlockY()
+    {
+        return getFirstVisibleBlockY() + (int) (canvas.getHeight()/ TILE_SIZE);
+    }
+    
+    @Override
+    public int toBlock(double c)
+    {
+        return (int) (c / TILE_SIZE);
+    }
+
+    @Override
+    public double toCoord(int b)
+    {
+        return b * TILE_SIZE;
+    }
+}

+ 3 - 23
src/me/hammerle/supersnuvi/rendering/GameWindow.java

@@ -1,17 +1,13 @@
 package me.hammerle.supersnuvi.rendering;
 
-import me.hammerle.supersnuvi.input.KeyHandler;
 import javafx.application.Application;
 import javafx.scene.Group;
 import javafx.scene.Scene;
 import javafx.scene.canvas.Canvas;
-import javafx.scene.input.KeyEvent;
 import javafx.stage.Stage;
 
 public class GameWindow extends Application
 {
-    private KeyHandler keys;
-    
     public static void main(String[] args) 
     {
         Application.launch(args);
@@ -21,7 +17,6 @@ public class GameWindow extends Application
     public void init() throws Exception 
     {
         super.init();
-        keys = new KeyHandler();
     }
     
     @Override
@@ -34,22 +29,12 @@ public class GameWindow extends Application
 
         stage.setScene(scene);
         
-        Canvas canvas = new Canvas(800, 400);
+        Canvas canvas = new Canvas(640, 400);     
         root.getChildren().add(canvas);
-         
-        RenderCanvas timer = new RenderCanvas(this, canvas.getGraphicsContext2D(), canvas.getWidth(), canvas.getHeight());
+              
+        GameRenderer timer = new GameRenderer(scene, canvas);
         timer.start();
         
-        scene.setOnKeyPressed((KeyEvent e) -> 
-        {
-            keys.pressKey(e.getCode());
-        });
-        
-        scene.setOnKeyReleased((KeyEvent e) -> 
-        {
-            keys.releaseKey(e.getCode());
-        });
-        
         stage.show();
     }
 
@@ -58,9 +43,4 @@ public class GameWindow extends Application
     {
         super.stop();
     }
-    
-    public final KeyHandler getKeyHandler()
-    {
-        return keys;
-    }
 }

+ 60 - 0
src/me/hammerle/supersnuvi/rendering/IGameRenderer.java

@@ -0,0 +1,60 @@
+package me.hammerle.supersnuvi.rendering;
+
+import javafx.scene.image.Image;
+
+public interface IGameRenderer 
+{
+    /** Draws an image at the desired position
+     *
+     * @param image the image
+     * @param x the x coord of the position
+     * @param y the y coord of the position
+     */
+    public void drawImage(Image image, double x, double y);
+    
+    /** Draws an image at the desired block position
+     *
+     * @param image the image
+     * @param x the x coord of the position
+     * @param y the y coord of the position
+     */
+    public void drawBlockImage(Image image, int x, int y);
+    
+    /** Gets the x coord of the first visible block
+     *
+     * @return the x coord
+     */
+    public int getFirstVisibleBlockX();
+    
+    /** Gets the y coord of the first visible block
+     *
+     * @return the y coord
+     */
+    public int getFirstVisibleBlockY();
+    
+    /** Gets the x coord of the last visible block
+     *
+     * @return the x coord
+     */
+    public int getLastVisibleBlockX();
+    
+    /** Gets the y coord of the last visible block
+     *
+     * @return the y coord
+     */
+    public int getLastVisibleBlockY();
+    
+    /** Translates a coord to a block coord
+     *
+     * @param c a coord
+     * @return a block coord
+     */
+    public int toBlock(double c);
+
+    /** Translates a block coord to a coord
+     *
+     * @param b a block coord
+     * @return a coord
+     */
+    public double toCoord(int b);
+}

+ 0 - 6
src/me/hammerle/supersnuvi/rendering/ITick.java

@@ -1,6 +0,0 @@
-package me.hammerle.supersnuvi.rendering;
-
-public interface ITick 
-{
-    public void tick(long delta);
-}

+ 0 - 67
src/me/hammerle/supersnuvi/rendering/RenderCanvas.java

@@ -1,67 +0,0 @@
-package me.hammerle.supersnuvi.rendering;
-
-import javafx.animation.AnimationTimer;
-import javafx.scene.canvas.GraphicsContext;
-
-public class RenderCanvas extends AnimationTimer
-{
-    private GraphicsContext gc;
-    
-    private final long startNanoTime;
-    private long lastHandle;
-    private long delta;
-    
-    private MapRenderer map;
-    
-    public RenderCanvas(GameWindow w, GraphicsContext gc, double width, double height)
-    {
-        this.gc = gc;
-        this.startNanoTime = System.nanoTime();
-        this.lastHandle = startNanoTime;
-        this.map = new MapRenderer(w, gc, width, height);
-        
-        map.loadMap(new int[][]
-        {
-            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
-        }, new int[][]
-        {
-            {1, 1, 1, 3, 2, 1, 1, 1, 1, 3, 2},
-            {1, 1, 1, 3, 2, 1, 1, 1, 1, 3, 2},
-            {1, 1, 1, 1, 3, 1, 1, 1, 1, 3, 2},
-            {1, 1, 1, 1, 3, 1, 1, 1, 3, 2, 2},
-            {1, 1, 1, 1, 3, 1, 1, 1, 3, 2, 2},
-            {1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 2},
-            {1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2},
-            {1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 2},
-            {1, 1, 1, 1, 1, 3, 2, 1, 1, 3, 2}
-        }, new int[][]
-        {
-            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
-            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
-            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
-            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
-            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
-            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
-            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
-            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
-            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}
-        });
-    }
-
-    @Override
-    public void handle(long now) 
-    {
-        delta = (now - lastHandle) / 1000;
-        lastHandle = now;
-        
-        map.tick(delta);
-    }
-}

+ 81 - 82
src/me/hammerle/supersnuvi/rendering/MapRenderer.java → src/me/hammerle/supersnuvi/rendering/WorldRenderer.java

@@ -1,62 +1,54 @@
 package me.hammerle.supersnuvi.rendering;
 
-import me.hammerle.supersnuvi.input.KeyHandler;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.stream.Collectors;
-import javafx.scene.canvas.GraphicsContext;
 import javafx.scene.image.Image;
 import javafx.scene.image.WritableImage;
 import javafx.scene.paint.Color;
 import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.entity.Hero;
+import me.hammerle.supersnuvi.input.IKeyHandler;
 import me.hammerle.supersnuvi.tiles.Tile;
 import me.hammerle.supersnuvi.util.CollisionBox;
+import me.hammerle.supersnuvi.util.Utils;
 
-public class MapRenderer implements ITick
+public class WorldRenderer
 {
+    // constants
     private static final Image FALLBACK_IMAGE = new WritableImage(1, 1);
     
-    private GraphicsContext gc;
+    // rendering
+    private IGameRenderer renderer;
     
+    // world tiles
+    private boolean worldLoaded;
+    private int width;
+    private int height;
     private int[][] background;
     private int[][] background2;
     private int[][] foreground;
     
     private HashMap<Integer, Entity> entities;
     
-    private int mWidth;
-    private int mHeight;
     private HashMap<Integer, Tile> registeredTiles;
     
-    private double cameraOffsetX;
-    private double cameraWidth;
-    private double cameraHeight;
-    
-    private double offsetY;
-   
-    private GameWindow w;
-    private KeyHandler keyHandler;
-    
-    public MapRenderer(GameWindow w, GraphicsContext gc, double width, double height)
+    public WorldRenderer(IGameRenderer renderer)
     {
-        this.w = w;
-        this.keyHandler = w.getKeyHandler();
-        this.gc = gc;
+        this.renderer = renderer;
+        
+        this.worldLoaded = false;
+        this.width = 0;
+        this.height = 0;
         this.background = null;
         this.background2 = null;
         this.foreground = null;
-        this.mWidth = 0;
-        this.mHeight = 0;
-        this.cameraWidth = width;
-        this.cameraHeight = height;
-        this.cameraOffsetX = 0;
+        
         this.registeredTiles = new HashMap<>();
         this.entities = new HashMap<>();
-        this.offsetY = 0;
         
-        Entity ent = new Entity(this, 48, 272, 32, 60);
-        Hero hero = new Hero(this, 48, 0, 32, 60);
+        Entity ent = new Entity(this, 60, 240, 32, 60);
+        Hero hero = new Hero(this, 0, 160, 32, 60);
         
         entities.put(1, ent);
         entities.put(2, hero);
@@ -74,6 +66,7 @@ public class MapRenderer implements ITick
         registeredTiles.put(1, new Tile("air"));
         registeredTiles.put(2, new Tile("dirt").setDefaultCollisionBox());
         registeredTiles.put(3, new Tile("grass").setDefaultCollisionBox());
+        
         registerTile(4, 255, 0, 0, 255);
         registeredTiles.get(4).setCollisionBox(CollisionBox.DEFAULT_BOX);
         registerTile(5, 255, 0, 255, 255);
@@ -91,51 +84,52 @@ public class MapRenderer implements ITick
         return tile.getImage();
     }
 
-    @Override
-    public void tick(long delta)
+    public void tick(long delta, IKeyHandler keys)
     {
-        keyHandler.tick(delta);
-        if(background != null)
+        if(worldLoaded)
         {
             // doing entity logic first
             entities.values().forEach(entity -> 
             {
-                entity.controlTick(keyHandler);
+                entity.controlTick(keys);
                 entity.tick(delta);
             });
 
-            int startX = Math.max((int) (cameraOffsetX / Tile.TILE_SIZE), 0);
+            int startX = renderer.getFirstVisibleBlockX();
+            int startY = renderer.getFirstVisibleBlockY();
+            int endX = Math.min(renderer.getLastVisibleBlockX() + 1, width);
+            int endY = Math.min(renderer.getLastVisibleBlockY() + 1, height);
             
             int id;
-            for(int x = startX; x < mWidth; x++)
+            for(int x = startX; x < endX; x++)
             {
-                for(int y = 0; y < mHeight; y++)
+                for(int y = startY; y < endY; y++)
                 {
                     id = background[x][y];
                     if(id != -1)
                     {
-                        gc.drawImage(getTile(id), x * Tile.TILE_SIZE - cameraOffsetX, y * Tile.TILE_SIZE + offsetY);
+                        renderer.drawBlockImage(getTile(id), x, y);
                     }
                     id = background2[x][y];
                     if(id != -1)
                     {
-                        gc.drawImage(getTile(id), x * Tile.TILE_SIZE - cameraOffsetX, y * Tile.TILE_SIZE + offsetY);
+                        renderer.drawBlockImage(getTile(id), x, y);
                     }
                 }
             }
             
             // render entities here
-            entities.values().forEach(en -> gc.drawImage(en.getImage(), en.getRenderX(), en.getRenderY()));
+            entities.values().forEach(en -> renderer.drawImage(en.getImage(), en.getRenderX(), en.getRenderY()));
             // end entity rendering
             
-            for(int x = startX; x < mWidth; x++)
+            for(int x = startX; x < endX; x++)
             {
-                for(int y = 0; y < mHeight; y++)
+                for(int y = startY; y < endY; y++)
                 {
                     id = foreground[x][y];
                     if(id != -1)
                     {
-                        gc.drawImage(getTile(id), x * Tile.TILE_SIZE  - cameraOffsetX, y * Tile.TILE_SIZE  + offsetY);
+                        renderer.drawBlockImage(getTile(id), x, y);
                     }
                 }
             }
@@ -144,66 +138,72 @@ public class MapRenderer implements ITick
     
     public Tile getInteractionTile(int x, int y)
     {
-        if(x < 0 || y < 0 || x >= mWidth || y >= mHeight)
+        if(x < 0 || y < 0 || x >= width || y >= height)
         {
             return Tile.FALLBACK_TILE;
         }
         return registeredTiles.get(background2[x][y]);
     }
     
-    public void loadMap(int[][] background, int[][] background2, int[][] foreground)
-    {
-        this.background = background;
-        this.background2 = background2;
-        this.foreground = foreground;
-        mWidth = background2.length;
-        if(mWidth > 0)
-        {
-            mHeight = background2[0].length;
-        }
-        else
-        {
-            mHeight = 0;
-        }
-        offsetY = cameraHeight - mHeight * Tile.TILE_SIZE;
-    }
-    
-    // -------------------------------------------------------------------------
-    // coord utils
-    // -------------------------------------------------------------------------
-    
-    public int getBlockX(double d)
-    {
-        return (int) (d / Tile.TILE_SIZE);
-    }
-    
-    public int getBlockY(double d)
+    private void throwSameLayerSizeException()
     {
-        return (int) ((d - offsetY) / Tile.TILE_SIZE);
+        throw new IllegalArgumentException("world layers must be of same size");
     }
     
-    public double getCordX(int d)
+    private void validateArraySize(int[][] layer, int h)
     {
-        return d * Tile.TILE_SIZE;
+        for(int[] he : layer)
+        {
+            if(he.length != h)
+            {
+                throwSameLayerSizeException();
+            }
+        }
     }
     
-    public double getCordY(int d)
+    public void loadWorld(String path)
     {
-        return d * Tile.TILE_SIZE + offsetY;
+        int[][][] layers = Utils.loadMap(path);
+        int[][] back = layers[0];
+        int[][] back2 = layers[1];
+        int[][] fore = layers[2];
+        if(back.length > 0)
+        {
+            width = back.length;
+            if(back2.length != width || fore.length != width)
+            {
+                throwSameLayerSizeException();
+            }
+            height = back2[0].length;
+            validateArraySize(back, height);
+            validateArraySize(back2, height);
+            validateArraySize(fore, height);
+            
+            this.background = back;
+            this.background2 = back2;
+            this.foreground = fore;
+        }
+        else
+        {
+            this.background = new int[0][0];
+            this.background2 = new int[0][0];
+            this.foreground = new int[0][0];
+        }
+        worldLoaded = true;
     }
-    
+       
     // -------------------------------------------------------------------------
     // collision box
     // -------------------------------------------------------------------------
     
     public CollisionBox getTileCollisionBox(int x, int y)
     {
-        if(x < 0 || y < 0 || x >= mWidth || y >= mHeight)
+        if(x < 0 || y < 0 || x >= width || y >= height)
         {
             return CollisionBox.NULL_BOX;
         }
         Tile tile = registeredTiles.get(background2[x][y]);
-        return tile.getCollisionBox().reset().offset(getCordX(x), getCordY(y));
+        return tile.getCollisionBox().reset().offset(renderer.toCoord(x), renderer.toCoord(y));
     }
     
     public LinkedList<CollisionBox> getCollisionBoxesAt(Entity not, CollisionBox cb)
@@ -214,11 +214,10 @@ public class MapRenderer implements ITick
                         .map(ent -> ent.getBox())
                         .collect(Collectors.toList()));
         
-        int startX = getBlockX(cb.getMinX());
-        int endX = getBlockX(cb.getMaxX());
-        int startY = getBlockY(cb.getMinY());
-        int endY = getBlockY(cb.getMaxY());
-        //System.out.println("x = " + startX + " - " + endX + "  y = " + startY + " - " + endY);
+        int startX = renderer.toBlock(cb.getMinX());
+        int endX = renderer.toBlock(cb.getMaxX());
+        int startY = renderer.toBlock(cb.getMinY());
+        int endY = renderer.toBlock(cb.getMaxY());
         
         CollisionBox box;
         for(int x = startX; x <= endX; x++)

+ 1 - 1
src/me/hammerle/supersnuvi/tiles/Tile.java

@@ -4,11 +4,11 @@ import javafx.scene.image.Image;
 import javafx.scene.image.PixelWriter;
 import javafx.scene.image.WritableImage;
 import javafx.scene.paint.Color;
+import static me.hammerle.supersnuvi.rendering.GameRenderer.TILE_SIZE;
 import me.hammerle.supersnuvi.util.CollisionBox;
 
 public class Tile 
 {
-    public final static int TILE_SIZE = 32;
     public final static Tile FALLBACK_TILE = new Tile(new Color(1, 1, 1, 1));
     
     private Image image;

+ 6 - 6
src/me/hammerle/supersnuvi/util/CollisionBox.java

@@ -1,11 +1,11 @@
 package me.hammerle.supersnuvi.util;
 
-import me.hammerle.supersnuvi.tiles.Tile;
+import me.hammerle.supersnuvi.rendering.GameRenderer;
 
 public class CollisionBox 
 {
     public final static CollisionBox NULL_BOX = new CollisionBox(0, 0, 0, 0);
-    public final static CollisionBox DEFAULT_BOX = new CollisionBox(0, 0, Tile.TILE_SIZE, Tile.TILE_SIZE);
+    public final static CollisionBox DEFAULT_BOX = new CollisionBox(0, 0, GameRenderer.TILE_SIZE, GameRenderer.TILE_SIZE);
     
     private final double minX;
     private final double maxX;
@@ -43,14 +43,14 @@ public class CollisionBox
         return this;
     }
 
-    public double getFixedStartX() 
+    public double getWidth() 
     {
-        return minX;
+        return maxX - minX;
     }
     
-    public double getFixedStartY() 
+    public double getHeight() 
     {
-        return minY;
+        return maxY - minY;
     }
     
     public double getMinX() 

+ 80 - 0
src/me/hammerle/supersnuvi/util/Utils.java

@@ -1,5 +1,13 @@
 package me.hammerle.supersnuvi.util;
 
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
 public class Utils 
 {
     public static double round(double d)
@@ -18,4 +26,76 @@ public class Utils
         double k = (x2 - x1) / (y2 - y1);
         return (y3 - y1) * k + x1;
     }
+    
+    public static int[][][] loadMap(String path)
+    {
+        try
+        {
+            File map = new File(path + ".map");  
+            if(!map.exists())
+            {
+                return new int[3][0][0];
+            }
+            List<String> strings;
+            try
+            {
+                strings = Files.readAllLines(map.toPath());
+            } 
+            catch(IOException ex) 
+            {
+                return new int[3][0][0];
+            }
+            if(strings.isEmpty())
+            {
+                return new int[3][0][0];
+            }
+
+            LinkedList<LinkedList<List<Integer>>> list = new LinkedList<>();
+            LinkedList<List<Integer>> active = new LinkedList<>();
+            list.add(active);
+            for(String s : strings)
+            {
+                if(s.equals("#"))
+                {
+                    active = new LinkedList<>();
+                    list.add(active);
+                    continue;
+                }
+                active.add(Arrays.stream(s.split(",")).map(st -> Integer.parseInt(st.trim())).collect(Collectors.toList()));
+            }
+
+            if(list.size() != 3)
+            {
+                return new int[3][0][0];
+            }
+
+            int height = list.getFirst().size();
+            int[][][] layers = new int[3][list.getFirst().getFirst().size()][height];
+            int x = 0;
+            int y;
+            int z = 0;
+            for(LinkedList<List<Integer>> layerXY : list)
+            {
+                y = height - 1;
+                for(List<Integer> layerY : layerXY)
+                {
+                    for(Integer i : layerY)
+                    {
+                        //System.out.println(x + " " + z + " " + y);
+                        layers[x][z][y] = i;
+                        z++;
+                    }
+                    z = 0;
+                    y--;
+                }
+                x++;
+            }
+            return layers;
+        }
+        catch(Exception ex)
+        {
+            ex.printStackTrace();
+            return new int[3][0][0];
+        } 
+    }
 }

+ 29 - 0
test.map

@@ -0,0 +1,29 @@
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+#
+1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2
+1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2
+1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2
+1, 1, 1, 1, 2, 1, 1, 1, 3, 2, 2
+1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2
+3, 3, 3, 1, 1, 1, 3, 1, 3, 3, 2
+2, 2, 2, 3, 1, 3, 2, 1, 2, 2, 2
+#
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1