diff --git a/img/enemy/slime/slimeDead.png b/img/enemy/slime/slimeDead.png new file mode 100644 index 0000000..fa64e72 Binary files /dev/null and b/img/enemy/slime/slimeDead.png differ diff --git a/img/enemy/slime/slimeWalk1.png b/img/enemy/slime/slimeWalk1.png new file mode 100644 index 0000000..fd38fb7 Binary files /dev/null and b/img/enemy/slime/slimeWalk1.png differ diff --git a/img/enemy/slime/slimeWalk2.png b/img/enemy/slime/slimeWalk2.png new file mode 100644 index 0000000..d2defb6 Binary files /dev/null and b/img/enemy/slime/slimeWalk2.png differ diff --git a/src/GamePanel.java b/src/GamePanel.java index 8918f46..dbcb338 100644 --- a/src/GamePanel.java +++ b/src/GamePanel.java @@ -11,13 +11,9 @@ import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.FileNotFoundException; import java.io.IOException; import javax.imageio.ImageIO; import java.util.ArrayList; -import java.util.Scanner; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; import javax.swing.*; @@ -33,13 +29,16 @@ public class GamePanel extends JPanel implements Runnable, KeyListener{ public Graphics graphics; public Player player; public BackgroundImage background; - public int frame; + public int playerFrame, enemyFrame; // keeps track of how many ticks has elapsed since last frame change - public int frameCounter = 0; + public int playerFrameCounter = 0; + public int enemyFrameCounter = 0; - public BufferedImage[][][] spriteArray = new BufferedImage[2][2][11]; + public BufferedImage[][][] playerSpriteArray = new BufferedImage[2][2][11]; + public BufferedImage[][][] slimeSpriteArray = new BufferedImage[2][2][3]; public static ArrayListmap = new ArrayList(); + public static ArrayListenemy = new ArrayList(); public static Camera camera; @@ -51,21 +50,35 @@ public class GamePanel extends JPanel implements Runnable, KeyListener{ public GamePanel() throws IOException, SpriteException, UnsupportedAudioFileException, LineUnavailableException { camera = new Camera(0); background = new BackgroundImage(0, 0, backgroundImage, GAME_WIDTH, GAME_HEIGHT, 10); - for (int i = 0; i < 11; i++) { - try { + try { + // load player sprites from disk here + for (int i = 0; i < 11; i++) { BufferedImage sprite = getImage(String.format("img/walk/p1_walk%s.png", String.format("%1$2s", i+1).replace(' ', '0'))); - spriteArray[1][0][i] = sprite; - spriteArray[1][1][i] = sprite; - spriteArray[0][0][i] = flipImageHorizontally(sprite); - spriteArray[0][1][i] = flipImageHorizontally(sprite); - - } catch (IOException e) { - e.printStackTrace(); + playerSpriteArray[1][0][i] = sprite; + playerSpriteArray[1][1][i] = sprite; + playerSpriteArray[0][0][i] = flipImageHorizontally(sprite); + playerSpriteArray[0][1][i] = flipImageHorizontally(sprite); } + // load slime sprites from disk here + // these variables were not defined above because they are temporary variables + BufferedImage[] temporarySlimeArray = {getImage("img/enemy/slime/slimeWalk1.png"), + getImage("img/enemy/slime/slimeWalk2.png"), + getImage("img/enemy/slime/slimeDead.png")}; + BufferedImage[] flippedTemporarySlimeArray = {flipImageHorizontally(getImage("img/enemy/slime/slimeWalk1.png")), + flipImageHorizontally(getImage("img/enemy/slime/slimeWalk2.png")), + flipImageHorizontally(getImage("img/enemy/slime/slimeDead.png"))}; + // please note that these sprites are reversed compared to the player sprites + slimeSpriteArray[0][0] = temporarySlimeArray; + slimeSpriteArray[0][1] = temporarySlimeArray; + slimeSpriteArray[1][0] = flippedTemporarySlimeArray; + slimeSpriteArray[1][1] = flippedTemporarySlimeArray; + } catch (IOException e) { + e.printStackTrace(); } - player = new Player(GAME_WIDTH/2, GAME_HEIGHT/2, 'W', 'A', 'S', 'D', spriteArray); //create a player controlled player, set start location to middle of screenk - // the height of 35 is set because it is half of the original tile height (i.e., 70px) + player = new Player(GAME_WIDTH/2, GAME_HEIGHT/2, 'W', 'A', 'S', 'D', playerSpriteArray); //create a player controlled player, set start location to middle of screenk + enemy.add(new NonPlayer(1200, 100, slimeSpriteArray, 50, 28)); + enemy.add(new NonPlayer(0, 100, slimeSpriteArray, 50, 28)); map.add(new SingleTile(1000, 500, box)); map.add(new SingleTile(700, 400, boxCoin)); map.add(new SingleTile(1000, 300, boxCoin)); @@ -91,15 +104,19 @@ public class GamePanel extends JPanel implements Runnable, KeyListener{ //we are using "double buffering here" - if we draw images directly onto the screen, it takes time and the human eye can actually notice flashes of lag as each pixel on the screen is drawn one at a time. Instead, we are going to draw images OFF the screen, then simply move the image on screen as needed. image = createImage(GAME_WIDTH, GAME_HEIGHT); //draw off screen graphics = image.getGraphics(); - draw(graphics, frame);//update the positions of everything on the screen + draw(graphics, playerFrame, enemyFrame);//update the positions of everything on the screen g.drawImage(image, 0, 0, this); //move the image on the screen } //call the draw methods in each class to update positions as things move - public void draw(Graphics g, int frame){ + public void draw(Graphics g, int playerFrame, int enemyFrame){ background.draw(g); - frameCounter += player.draw(g, frame); + playerFrameCounter += player.draw(g, playerFrame); + for (NonPlayer n: enemy) { + // this is a feature; the more enemies there are, the faster the enemies will (appear) to move + enemyFrameCounter += n.draw(g, enemyFrame); + } for(Tile i: map){ i.draw(g); } @@ -110,14 +127,22 @@ public class GamePanel extends JPanel implements Runnable, KeyListener{ //this method is constantly called from run(). By doing this, movements appear fluid and natural. If we take this out the movements appear sluggish and laggy public void move(){ player.move(); + for (NonPlayer n: enemy) { + n.move(); + } } //handles all collision detection and responds accordingly public void checkCollision() { player.isGrounded = false; - + for (NonPlayer n: enemy) { + n.isGrounded = false; + } for (Tile i : map) { - i.collide(player); + i.playerCollide(player); + for (NonPlayer n: enemy) { + i.npcCollide(n); + } } //force player to remain on screen if (player.y <= 0) { @@ -127,17 +152,22 @@ public class GamePanel extends JPanel implements Runnable, KeyListener{ player.y = GAME_HEIGHT - Player.PLAYER_HEIGHT; player.yVelocity = 0; player.isGrounded = true; - } - if (player.x <= 0) { - player.x = 0; - } - if (player.x <= 0) { - player.x = 0; - } - if (player.x + Player.PLAYER_WIDTH >= GAME_WIDTH) { - player.x = GAME_WIDTH - Player.PLAYER_WIDTH; + } + if (player.x <= 0) { + player.x = 0; + } + if (player.x + Player.PLAYER_WIDTH >= GAME_WIDTH) { + player.x = GAME_WIDTH - Player.PLAYER_WIDTH; + } + + for (NonPlayer n: enemy) { + if (n.y >= GAME_HEIGHT - n.npcHeight) { + n.y = GAME_HEIGHT - n.npcHeight; + n.yVelocity = 0; + n.isGrounded = true; } } + } //run() method is what makes the game continue running without end. It calls other methods to move objects, check for collision, and update the screen @@ -164,10 +194,15 @@ public class GamePanel extends JPanel implements Runnable, KeyListener{ move(); checkCollision(); repaint(); - if (frameCounter > 5) { + if (playerFrameCounter > 5) { // increment sprite image to be used and keeps it below 12 - frame = (frame + 1) % 11; - frameCounter -= 5; + playerFrame += 1; + playerFrameCounter -= 5; + } + if (enemyFrameCounter > 5) { + // increment sprite image to be used and keeps it below 12 + enemyFrame += 1; + enemyFrameCounter -= 5; } delta--; } diff --git a/src/GenericSprite.java b/src/GenericSprite.java index ce3c20d..f5c13a2 100644 --- a/src/GenericSprite.java +++ b/src/GenericSprite.java @@ -13,16 +13,19 @@ public class GenericSprite extends Rectangle{ public double xVelocity; public final double SPEED = 20; //movement speed of ball public final double speedCap = 5; //Speed cap of ball - public int WIDTH = 20; //size of ball - public int HEIGHT = 20; //size of ball + public int WIDTH; //size of ball + public int HEIGHT; //size of ball public boolean rightPressed = false; public boolean leftPressed = false; public boolean upPressed= false; public boolean downPressed = false; public boolean isGrounded = false; //constructor creates ball at given location with given dimensions + // TODO: reverse order of height and width public GenericSprite(int x, int y, int height, int width){ - super(x, y, height, width);WIDTH = width; HEIGHT = height; + super(x, y, width, height); + WIDTH = width; + HEIGHT = height; } //called from GamePanel when any keyboard input is detected @@ -41,12 +44,12 @@ public class GenericSprite extends Rectangle{ //called from GamePanel whenever a mouse click is detected //changes the current location of the ball to be wherever the mouse is located on the screen public void mousePressed(MouseEvent e){ - //x = e.getX(); - // y = e.getY(); + } public void move(){ + } public void capSpeed(){ @@ -61,8 +64,8 @@ public class GenericSprite extends Rectangle{ //called frequently from the GamePanel class //draws the current location of the ball to the screen public void draw(Graphics g){ - g.setColor(Color.black); - g.fillOval(x, y, WIDTH, HEIGHT); + + } } \ No newline at end of file diff --git a/src/NonPlayer.java b/src/NonPlayer.java new file mode 100644 index 0000000..d2818b9 --- /dev/null +++ b/src/NonPlayer.java @@ -0,0 +1,56 @@ +/* Eric Li, ICS4U, Completed 5/29/2022 + +Paddle class defines behaviours for the left and right player-controlled paddles */ + +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.UnsupportedAudioFileException; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; + +public class NonPlayer extends GenericSprite { + public final int SPEED = 3; + // please note that these are not static, in contrast to the player class, as different enemies will have different heights + public int npcWidth; + public int npcHeight; + public int currentXDirection, currentYDirection; + public boolean isDead; + + // private final Sound bump; + + public BufferedImage[][][] spriteArray; + public NonPlayer(int x, int y, BufferedImage[][][] sprites, int npcWidth, int npcHeight) throws UnsupportedAudioFileException, LineUnavailableException, IOException { + super(x, y, npcHeight, npcWidth); + // bump = new Sound("sound/bump.wav"); + spriteArray = sprites; + // TODO: remove + this.npcWidth = npcWidth; + WIDTH = npcWidth; + this.npcHeight = npcHeight; + HEIGHT = npcHeight; + + xVelocity = 3; + } + + public void move(){ + x += (int)xVelocity; + y += (int)yVelocity; + yVelocity+=0.3; + capSpeed(); + } + + public int draw(Graphics g, int frame) { + if (!isDead) { + // last frame is reserved for death animation + frame %= spriteArray[0][0].length - 1; + currentXDirection = (int)(Math.signum(xVelocity) + 1) / 2; + currentYDirection = (int)(Math.signum(yVelocity) + 1) / 2; + // x-GamePanel.camera.x is used as the camera doesn't follow NPCs + g.drawImage(spriteArray[currentXDirection][currentYDirection][frame], x-GamePanel.camera.x, y, null); + return 1; + } else { + g.drawImage(spriteArray[currentXDirection][currentYDirection][spriteArray[0][0].length-1], x-GamePanel.camera.x, y, null); + return 0; + } + } +} \ No newline at end of file diff --git a/src/Player.java b/src/Player.java index 9658853..156fdef 100644 --- a/src/Player.java +++ b/src/Player.java @@ -17,7 +17,7 @@ public class Player extends GenericSprite { public int lastXDirection, lastYDirection, lastFrame; public int upKey, downKey, rightKey, leftKey; - private Sound jump; + private final Sound jump; // sA[0] is -x, -y // sA[1] is x, -y // sA[2] is -x, y @@ -76,9 +76,8 @@ public class Player extends GenericSprite { } } - // calls parent public void move(){ - y = y + (int)yVelocity; + y += (int)yVelocity; GamePanel.camera.x = GamePanel.camera.x + (int)xVelocity; if(rightPressed){ xVelocity+=1; @@ -95,16 +94,8 @@ public class Player extends GenericSprite { capSpeed(); } - public void capSpeed(){ - if(xVelocity>speedCap){ - xVelocity = speedCap; - } else if(xVelocity<-1*speedCap) { - xVelocity = -1*speedCap; - } - } - public int draw(Graphics g, int frame) { - // g.setColor(Color.WHITE); + frame %= spriteArray[0][0].length; if (!upPressed && !downPressed && !leftPressed && !rightPressed) { g.drawImage(spriteArray[lastXDirection][lastYDirection][0], x, y, null); return 0; diff --git a/src/Tile.java b/src/Tile.java index ade3e1f..11a5144 100644 --- a/src/Tile.java +++ b/src/Tile.java @@ -10,8 +10,8 @@ public class Tile { this.y = y; } - //Actions when tile interacts with sprites - public void collide(GenericSprite s){ + //Actions when tile interacts with players + public void playerCollide(GenericSprite s){ int realX = x-GamePanel.camera.x; if(s.x+s.WIDTH>realX&s.x=y&&s.y<=y+length){ if(s.x+s.WIDTH>realX+20&&s.xrealX&s.x=y&&s.y<=y+length){ + if(s.x+s.WIDTH>realX+20&&s.xrealX+length-length/2){ + s.xVelocity *= -1; + } + } + } public void draw(Graphics g){ g.setColor(Color.black); g.fillRect(x-GamePanel.camera.x, y, length, length);