diff --git a/.idea/vcs.xml b/.idea/vcs.xml index d175698..4c0d94b 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -4,5 +4,6 @@ + \ No newline at end of file diff --git a/Game.iml b/Game.iml index 9d3a67c..7abfbed 100644 --- a/Game.iml +++ b/Game.iml @@ -22,6 +22,8 @@ + + diff --git a/src/main/java/io/github/twhscs/game/App.java b/src/main/java/io/github/twhscs/game/App.java index 3b1911a..c89b391 100644 --- a/src/main/java/io/github/twhscs/game/App.java +++ b/src/main/java/io/github/twhscs/game/App.java @@ -7,6 +7,7 @@ import org.jsfml.graphics.RenderWindow; import org.jsfml.graphics.View; import org.jsfml.system.Clock; +import org.jsfml.system.Vector2f; import org.jsfml.system.Vector2i; import org.jsfml.window.Keyboard; import org.jsfml.window.VideoMode; @@ -42,7 +43,13 @@ private App() { PLAYER = new Player(RESOURCE_MANAGER.getTexture("ryuk"), GAME_VIEW, TILE_SIZE, 4, 2); MAP = new Map(100, 100, TILE_SIZE, ZOOM, 25, RESOURCE_MANAGER.getTexture("tiles"), WINDOW); MAP.setPlayer(PLAYER); - + for (int i = 0; i < 10; i++) { + MAP.setEntity( + "test" + i, + new NonPlayableCharacter(RESOURCE_MANAGER.getTexture("ryuk"), TILE_SIZE, 4, 2) + ); + MAP.getEntity("test" + i).setPosition(new Vector2f(20,20)); + } // Start the main loop. run(); } @@ -118,7 +125,7 @@ private void processInput() { Vector2i size = event.asSizeEvent().size; GAME_VIEW.reset(new FloatRect(0.0f, 0.0f, size.x, size.y)); GAME_VIEW.zoom(ZOOM); - PLAYER.updateSprite(); + MAP.updateSprites(); break; case KEY_PRESSED: switch (event.asKeyEvent().key) { @@ -139,15 +146,13 @@ private void processInput() { private void update() { MAP.update(); - PLAYER.update(); } private void render(float positionBetweenUpdates) { WINDOW.setView(GAME_VIEW); WINDOW.clear(); - WINDOW.draw(MAP); PLAYER.interpolate(positionBetweenUpdates); - WINDOW.draw(PLAYER); + WINDOW.draw(MAP); WINDOW.display(); } } diff --git a/src/main/java/io/github/twhscs/game/Character.java b/src/main/java/io/github/twhscs/game/Character.java new file mode 100644 index 0000000..481098d --- /dev/null +++ b/src/main/java/io/github/twhscs/game/Character.java @@ -0,0 +1,126 @@ +package io.github.twhscs.game; + +import io.github.twhscs.game.util.Direction; +import io.github.twhscs.game.util.Position; +import org.jsfml.graphics.IntRect; +import org.jsfml.graphics.Texture; +import org.jsfml.system.Vector2f; +import org.jsfml.system.Vector2i; + +class Character extends Entity { + protected int ANIMATION_FRAMES; + protected int ANIMATION_SPEED; + protected float ANIMATION_STEP; + protected int animationFrame; + protected boolean animating; + + Character(Texture charTexture, int TILE_SIZE, int ANIMATION_FRAMES, int ANIMATION_SPEED) { + super(charTexture, TILE_SIZE); + super.SPRITE_SIZE = Vector2i.div(charTexture.getSize(), ANIMATION_FRAMES); + this.ANIMATION_FRAMES = ANIMATION_FRAMES; + this.ANIMATION_SPEED = ANIMATION_SPEED; + this.ANIMATION_STEP = 1.0f / (ANIMATION_FRAMES * ANIMATION_SPEED); + animationFrame = 0; + animating = false; + } + + @Override + public String toString() { + return "Character{" + + "SPRITE_SIZE=" + SPRITE_SIZE + + ", ANIMATION_FRAMES=" + ANIMATION_FRAMES + + ", ANIMATION_SPEED=" + ANIMATION_SPEED + + ", ANIMATION_STEP=" + ANIMATION_STEP + + ", position=" + position + + ", direction=" + direction + + ", animationFrame=" + animationFrame + + ", animating=" + animating + + '}'; + } + + @Override + public void updateSprite() { + // Calculate the sprite position by multiplying the map position by the tile size. + // Subtract half of the sprite width minus the tile size to center it horizontally. + // Subtract the sprite height minus the tile size to center it vertically. + Vector2f spritePosition = new Vector2f(position.x * TILE_SIZE - ((SPRITE_SIZE.x - TILE_SIZE) / 2.0f), position.y * TILE_SIZE - (SPRITE_SIZE.y - TILE_SIZE)); + // Round the position to prevent graphical errors. + spritePosition = Position.round(spritePosition); + // Update the sprite's position. + SPRITE.setPosition(spritePosition); + // Apply the appropriate texture based on direction and animation. + SPRITE.setTextureRect(getTextureRect()); + } + + public boolean nextPositionIsValid(Direction direction) { + // Calculate the position to move towards. + Vector2f newPosition = Position.getRelativePosition(position, direction, 1.0f); + return map.isValidPosition(newPosition); + } + + public void move(Direction direction) { + // TODO: Allow for faster movement. (Do not have to wait until current move is finished before initiating next move.) + // Only move the player if they are not already moving. + if (!animating) { + // Make sure the new position is valid. + if (nextPositionIsValid(direction)) { + // If it is valid, update the direction and start moving.. + super.direction = direction; + animating = true; + } + } + } + + @Override + public void update() { + // Check if the player is moving. + if (animating) { + // Move the player by the animation step. + super.position = Position.getRelativePosition(position, direction, ANIMATION_STEP); + // Check if it is time to stop moving. + if (animationFrame + 1 >= ANIMATION_FRAMES * ANIMATION_SPEED) { + // Reset the animation frame and stop moving. + animationFrame = 0; + animating = false; + // Round the position to prevent float rounding errors. + super.position = Position.round(position); + } else { + // If it is not time to stop, keep going. + animationFrame++; + } + // Update the sprite. + updateSprite(); + } + } + + private IntRect getTextureRect() { + // Normalize the current frame based on the amount of actual frames. + int adjustedFrame = Math.round((animationFrame * ANIMATION_FRAMES) / (ANIMATION_FRAMES * ANIMATION_SPEED)); + // Use math to calculate the player's current texture. + switch (super.direction) { + case NORTH: + return new IntRect(adjustedFrame * super.SPRITE_SIZE.x, 3 * super.SPRITE_SIZE.y, super.SPRITE_SIZE.x, super.SPRITE_SIZE.y); + case SOUTH: + return new IntRect(adjustedFrame * super.SPRITE_SIZE.x, 0, super.SPRITE_SIZE.x, super.SPRITE_SIZE.y); + case WEST: + return new IntRect(adjustedFrame * super.SPRITE_SIZE.x, super.SPRITE_SIZE.y, super.SPRITE_SIZE.x, super.SPRITE_SIZE.y); + case EAST: + return new IntRect(adjustedFrame * super.SPRITE_SIZE.x, 2 * super.SPRITE_SIZE.y, super.SPRITE_SIZE.x, super.SPRITE_SIZE.y); + default: + return new IntRect(0, 0, 0, 0); + } + } + + public void interpolate(float positionBetweenUpdates) { + if (animating) { + // Multiply the animation step by the position between frames (0.0f - 1.0f). + float interpolationStep = ANIMATION_STEP * positionBetweenUpdates; + // Get the current position. + Vector2f currentPosition = super.position; + // Temporarily update the position with the interpolation step applied, update the sprite, then revert the position. + super.position = Position.getRelativePosition(super.position, super.direction, interpolationStep); + updateSprite(); + super.position = currentPosition; + } + } +} diff --git a/src/main/java/io/github/twhscs/game/Entity.java b/src/main/java/io/github/twhscs/game/Entity.java new file mode 100644 index 0000000..f453934 --- /dev/null +++ b/src/main/java/io/github/twhscs/game/Entity.java @@ -0,0 +1,56 @@ +package io.github.twhscs.game; + +import io.github.twhscs.game.util.Direction; +import org.jsfml.graphics.*; +import org.jsfml.system.Vector2f; +import org.jsfml.system.Vector2i; + +abstract class Entity implements Drawable, Comparable { + protected Sprite SPRITE; + protected Vector2i SPRITE_SIZE; + protected int TILE_SIZE; + protected Vector2f position; + protected Direction direction; + protected Map map; + + Entity(Texture entityTexture, int TILE_SIZE) { + this.TILE_SIZE = TILE_SIZE; + SPRITE = new Sprite(entityTexture); + SPRITE_SIZE = new Vector2i(0,0); + position = new Vector2f(0.0f, 0.0f); + direction = Direction.NORTH; + } + + public Vector2f getPosition() { + return position; + } + + public void setPosition(Vector2f position) { + this.position = position; + } + + public void setMap(Map map) { + this.map = map; + } + + public void update() {} + + public void updateSprite() {} + + public void draw(RenderTarget renderTarget, RenderStates renderStates) { + renderTarget.draw(SPRITE); + } + + public String toString() { + return "Entity{" + + "SPRITE_SIZE=" + SPRITE_SIZE + + ", position=" + position + + ", direction=" + direction + + '}'; + } + + public int compareTo(Entity compareEntity) { + int comparePosition = (int)((Entity) compareEntity).getPosition().y; + return (int)this.getPosition().y - comparePosition; + } +} diff --git a/src/main/java/io/github/twhscs/game/Map.java b/src/main/java/io/github/twhscs/game/Map.java index 170d6b7..933218a 100644 --- a/src/main/java/io/github/twhscs/game/Map.java +++ b/src/main/java/io/github/twhscs/game/Map.java @@ -4,9 +4,7 @@ import org.jsfml.system.Vector2f; import org.jsfml.system.Vector2i; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; +import java.util.*; class Map implements Drawable { private final Vector2i DIMENSIONS; @@ -19,6 +17,7 @@ class Map implements Drawable { private final int TOTAL_CHUNKS; private final int X_CHUNKS; private final VertexArray[] VERTEX_ARRAYS; + private final HashMap ENTITIES; private Player player; @@ -37,12 +36,23 @@ class Map implements Drawable { // Calculate the total amount of chunks. TOTAL_CHUNKS = X_CHUNKS * yChunks; VERTEX_ARRAYS = new VertexArray[TOTAL_CHUNKS]; + ENTITIES = new HashMap(); // Load the tiles into the map. load(); } + public void setEntity(String name, Entity entity) { + ENTITIES.put(name, entity); + entity.setMap(this); + } + + public Entity getEntity(String name) { + return ENTITIES.get(name); + } + public void setPlayer(Player player) { this.player = player; + ENTITIES.put("player", player); player.setMap(this); } @@ -87,8 +97,24 @@ private int positionToChunkID(Vector2f position) { return ((int) position.x / CHUNK_SIZE) + (((int) position.y / CHUNK_SIZE) * X_CHUNKS); } + public boolean isOccupiedPosition(Vector2f position) { + if (player != null) { + Vector2f playerDifference = Vector2f.sub(position, player.getPosition()); + if ((Math.abs(playerDifference.x) < 1.0f && Math.abs(playerDifference.y) < 1.0f)) { + return true; + } + } + for (Entity e : ENTITIES.values()) { + Vector2f entityDifference = Vector2f.sub(position, e.getPosition()); + if ((Math.abs(entityDifference.x) < 1.0f && Math.abs(entityDifference.y) < 1.0f)) { + return true; + } + } + return false; + } + public boolean isValidPosition(Vector2f position) { - return position.x >= 0.0f && position.y >= 0.0f && position.x < DIMENSIONS.x && position.y < DIMENSIONS.y; + return position.x >= 0.0f && position.y >= 0.0f && position.x < DIMENSIONS.x && position.y < DIMENSIONS.y && !isOccupiedPosition(position); } private void partition() { @@ -152,8 +178,20 @@ private void partition() { } } - public void update() { + public void updateEntities() { + for (Entity e : ENTITIES.values()) { + e.update(); + } + } + public void updateSprites() { + for (Entity e : ENTITIES.values()) { + e.updateSprite(); + } + } + + public void update() { + updateEntities(); } @@ -190,5 +228,10 @@ public void draw(RenderTarget renderTarget, RenderStates renderStates) { } } } + List drawList = new ArrayList(ENTITIES.values()); + Collections.sort(drawList); + for (Entity e : drawList) { + WINDOW.draw(e); + } } } diff --git a/src/main/java/io/github/twhscs/game/NonPlayableCharacter.java b/src/main/java/io/github/twhscs/game/NonPlayableCharacter.java new file mode 100644 index 0000000..0e77767 --- /dev/null +++ b/src/main/java/io/github/twhscs/game/NonPlayableCharacter.java @@ -0,0 +1,93 @@ +package io.github.twhscs.game; + +import io.github.twhscs.game.util.Direction; +import org.jsfml.graphics.*; +import org.jsfml.system.Vector2f; +import org.jsfml.system.Vector2i; + +import java.util.*; + +class NonPlayableCharacter extends Character { + + NonPlayableCharacter(Texture npcTexture, int TILE_SIZE, int ANIMATION_FRAMES, int ANIMATION_SPEED) { + super(npcTexture, TILE_SIZE, ANIMATION_FRAMES, ANIMATION_SPEED); + updateSprite(); + } + + @Override + public String toString() { + return "NonPlayableCharacter{" + + "SPRITE_SIZE=" + SPRITE_SIZE + + ", ANIMATION_FRAMES=" + ANIMATION_FRAMES + + ", ANIMATION_SPEED=" + ANIMATION_SPEED + + ", ANIMATION_STEP=" + ANIMATION_STEP + + ", position=" + position + + ", direction=" + direction + + ", animationFrame=" + animationFrame + + ", animating=" + animating + + '}'; + } + + public void randomlyChooseMove() { + if ((int) (Math.random() * 10) == 1) { + for (int i = 0; i < 10; i++) { + Direction newDirection = Direction.getRandomCardinalDirection(); + if (nextPositionIsValid(newDirection)) { + this.move(newDirection); + } + } + } + } + + public LinkedList findPath(Vector2f targetPosition) { + Vector2i[] xyOffsets = {new Vector2i(-1, 0), new Vector2i(1, 0), new Vector2i(0, -1), new Vector2i(0, 1)}; + Vector2i startPos = new Vector2i(position); + Vector2i endPos = new Vector2i(targetPosition); + LinkedList openList = new LinkedList(); + LinkedList closedList = new LinkedList(); + LinkedList finalList = new LinkedList(); + HashMap cameFrom = new HashMap(); + openList.add(startPos); + Vector2i currentPos = startPos; + int loopCount = 0; + while (!openList.isEmpty() && loopCount < 10000) { + loopCount++; + int lowestF = Integer.MAX_VALUE; + for (Vector2i currentVector : openList) { + int f = (int) Math.sqrt(Math.pow((startPos.x - endPos.x), 2) + Math.pow((startPos.y - endPos.y), 2)); + if (f < lowestF) { + lowestF = f; + currentPos = currentVector; + } + } + if (currentPos == endPos) { + while (currentPos != startPos) { + finalList.addFirst(currentPos); + currentPos = cameFrom.get(currentPos); + } + return finalList; + } + openList.remove(currentPos); + closedList.add(currentPos); + for (Vector2i xyOffset : xyOffsets) { + Vector2i y = Vector2i.add(currentPos, xyOffset); + if (!map.isValidPosition(new Vector2f(y)) && closedList.contains(y)) { + continue; + } + if (!openList.contains(y)) { + openList.add(y); + cameFrom.put(y, currentPos); + } + } + } + return null; + } + + @Override + public void update() { + //This randomly chooses when and where to move + //We will hopefully replace this with path finding or something more advanced + randomlyChooseMove(); + super.update(); + } +} diff --git a/src/main/java/io/github/twhscs/game/Player.java b/src/main/java/io/github/twhscs/game/Player.java index 8c4b205..0789dae 100644 --- a/src/main/java/io/github/twhscs/game/Player.java +++ b/src/main/java/io/github/twhscs/game/Player.java @@ -1,154 +1,33 @@ package io.github.twhscs.game; -import io.github.twhscs.game.util.Direction; -import io.github.twhscs.game.util.Position; import org.jsfml.graphics.*; import org.jsfml.system.Vector2f; -import org.jsfml.system.Vector2i; -class Player implements Drawable { - private final Sprite SPRITE; +class Player extends Character { private final View GAME_VIEW; - private final Vector2i SPRITE_SIZE; - private final int TILE_SIZE; - private final int ANIMATION_FRAMES; - private final int ANIMATION_SPEED; - private final float ANIMATION_STEP; - private Vector2f position; - private Direction direction; - private int animationFrame; - private boolean animating; - private Map map; Player(Texture playerTexture, View GAME_VIEW, int TILE_SIZE, int ANIMATION_FRAMES, int ANIMATION_SPEED) { - // Create a new sprite with the specified texture. - this.SPRITE = new Sprite(playerTexture); + super(playerTexture, TILE_SIZE, ANIMATION_FRAMES, ANIMATION_SPEED); this.GAME_VIEW = GAME_VIEW; - // Calculate the sprite size by dividing the texture size by the number of animations. - SPRITE_SIZE = Vector2i.div(playerTexture.getSize(), ANIMATION_FRAMES); - this.TILE_SIZE = TILE_SIZE; - this.ANIMATION_FRAMES = ANIMATION_FRAMES; - this.ANIMATION_SPEED = ANIMATION_SPEED; - // Calculate the animation step by taking the reciprocal of the frame count times speed. - ANIMATION_STEP = 1.0f / (ANIMATION_FRAMES * ANIMATION_SPEED); - position = new Vector2f(0.0f, 0.0f); - direction = Direction.NORTH; - animationFrame = 0; - animating = false; - // Initialize the sprite. updateSprite(); } @Override public String toString() { return "Player{" + - "SPRITE_SIZE=" + SPRITE_SIZE + + "SPRITE_SIZE=" + super.SPRITE_SIZE + ", ANIMATION_FRAMES=" + ANIMATION_FRAMES + ", ANIMATION_SPEED=" + ANIMATION_SPEED + ", ANIMATION_STEP=" + ANIMATION_STEP + - ", position=" + position + - ", direction=" + direction + + ", position=" + super.position + + ", direction=" + super.direction + ", animationFrame=" + animationFrame + ", animating=" + animating + '}'; } - public Vector2f getPosition() { - return position; - } - - public void setPosition(Vector2f position) { - this.position = position; - } - public void updateSprite() { - // Calculate the sprite position by multiplying the map position by the tile size. - // Subtract half of the sprite width minus the tile size to center it horizontally. - // Subtract the sprite height minus the tile size to center it vertically. - Vector2f spritePosition = new Vector2f(position.x * TILE_SIZE - ((SPRITE_SIZE.x - TILE_SIZE) / 2.0f), position.y * TILE_SIZE - (SPRITE_SIZE.y - TILE_SIZE)); - // Round the position to prevent graphical errors. - spritePosition = Position.round(spritePosition); - // Update the sprite's position. - SPRITE.setPosition(spritePosition); - // Apply the appropriate texture based on direction and animation. - SPRITE.setTextureRect(getTextureRect()); - // Add half of the sprite's width and height to the view in order to center the sprite. - GAME_VIEW.setCenter(Vector2f.add(spritePosition, Vector2f.div(new Vector2f(SPRITE_SIZE), 2.0f))); - } - - public void move(Direction direction) { - // TODO: Allow for faster movement. (Do not have to wait until current move is finished before initiating next move.) - // Only move the player if they are not already moving. - if (!animating) { - // Calculate the position to move towards. - Vector2f newPosition = Position.getRelativePosition(position, direction, 1.0f); - // Make sure the new position is valid. - if (map.isValidPosition(newPosition)) { - // If it is valid, update the direction and start moving.. - this.direction = direction; - animating = true; - } - } - } - - public void update() { - // Check if the player is moving. - if (animating) { - // Move the player by the animation step. - position = Position.getRelativePosition(position, direction, ANIMATION_STEP); - // Check if it is time to stop moving. - if (animationFrame + 1 >= ANIMATION_FRAMES * ANIMATION_SPEED) { - // Reset the animation frame and stop moving. - animationFrame = 0; - animating = false; - // Round the position to prevent float rounding errors. - position = Position.round(position); - } else { - // If it is not time to stop, keep going. - animationFrame++; - } - // Update the sprite. - updateSprite(); - } - } - - public void setMap(Map map) { - this.map = map; - } - - private IntRect getTextureRect() { - // Normalize the current frame based on the amount of actual frames. - int adjustedFrame = Math.round((animationFrame * ANIMATION_FRAMES) / (ANIMATION_FRAMES * ANIMATION_SPEED)); - // Use math to calculate the player's current texture. - switch (direction) { - case NORTH: - return new IntRect(adjustedFrame * SPRITE_SIZE.x, 3 * SPRITE_SIZE.y, SPRITE_SIZE.x, SPRITE_SIZE.y); - case SOUTH: - return new IntRect(adjustedFrame * SPRITE_SIZE.x, 0, SPRITE_SIZE.x, SPRITE_SIZE.y); - case WEST: - return new IntRect(adjustedFrame * SPRITE_SIZE.x, SPRITE_SIZE.y, SPRITE_SIZE.x, SPRITE_SIZE.y); - case EAST: - return new IntRect(adjustedFrame * SPRITE_SIZE.x, 2 * SPRITE_SIZE.y, SPRITE_SIZE.x, SPRITE_SIZE.y); - default: - return new IntRect(0, 0, 0, 0); - } - } - - @Override - public void draw(RenderTarget renderTarget, RenderStates renderStates) { - renderTarget.draw(SPRITE); - } - - public void interpolate(float positionBetweenUpdates) { - if (animating) { - // Multiply the animation step by the position between frames (0.0f - 1.0f). - float interpolationStep = ANIMATION_STEP * positionBetweenUpdates; - // Get the current position. - Vector2f currentPosition = position; - // Temporarily update the position with the interpolation step applied, update the sprite, then revert the position. - position = Position.getRelativePosition(position, direction, interpolationStep); - updateSprite(); - position = currentPosition; - } + super.updateSprite(); + GAME_VIEW.setCenter(Vector2f.add(SPRITE.getPosition(), Vector2f.div(new Vector2f(SPRITE_SIZE), 2.0f))); } } diff --git a/src/main/java/io/github/twhscs/game/util/Direction.java b/src/main/java/io/github/twhscs/game/util/Direction.java index 0b52a0b..afbc6e8 100644 --- a/src/main/java/io/github/twhscs/game/util/Direction.java +++ b/src/main/java/io/github/twhscs/game/util/Direction.java @@ -1,5 +1,9 @@ package io.github.twhscs.game.util; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** * Enumeration of the eight cardinal and ordinal directions. */ @@ -34,4 +38,20 @@ public static Direction getOppositeDirection(Direction direction) { return null; } } + + /** + * Gets a random direction. + * @return a random direction. + */ + public static Direction getRandomDirection() { + return Direction.values()[(int)(Math.random() * 8)]; + } + + /** + * Gets a random cardinal direction. + * @return a random cardinal direction. + */ + public static Direction getRandomCardinalDirection() { + return Direction.values()[(int)(Math.random() * 4)]; + } }