فهرست منبع

completely removed entity hierarchy in favour of components, entity components, refactoring

Kajetan Johannes Hammerle 6 سال پیش
والد
کامیت
f3f7d269c6
32فایلهای تغییر یافته به همراه749 افزوده شده و 357 حذف شده
  1. 97 191
      src/me/hammerle/supersnuvi/entity/Entity.java
  2. 90 0
      src/me/hammerle/supersnuvi/entity/EntityBuilder.java
  3. 0 48
      src/me/hammerle/supersnuvi/entity/Hero.java
  4. 0 28
      src/me/hammerle/supersnuvi/entity/animator/EntityAnimator.java
  5. 13 0
      src/me/hammerle/supersnuvi/entity/components/Component.java
  6. 16 0
      src/me/hammerle/supersnuvi/entity/components/Death.java
  7. 51 0
      src/me/hammerle/supersnuvi/entity/components/DefaultEnergy.java
  8. 42 0
      src/me/hammerle/supersnuvi/entity/components/DefaultHealth.java
  9. 31 0
      src/me/hammerle/supersnuvi/entity/components/DefaultMovement.java
  10. 41 0
      src/me/hammerle/supersnuvi/entity/components/Energy.java
  11. 54 0
      src/me/hammerle/supersnuvi/entity/components/Health.java
  12. 23 0
      src/me/hammerle/supersnuvi/entity/components/HeroItemCollector.java
  13. 8 0
      src/me/hammerle/supersnuvi/entity/components/IJump.java
  14. 23 0
      src/me/hammerle/supersnuvi/entity/components/ItemCollector.java
  15. 23 0
      src/me/hammerle/supersnuvi/entity/components/LandMovement.java
  16. 27 0
      src/me/hammerle/supersnuvi/entity/components/Movement.java
  17. 90 0
      src/me/hammerle/supersnuvi/entity/components/MovementPenalty.java
  18. 23 0
      src/me/hammerle/supersnuvi/entity/components/WaterMovement.java
  19. 6 5
      src/me/hammerle/supersnuvi/entity/components/ai/EntityController.java
  20. 3 4
      src/me/hammerle/supersnuvi/entity/components/ai/FollowHeroController.java
  21. 7 8
      src/me/hammerle/supersnuvi/entity/components/ai/HumanController.java
  22. 1 1
      src/me/hammerle/supersnuvi/entity/components/ai/WalkController.java
  23. 3 3
      src/me/hammerle/supersnuvi/entity/components/animator/DefaultAnimator.java
  24. 44 0
      src/me/hammerle/supersnuvi/entity/components/animator/EntityAnimator.java
  25. 19 51
      src/me/hammerle/supersnuvi/gamelogic/Level.java
  26. 7 5
      src/me/hammerle/supersnuvi/tiles/BottledSoulTile.java
  27. 1 2
      src/me/hammerle/supersnuvi/tiles/GoalTile.java
  28. 1 5
      src/me/hammerle/supersnuvi/tiles/KillTile.java
  29. 1 1
      src/me/hammerle/supersnuvi/tiles/SlipperyTile.java
  30. 2 3
      src/me/hammerle/supersnuvi/tiles/SpikeTile.java
  31. 1 1
      src/me/hammerle/supersnuvi/tiles/TrampolinTile.java
  32. 1 1
      src/me/hammerle/supersnuvi/tiles/WaterTile.java

+ 97 - 191
src/me/hammerle/supersnuvi/entity/Entity.java

@@ -1,8 +1,13 @@
 package me.hammerle.supersnuvi.entity;
 
-import me.hammerle.supersnuvi.entity.animator.EntityAnimator;
-import me.hammerle.supersnuvi.entity.ai.EntityController;
+import me.hammerle.supersnuvi.entity.components.animator.EntityAnimator;
+import me.hammerle.supersnuvi.entity.components.ai.EntityController;
 import java.util.LinkedList;
+import me.hammerle.supersnuvi.entity.components.Energy;
+import me.hammerle.supersnuvi.entity.components.Health;
+import me.hammerle.supersnuvi.entity.components.ItemCollector;
+import me.hammerle.supersnuvi.entity.components.Movement;
+import me.hammerle.supersnuvi.entity.components.MovementPenalty;
 import me.hammerle.supersnuvi.gamelogic.Level;
 import me.hammerle.supersnuvi.rendering.Game;
 import me.hammerle.supersnuvi.tiles.Location;
@@ -10,7 +15,7 @@ import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.Face;
 import me.hammerle.supersnuvi.util.Utils;
 
-public class Entity
+public final class Entity
 {
     // 1 Tile = 1 m
     // Pixel    m       s      Pixel
@@ -19,34 +24,34 @@ public class Entity
     // added correction factor, real life gravity seems to strong
     public static double GRAVITY = Game.TILE_SIZE * 9.81 / 60 * 0.1;
     
-    private double prevPosX;
-    private double prevPrevY;
     private double posX;
     private double posY;
+    private final double width;
+    private final double height;
+    
+    private double prevPosX;
+    private double prevPrevY;
     
     private double prevMotionX;
     private double prevMotionY;
     private double motionX;
     private double motionY;
     private boolean onGround;
-    private boolean inWater;
     
     private final CollisionBox collisionBox;
-    private final double width;
-    private final double height;
-    
-    private double friction;
-    
-    private final double maxHealth;
-    private double health;
-    
-    private EntityAnimator animator;
-    
-    private EntityController controller;
     
     private final Level level;
     
-    public Entity(Level level, double x, double y, double width, double height, double health)
+    // components
+    protected EntityAnimator animator;   
+    protected EntityController controller;
+    protected Health health;
+    protected Energy energy;
+    protected Movement move;
+    protected ItemCollector itemCollector;
+    protected MovementPenalty movePenalty;
+    
+    protected Entity(Level level, double x, double y, double width, double height)
     {
         this.level = level;
         this.posX = x;
@@ -56,14 +61,18 @@ public class Entity
         this.motionX = 0;
         this.motionY = 0;
         this.onGround = false;
-        this.inWater = false;
         this.collisionBox = new CollisionBox(0, 0, width, height);
         this.width = width;
         this.height = height;
-        this.maxHealth = health;
-        this.health = health;
-        this.animator = new EntityAnimator(this);
-        this.controller = new EntityController(this);
+        
+        // components
+        this.animator = EntityAnimator.NULL;
+        this.controller = EntityController.NULL;
+        this.health = Health.NULL;
+        this.energy = Energy.NULL;
+        this.move =  Movement.NULL;
+        this.itemCollector =  ItemCollector.NULL;
+        this.movePenalty = MovementPenalty.NULL;
     }
 
     public Level getLevel()
@@ -72,15 +81,49 @@ public class Entity
     }
     
     //--------------------------------------------------------------------------
-    // utility
+    // components
+    //--------------------------------------------------------------------------
+    
+    public EntityAnimator getAnimator()
+    {
+        return animator;
+    }
+    
+    public Health getHealth()
+    {
+        return health;
+    }
+    
+    public Energy getEnergy()
+    {
+        return energy;
+    }
+    
+    public Movement getMovement()
+    {
+        return move;
+    }
+    
+    public ItemCollector getItemCollector()
+    {
+        return itemCollector;
+    }
+    
+    public MovementPenalty getMovementPenalty()
+    {
+        return movePenalty;
+    }
+    
+    //--------------------------------------------------------------------------
+    // basic stuff
     //--------------------------------------------------------------------------
     
-    public final double squaredDistance(Entity other)
+    public double squaredDistance(Entity other)
     {
         return (posX - other.posX) * (posX - other.posX) + (posY - other.posY) * (posY - other.posY);
     }
     
-    public final double signedDistanceX(Entity other)
+    public double signedDistanceX(Entity other)
     {
         if(posX < other.posX)
         {
@@ -89,24 +132,34 @@ public class Entity
         return Math.max(0.0, posX - (other.posX + other.width));
     }
     
-    //--------------------------------------------------------------------------
-    // controller - either ai or player
-    //--------------------------------------------------------------------------
+    public double getWidth()
+    {
+        return width;
+    }
     
-    public final void setController(EntityController controller)
+    public double getHeight()
     {
-        this.controller = controller;
+        return height;
+    }
+    
+    public double getX()
+    {
+        return posX;
+    }
+    
+    public double getY()
+    {
+        return posY;
     }
     
     //--------------------------------------------------------------------------
     // ticking
     //--------------------------------------------------------------------------
     
-    public final void tick() 
+    public void tick() 
     {
         controller.tick();
-        
-        inWater = false;
+        movePenalty.reset();
         
         if(isAffectedByGravity())
         {
@@ -143,89 +196,32 @@ public class Entity
             posY = prevPrevY;
         }
         
-        friction = 0.7f;
+        movePenalty.setFriction(0.7);
         doCollision();
-        
-        if(motionX < 0.0)
-        {
-            motionX += friction;
-            if(motionX > 0.0)
-            {
-                motionX = 0.0;
-            }
-        }
-        else if(motionX > 0.0)
-        {
-            motionX -= friction;
-            if(motionX < 0.0)
-            {
-                motionX = 0.0;
-            }
-        }
-        
+        movePenalty.applyFriction();
+        energy.tick();
         animator.tick();
     }
     
     //--------------------------------------------------------------------------
     // gravity, friction
     //--------------------------------------------------------------------------
-    
-    public final boolean jump()
-    {
-        if(onGround)
-        {
-            onGround = false;
-            motionY += getJumpPower();
-            return true;
-        }
-        return false;
-    }
-    
+
     public boolean isAffectedByGravity()
     {
         return true;
     }
     
-    public double getJumpPower()
-    {
-        return 0;
-    }
-    
     public boolean isOnGround()
     {
         return onGround;
-    }
-    
-    public final double getMaxSpeedModifier()
-    {
-        double max = 1;
-        if(inWater)
-        {
-            max *= 0.65;
-        }
-        return max;
-    }
-    
-    public final void setInWater(boolean b)
-    {
-        inWater = b;
-    }
-    
-    public final boolean isInWater()
-    {
-        return inWater;
-    }
-    
-    public void setFriction(double friction)
-    {
-        this.friction = friction;
-    }
+    }   
     
     //--------------------------------------------------------------------------
     // collision stuff
     //--------------------------------------------------------------------------
     
-    public final CollisionBox getBox() 
+    public CollisionBox getBox() 
     {
         return collisionBox.reset().offset(posX, posY);
     }
@@ -546,72 +542,26 @@ public class Entity
         }
     }
     
-    public final void onCollideWithTile(Location loc, Face face)
+    public void onCollideWithTile(Location loc, Face face)
     {
         controller.onCollideWithTile(loc, face);
     }
     
-    public final void onCollideWithEntity(Entity ent, Face face)
+    public void onCollideWithEntity(Entity ent, Face face)
     {
         controller.onCollideWithEntity(ent, face);
     }
     
-    //--------------------------------------------------------------------------
-    // health
-    //--------------------------------------------------------------------------
-
-    public double getHealth() 
-    {
-        return health;
-    }
-
-    public double getMaxHealth() 
-    {
-        return maxHealth;
-    }
-    
-    public void addHealth(double h)
-    {
-        health += h;
-        if(health > maxHealth)
-        {
-            health = maxHealth;
-        }
-        else if(health < 0.0)
-        {
-            onDeath();
-        }
-    }
-    
-    public void addHealthPercent(double h)
-    {
-        addHealth(h * maxHealth);
-    }
-    
-    public void onDeath()
-    {
-    }
-    
     //--------------------------------------------------------------------------
     // basic coord and motion stuff
     //--------------------------------------------------------------------------
     
-    public final double getX()
-    {
-        return posX;
-    }
-    
-    public final double getY()
-    {
-        return posY;
-    }
-    
-    public final double getPreviousX()
+    public double getPreviousX()
     {
         return prevPosX;
     }
     
-    public final double getPreviousY()
+    public double getPreviousY()
     {
         return prevPrevY;
     }
@@ -650,48 +600,4 @@ public class Entity
     {
         return prevMotionY;
     }
-    
-    //--------------------------------------------------------------------------
-    // rendering stuff
-    //--------------------------------------------------------------------------
-    
-    public final double getWidth()
-    {
-        return width;
-    }
-    
-    public final double getHeight()
-    {
-        return height;
-    }
-    
-    public final double getRenderX()
-    {
-        return posX;
-    }
-    
-    public final double getRenderY()
-    {
-        return posY + height;
-    }
-    
-    public final void setAnimator(EntityAnimator animator)
-    {
-        this.animator = animator;
-    }
-    
-    public EntityAnimator getAnimator()
-    {
-        return animator;
-    }
-    
-    public double getRenderOffsetX()
-    {
-        return 0;
-    }
-    
-    public double getRenderOffsetY()
-    {
-        return 0;
-    }
 }

+ 90 - 0
src/me/hammerle/supersnuvi/entity/EntityBuilder.java

@@ -0,0 +1,90 @@
+package me.hammerle.supersnuvi.entity;
+
+import me.hammerle.supersnuvi.entity.components.DefaultHealth;
+import me.hammerle.supersnuvi.entity.components.DefaultMovement;
+import me.hammerle.supersnuvi.entity.components.Death;
+import me.hammerle.supersnuvi.entity.components.DefaultEnergy;
+import me.hammerle.supersnuvi.entity.components.Health;
+import me.hammerle.supersnuvi.entity.components.Movement;
+import me.hammerle.supersnuvi.entity.components.HeroItemCollector;
+import me.hammerle.supersnuvi.entity.components.LandMovement;
+import me.hammerle.supersnuvi.entity.components.MovementPenalty;
+import me.hammerle.supersnuvi.entity.components.ai.EntityController;
+import me.hammerle.supersnuvi.entity.components.ai.HumanController;
+import me.hammerle.supersnuvi.entity.components.animator.DefaultAnimator;
+import me.hammerle.supersnuvi.entity.components.animator.EntityAnimator;
+import me.hammerle.supersnuvi.gamelogic.Level;
+import me.hammerle.supersnuvi.gamelogic.StateRenderer;
+
+public final class EntityBuilder 
+{
+    private final Entity ent;
+    
+    private EntityBuilder(Entity ent)
+    {
+        this.ent = ent;
+    }
+    
+    public static EntityBuilder newBuilder(Level level, double x, double y, double width, double height)
+    {
+        return new EntityBuilder(new Entity(level, x, y, width, height));
+    }
+    
+    public static Entity buildHero(Level level, double x, double y)
+    {
+        Entity hero = new Entity(level, x, y, 32.0, 64.0);
+        hero.animator = new DefaultAnimator(hero, 0.0, -2.0, "snuvi/snuvi", new String[] 
+                {
+                    "snuvi/snuvi_frame1", "snuvi/snuvi_frame2", "snuvi/snuvi_frame3",
+                    "snuvi/snuvi_frame4", "snuvi/snuvi_frame5", "snuvi/snuvi_frame6",
+                    "snuvi/snuvi_frame7", "snuvi/snuvi_frame8", "snuvi/snuvi_frame9"
+                }, new String[] 
+                {
+                    "snuvi/snuvi_jump"
+                });
+        hero.controller = new HumanController(hero);
+        hero.health = new DefaultHealth(hero, new Death() 
+                {
+                    @Override
+                    public void onDeath(Entity ent) 
+                    {
+                        ent.getLevel().scheduleReset();
+                    }
+                }, 100.0);
+        hero.energy = new DefaultEnergy(hero, 100.0);
+        hero.move = new DefaultMovement(hero, ent -> StateRenderer.CONFIG.getDouble("jump", 10));
+        hero.itemCollector = new HeroItemCollector(hero);
+        hero.movePenalty = new LandMovement(hero);
+        return hero;
+    }
+    
+    public EntityBuilder setController(EntityController controller)
+    {
+        ent.controller = controller;
+        return this;
+    }
+    
+    public EntityBuilder setAnimator(EntityAnimator animator)
+    {
+        ent.animator = animator;
+        return this;
+    }
+    
+    public EntityBuilder setHealth(Health health)
+    {
+        ent.health = health;
+        return this;
+    }
+    
+    public EntityBuilder setMovement(Movement move)
+    {
+        ent.move = move;
+        return this;
+    }
+    
+    public EntityBuilder setMovementPenalty(MovementPenalty move)
+    {
+        ent.movePenalty = move;
+        return this;
+    }
+}

+ 0 - 48
src/me/hammerle/supersnuvi/entity/Hero.java

@@ -1,48 +0,0 @@
-package me.hammerle.supersnuvi.entity;
-
-import me.hammerle.supersnuvi.entity.ai.HumanController;
-import me.hammerle.supersnuvi.entity.animator.DefaultAnimator;
-import me.hammerle.supersnuvi.gamelogic.Level;
-import me.hammerle.supersnuvi.gamelogic.StateRenderer;
-
-public class Hero extends Entity
-{
-    private final double jumpPower;
-    
-    public Hero(Level level,  double x, double y) 
-    {
-        super(level, x, y, 32, 64, 100.0);
-        
-        jumpPower = StateRenderer.CONFIG.getDouble("jump", 10);
-                
-        setAnimator(new DefaultAnimator(this, "snuvi/snuvi", new String[] 
-                {
-                    "snuvi/snuvi_frame1", "snuvi/snuvi_frame2", "snuvi/snuvi_frame3",
-                    "snuvi/snuvi_frame4", "snuvi/snuvi_frame5", "snuvi/snuvi_frame6",
-                    "snuvi/snuvi_frame7", "snuvi/snuvi_frame8", "snuvi/snuvi_frame9"
-                }, new String[] 
-                {
-                    "snuvi/snuvi_jump"
-                }));
-        
-        setController(new HumanController(this));
-    }
-
-    @Override
-    public double getJumpPower() 
-    {
-        return jumpPower * getMaxSpeedModifier();
-    }
-
-    @Override
-    public double getRenderOffsetY()
-    {
-        return -2;
-    }   
-
-    @Override
-    public void onDeath() 
-    {
-        getLevel().scheduleReset();
-    }
-}

+ 0 - 28
src/me/hammerle/supersnuvi/entity/animator/EntityAnimator.java

@@ -1,28 +0,0 @@
-package me.hammerle.supersnuvi.entity.animator;
-
-import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.javafx.Image;
-
-public class EntityAnimator 
-{
-    protected final Entity ent;
-    
-    public EntityAnimator(Entity ent)
-    {
-        this.ent = ent;
-    }
-    
-    public void tick()
-    {
-    }
-    
-    public Image getImage()
-    {
-        return null;
-    }
-    
-    public boolean drawImageFlipped()
-    {
-        return false;
-    }
-}

+ 13 - 0
src/me/hammerle/supersnuvi/entity/components/Component.java

@@ -0,0 +1,13 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public abstract class Component 
+{
+    protected final Entity ent;
+    
+    protected Component(Entity ent)
+    {
+        this.ent = ent;
+    }
+}

+ 16 - 0
src/me/hammerle/supersnuvi/entity/components/Death.java

@@ -0,0 +1,16 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public abstract class Death
+{
+    public static final Death NULL = new Death() 
+    {
+        @Override
+        public void onDeath(Entity ent) 
+        {
+        }
+    };
+    
+    public abstract void onDeath(Entity ent);
+}

+ 51 - 0
src/me/hammerle/supersnuvi/entity/components/DefaultEnergy.java

@@ -0,0 +1,51 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public class DefaultEnergy extends Energy
+{
+    private final double maxEnergy;
+    private double energy;
+    
+    public DefaultEnergy(Entity ent, double maxEnergy) 
+    {
+        super(ent);
+        this.maxEnergy = maxEnergy;
+        this.energy = maxEnergy;
+    }
+
+    @Override
+    public void tick() 
+    {
+        if(!ent.isMoving())
+        {
+            addEnergy(0.1);
+        }
+    }
+
+    @Override
+    public double getMaxEnergy() 
+    {
+        return maxEnergy;
+    }
+
+    @Override
+    public double getEnergy() 
+    {
+        return energy;
+    }
+
+    @Override
+    public void addEnergy(double h) 
+    {
+        energy += h;
+        if(energy > maxEnergy)
+        {
+            energy = maxEnergy;
+        }
+        else if(energy < 0.0)
+        {
+            energy = 0.0;
+        }
+    }
+}

+ 42 - 0
src/me/hammerle/supersnuvi/entity/components/DefaultHealth.java

@@ -0,0 +1,42 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public class DefaultHealth extends Health
+{
+    private final double maxHealth;
+    private double health;
+    
+    public DefaultHealth(Entity ent, Death death, double maxHealth) 
+    {
+        super(ent, death);
+        this.maxHealth = maxHealth;
+        this.health = maxHealth;
+    }
+
+    @Override
+    public double getMaxHealth() 
+    {
+        return maxHealth;
+    }
+
+    @Override
+    public double getHealth() 
+    {
+        return health;
+    }
+
+    @Override
+    public void addHealth(double h) 
+    {
+        health += h;
+        if(health > maxHealth)
+        {
+            health = maxHealth;
+        }
+        else if(health < 0.0)
+        {
+            death.onDeath(ent);
+        }
+    }
+}

+ 31 - 0
src/me/hammerle/supersnuvi/entity/components/DefaultMovement.java

@@ -0,0 +1,31 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public class DefaultMovement extends Movement
+{
+    private final IJump jump;
+    
+    public DefaultMovement(Entity ent, IJump jump) 
+    {
+        super(ent);
+        this.jump = jump;
+    }
+
+    @Override
+    public boolean jump() 
+    {
+        if(ent.isOnGround())
+        {
+            ent.setMotionY(ent.getMotionY() + getJumpPower());
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public double getJumpPower() 
+    {
+        return jump.getJumpPower(ent) * ent.getMovementPenalty().getFactor();
+    }
+}

+ 41 - 0
src/me/hammerle/supersnuvi/entity/components/Energy.java

@@ -0,0 +1,41 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public class Energy extends Component
+{
+    public final static Energy NULL = new Energy(null);
+    
+    protected Energy(Entity ent)
+    {
+        super(ent);
+    }
+    
+    public void tick()
+    {
+    }
+
+    public void addEnergy(double e)
+    {
+    }
+    
+    public final void addEnergyPercent(double percent)
+    {
+        addEnergy(percent * getMaxEnergy());
+    }
+    
+    public double getMaxEnergy()
+    {
+        return 0.0;
+    }
+    
+    public double getEnergyPercent()
+    {
+        return getEnergy() / getMaxEnergy();
+    }
+    
+    public double getEnergy()
+    {
+        return 0.0;
+    }
+}

+ 54 - 0
src/me/hammerle/supersnuvi/entity/components/Health.java

@@ -0,0 +1,54 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public class Health extends Component
+{
+    public final static Health NULL = new Health(null, null);
+    
+    protected final Death death;
+    
+    protected Health(Entity ent, Death death)
+    {
+        super(ent);
+        this.death = death;
+    }
+    
+    public void tick()
+    {
+    }
+    
+    public boolean isHurt()
+    {
+        return false;
+    }
+    
+    public boolean isHealed()
+    {
+        return false;
+    }
+    
+    public void addHealth(double h)
+    {
+    }
+    
+    public final void addHealthPercent(double percent)
+    {
+        addHealth(percent * getMaxHealth());
+    }
+    
+    public double getMaxHealth()
+    {
+        return 0.0;
+    }
+    
+    public double getHealthPercent()
+    {
+        return getHealth() / getMaxHealth();
+    }
+    
+    public double getHealth()
+    {
+        return 0.0;
+    }
+}

+ 23 - 0
src/me/hammerle/supersnuvi/entity/components/HeroItemCollector.java

@@ -0,0 +1,23 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public class HeroItemCollector extends ItemCollector
+{
+    public HeroItemCollector(Entity ent) 
+    {
+        super(ent);
+    }
+
+    @Override
+    public boolean canCollect() 
+    {
+        return true;
+    }
+
+    @Override
+    public boolean isHero() 
+    {
+        return true;
+    }
+}

+ 8 - 0
src/me/hammerle/supersnuvi/entity/components/IJump.java

@@ -0,0 +1,8 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public interface IJump 
+{
+    public double getJumpPower(Entity ent);
+}

+ 23 - 0
src/me/hammerle/supersnuvi/entity/components/ItemCollector.java

@@ -0,0 +1,23 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public class ItemCollector extends Component
+{
+    public final static ItemCollector NULL = new ItemCollector(null);
+    
+    public ItemCollector(Entity ent) 
+    {
+        super(ent);
+    }
+    
+    public boolean canCollect()
+    {
+        return false;
+    }
+    
+    public boolean isHero()
+    {
+        return false;
+    }
+}

+ 23 - 0
src/me/hammerle/supersnuvi/entity/components/LandMovement.java

@@ -0,0 +1,23 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public class LandMovement extends MovementPenalty
+{
+    public LandMovement(Entity ent) 
+    {
+        super(ent);
+    }
+
+    @Override
+    public boolean isSlowedByWater() 
+    {
+        return true;
+    }
+
+    @Override
+    public boolean isSlowedByLand() 
+    {
+        return false;
+    }
+}

+ 27 - 0
src/me/hammerle/supersnuvi/entity/components/Movement.java

@@ -0,0 +1,27 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public class Movement extends Component
+{
+    public final static Movement NULL = new Movement(null);
+    
+    public Movement(Entity ent) 
+    {
+        super(ent);
+    }
+    
+    //--------------------------------------------------------------------------
+    // jumping
+    //--------------------------------------------------------------------------
+    
+    public boolean jump()
+    {
+        return false;
+    }
+    
+    public double getJumpPower()
+    {
+        return 0.0;
+    }
+}

+ 90 - 0
src/me/hammerle/supersnuvi/entity/components/MovementPenalty.java

@@ -0,0 +1,90 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public class MovementPenalty extends Component
+{
+    public final static MovementPenalty NULL = new MovementPenalty(null);
+    
+    private double friction;
+    private boolean inWater;
+    
+    public MovementPenalty(Entity ent)
+    {
+        super(ent);
+    }
+    
+    public final void reset()
+    {
+        inWater = false;
+    }
+    
+    public final void setInWater(boolean b)
+    {
+        inWater = b;
+    }
+    
+    public final boolean isInWater()
+    {
+        return inWater;
+    }
+    
+    public final double getFactor()
+    {
+        double factor = 1.0;
+        if(inWater)
+        {
+            if(isSlowedByWater())
+            {
+                factor *= 0.65;
+            }
+        }
+        else if(isSlowedByLand())
+        {
+            factor *= 0.65;
+        }
+        return factor;
+    }
+
+    public void setFriction(double friction) 
+    {
+        this.friction = friction;
+    }
+
+    public void applyFriction() 
+    {
+        double motionX = ent.getMotionX();
+        if(motionX < 0.0)
+        {
+            motionX += friction;
+            if(motionX > 0.0)
+            {
+                motionX = 0.0;
+            }
+            ent.setMotionX(motionX);
+        }
+        else if(motionX > 0.0)
+        {
+            motionX -= friction;
+            if(motionX < 0.0)
+            {
+                motionX = 0.0;
+            }
+            ent.setMotionX(motionX);
+        }
+    }
+    
+    //--------------------------------------------------------------------------
+    // overwriteable data
+    //--------------------------------------------------------------------------
+    
+    public boolean isSlowedByWater()
+    {
+        return true;
+    }
+    
+    public boolean isSlowedByLand()
+    {
+        return true;
+    }
+}

+ 23 - 0
src/me/hammerle/supersnuvi/entity/components/WaterMovement.java

@@ -0,0 +1,23 @@
+package me.hammerle.supersnuvi.entity.components;
+
+import me.hammerle.supersnuvi.entity.Entity;
+
+public class WaterMovement extends MovementPenalty
+{
+    public WaterMovement(Entity ent) 
+    {
+        super(ent);
+    }
+
+    @Override
+    public boolean isSlowedByWater() 
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isSlowedByLand() 
+    {
+        return true;
+    }
+}

+ 6 - 5
src/me/hammerle/supersnuvi/entity/ai/EntityController.java → src/me/hammerle/supersnuvi/entity/components/ai/EntityController.java

@@ -1,16 +1,17 @@
-package me.hammerle.supersnuvi.entity.ai;
+package me.hammerle.supersnuvi.entity.components.ai;
 
 import me.hammerle.supersnuvi.entity.Entity;
+import me.hammerle.supersnuvi.entity.components.Component;
 import me.hammerle.supersnuvi.tiles.Location;
 import me.hammerle.supersnuvi.util.Face;
 
-public class EntityController
+public class EntityController extends Component
 {
-    protected final Entity ent;
+    public final static EntityController NULL = new EntityController(null);
     
-    public EntityController(Entity ent)
+    protected EntityController(Entity ent)
     {
-        this.ent = ent;
+        super(ent);
     }
     
     public void tick()

+ 3 - 4
src/me/hammerle/supersnuvi/entity/ai/FollowHeroController.java → src/me/hammerle/supersnuvi/entity/components/ai/FollowHeroController.java

@@ -1,7 +1,6 @@
-package me.hammerle.supersnuvi.entity.ai;
+package me.hammerle.supersnuvi.entity.components.ai;
 
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.entity.Hero;
 import me.hammerle.supersnuvi.tiles.Location;
 import me.hammerle.supersnuvi.util.Face;
 
@@ -18,7 +17,7 @@ public class FollowHeroController extends EntityController
     @Override
     public void tick() 
     {
-        Hero hero = ent.getLevel().getHero();
+        Entity hero = ent.getLevel().getHero();
         if(hero.squaredDistance(ent) <= 102400)
         {
             double distance = ent.signedDistanceX(hero);
@@ -45,7 +44,7 @@ public class FollowHeroController extends EntityController
                 Math.abs(ent.getPreviousMotionX() - ent.getMotionX()) > 0.001 &&
                 Math.abs(ent.signedDistanceX(ent.getLevel().getHero())) > 0.001)
         {
-            ent.jump();
+            ent.getMovement().jump();
         }
     }
 }

+ 7 - 8
src/me/hammerle/supersnuvi/entity/ai/HumanController.java → src/me/hammerle/supersnuvi/entity/components/ai/HumanController.java

@@ -1,7 +1,6 @@
-package me.hammerle.supersnuvi.entity.ai;
+package me.hammerle.supersnuvi.entity.components.ai;
 
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.entity.ai.EntityController;
 import me.hammerle.supersnuvi.gamelogic.Level;
 import me.hammerle.supersnuvi.javafx.KeyHandler;
 import me.hammerle.supersnuvi.util.SoundUtils;
@@ -22,7 +21,7 @@ public class HumanController extends EntityController
     @Override
     public void tick() 
     {
-        if(KeyHandler.JUMP.isDown() && ent.jump())
+        if(KeyHandler.JUMP.isDown() && ent.getMovement().jump())
         {
             SoundUtils.playSound(SoundUtils.Sound.JUMP);
         }
@@ -30,28 +29,28 @@ public class HumanController extends EntityController
         double speed = 3.0;
         if(isRunning())
         {
-            if((ent.getLevel().getEnergy() / Level.MAX_ENERGY) > 0.25)
+            if(ent.getEnergy().getEnergyPercent() > 0.0)
             {
                 speed = 4.5;
             }
             if(ent.isMoving())
             {
-                ent.getLevel().addEnergy(-0.3);
+                ent.getEnergy().addEnergy(-0.3);
             }
         }
         
         if(KeyHandler.LEFT.isDown())
         {
-            ent.setMotionX(-speed * ent.getMaxSpeedModifier());
+            ent.setMotionX(-speed * ent.getMovementPenalty().getFactor());
         }
         else if(KeyHandler.RIGHT.isDown())
         {
-            ent.setMotionX(speed * ent.getMaxSpeedModifier());
+            ent.setMotionX(speed * ent.getMovementPenalty().getFactor());
         }
         
         if(ent.isOnGround() && ent.getPreviousMotionX() != 0)
         {
-            if(ent.isInWater())
+            if(ent.getMovementPenalty().isInWater())
             {
                 SoundUtils.playSound(SoundUtils.Sound.WALK_WATER);
             }

+ 1 - 1
src/me/hammerle/supersnuvi/entity/ai/WalkController.java → src/me/hammerle/supersnuvi/entity/components/ai/WalkController.java

@@ -1,4 +1,4 @@
-package me.hammerle.supersnuvi.entity.ai;
+package me.hammerle.supersnuvi.entity.components.ai;
 
 import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.tiles.Location;

+ 3 - 3
src/me/hammerle/supersnuvi/entity/animator/DefaultAnimator.java → src/me/hammerle/supersnuvi/entity/components/animator/DefaultAnimator.java

@@ -1,4 +1,4 @@
-package me.hammerle.supersnuvi.entity.animator;
+package me.hammerle.supersnuvi.entity.components.animator;
 
 import me.hammerle.supersnuvi.entity.Entity;
 import me.hammerle.supersnuvi.javafx.Image;
@@ -16,9 +16,9 @@ public class DefaultAnimator extends EntityAnimator
     private final Image[] moveAir;
     private boolean flipped;
     
-    public DefaultAnimator(Entity ent, String face, String[] move, String[] moveAir) 
+    public DefaultAnimator(Entity ent, double offsetX, double offsetY, String face, String[] move, String[] moveAir) 
     {
-        super(ent);
+        super(ent, offsetX, offsetY);
         flipped = false;
         counter = 0;
         frame = 0;  

+ 44 - 0
src/me/hammerle/supersnuvi/entity/components/animator/EntityAnimator.java

@@ -0,0 +1,44 @@
+package me.hammerle.supersnuvi.entity.components.animator;
+
+import me.hammerle.supersnuvi.entity.Entity;
+import me.hammerle.supersnuvi.javafx.Image;
+
+public class EntityAnimator 
+{
+    public final static EntityAnimator NULL = new EntityAnimator(null, 0.0, 0.0);
+    
+    protected final Entity ent;
+    private final double offsetX;
+    private final double offsetY;
+    
+    protected EntityAnimator(Entity ent, double offsetX, double offsetY)
+    {
+        this.ent = ent;
+        this.offsetX = offsetX;
+        this.offsetY = offsetY;
+    }
+    
+    public void tick()
+    {
+    }
+    
+    public Image getImage()
+    {
+        return null;
+    }
+    
+    public boolean drawImageFlipped()
+    {
+        return false;
+    }
+    
+    public final double getRenderX()
+    {
+        return ent.getX() + offsetX;
+    }
+    
+    public final double getRenderY()
+    {
+        return ent.getY() + ent.getHeight() + offsetY;
+    }
+}

+ 19 - 51
src/me/hammerle/supersnuvi/gamelogic/Level.java

@@ -7,10 +7,10 @@ import java.util.List;
 import java.util.TreeSet;
 import java.util.stream.Collectors;
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.entity.animator.EntityAnimator;
-import me.hammerle.supersnuvi.entity.Hero;
-import me.hammerle.supersnuvi.entity.ai.FollowHeroController;
-import me.hammerle.supersnuvi.entity.ai.WalkController;
+import me.hammerle.supersnuvi.entity.EntityBuilder;
+import me.hammerle.supersnuvi.entity.components.Energy;
+import me.hammerle.supersnuvi.entity.components.animator.EntityAnimator;
+import me.hammerle.supersnuvi.entity.components.Health;
 import me.hammerle.supersnuvi.tiles.BottledSoulTile;
 import me.hammerle.supersnuvi.tiles.Location;
 import me.hammerle.supersnuvi.tiles.StartTile;
@@ -20,8 +20,6 @@ import me.hammerle.supersnuvi.rendering.IRenderer;
 
 public final class Level 
 {
-    public final static double MAX_ENERGY = 100.0;
-    
     private final StateRenderer state;
     private final IRenderer renderer;
     
@@ -31,7 +29,7 @@ public final class Level
     private final String name;
     
     private final HashMap<Integer, Entity> entities;
-    private Hero hero;
+    private Entity hero;
     private int entityCounter;
     
     private boolean shouldReset;
@@ -44,8 +42,6 @@ public final class Level
     
     private TreeSet<Point> spawns;
     
-    private double energy;
-    
     public Level(StateRenderer state, File f)
     {
         this.state = state;
@@ -89,10 +85,6 @@ public final class Level
         }
         
         resetLevel();
-        
-        //Hero test = new Hero(this, 150, 150);
-        //test.setController(new FollowHeroController(test, 1.5));
-        //entities.put(entityCounter++, test);
     }
     
     // -------------------------------------------------------------------------
@@ -128,35 +120,34 @@ public final class Level
     public void resetLevel()
     {
         state.resetTiles();
-        energy = MAX_ENERGY;
         souls = 0;
         shouldReset = false;
         done = false;
-        Hero h = spawnHero();
+        Entity h = spawnHero();
         hero = h;
         entities.clear();
         entities.put(entityCounter++, h);
     }
     
-    public Hero spawnHero()
+    public Entity spawnHero()
     {
-        Hero h = (Hero) entities.values().stream().filter(ent -> ent instanceof Hero).findFirst().orElse(null);
+        Entity h = hero;
+        Point p;
         if(h == null || hero.getX() < 0)
         {
             // first spawn or out of map, use first spawn
-            Point p = spawns.first();
-            return new Hero(this, renderer.toCoord(p.getX()), renderer.toCoord(p.getY()));
+            p = spawns.first();
         }
         else
         {
             // hero is somewhere in the map, getting last spawn
-            Point p = spawns.floor(new Point(renderer.toBlock(hero.getX()), renderer.toBlock(hero.getY())));
+            p = spawns.floor(new Point(renderer.toBlock(hero.getX()), renderer.toBlock(hero.getY())));
             if(p == null)
             {
                 p = spawns.first();
             }
-            return new Hero(this, renderer.toCoord(p.getX()), renderer.toCoord(p.getY()));
         }
+        return EntityBuilder.buildHero(this, renderer.toCoord(p.getX()), renderer.toCoord(p.getY()));
     }
     
     public void increaseSouls()
@@ -164,24 +155,6 @@ public final class Level
         souls++;
     }
     
-    public void addEnergy(double d)
-    {
-        energy += d;
-        if(energy > MAX_ENERGY)
-        {
-            energy = MAX_ENERGY;
-        }
-        else if(energy < 0.0)
-        {
-            energy = 0.0;
-        }
-    }
-    
-    public double getEnergy()
-    {
-        return energy;
-    }
-    
     // -------------------------------------------------------------------------
     // tick
     // -------------------------------------------------------------------------
@@ -205,11 +178,6 @@ public final class Level
             {
                 entity.tick();
             });
-
-            if(!hero.isMoving())
-            {
-                addEnergy(0.1);
-            }
         }
     }   
     
@@ -242,14 +210,14 @@ public final class Level
             
             entities.values().forEach(en -> 
             {
-                EntityAnimator animator = en.getAnimator();
-                if(animator.drawImageFlipped())
+                EntityAnimator a = en.getAnimator();
+                if(a.drawImageFlipped())
                 {
-                    renderer.drawFlippedImage(animator.getImage(), en.getRenderX() + en.getRenderOffsetX(), en.getRenderY() + en.getRenderOffsetY());
+                    renderer.drawFlippedImage(a.getImage(), a.getRenderX(), a.getRenderY());
                 }
                 else
                 {
-                    renderer.drawImage(animator.getImage(), en.getRenderX() + en.getRenderOffsetX(), en.getRenderY() + en.getRenderOffsetY());
+                    renderer.drawImage(a.getImage(), a.getRenderX(), a.getRenderY());
                 }
             });
             
@@ -329,7 +297,7 @@ public final class Level
                     renderer.setFillColor(0, 0, 0, 1.0);
                     renderer.fillRectangle(x, y, leftX, baseLine);
                     renderer.setFillColor(0, 0, 150, 1.0);
-                    renderer.fillRectangle(x + 3, y + 3, (leftX * (energy / MAX_ENERGY)) - 6, baseLine - 6);
+                    renderer.fillRectangle(x + 3, y + 3, (leftX * hero.getEnergy().getEnergyPercent()) - 6, baseLine - 6);
                 }
                 renderer.restore();
                 x += leftX + 10;
@@ -340,7 +308,7 @@ public final class Level
                     renderer.setFillColor(0, 0, 0, 1.0);
                     renderer.fillRectangle(x, y, leftX, baseLine);
                     renderer.setFillColor(150, 0, 0, 1.0);
-                    renderer.fillRectangle(x + 3, y + 3, (leftX * (hero.getHealth() / hero.getMaxHealth())) - 6, baseLine - 6);
+                    renderer.fillRectangle(x + 3, y + 3, (leftX * hero.getHealth().getHealthPercent()) - 6, baseLine - 6);
                 }
                 renderer.restore();
                 x += leftX + 10;
@@ -473,7 +441,7 @@ public final class Level
         return boxes;
     }
     
-    public Hero getHero()
+    public Entity getHero()
     {
         return hero;
     }

+ 7 - 5
src/me/hammerle/supersnuvi/tiles/BottledSoulTile.java

@@ -3,7 +3,6 @@ package me.hammerle.supersnuvi.tiles;
 import java.util.HashSet;
 import me.hammerle.supersnuvi.javafx.Image;
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.entity.Hero;
 import me.hammerle.supersnuvi.gamelogic.StateRenderer;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.Face;
@@ -26,13 +25,16 @@ public class BottledSoulTile extends Tile
     @Override
     public void onEntityCollide(Entity ent, int x, int y, Face face) 
     {
-        if(ent instanceof Hero)
+        if(ent.getItemCollector().canCollect())
         {
             if(states.add(getKey(x, y)))
             {
-                SoundUtils.playSound(SoundUtils.Sound.COLLECT);
-                ent.getLevel().increaseSouls();
-                ent.addHealthPercent(0.2);
+                if(ent.getItemCollector().isHero())
+                {
+                    SoundUtils.playSound(SoundUtils.Sound.COLLECT);
+                    ent.getLevel().increaseSouls();
+                }
+                ent.getHealth().addHealthPercent(0.2);
             }
         }
     }

+ 1 - 2
src/me/hammerle/supersnuvi/tiles/GoalTile.java

@@ -1,7 +1,6 @@
 package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.entity.Hero;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.Face;
 
@@ -16,7 +15,7 @@ public class GoalTile extends BaseTile
     @Override
     public void onEntityCollide(Entity ent, int x, int y, Face face) 
     {
-        if(ent instanceof Hero)
+        if(ent.getItemCollector().isHero())
         {
             ent.getLevel().finishLevel();
         }

+ 1 - 5
src/me/hammerle/supersnuvi/tiles/KillTile.java

@@ -1,7 +1,6 @@
 package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.entity.Hero;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.Face;
 
@@ -20,10 +19,7 @@ public class KillTile extends BaseTile
     public void onEntityCollide(Entity ent, int x, int y, Face face) 
     {
         super.onEntityCollide(ent, x, y, face);
-        if(ent instanceof Hero)
-        {
-            ent.addHealth(-0.05);
-        }
+        ent.getHealth().addHealth(-0.05);
     }
 
     @Override

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

@@ -19,7 +19,7 @@ public class SlipperyTile extends BaseBoxTile
             {
                 ent.setMotionX(ent.getAnimator().drawImageFlipped() ? -2.5 : 2.5);
             }
-            ent.setFriction(0.08);
+            ent.getMovementPenalty().setFriction(0.08);
         }
     }
 }

+ 2 - 3
src/me/hammerle/supersnuvi/tiles/SpikeTile.java

@@ -2,7 +2,6 @@ package me.hammerle.supersnuvi.tiles;
 
 import me.hammerle.supersnuvi.javafx.Image;
 import me.hammerle.supersnuvi.entity.Entity;
-import me.hammerle.supersnuvi.entity.Hero;
 import me.hammerle.supersnuvi.util.CollisionBox;
 import me.hammerle.supersnuvi.util.Face;
 import me.hammerle.supersnuvi.util.Utils;
@@ -33,9 +32,9 @@ public class SpikeTile extends Tile
     public void onEntityCollide(Entity ent, int x, int y, Face face) 
     {
         super.onEntityCollide(ent, x, y, face);
-        if(frame >= 4 && ent instanceof Hero)
+        if(frame >= 4)
         {
-            ent.addHealth(-0.05);
+            ent.getHealth().addHealth(-0.05);
         }
     }
 

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

@@ -17,7 +17,7 @@ public class TrampolinTile extends BaseBoxTile
         super.onEntityCollide(ent, x, y, face);
         if(face == Face.UP)
         {
-            ent.setMotionY(ent.getJumpPower() * 1.5);
+            ent.setMotionY(ent.getMovement().getJumpPower() * 1.5);
             SoundUtils.playSound(SoundUtils.Sound.JUMP_ON_BOUNCE_SHROOM);
             /*double motionY = ent.getPreviousMotionY();
             if(motionY < 0)

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

@@ -39,7 +39,7 @@ public class WaterTile extends Tile
     {
         super.onEntityCollide(ent, x, y, face);
         
-        ent.setInWater(true);
+        ent.getMovementPenalty().setInWater(true);
         
         double motionY = ent.getMotionY();
         if(motionY < 0)