View Javadoc
1   package com.jed.actor;
2   
3   import com.jed.state.AbstractDisplayableState;
4   import com.jed.state.GameMap;
5   import com.jed.state.State;
6   import com.jed.util.Util;
7   import com.jed.util.Vector2f;
8   import org.lwjgl.opengl.GL11;
9   import org.newdawn.slick.Color;
10  import org.newdawn.slick.opengl.Texture;
11  import org.slf4j.Logger;
12  import org.slf4j.LoggerFactory;
13  
14  import javax.annotation.Nonnull;
15  
16  /**
17   *
18   * Base class representing a player. The player in this context is only applicable to an entity in a
19   * two dimensional side scrolling game world. Future implementations might allow for additional game genre
20   * types to be used with this class, however at this time that is not the case.
21   *
22   * @author jlinde, Peter Colapietro
23   * @since 0.1.0
24   *
25   */
26  public class Player extends AbstractEntity {
27  
28      /**
29       *
30       */
31      private static final Logger LOGGER = LoggerFactory.getLogger(Player.class);
32  
33      /**
34       *
35       */
36      private static final float X_MOVEMENT_SCALAR = 0.5f;
37      /**
38       * 
39       */
40      private final int height;
41  
42      /**
43       *
44       */
45      private final int width;
46  
47      /**
48       *
49       */
50      private int xDir;
51  
52      /**
53       *
54       */
55      private static final String TEXTURE_PATH = "MEGA_MAN_SH.png";
56  
57      /**
58       *
59       */
60      @Nonnull
61      private Texture texture;
62  
63      /**
64       *
65       */
66      private static final int PLAYER_RIGHT = 1;
67  
68      /**
69       *
70       */
71      private static final int PLAYER_LEFT = 0;
72  
73      /**
74       *
75       */
76      @Nonnull
77      private AbstractPlayerState currentState;
78  
79      /**
80       *
81       */
82      @Nonnull
83      private final AbstractPlayerState fallingState;
84  
85      /**
86       *
87       */
88      @Nonnull
89      private final AbstractPlayerState idleState;
90  
91      /**
92       *
93       */
94      @Nonnull
95      private final AbstractPlayerState walkingState;
96  
97      /**
98       *
99       */
100     @Nonnull
101     private final AbstractPlayerState jumpingState;
102 
103     /**
104      * Indicates the player is currently colliding with a map tile below it.
105      */
106     private boolean collideDown = false;
107 
108     /**
109      * TODO: Friction should come from the individual map tiles or from the tileset.
110      */
111     private static final float FRICTION = .046875f;
112 
113     /**
114      *
115      */
116     private int jumpCount = 0;
117 
118     /**
119      *
120      */
121     private final GameMap map;
122 
123     /**
124      *
125      */
126     private boolean isMovingLeft;
127 
128     /**
129      *
130      */
131     private boolean isMovingRight;
132 
133     /**
134      *
135      */
136     private boolean isJumping;
137 
138     /**
139      *
140      * TODO: The Bounds should be scaled to the size of the player sprite so that it can be scaled.
141      * 
142      * @param position position vector
143      * @param height height
144      * @param width width
145      * @param map game map
146      */
147     public Player(Vector2f position, int height, int width, GameMap map) {
148         super(
149                 position,
150                 new Vector2f(0, 0),
151                 new PolygonBoundary(
152                         new Vector2f(110, 130),
153                         new Vector2f[]{
154                                 new Vector2f(0, 0),
155                                 new Vector2f(40, 0),
156                                 new Vector2f(40, 120),
157                                 new Vector2f(0, 120)
158                         })
159         );
160 
161         this.setAcceleration(FRICTION);
162         this.height = height;
163         this.width = width;
164         this.map = map;
165         this.texture = Util.loadTexture(TEXTURE_PATH);
166 
167         this.fallingState = new Falling();
168         this.idleState = new Idle();
169         this.walkingState = new Walking();
170         this.jumpingState = new Jumping();
171     }
172 
173     /**
174      * @param state state to change current player to.
175      */
176     public void changeState(State state) {
177         currentState = (AbstractPlayerState) state;
178         currentState.entered();
179     }
180 
181     @Override
182     public void entered() {
183         changeState(fallingState);
184     }
185 
186     @Override
187     public void update() {
188         currentState.handleInput();
189 
190         if (!currentState.falling && !collideDown) {
191             changeState(fallingState);
192         }
193         collideDown = false;
194 
195         if (currentState.falling) {
196             getMovement().y += map.getGravity();
197         }
198 
199         getPosition().x = this.getPosition().x + getMovement().x;
200         getPosition().y = getPosition().y + getMovement().y;
201 
202         currentState.update();
203     }
204 
205     @Override
206     public void render() {
207         currentState.render();
208         getBounds().render();
209     }
210 
211     /**
212      *
213      * TODO: Name this something else or refactor.
214      * indicates player has just landed on a tile and to stop "Falling" (changes animation).
215      *
216      * @param sEntity
217      */
218     @Override
219     public void collideDown(AbstractEntity sEntity) {
220         collideDown = true;
221         if (currentState.falling) {
222             changeState(idleState);
223         }
224     }
225 
226     /**
227      *
228      * @author jlinde, Peter Colapietro
229      *
230      */
231     private abstract class AbstractPlayerState extends AbstractDisplayableState {
232 
233         /**
234          *
235          */
236         boolean falling;
237 
238         /**
239          *
240          */
241         public AbstractPlayerState() {
242             falling = false;
243         }
244 
245         /**
246          *
247          */
248         public abstract void handleInput();
249     }
250 
251     /**
252      *
253      */
254     private abstract class AbstractNonEnterablePlayerState extends AbstractPlayerState {
255 
256         /**
257          *
258          */
259         final Logger LOGGER = LoggerFactory.getLogger(AbstractNonEnterablePlayerState.class);
260 
261         @Override
262         public void entered() {
263             LOGGER.debug("com.jed.actor.Player.AbstractNonEnterablePlayerState#entered");
264         }
265     }
266 
267     /**
268      *
269      * @author jlinde, Peter Colapietro
270      *
271      */
272     private class Falling extends AbstractNonEnterablePlayerState {
273 
274         /**
275          *
276          */
277         private float bottomLeftX;
278 
279         /**
280          *
281          */
282         private float bottomRightX;
283 
284         /**
285          *
286          */
287         private float topRightX;
288 
289         /**
290          *
291          */
292         private float topLeftX;
293 
294         /**
295          *
296          */
297         public Falling() {
298             this.falling = true;
299         }
300 
301         @Override
302         public void update() {
303             //Player Landed on something
304             if (getMovement().y == 0) {
305                 if (getMovement().x != 0) {
306                     changeState(walkingState);
307                 } else {
308                     changeState(idleState);
309                 }
310             }
311         }
312 
313         @Override
314         public void handleInput() {
315             keyHoldEvent();
316         }
317 
318         @Override
319         public void render() {
320             Color.white.bind();
321             texture.bind();
322             GL11.glEnable(GL11.GL_TEXTURE_2D);
323             GL11.glBegin(GL11.GL_QUADS);
324 
325             if (xDir == PLAYER_LEFT) {
326                 bottomLeftX  = getPosition().x + width;
327                 bottomRightX = getPosition().x;
328                 topLeftX     = getPosition().x;
329                 topRightX    = getPosition().x + width;
330             } else {
331                 bottomLeftX  = getPosition().x;
332                 bottomRightX = getPosition().x + width;
333                 topLeftX     = getPosition().x + width;
334                 topRightX    = getPosition().x;
335             }
336 
337             GL11.glTexCoord2f(.25f, .5f);
338             map.drawChildVertex2f(bottomLeftX, getPosition().y);
339             GL11.glTexCoord2f(.3125f, .5f);
340             map.drawChildVertex2f(bottomRightX, getPosition().y);
341             GL11.glTexCoord2f(.3125f, 1);
342             map.drawChildVertex2f(topLeftX, getPosition().y + height);
343             GL11.glTexCoord2f(.25f, 1);
344             map.drawChildVertex2f(topRightX, getPosition().y + height);
345             GL11.glEnd();
346             GL11.glDisable(GL11.GL_TEXTURE_2D);
347         }
348 
349     }
350 
351     /**
352      *
353      * @author jlinde, Peter Colapietro
354      *
355      */
356     private final class Jumping extends Falling {
357 
358         /**
359          *
360          */
361         final float[] animation = {.0625f, .125f, .1875f, .25f, .3125f, .375f, .4375f};
362 
363         /**
364          *
365          */
366         final float frameWidth = .0625f;
367 
368         /**
369          *
370          */
371         int frame, ticks;
372 
373         /**
374          *
375          */
376         public Jumping() {
377             this.falling = true;
378         }
379 
380         @Override
381         public void entered() {
382             frame = 0;
383             ticks = 0;
384         }
385 
386         @Override
387         public void update() {
388             ticks++;
389             if (ticks % 16 == 0) {
390                 frame = frame == animation.length - 1 ? frame : frame + 1;
391             }
392 
393             super.update();
394         }
395 
396         @Override
397         public void render() {
398             Color.white.bind();
399             texture.bind();
400             GL11.glEnable(GL11.GL_TEXTURE_2D);
401             GL11.glBegin(GL11.GL_QUADS);
402             if (xDir != PLAYER_LEFT) {
403                 GL11.glTexCoord2f(animation[frame] - frameWidth, .5f);
404                 map.drawChildVertex2f(getPosition().x, getPosition().y);
405                 GL11.glTexCoord2f(animation[frame], .5f);
406                 map.drawChildVertex2f(getPosition().x + width, getPosition().y);
407                 GL11.glTexCoord2f(animation[frame], 1);
408                 map.drawChildVertex2f(getPosition().x + width, getPosition().y + height);
409                 GL11.glTexCoord2f(animation[frame] - frameWidth, 1);
410                 map.drawChildVertex2f(getPosition().x, getPosition().y + height);
411             } else {
412                 GL11.glTexCoord2f(animation[frame] - frameWidth, .5f);
413                 map.drawChildVertex2f(getPosition().x + width, getPosition().y);
414                 GL11.glTexCoord2f(animation[frame], .5f);
415                 map.drawChildVertex2f(getPosition().x, getPosition().y);
416                 GL11.glTexCoord2f(animation[frame], 1);
417                 map.drawChildVertex2f(getPosition().x, getPosition().y + height);
418                 GL11.glTexCoord2f(animation[frame] - frameWidth, 1);
419                 map.drawChildVertex2f(getPosition().x + width, getPosition().y + height);
420             }
421             GL11.glEnd();
422             GL11.glDisable(GL11.GL_TEXTURE_2D);
423 
424         }
425 
426     }
427 
428     /**
429      *
430      * @author jlinde, Peter Colapietro
431      *
432      */
433     private final class Idle extends AbstractNonEnterablePlayerState {
434 
435         @Override
436         public void update() {
437             if (getMovement().y != 0) {
438                 changeState(fallingState);
439             } else if (getMovement().x != 0) {
440                 changeState(walkingState);
441             }
442         }
443 
444         @Override
445         public void handleInput() {
446             keyHoldEvent();
447         }
448 
449         @Override
450         public void render() {
451             Color.white.bind();
452             texture.bind();
453             GL11.glEnable(GL11.GL_TEXTURE_2D);
454             GL11.glBegin(GL11.GL_QUADS);
455 
456             if (xDir == PLAYER_LEFT) {
457                 GL11.glTexCoord2f(0, 0);
458                 map.drawChildVertex2f(getPosition().x + width, getPosition().y);
459                 GL11.glTexCoord2f(.0625f, 0);
460                 map.drawChildVertex2f(getPosition().x, getPosition().y);
461                 GL11.glTexCoord2f(.0625f, .5f);
462                 map.drawChildVertex2f(getPosition().x, getPosition().y + height);
463                 GL11.glTexCoord2f(0, .5f);
464                 map.drawChildVertex2f(getPosition().x + width, getPosition().y + height);
465             } else {
466                 GL11.glTexCoord2f(0, 0);
467                 map.drawChildVertex2f(getPosition().x, getPosition().y);
468                 GL11.glTexCoord2f(.0625f, 0);
469                 map.drawChildVertex2f(getPosition().x + width, getPosition().y);
470                 GL11.glTexCoord2f(.0625f, .5f);
471                 map.drawChildVertex2f(getPosition().x + width, getPosition().y + height);
472                 GL11.glTexCoord2f(0, .5f);
473                 map.drawChildVertex2f(getPosition().x, getPosition().y + height);
474             }
475             GL11.glEnd();
476             GL11.glDisable(GL11.GL_TEXTURE_2D);
477         }
478 
479     }
480 
481     /**
482      *
483      * @author jlinde, Peter Colapietro
484      *
485      */
486     private final class Walking extends AbstractPlayerState {
487 
488         /**
489          *
490          */
491         final float[] animation = {.125f, .1875f, .25f, .3125f, .375f, .4375f, .5f, .5625f, .625f, .6875f, .75f};
492 
493         /**
494          *
495          */
496         final float frameWidth = .0625f;
497 
498         /**
499          *
500          */
501         int frame, ticks;
502 
503         @Override
504         public void entered() {
505             frame = 0;
506             ticks = 0;
507         }
508 
509         @Override
510         public void handleInput() {
511             keyHoldEvent();
512         }
513 
514         @Override
515         public void update() {
516             ticks++;
517             if (ticks % 8 == 0) {
518                 frame = frame == animation.length - 1 ? 1 : frame + 1;
519             }
520 
521             if (getMovement().x == 0) {
522                 changeState(idleState);
523             }
524         }
525 
526         @Override
527         public void render() {
528             Color.white.bind();
529             texture.bind();
530             GL11.glEnable(GL11.GL_TEXTURE_2D);
531             GL11.glBegin(GL11.GL_QUADS);
532             if (getMovement().x > 0) {
533                 GL11.glTexCoord2f(animation[frame] - frameWidth, 0);
534                 map.drawChildVertex2f(getPosition().x, getPosition().y);
535                 GL11.glTexCoord2f(animation[frame], 0);
536                 map.drawChildVertex2f(getPosition().x + width, getPosition().y);
537                 GL11.glTexCoord2f(animation[frame], .5f);
538                 map.drawChildVertex2f(getPosition().x + width, getPosition().y + height);
539                 GL11.glTexCoord2f(animation[frame] - frameWidth, .5f);
540                 map.drawChildVertex2f(getPosition().x, getPosition().y + height);
541             } else {
542                 GL11.glTexCoord2f(animation[frame] - frameWidth, 0);
543                 map.drawChildVertex2f(getPosition().x + width, getPosition().y);
544                 GL11.glTexCoord2f(animation[frame], 0);
545                 map.drawChildVertex2f(getPosition().x, getPosition().y);
546                 GL11.glTexCoord2f(animation[frame], .5f);
547                 map.drawChildVertex2f(getPosition().x, getPosition().y + height);
548                 GL11.glTexCoord2f(animation[frame] - frameWidth, .5f);
549                 map.drawChildVertex2f(getPosition().x + width, getPosition().y + height);
550             }
551             GL11.glEnd();
552             GL11.glDisable(GL11.GL_TEXTURE_2D);
553         }
554     }
555 
556     @Override
557     public void drawChildVertex2f(float x, float y) {
558         map.drawChildVertex2f(getPosition().x + x, getPosition().y + y);
559     }
560 
561     /**
562      *
563      */
564     private void jump() {
565             boolean isJumpCountLessThanTwo = jumpCount < 2;
566             int heightOffsetWithYPosition = Math.round(getPosition().y) + height; //TODO Test me.
567             if (isJumpCountLessThanTwo || heightOffsetWithYPosition == map.getHeight() * map.getTileHeight()) {
568                 getMovement().y = -8;
569                 jumpCount++;
570                 changeState(jumpingState);
571             }
572         isJumping = false;
573     }
574 
575     /**
576      *
577      */
578     private void moveRight() {
579         LOGGER.info("moveRight");
580         if (Float.compare(getMovement().x, 0) < 0) {
581             getMovement().x += X_MOVEMENT_SCALAR;
582         } else {
583             getMovement().x += getAcceleration();
584             xDir = PLAYER_RIGHT;
585         }
586     }
587 
588     /**
589      *
590      */
591     private void moveLeft() {
592         LOGGER.info("moveLeft");
593         if (Float.compare(getMovement().x, 0) > 0) {
594             getMovement().x -= X_MOVEMENT_SCALAR;
595         } else {
596             getMovement().x -= getAcceleration();
597             xDir = PLAYER_LEFT;
598         }
599     }
600 
601     /**
602      * Key Hold Events (walking etc).
603      *
604      * Constant key "hold" events
605      */
606     private void keyHoldEvent() {
607         if(isJumping) {
608             jump();
609         }
610         if(isMovingLeft) {
611             moveLeft();
612         } else if(isMovingRight) {
613             moveRight();
614         } else if (Float.compare(getMovement().x, 0) != 0) {
615             getMovement().x = getMovement().x - Math.min(Math.abs(getMovement().x), FRICTION)
616                     * Math.signum(getMovement().x);
617         }
618         if (!isJumping && !currentState.falling) {
619             jumpCount = 0;
620         }
621     }
622 
623     /**
624      *
625      * @return height
626      */
627     public int getHeight() {
628         return height;
629     }
630 
631     /**
632      *
633      * @return width
634      */
635     public int getWidth() {
636         return width;
637     }
638 
639     /**
640      *
641      * @param isMovingLeft isMovingLeft
642      */
643     public void setMovingLeft(boolean isMovingLeft) {
644         this.isMovingLeft = isMovingLeft;
645     }
646 
647     /**
648      *
649      * @param isMovingRight isMovingRight
650      */
651     public void setMovingRight(boolean isMovingRight) {
652         this.isMovingRight = isMovingRight;
653     }
654 
655     /**
656      *
657      * @param isJumping isJumping
658      */
659     public void setJumping(boolean isJumping) {
660         this.isJumping = isJumping;
661     }
662 }