This commit is contained in:
m-mun
2026-09-04 12:52:46 +09:00
commit e905477c8d
54 changed files with 2813 additions and 0 deletions

14
TODO Normal file
View File

@@ -0,0 +1,14 @@
タイルの当たり判定の実装 - DONE
└バグ修正 - DONE
敵の生成 - WIP
└複数をランダムの位置に生成 - DONE
└キャラクタ同士の当たり判定 - WIP
└敵の行動AI - WIP
UIの実装
┗ステータスの表示 - DONE
┗ボタンの実装 - WIP
┗セリフの表示 - NONE
使用素材
https://absuredmrsteel.itch.io/16x16-home-interior-pixel-art
https://opengameart.org/content/rpg-asset-character-prince-nes

1
build.bat Normal file
View File

@@ -0,0 +1 @@
javac -Xlint:deprecation -Xdiags:verbose -Xlint:unchecked -deprecation -d bin -encoding utf8 -classpath res -sourcepath src src/Launcher.java

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
res/default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.8" tiledversion="1.8.2" name="House_Tileset" tilewidth="16" tileheight="16" tilecount="54" columns="9">
<image source="House_Tileset.png" width="144" height="96"/>
</tileset>

100
res/tileset/map1.tmx Normal file
View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="20" height="20" tilewidth="16" tileheight="16" infinite="0" nextlayerid="5" nextobjectid="1">
<tileset firstgid="1" source="House_Tileset.tsx"/>
<layer id="1" name="タイルレイヤー1" width="20" height="20" visible="0">
<data encoding="csv">
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
</data>
</layer>
<layer id="2" name="タイルレイヤー2" width="20" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,20,0,0,0,27,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,36,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,0,0,0,0,
0,0,0,0,0,0,0,49,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,40,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,37,38,39,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,46,47,48,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,23,0,0,0,0,0,0,0,0,0,0,
0,0,0,28,29,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="3" name="タイルレイヤー3" width="20" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="4" name="タイルレイヤー4" width="20" height="20">
<data encoding="csv">
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,
1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
</data>
</layer>
</map>

1
run.bat Normal file
View File

@@ -0,0 +1 @@
java -classpath ./res;./bin Launcher

12
src/Launcher.java Normal file
View File

@@ -0,0 +1,12 @@
import game.GameLoop;
public class Launcher {
private Launcher() {
Thread thread = new Thread(new GameLoop());
thread.start();
}
public static void main(String[] args) {
new Launcher();
}
}

8
src/ai/AICondition.java Normal file
View File

@@ -0,0 +1,8 @@
package ai;
import entity.Entity;
import state.State;
public interface AICondition {
boolean isMet(State state, Entity entity);
}

38
src/ai/AIManager.java Normal file
View File

@@ -0,0 +1,38 @@
package ai;
import ai.state.AIState;
import ai.state.Stand;
import ai.state.Wander;
import entity.Entity;
import state.State;
public class AIManager {
private AIState currentAIState;
public AIManager() {
transitionTo("stand");
}
public AIState getCurrentAIState() {
return currentAIState;
}
private void transitionTo(String nextState) {
switch (nextState) {
case "wander":
currentAIState = new Wander();
break;
case "stand":
currentAIState = new Stand();
break;
default:
}
}
public void update(State state, Entity entity) {
currentAIState.update(state, entity);
if (currentAIState.shouldTransition(state, entity)) {
transitionTo(currentAIState.getNextState());
}
}
}

22
src/ai/AITransition.java Normal file
View File

@@ -0,0 +1,22 @@
package ai;
import entity.Entity;
import state.State;
public class AITransition {
private String nextState;
private AICondition condition;
public AITransition(String nextState, AICondition condition) {
this.nextState = nextState;
this.condition = condition;
}
public boolean shouldTransition(State state, Entity entity) {
return condition.isMet(state, entity);
}
public String getNextState() {
return nextState;
}
}

25
src/ai/state/AIState.java Normal file
View File

@@ -0,0 +1,25 @@
package ai.state;
import ai.AITransition;
import entity.Entity;
import state.State;
public abstract class AIState {
private AITransition transition;
public AIState() {
transition = initTransition();
}
protected abstract AITransition initTransition();
public String getNextState() {
return transition.getNextState();
}
public boolean shouldTransition(State state, Entity entity) {
return transition.shouldTransition(state, entity);
}
public abstract void update(State state, Entity entity);
}

24
src/ai/state/Stand.java Normal file
View File

@@ -0,0 +1,24 @@
package ai.state;
import ai.AITransition;
import entity.Entity;
import game.GameLoop;
import state.State;
public class Stand extends AIState {
private int alive;
public Stand() {
this.alive = 0;
}
@Override
public AITransition initTransition() {
return new AITransition("wander", ((state, eneity) -> alive >= GameLoop.ups * 3));
}
@Override
public void update(State state, Entity entity) {
alive++;
}
}

38
src/ai/state/Wander.java Normal file
View File

@@ -0,0 +1,38 @@
package ai.state;
import ai.AITransition;
import controller.EnemyController;
import core.Position;
import entity.Entity;
import state.State;
public class Wander extends AIState {
private Position target;
public Wander() {
super();
}
private boolean arrived(Entity entity) {
return target != null && entity.getPosition().isInRangeOf(target);
}
@Override
protected AITransition initTransition() {
return new AITransition("stand", ((state, entity) -> arrived(entity)));
}
@Override
public void update(State state, Entity entity) {
EnemyController controller = (EnemyController)entity.getController();
if (target == null) {
target = state.getGameMap().findRandomPosition();
}
if (arrived(entity)) {
controller.stop();
}
if (target != null) {
controller.moveToTarget(target, entity.getPosition());
}
}
}

View File

@@ -0,0 +1,43 @@
package controller;
import core.Position;
public class EnemyController implements EntityController {
private boolean up;
private boolean down;
private boolean right;
private boolean left;
public boolean isRequestKeyAction() {
return false;
}
public boolean isRequestKeyDown() {
return down;
}
public boolean isRequestKeyLeft() {
return left;
}
public boolean isRequestKeyRight() {
return right;
}
public boolean isRequestKeyUp() {
return up;
}
public void moveToTarget(Position target, Position current) {
double deltaX = target.getX() - current.getX();
double deltaY = target.getY() - current.getY();
up = deltaY < 0 && Math.abs(deltaY) > Position.PROXIMITY_RANGE;
right = deltaX > 0 && Math.abs(deltaX) > Position.PROXIMITY_RANGE;
down = deltaY > 0 && Math.abs(deltaY) > Position.PROXIMITY_RANGE;
left = deltaX < 0 && Math.abs(deltaX) > Position.PROXIMITY_RANGE;
}
public void stop() {
up = down = left = right = false;
}
}

View File

@@ -0,0 +1,9 @@
package controller;
public interface EntityController {
boolean isRequestKeyAction();
boolean isRequestKeyDown();
boolean isRequestKeyLeft();
boolean isRequestKeyRight();
boolean isRequestKeyUp();
}

58
src/core/Position.java Normal file
View File

@@ -0,0 +1,58 @@
package core;
public class Position {
public static final int PROXIMITY_RANGE = 3;
private double x;
private double y;
public Position() {
x = 0;
y = 0;
}
public Position(int x, int y) {
this.x = x;
this.y = y;
}
public Position(double x, double y) {
this.x = x;
this.y = y;
}
public Position(Position position) {
x = position.getX();
y = position.getY();
}
public void add(Vector2D vector) {
x += vector.getX();
y += vector.getY();
}
public boolean isInRangeOf(Position position) {
return Math.abs(x - position.getX()) < PROXIMITY_RANGE && Math.abs(y - position.getY()) < PROXIMITY_RANGE;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
@Override
public String toString() {
return "POSITION: (X: " + x + ", Y: " + y + ")";
}
}

40
src/core/Size.java Normal file
View File

@@ -0,0 +1,40 @@
package core;
public class Size {
private int height;
private int width;
public Size() {
width = 0;
height = 0;
}
public Size(int width, int height) {
this.width = width;
this.height = height;
}
public Size(Size size) {
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
public void setHeight(int height) {
this.height = height;
}
public void setWidth(int width) {
this.width = width;
}
@Override
public String toString() {
return "SIZE: (W: " + width + ", H: " + height + ")";
}
}

72
src/core/Vector2D.java Normal file
View File

@@ -0,0 +1,72 @@
package core;
public class Vector2D {
private double x;
private double y;
public Vector2D() {
this(0, 0);
}
public Vector2D(double x, double y) {
this.x = x;
this.y = y;
}
public static Vector2D copyOf(Vector2D vector) {
return new Vector2D(vector.getX(), vector.getY());
}
public static Vector2D directionBetweenPositions(Position start, Position end) {
Vector2D direction = new Vector2D(start.getX() - end.getX(), start.getY() - end.getY());
direction.normalize();
return direction;
}
public static double dotProduct(Vector2D v1, Vector2D v2) {
return v1.getX() * v2.getX() + v1.getY() * v2.getY();
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double length() {
return Math.sqrt(x * x + y * y);
}
public void multiply(double speed) {
x *= speed;
y *= speed;
}
public void normalize() {
double length = length();
x = x == 0 ? 0 : x/length;
y = y == 0 ? 0 : y/length;
}
public void rotateByDegrees(int degrees) {
double radians = Math.toRadians(degrees);
double cosAngle = Math.cos(radians);
double sinAngle = Math.sin(radians);
double rotatedX = cosAngle * x - sinAngle * y;
double rotatedY = sinAngle * x + cosAngle * y;
x = rotatedX;
y = rotatedY;
}
public void setVector(double x, double y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "VECTOR: (X: " + x + ", Y: " + y + ")";
}
}

View File

@@ -0,0 +1,74 @@
package display;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import core.Position;
import core.Size;
import entity.creature.Enemy;
import entity.creature.Player;
import game.GameLoop;
import map.GameMap;
import state.PlayState;
import state.State;
public class DebugRenderer {
public void renderDebug(Graphics2D g, State state) {
renderMapDataCollisionBox(g, state);
renderDebugCollisionBox(g, state);
}
private void renderDebugCollisionBox(Graphics2D g, State state) {
g.setColor(Color.RED);
g.setStroke(new BasicStroke(2));
if (state instanceof PlayState playState) {
Position cp = state.getGameCamera().getPosition();
Player p = playState.getPlayer();
int scale = state.getGameMap().getScale();
playState.getEntities().stream().forEach(entity -> {
g.drawRect(
(int)(entity.getCollisionBox().getX() - cp.getX()), (int)(entity.getCollisionBox().getY() - cp.getY()),
(int)entity.getCollisionBox().getWidth(), (int)entity.getCollisionBox().getHeight());
if (entity instanceof Enemy enemy) {
g.setFont(new Font(Font.MONOSPACED, Font.BOLD, 30));
g.setColor(Color.GRAY);
g.drawString(enemy.getCurrentState(), (int)(entity.getX() - cp.getX() + 2), (int)(entity.getY() - 8 - cp.getY()));
g.setColor(Color.WHITE);
g.drawString(enemy.getCurrentState(), (int)(entity.getX() - cp.getX()), (int)(entity.getY() - 10 - cp.getY()));
g.setColor(Color.RED);
}
});
g.drawRect(
(int)(p.getCollisionBox().getX() - cp.getX()), (int)(p.getCollisionBox().getY() - cp.getY()),
(int)p.getCollisionBox().getWidth(), (int)p.getCollisionBox().getHeight());
}
}
private void renderMapDataCollisionBox(Graphics2D g, State state) {
Color color = Color.RED;
GameMap gm = state.getGameMap();
Size tileSize = gm.getTileSize();
Position cp = state.getGameCamera().getPosition();
int scale = gm.getScale();
int[][] mapData = gm.getMapData().get(GameMap.MAP_LAYER_4);
for (int y = 0; y < mapData.length; y++) {
for (int x = 0; x < mapData[y].length; x++) {
color = mapData[y][x] == 0 ? Color.YELLOW : Color.GREEN;
g.setColor(color);
g.drawRect(x * tileSize.getWidth() * scale - (int) cp.getX(), y * tileSize.getHeight() * scale - (int) cp.getY(),
tileSize.getWidth() * scale, tileSize.getHeight() * scale);
}
}
if (state instanceof PlayState playState) {
if (gm.getTileRect().x != -1) {
Player player = playState.getPlayer();
g.setColor(player.isWalkable() ? Color.BLUE : Color.RED);
g.setStroke(new BasicStroke(3));
g.drawRect(gm.getTileRect().x - (int) cp.getX(), gm.getTileRect().y - (int) cp.getY(), tileSize.getWidth() * scale, tileSize.getHeight() * scale);
g.drawRect(gm.getTileRect2().x - (int) cp.getX(), gm.getTileRect2().y - (int) cp.getY(), tileSize.getWidth() * scale, tileSize.getHeight() * scale);
}
}
}
}

82
src/display/Display.java Normal file
View File

@@ -0,0 +1,82 @@
package display;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
import core.Size;
import input.Keyboard;
import input.Mouse;
import state.State;
public class Display extends JFrame {
public static boolean debug;
private Canvas canvas;
private Keyboard keyboard;
private Mouse mouse;
private Renderer renderer;
private DebugRenderer debugRenderer;
public Display(Keyboard keyboard, Mouse mouse, String title, Size windowSize) {
super(title);
this.keyboard = keyboard;
this.mouse = mouse;
renderer = new Renderer();
debugRenderer = new DebugRenderer();
createFrame(windowSize);
createCanvas(windowSize);
add(canvas);
pack();
setVisible(true);
canvas.createBufferStrategy(2);
addKeyListener(this.keyboard);
canvas.addKeyListener(this.keyboard);
canvas.addMouseListener(this.mouse);
canvas.addMouseMotionListener(this.mouse);
canvas.setFocusable(true);
}
private void createCanvas(Size windowSize) {
GraphicsConfiguration config = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
canvas = new Canvas(config);
Dimension dimension = new Dimension(windowSize.getWidth(), windowSize.getHeight());
canvas.setPreferredSize(dimension);
canvas.setMaximumSize(dimension);
canvas.setMinimumSize(dimension);
canvas.setBackground(Color.BLACK);
}
private void createFrame(Size windowSize) {
setSize(windowSize.getWidth(), windowSize.getHeight());
setUndecorated(true);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
public Size getCanvasSize() {
return new Size(canvas.getWidth(), canvas.getHeight());
}
public void render(State state) {
BufferStrategy bs = canvas.getBufferStrategy();
if (bs == null) {
canvas.createBufferStrategy(2);
return;
}
Graphics2D g = (Graphics2D) bs.getDrawGraphics();
g.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
renderer.render(g, state);
if (debug) {
debugRenderer.renderDebug(g, state);
}
g.dispose();
bs.show();
}
}

View File

@@ -0,0 +1,55 @@
package display;
import java.awt.Rectangle;
import java.util.Optional;
import core.Position;
import core.Size;
import entity.creature.Player;
import state.State;
public class GameCamera {
private Optional<Player> focusObject;
private Position position;
private Size windowSize;
private Rectangle viewBounds;
public GameCamera(Size windowSize) {
this.windowSize = windowSize;
position = new Position();
focusObject = Optional.empty();
calculateViewBounds();
}
private void calculateViewBounds() {
viewBounds = new Rectangle((int) position.getX(), (int) position.getY(), windowSize.getWidth(), windowSize.getHeight());
}
public void focusOn(Player player) {
focusObject = Optional.of(player);
}
public boolean isInView(Player player) {
return viewBounds.intersects(player.getX(), player.getY(), player.getWidth(), player.getHeight());
}
public Position getPosition() {
return position;
}
public Size getWindowSize() {
return windowSize;
}
public void setPosition(Position position) {
this.position = position;
}
public void update(State state) {
if (focusObject.isPresent()) {
Position objectPosition = focusObject.get().getPosition();
position.setX(objectPosition.getX() - windowSize.getWidth() / 2);
position.setY(objectPosition.getY() - windowSize.getHeight() / 2);
}
}
}

86
src/display/Renderer.java Normal file
View File

@@ -0,0 +1,86 @@
package display;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.List;
import core.Position;
import core.Size;
import entity.creature.Player;
import map.GameMap;
import state.PlayState;
import state.State;
public class Renderer {
public void render(Graphics2D g, State state) {
renderGameMap(g, state);
renderGameObjects(g, state);
renderUI(g, state);
}
private void renderGameMap(Graphics2D g, State state) {
GameMap gameMap = state.getGameMap();
List<BufferedImage> tileset = gameMap.getTileSet();
Size stageSize = gameMap.getStageSize();
GameCamera camera = state.getGameCamera();
for (int i = 0; i < GameMap.NUMBER_OF_MAP_LAYER - 1; i++) {
for (int y = 0; y < stageSize.getHeight(); y++) {
for (int x = 0; x < stageSize.getWidth(); x++) {
int id = gameMap.getMapDataCell(x, y, i);
if (id >= 1) {
Size tileSize = gameMap.getTileSize();
int scale = gameMap.getScale();
Position cp = camera.getPosition();
g.drawImage(
tileset.get(id - 1),
x * tileSize.getWidth() * scale - (int) cp.getX(), y * tileSize.getHeight() * scale - (int) cp.getY(),
tileSize.getWidth() * scale, tileSize.getHeight() * scale,
null);
}
}
}
}
}
private void renderGameObjects(Graphics2D g, State state) {
GameMap gameMap = state.getGameMap();
if (state instanceof PlayState playState) {
GameCamera camera = state.getGameCamera();
Position cp = camera.getPosition();
Player player = playState.getPlayer();
playState.getEntities().stream().forEach(entity -> {
g.drawImage(
entity.getCurrentImage(),
(int) entity.getX() - (int) cp.getX(),
(int) entity.getY() - (int) cp.getY(),
gameMap.getTileSize().getWidth() * gameMap.getScale(),
gameMap.getTileSize().getHeight() * gameMap.getScale(),
null);
});
g.drawImage(
player.getCurrentImage(),
(int) player.getX() - (int) cp.getX(),
(int) player.getY() - (int) cp.getY(),
gameMap.getTileSize().getWidth() * gameMap.getScale(),
gameMap.getTileSize().getHeight() * gameMap.getScale(),
null);
}
}
private void renderUI(Graphics2D g, State state) {
state.getUiContainers().forEach(uiContainer ->
g.drawImage(
uiContainer.getSprite(),
(int)uiContainer.getRelativePosition().getX(),
(int)uiContainer.getRelativePosition().getY(),
null));
if (Display.debug) {
state.getUiDebugContainers().forEach(uiContainer ->
g.drawImage(
uiContainer.getSprite(),
(int)uiContainer.getRelativePosition().getX(),
(int)uiContainer.getRelativePosition().getY(),
null));
}
}
}

199
src/entity/Entity.java Normal file
View File

@@ -0,0 +1,199 @@
package entity;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import controller.EnemyController;
import controller.EntityController;
import core.Position;
import core.Size;
import core.Vector2D;
import entity.creature.Direction;
import gfx.Animation;
import gfx.SpriteSheet;
import input.Keyboard;
import map.GameMap;
import state.PlayState;
import state.State;
public abstract class Entity {
private static final int DISTANCE_TO_ENTITY = 100;
protected GameMap gameMap;
protected Position position;
protected Size size;
protected Vector2D vector;
protected Keyboard keyboard;
protected Map<Direction, Animation> animations;
protected Animation currentAnimation;
protected Direction direction;
protected Rectangle collisionBox;
protected double walkSpeed;
protected EntityController controller;
public Entity(GameMap gameMap, Keyboard keyboard, Position position, Size size, String fileName, int tileWidth, int tileHeight) {
this.gameMap = gameMap;
this.position = position;
this.size = size;
this.keyboard = keyboard;
vector = new Vector2D();
SpriteSheet spriteSheet = new SpriteSheet(fileName, tileWidth, tileHeight);
registerAnimations(spriteSheet);
currentAnimation = animations.get(Direction.DOWN);
direction = Direction.STOP;
collisionBox = new Rectangle((int)position.getX(), (int)position.getY(),
size.getWidth() * gameMap.getScale(), size.getHeight() * gameMap.getScale());
walkSpeed = 6;
}
protected void changeAnimation(Direction direction) {
currentAnimation.start();
if (!currentAnimation.equals(animations.get(direction))) {
currentAnimation = animations.get(direction);
}
}
public BufferedImage getCurrentImage() {
return currentAnimation.getCurrentImage();
}
public Direction getDirection() {
return direction;
}
public Map<Direction, Animation> getAnimations() {
return animations;
}
public Animation getCurrentAnimation() {
return currentAnimation;
}
public Rectangle getCollisionBox() {
return collisionBox;
}
public EntityController getController() {
return controller;
}
public Keyboard getKeyboard() {
return keyboard;
}
public int getHeight() {
return size.getHeight();
}
public Position getPosition() {
return position;
}
public Size getSize() {
return size;
}
public Vector2D getVector() {
return vector;
}
public int getWidth() {
return size.getWidth();
}
public double getX() {
return position.getX();
}
public double getY() {
return position.getY();
}
private void handleAnimation() {
switch (direction) {
case UP:
changeAnimation(Direction.UP);
break;
case LEFT:
changeAnimation(Direction.LEFT);
break;
case DOWN:
changeAnimation(Direction.DOWN);
break;
case RIGHT:
changeAnimation(Direction.RIGHT);
break;
case STOP:
currentAnimation.stop();
default:
break;
}
currentAnimation.update();
}
protected void handleCollision(State state) {
GameMap map = state.getGameMap();
collisionBox.x += vector.getX();
collisionBox.y += vector.getY();
switch (direction) {
case UP:
vector.setVector(0, -walkSpeed);
break;
case LEFT:
vector.setVector(-walkSpeed, 0);
break;
case DOWN:
vector.setVector(0, walkSpeed);
break;
case RIGHT:
vector.setVector(walkSpeed, 0);
break;
case STOP:
default:
vector.setVector(0, 0);
break;
}
if (!map.isWalkable(this) || !isWalkable(state)) {
vector.setVector(0, 0);
}
position.add(vector);
}
private boolean isCollision(Entity entity) {
Rectangle rect = new Rectangle(
collisionBox.x + (int)vector.getX(), collisionBox.y + (int)vector.getY(),
collisionBox.width, collisionBox.height);
return entity.getCollisionBox().intersects(rect);
}
public boolean isWalkable() {
return (vector.getX() != 0 || vector.getY() != 0);
}
protected boolean isWalkable(State state) {
if (state instanceof PlayState playState) {
List<Entity> filterList = playState.getEntities().stream()
.filter(entity -> Math.abs(getX() - entity.getX()) < DISTANCE_TO_ENTITY && Math.abs(getY() - entity.getY()) < DISTANCE_TO_ENTITY)
.collect(Collectors.toList());
for (int i = 0; i < filterList.size(); i++) {
if (isCollision(filterList.get(i))) {
return false;
}
}
}
return true;
}
protected void registerAnimations(SpriteSheet spriteSheet) {
animations = new HashMap<>();
}
public void update(State state) {
handleAnimation();
handleCollision(state);
}
}

View File

@@ -0,0 +1,138 @@
package entity.creature;
import core.Position;
import core.Size;
import entity.Entity;
import input.Keyboard;
import map.GameMap;
/*
* NESS LEVEL UP MAP
* LV | NEED EXP
* 1 | 0
* 2 | 4
* 3 | 17
* 4 | 44
* 5 | 109
* 6 | 236
* 7 | 449
* 8 | 772
* 9 | 1,229
* 10 | 1,884
* 11 | 2,611
* 12 | 3,644
* 13 | 4,877
* 14 | 6,364
* 15 | 8,129
* 16 | 10,703
* 17 | 13,241
* 18 | 16,214
* 19 | 19,673
* 20 | 23,699
* 21 | 28,253
* 22 | 33,476
* 23 | 39,389
* 24 | 46,043
* 25 | 53,489
* 26 | 61,778
* 27 | 70,961
* 28 | 81,089
* 29 | 92,213
* 30 | 104,384
* 31 | 117,653
* 32 | 132,071
* 33 | 147,689
* 34 | 164,558
* 35 | 182,729
* 36 | 202,270
* 37 | 223,249
* 38 | 245,734
* 39 | 269,793
* 40 | 295,494
* 41 | 322,905
* 42 | 352,094
* 43 | 383,129
* 44 | 416,078
* 45 | 451,009
* 46 | 487,990
* 47 | 527,089
* 48 | 568,374
* 49 | 611,913
* 50 | 657,774
* 51 | 706,025
* 52 | 756,734
* 53 | 809,969
* 54 | 865,798
* 55 | 924,289
* 56 | 985,510
* 57 | 1,049,529
* 58 | 1,116,414
* 59 | 1,186,233
* 60 | 1,259,054
* 61 | 1,335,030
* 62 | 1,414,314
* 63 | 1,497,059
* 64 | 1,583,418
* 65 | 1,673,544
* 66 | 1,767,590
* 67 | 1,865,709
* 68 | 1,968,054
* 69 | 2,074,778
* 70 | 2,186,034
* 71 | 2,301,975
* 72 | 2,422,754
* 73 | 2,548,524
* 74 | 2,679,438
* 75 | 2,815,649
* 76 | 2,957,310
* 77 | 3,104,574
* 78 | 3,257,594
* 79 | 3,416,523
* 80 | 3,581,514
* 81 | 3,752,720
* 82 | 3,930,294
* 83 | 4,114,389
* 84 | 4,305,158
* 85 | 4,502,754
* 86 | 4,707,330
* 87 | 4,919,039
* 88 | 5,138,034
* 89 | 5,364,468
* 90 | 5,598,494
* 91 | 5,840,265
* 92 | 6,089,934
* 93 | 6,347,654
* 94 | 6,613,578
* 95 | 6,887,859
* 96 | 7,170,650
* 97 | 7,462,104
* 98 | 7,762,374
* 99 | 8,071,613
*/
public abstract class Creature extends Entity {
protected String name;
protected int level;
protected int maxhp;
protected int maxmp;
protected int hp;
protected int mp;
protected int exp;
protected int nextlevel;
protected int offens;
protected int defens;
protected int speed;
protected int guts;
protected int vitality;
protected int iq;
protected int luck;
// TODO protected List<Magic> psis;
public Creature(GameMap gameMap, String name, Keyboard keyboard, Position position, Size size, String fileName, int tileWidth, int tileHeight) {
super(gameMap, keyboard, position, size, fileName, tileWidth, tileHeight);
this.name = name;
this.level = 1;
determiningStatus();
}
protected abstract void determiningStatus();
}

View File

@@ -0,0 +1,9 @@
package entity.creature;
public enum Direction {
STOP,
UP,
DOWN,
LEFT,
RIGHT,
}

View File

@@ -0,0 +1,79 @@
package entity.creature;
import java.awt.Rectangle;
import ai.AIManager;
import controller.EnemyController;
import core.Position;
import core.Size;
import display.DebugRenderer;
import entity.creature.Direction;
import game.GameLoop;
import gfx.Animation;
import gfx.SpriteSheet;
import input.Keyboard;
import map.GameMap;
import state.State;
public class Enemy extends Creature {
private AIManager aiManager;
public Enemy(GameMap gameMap, String name, Keyboard keyboard, Position position, Size size, String fileName, int tileWidth, int tileHeight) {
super(gameMap, name, keyboard, position, size, fileName, tileWidth, tileHeight);
aiManager = new AIManager();
collisionBox = new Rectangle((int)position.getX() + 25, (int)position.getY() + 25, 4 * gameMap.getScale(), 8 * gameMap.getScale());
controller = new EnemyController();
}
@Override
protected void determiningStatus() {
}
public String getCurrentState() {
return aiManager.getCurrentAIState().getClass().getSimpleName();
}
@Override
protected void handleCollision(State state) {
GameMap map = state.getGameMap();
collisionBox.x += vector.getX();
collisionBox.y += vector.getY();
vector.setVector(0, 0);
if (controller.isRequestKeyUp()) {
changeAnimation(Direction.UP);
vector.setVector(0, -walkSpeed);
}
if (controller.isRequestKeyDown()) {
changeAnimation(Direction.DOWN);
vector.setVector(0, walkSpeed);
}
if (controller.isRequestKeyLeft()) {
changeAnimation(Direction.LEFT);
vector.setVector(-walkSpeed, 0);
}
if (controller.isRequestKeyRight()) {
changeAnimation(Direction.RIGHT);
vector.setVector(walkSpeed, 0);
}
// TODO
// if (!map.isWalkable(this) || !isWalkable(state)) {
// vector.setVector(0, 0);
// }
position.add(vector);
}
@Override
protected void registerAnimations(SpriteSheet spriteSheet) {
super.registerAnimations(spriteSheet);
animations.put(Direction.DOWN , new Animation(spriteSheet.getTextures(0, 4 ), (long) (0.3 * GameLoop.ups), true));
animations.put(Direction.RIGHT, new Animation(spriteSheet.getTextures(4, 8 ), (long) (0.3 * GameLoop.ups), true));
animations.put(Direction.LEFT , new Animation(spriteSheet.getTextures(8, 12), (long) (0.3 * GameLoop.ups), true));
animations.put(Direction.UP , new Animation(spriteSheet.getTextures(12, 16), (long) (0.3 * GameLoop.ups), true));
}
@Override
public void update(State state) {
super.update(state);
aiManager.update(state, this);
}
}

View File

@@ -0,0 +1,68 @@
package entity.creature;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.Map;
import core.Position;
import core.Size;
import core.Vector2D;
import game.GameLoop;
import gfx.Animation;
import gfx.SpriteSheet;
import input.Keyboard;
import map.GameMap;
import state.State;
public class Player extends Creature {
public Player(GameMap gameMap, String name, Keyboard keyboard, Position position, Size size, String fileName, int tileWidth, int tileHeight) {
super(gameMap, name, keyboard, position, size, fileName, tileWidth, tileHeight);
collisionBox = new Rectangle((int)position.getX() + 25, (int)position.getY() + 25, 4 * gameMap.getScale(), 8 * gameMap.getScale());
}
@Override
protected void determiningStatus() {
maxhp = 30;
maxmp = 10;
hp = maxhp;
mp = maxmp;
// TODO nextlevel;
offens = 2;
defens = 2;
speed = 2;
guts = 2;
vitality = 2;
iq = 2;
luck = 2;
}
private void handleInput() {
if (keyboard.isPressedKey(KeyEvent.VK_W)) {
direction = Direction.UP;
} else if (keyboard.isPressedKey(KeyEvent.VK_A)) {
direction = Direction.LEFT;
} else if (keyboard.isPressedKey(KeyEvent.VK_S)) {
direction = Direction.DOWN;
} else if (keyboard.isPressedKey(KeyEvent.VK_D)) {
direction = Direction.RIGHT;
} else {
direction = Direction.STOP;
}
}
@Override
protected void registerAnimations(SpriteSheet spriteSheet) {
super.registerAnimations(spriteSheet);
animations.put(Direction.DOWN , new Animation(spriteSheet.getTextures(4, 7 ), (long) (0.3 * GameLoop.ups), true));
animations.put(Direction.RIGHT, new Animation(spriteSheet.getTextures(48, 51), (long) (0.3 * GameLoop.ups), true));
animations.put(Direction.LEFT , new Animation(spriteSheet.getTextures(63, 66), (long) (0.3 * GameLoop.ups), true));
animations.put(Direction.UP , new Animation(spriteSheet.getTextures(19, 22), (long) (0.3 * GameLoop.ups), true));
}
@Override
public void update(State state) {
super.update(state);
handleInput();
}
}

57
src/game/Game.java Normal file
View File

@@ -0,0 +1,57 @@
package game;
import java.awt.event.KeyEvent;
import core.Size;
import display.Display;
import input.Keyboard;
import input.Mouse;
import state.StartState;
import state.State;
public class Game {
private GameLoop gameLoop;
private Display display;
private Keyboard keyboard;
private Mouse mouse;
private State state;
public Game(GameLoop gameLoop) {
this.gameLoop = gameLoop;
keyboard = new Keyboard();
mouse = new Mouse();
display = new Display(keyboard, mouse, "2D Game Engine Example", new Size(1280, 720));
state = new StartState(display.getCanvasSize(), this);
}
public Display getDisplay() {
return display;
}
public Keyboard getKeyboard() {
return keyboard;
}
public Mouse getMouse() {
return mouse;
}
public void render() {
display.render(state);
}
public void setState(State state) {
this.state = state;
}
public void stop() {
gameLoop.stop();
}
public void update() {
if (keyboard.isJustPressedKey(KeyEvent.VK_ESCAPE)) {
stop();
}
state.update();
}
}

67
src/game/GameLoop.java Normal file
View File

@@ -0,0 +1,67 @@
package game;
public class GameLoop implements Runnable {
public static int fpsCount = 0;
public static int upsCount = 0;
public static long loopCount = 0;
public static double fps = 30.0;
public static double ups = 30.0;
private boolean running;
private double targetUpsFrame = 1000000000.0 / ups;
private double targetFpsFrame = 1000000000.0 / fps;
private Game game;
public GameLoop() {
running = true;
game = new Game(this);
}
private void render() {
game.render();
}
@Override
public void run() {
int fpsCnt = 0;
double fpsDelta = 0;
long timer = 0;
long last = System.nanoTime();
long loopCnt = 0;
int upsCnt = 0;
double upsDelta = 0;
while (running) {
long now = System.nanoTime();
upsDelta += (now - last) / targetUpsFrame;
fpsDelta += (now - last) / targetFpsFrame;
timer += now - last;
last = now;
while (upsDelta >= 1) {
update();
upsDelta--;
upsCnt++;
}
while (fpsDelta >= 1) {
render();
fpsDelta--;
fpsCnt++;
}
loopCnt++;
if (timer >= 1000000000) {
fpsCount = fpsCnt;
upsCount = upsCnt;
loopCount = loopCnt;
timer = fpsCnt = upsCnt = 0;
loopCnt = 0;
}
}
System.exit(0);
}
public void stop() {
running = false;
}
private void update() {
game.update();
}
}

53
src/gfx/Animation.java Normal file
View File

@@ -0,0 +1,53 @@
package gfx;
import java.awt.image.BufferedImage;
import java.util.List;
public class Animation {
private BufferedImage currentImage;
private List<BufferedImage> images;
private long currentFrame;
private long nextFrame;
private int idx;
private boolean loop;
private boolean running;
public Animation(List<BufferedImage> images, long nextFrame, boolean loop) {
this.images = images;
this.nextFrame = nextFrame;
this.loop = loop;
currentImage = images.get(0);
}
public BufferedImage getCurrentImage() {
return currentImage;
}
public void start() {
running = true;
}
public void stop() {
running = false;
}
public void update() {
if (running) {
currentFrame++;
} else {
if (currentFrame != 0 && idx != 0) {
currentFrame = 0;
idx = 0;
}
}
if (currentFrame == nextFrame) {
currentFrame = 0;
if (loop && idx == images.size() - 1) {
idx = 0;
} else {
idx++;
}
}
currentImage = images.get(idx);
}
}

53
src/gfx/SpriteSheet.java Normal file
View File

@@ -0,0 +1,53 @@
package gfx;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import util.ImageUtil;
public class SpriteSheet {
private int tileWidth;
private int tileHeight;
private BufferedImage sheet;
private List<BufferedImage> sprites;
public SpriteSheet(String fileName, int tileWidth, int tileHeight) {
this.tileWidth = tileWidth;
this.tileHeight = tileHeight;
sprites = new ArrayList<>();
loadImage(fileName);
loadTextures();
}
private void loadImage(String fileName) {
try {
sheet = ImageUtil.loadImage(fileName);
} catch (IOException | IllegalArgumentException e) {
throw new IllegalStateException("スプライトシートの読み込みに失敗しました。", e);
}
}
private void loadTextures() {
for (int y = 0; y < sheet.getHeight(null); y += tileHeight) {
for (int x = 0; x < sheet.getWidth(null); x += tileWidth) {
if (x + tileWidth <= sheet.getWidth() && y + tileHeight <= sheet.getHeight()) {
sprites.add(sheet.getSubimage(x, y, tileWidth, tileHeight));
}
}
}
}
public BufferedImage getSprite(int idx) {
return sprites.get(idx);
}
public List<BufferedImage> getTextures(int start, int end) {
List<BufferedImage> textures = new ArrayList<>();
for (int i = start; i < end; i++) {
textures.add(getSprite(i));
}
return textures;
}
}

63
src/input/Keyboard.java Normal file
View File

@@ -0,0 +1,63 @@
package input;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Keyboard implements KeyListener {
private boolean[] isKeyPressed;
private byte[] keyPressedTime;
public Keyboard() {
isKeyPressed = new boolean[0xFFFF];
keyPressedTime = new byte[0xFFFF];
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
isKeyPressed[e.getKeyCode()] = true;
}
@Override
public void keyReleased(KeyEvent e) {
isKeyPressed[e.getKeyCode()] = false;
}
private void handleKeyPressTime(int keyCode) {
if (isKeyPressed[keyCode]) {
if (keyPressedTime[keyCode] < Byte.MAX_VALUE) {
keyPressedTime[keyCode]++;
}
} else {
keyPressedTime[keyCode] = 0;
}
}
public boolean isJustPressedKey(int keyCode) {
return keyPressedTime[keyCode] == 1;
}
public boolean isPressedKey(int keyCode) {
return keyPressedTime[keyCode] > 0;
}
public boolean isPressedKey(int... keyCodes) {
for (int keyCode : keyCodes) {
if (keyPressedTime[keyCode] == 0) {
return false;
}
}
return true;
}
public void update() {
handleKeyPressTime(KeyEvent.VK_A);
handleKeyPressTime(KeyEvent.VK_D);
handleKeyPressTime(KeyEvent.VK_S);
handleKeyPressTime(KeyEvent.VK_W);
handleKeyPressTime(KeyEvent.VK_ESCAPE);
}
}

78
src/input/Mouse.java Normal file
View File

@@ -0,0 +1,78 @@
package input;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import core.Position;
public class Mouse implements MouseListener, MouseMotionListener {
private Position mousePosition;
private boolean mouseClicked;
private boolean mouseReleased;
private boolean mousePressed;
public Mouse() {
mousePosition = new Position(-1, -1);
}
public void cleanUpInputEvents() {
mouseClicked = false;
mouseReleased = false;
mousePressed = false;
}
public Position getMousePosition() {
return mousePosition;
}
public boolean isMouseClicked() {
return mouseClicked;
}
public boolean isMouseReleased() {
return mouseReleased;
}
public boolean isMousePressed() {
return mousePressed;
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mouseDragged(MouseEvent e) {
mousePosition.setX(e.getPoint().getX());
mousePosition.setY(e.getPoint().getY());
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mouseMoved(MouseEvent e) {
mousePosition.setX(e.getPoint().getX());
mousePosition.setY(e.getPoint().getY());
}
@Override
public void mousePressed(MouseEvent e) {
mousePressed = e.getButton() == MouseEvent.BUTTON1;
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
mouseClicked = true;
mousePressed = false;
mouseReleased = true;
}
}
}

View File

@@ -0,0 +1,9 @@
package input;
import state.State;
public interface MouseConsumer {
void onClick(State state);
void onDrag(State state);
void onRelease(State state);
}

213
src/map/GameMap.java Normal file
View File

@@ -0,0 +1,213 @@
package map;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import core.Position;
import core.Size;
import core.Vector2D;
import entity.Entity;
import util.ImageUtil;
import util.MapUtil;
public class GameMap {
public static final int MAP_LAYER_1 = 0;
public static final int MAP_LAYER_2 = 1;
public static final int MAP_LAYER_3 = 2;
public static final int MAP_LAYER_4 = 3;
public static final int NUMBER_OF_MAP_LAYER = 4;
public static final int UNWALKABLE_TILE = 0;
public static final int WALKABLE_TILE = 1;
private List<int[][]> mapData;
private List<BufferedImage> tileset;
private String tileSetFileName;
private Size stageSize;
private Size tileSize;
private int scale;
private Rectangle tileRect;
private Rectangle tileRect2;
private Random random;
public GameMap() {
mapData = new ArrayList<>();
tileset = new ArrayList<>();
scale = 4;
tileRect = new Rectangle(-1, -1, 0, 0);
tileRect2 = new Rectangle(-1, -1, 0, 0);
random = new Random();
buildGameMap();
loadTileSet();
}
private void buildGameMap() {
try {
Map<String, String> map = MapUtil.loadMapFromFileName("map1.tmx");
tileSetFileName = map.get(MapUtil.MAP_KEY_SOURCE);
for (int i = 1; i <= NUMBER_OF_MAP_LAYER; i++) {
// TODO 今IDは使われていない
int id = Integer.parseInt(map.get(MapUtil.MAP_KEY_ID + i));
int width = Integer.parseInt(map.get(MapUtil.MAP_KEY_WIDTH + i));
int height = Integer.parseInt(map.get(MapUtil.MAP_KEY_HEIGHT + i));
int tileWidth = Integer.parseInt(map.get(MapUtil.MAP_KEY_TILE_WIDTH));
int tileHeight = Integer.parseInt(map.get(MapUtil.MAP_KEY_TILE_HEIGHT));
stageSize = new Size(width, height);
tileSize = new Size(tileWidth, tileHeight);
String data = map.get(MapUtil.MAP_KEY_DATA + i);
int[][] arrData = new int[height][width];
String[] splitData = data.replace(" ", "").replace("\n", "").split(",");
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
arrData[y][x] = Integer.parseInt(splitData[y * width + x]);
}
}
mapData.add(arrData);
}
} catch (Exception e) {
throw new IllegalStateException("ゲームマップの構築に失敗しました。", e);
}
}
public Position findRandomPosition() {
return new Position(
random.nextInt(stageSize.getWidth() * (tileSize.getWidth() * scale)),
random.nextInt(stageSize.getHeight() * (tileSize.getHeight() * scale)));
}
public int getHeight() {
return stageSize.getHeight();
}
public List<int[][]> getMapData() {
return mapData;
}
public int getMapDataCell(int x, int y, int layer) {
return mapData.get(layer)[y][x];
}
private Points getCollisionPoints(Entity entity) {
Vector2D vec = entity.getVector();
Rectangle rect = entity.getCollisionBox();
Points pos = new Points(-1);
switch (entity.getDirection()) {
case UP:
pos.p1.setX(rect.x);
pos.p1.setY(rect.y + vec.getY());
pos.p2.setX(rect.x + rect.width);
pos.p2.setY(rect.y + vec.getY());
break;
case DOWN:
pos.p1.setX(rect.x);
pos.p1.setY(rect.y + rect.height + vec.getY());
pos.p2.setX(rect.x + rect.width);
pos.p2.setY(rect.y + rect.height + vec.getY());
break;
case LEFT:
pos.p1.setX(rect.x + vec.getX());
pos.p1.setY(rect.y);
pos.p2.setX(rect.x + vec.getX());
pos.p2.setY(rect.y + rect.height);
break;
case RIGHT:
pos.p1.setX(rect.x + rect.width + vec.getX());
pos.p1.setY(rect.y);
pos.p2.setX(rect.x + rect.width + vec.getX());
pos.p2.setY(rect.y + rect.height);
break;
}
return pos;
}
public Rectangle getTileRect() {
return tileRect;
}
public Rectangle getTileRect2() {
return tileRect2;
}
public List<BufferedImage> getTileSet() {
return tileset;
}
public int getScale() {
return scale;
}
public Size getStageSize() {
return stageSize;
}
public Size getTileSize() {
return tileSize;
}
public int getWidth() {
return stageSize.getWidth();
}
public boolean isOutBoundsOfMap(int x, int y) {
return x < 0 || x >= stageSize.getWidth() || y < 0 || y >= stageSize.getHeight();
}
public boolean isWalkable(Entity entity) {
Points pos = getCollisionPoints(entity);
double gridX1d = pos.p1.getX() / (tileSize.getWidth() * scale);
double gridY1d = pos.p1.getY() / (tileSize.getHeight() * scale);
double gridX2d = pos.p2.getX() / (tileSize.getWidth() * scale);
double gridY2d = pos.p2.getY() / (tileSize.getHeight() * scale);
int gridX1 = gridX1d < 0 ? -1 : (int)gridX1d;
int gridY1 = gridY1d < 0 ? -1 : (int)gridY1d;
int gridX2 = gridX2d < 0 ? -1 : (int)gridX2d;
int gridY2 = gridY2d < 0 ? -1 : (int)gridY2d;
if (isOutBoundsOfMap(gridX1, gridY1) && isOutBoundsOfMap(gridX2, gridY2)) {
return false;
}
tileRect = new Rectangle(gridX1 * (tileSize.getWidth() * scale), gridY1 * (tileSize.getHeight() * scale), tileSize.getWidth() * scale, tileSize.getHeight() * scale);
tileRect2 = new Rectangle(gridX2 * (tileSize.getWidth() * scale), gridY2 * (tileSize.getHeight() * scale), tileSize.getWidth() * scale, tileSize.getHeight() * scale);
if (mapData.get(MAP_LAYER_4)[gridY1][gridX1] == UNWALKABLE_TILE ||
mapData.get(MAP_LAYER_4)[gridY2][gridX2] == UNWALKABLE_TILE) {
return false;
}
return true;
}
private void loadTileSet() {
try {
BufferedImage image = ImageUtil.loadImage(MapUtil.TILE_SET_PATH + tileSetFileName);
for (int y = 0; y < image.getHeight(null); y += tileSize.getHeight()) {
for (int x = 0; x < image.getWidth(null); x += tileSize.getWidth()) {
tileset.add(image.getSubimage(x, y, tileSize.getWidth(), tileSize.getHeight()));
}
}
} catch (IOException e) {
throw new IllegalStateException("指定されたパスから正しくタイルセットが読み込めませんでした。 path: " + MapUtil.TILE_SET_PATH + tileSetFileName, e);
}
}
private class Points {
public Position p1;
public Position p2;
public Points() {
p1 = new Position();
p2 = new Position();
}
public Points(int p) {
p1 = new Position(p, p);
p2 = new Position(p, p);
}
public Points(int x1, int y1, int x2, int y2) {
p1 = new Position(x1, y1);
p2 = new Position(y1, y2);
}
}
}

44
src/map/Tile.java Normal file
View File

@@ -0,0 +1,44 @@
package map;
import java.awt.image.BufferedImage;
import gfx.Animation;
public class Tile {
private Animation animation;
private int tileWidth;
private int tileHeight;
private boolean walkable;
public Tile() {
walkable = true;
}
private Tile(Animation animation, int tileWidth, int tileHeight, boolean walkable) {
this.animation = animation;
this.tileWidth = tileWidth;
this.tileHeight = tileHeight;
this.walkable = walkable;
}
// FIXME 必要なければ削除
public static Tile copyOf(Tile tile) {
return new Tile(tile.getSprite(), tile.getTileIndex(), tile.getTileName(), tile.isWalkable());
}
public BufferedImage getSprite() {
return sprite;
}
public int getTileIndex() {
return tileIndex;
}
public String getTileName() {
return tileName;
}
public boolean isWalkable() {
return walkable;
}
}

56
src/state/PlayState.java Normal file
View File

@@ -0,0 +1,56 @@
package state;
import java.awt.Color;
import core.Position;
import core.Size;
import display.Display;
import entity.Entity;
import entity.creature.Player;
import game.Game;
import game.GameLoop;
import ui.Alignment;
import ui.UIContainer;
import ui.VerticalContainer;
import ui.clickable.UIButton;
import ui.debug.UIDebugInfo;
public class PlayState extends State {
private Player player;
public PlayState(Size windowSize, Game game) {
super(windowSize, game);
player = new Player(
gameMap,
"Tester",
keyboard,
new Position(320, 192),
new Size(gameMap.getTileSize().getWidth(), gameMap.getTileSize().getHeight()),
"/character/Full_SpriteSheet.png",
16, 32);
generateEntity(10);
gameCamera.focusOn(player);
}
@Override
public void initializeUI(Size windowSize) {
UIContainer container = new VerticalContainer(windowSize);
container.setAlignment(Alignment.Position.END, Alignment.Position.START);
container.addUIComponent(new UIButton("デバッグ", () -> Display.debug = !Display.debug));
container.addUIComponent(new UIButton("終了", () -> game.stop()));
uiContainers.add(container);
UIDebugInfo uiDebugInfo = new UIDebugInfo(windowSize);
uiDebugContainers.add(uiDebugInfo);
}
public Player getPlayer() {
return player;
}
@Override
public void update() {
super.update();
player.update(this);
entities.forEach(entity -> entity.update(this));
}
}

17
src/state/StartState.java Normal file
View File

@@ -0,0 +1,17 @@
package state;
import core.Size;
import game.Game;
import ui.state.start.UIStartStateMenu;
public class StartState extends State {
public StartState(Size windowSize, Game game) {
super(windowSize, game);
}
@Override
public void initializeUI(Size windowSize) {
UIStartStateMenu menu = new UIStartStateMenu(windowSize, game);
uiContainers.add(menu);
}
}

106
src/state/State.java Normal file
View File

@@ -0,0 +1,106 @@
package state;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import core.Position;
import core.Size;
import display.Display;
import display.GameCamera;
import entity.Entity;
import entity.creature.Enemy;
import game.Game;
import input.Keyboard;
import input.Mouse;
import input.MouseConsumer;
import map.GameMap;
import ui.UIContainer;
import ui.debug.UIDebugInfo;
public abstract class State {
protected Game game;
protected Keyboard keyboard;
protected Mouse mouse;
protected GameMap gameMap;
protected GameCamera gameCamera;
protected MouseConsumer activeConsumer;
protected Random random;
protected List<Entity> entities;
protected List<UIContainer> uiContainers;
protected List<UIContainer> uiDebugContainers;
protected UIDebugInfo uiDebugInfo;
public State(Size windowSize, Game game) {
this.game = game;
keyboard = game.getKeyboard();
mouse = game.getMouse();
gameMap = new GameMap();
gameCamera = new GameCamera(game.getDisplay().getCanvasSize());
random = new Random();
entities = new ArrayList<>();
uiContainers = new ArrayList<>();
uiDebugContainers = new ArrayList<>();
initializeUI(windowSize);
}
protected void generateEntity(int num) {
for (int i = 0; i < num; i++) {
int randomX = random.nextInt(gameMap.getWidth()) * gameMap.getTileSize().getWidth() * gameMap.getScale();
int randomY = random.nextInt(gameMap.getHeight()) * gameMap.getTileSize().getHeight() * gameMap.getScale();
entities.add(
new Enemy(
gameMap,
"Enemy" + i,
keyboard,
new Position(randomX, randomY),
new Size(gameMap.getTileSize().getWidth(), gameMap.getTileSize().getHeight()),
"/character/Full_SpriteSheet.png",
16, 16
)
);
}
}
public List<Entity> getEntities() {
return entities;
}
public GameCamera getGameCamera() {
return gameCamera;
}
public GameMap getGameMap() {
return gameMap;
}
public Mouse getMouse() {
return mouse;
}
public List<UIContainer> getUiContainers() {
return uiContainers;
}
public List<UIContainer> getUiDebugContainers() {
return uiDebugContainers;
}
public abstract void initializeUI(Size windowSize);
public void setActiveConsumer(MouseConsumer activeConsumer) {
this.activeConsumer = activeConsumer;
}
public void update() {
keyboard.update();
entities.forEach(entity -> entity.update(this));
uiContainers.forEach(uiContainer -> uiContainer.update(this));
if (Display.debug) {
uiDebugContainers.forEach(uiContainer -> uiContainer.update(this));
}
gameCamera.update(this);
mouse.cleanUpInputEvents();
}
}

25
src/ui/Alignment.java Normal file
View File

@@ -0,0 +1,25 @@
package ui;
public class Alignment {
public enum Position {
START,
CENTER,
END
}
private final Position horizontal;
private final Position vertical;
public Alignment(Position horizontal, Position vertical) {
this.vertical = vertical;
this.horizontal = horizontal;
}
public Position getHorizontal() {
return horizontal;
}
public Position getVertical() {
return vertical;
}
}

View File

@@ -0,0 +1,35 @@
package ui;
import core.Position;
import core.Size;
public class HorizontalContainer extends UIContainer {
public HorizontalContainer(Size windowSize) {
super(windowSize);
}
@Override
protected void calculateChildrenPosition() {
int currentX = padding.getLeft();
for (UIComponent child : children) {
currentX += child.getMargin().getLeft();
child.setRelativePosition(new Position(currentX, padding.getTop()));
child.setAbsolutePosition(new Position(currentX + (int)absolutePosition.getX(), padding.getTop() + (int)absolutePosition.getY()));
currentX += child.getSize().getWidth();
currentX += child.getMargin().getRight();
}
}
@Override
protected Size calculateChildrenSize() {
int combinedChildW = 0;
int tallestChildH = 0;
for (UIComponent child : children) {
combinedChildW += child.getSize().getWidth() + child.getMargin().getHorizontal();
if (child.getSize().getHeight() > tallestChildH) {
tallestChildH = child.getSize().getHeight();
}
}
return new Size(combinedChildW, tallestChildH);
}
}

47
src/ui/Space.java Normal file
View File

@@ -0,0 +1,47 @@
package ui;
public class Space {
private int top;
private int bottom;
private int left;
private int right;
public Space(int space) {
this(space, space);
}
public Space(int horizontal, int vertical) {
this(vertical, vertical, horizontal, horizontal);
}
public Space(int top, int bottom, int left, int right) {
this.top = top;
this.bottom = bottom;
this.left = left;
this.right = right;
}
public int getBottom() {
return bottom;
}
public int getHorizontal() {
return left + right;
}
public int getLeft() {
return left;
}
public int getRight() {
return right;
}
public int getTop() {
return top;
}
public int getVertical() {
return top + bottom;
}
}

67
src/ui/UIComponent.java Normal file
View File

@@ -0,0 +1,67 @@
package ui;
import java.awt.image.BufferedImage;
import core.Position;
import core.Size;
import state.State;
public abstract class UIComponent {
protected Position relativePosition;
protected Position absolutePosition;
protected Size size;
protected Space margin;
protected Space padding;
public UIComponent() {
relativePosition = new Position(0, 0);
absolutePosition = new Position(0, 0);
size = new Size(1, 1);
margin = new Space(0);
padding = new Space(0);
}
public Space getMargin() {
return margin;
}
public Space getPadding() {
return padding;
}
public Position getAbsolutePosition() {
return absolutePosition;
}
public Position getRelativePosition() {
return relativePosition;
}
public Size getSize() {
return size;
}
public abstract BufferedImage getSprite();
public void setMargin(Space margin) {
this.margin = margin;
}
public void setPadding(Space padding) {
this.padding = padding;
}
public void setAbsolutePosition(Position absolutePosition) {
this.absolutePosition = absolutePosition;
}
public void setRelativePosition(Position relativePosition) {
this.relativePosition = relativePosition;
}
public void setSize(Size size) {
this.size = size;
}
public abstract void update(State state);
}

124
src/ui/UIContainer.java Normal file
View File

@@ -0,0 +1,124 @@
package ui;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import core.Position;
import core.Size;
import state.State;
import ui.Space;
import util.ImageUtil;
public abstract class UIContainer extends UIComponent {
protected Color backgroundColor;
protected List<UIComponent> children;
protected Size windowSize;
protected Alignment alignment;
protected Size fixedSize;
public UIContainer(Size windowSize) {
super();
this.windowSize = windowSize;
backgroundColor = new Color(20, 20, 20);
alignment = new Alignment(Alignment.Position.START, Alignment.Position.START);
margin = new Space(5);
padding = new Space(5);
children = new ArrayList<>();
calculateSize();
calculatePosition();
}
public void addUIComponent(UIComponent uiComponent) {
children.add(uiComponent);
}
public void addUIComponentAll(UIComponent... uiComponents) {
for (UIComponent uiComponent : uiComponents) {
children.add(uiComponent);
}
}
protected abstract Size calculateChildrenSize();
protected abstract void calculateChildrenPosition();
private void calculatePosition() {
int x = padding.getLeft();
if (alignment.getHorizontal() == Alignment.Position.CENTER) {
x = windowSize.getWidth() / 2 - size.getWidth() / 2;
}
if (alignment.getHorizontal() == Alignment.Position.END) {
x = windowSize.getWidth() - size.getWidth() - margin.getRight();
}
int y = padding.getTop();
if (alignment.getVertical() == Alignment.Position.CENTER) {
y = windowSize.getHeight() / 2 - size.getHeight() / 2;
}
if (alignment.getVertical() == Alignment.Position.END) {
y = windowSize.getHeight() - size.getHeight() - margin.getBottom();
}
relativePosition = new Position(x, y);
absolutePosition = new Position(x, y);
calculateChildrenPosition();
}
private void calculateSize() {
Size calculateChildrenSize = calculateChildrenSize();
if (fixedSize != null) {
size = fixedSize;
} else {
size = new Size(
padding.getHorizontal() + calculateChildrenSize.getWidth(),
padding.getVertical() + calculateChildrenSize.getHeight());
}
}
protected void clearChildren() {
children.clear();
}
public Color getBackgroundColor() {
return backgroundColor;
}
@Override
public BufferedImage getSprite() {
BufferedImage image = ImageUtil.createCompatibleImage(size, Transparency.BITMASK);
Graphics2D g = image.createGraphics();
g.setColor(backgroundColor);
g.fillRect(0, 0, size.getWidth(), size.getHeight());
for (UIComponent child : children) {
g.drawImage(
child.getSprite(),
(int)child.getRelativePosition().getX(),
(int)child.getRelativePosition().getY(),
null
);
}
g.dispose();
return image;
}
public void setAlignment(Alignment.Position vertical, Alignment.Position horizontal) {
alignment = new Alignment(vertical, horizontal);
}
public void setBackgroundColor(Color backgroundColor) {
this.backgroundColor = backgroundColor;
}
public void setFixedSize(Size fixedSize) {
this.fixedSize = fixedSize;
}
@Override
public void update(State state) {
children.forEach(component -> component.update(state));
calculateSize();
calculatePosition();
}
}

73
src/ui/UIText.java Normal file
View File

@@ -0,0 +1,73 @@
package ui;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import core.Size;
import state.State;
import util.ImageUtil;
public class UIText extends UIComponent {
private String text;
private int fontSize;
private int fontStyle;
private String fontFamily;
private Color color;
private boolean dropShadow;
private int dropShadowOffset;
private Color shadowColor;
private Font font;
public UIText(String text) {
this.text = text;
fontSize = 24;
fontStyle = Font.BOLD;
fontFamily = Font.MONOSPACED;
color = Color.WHITE;
dropShadow = true;
dropShadowOffset = 2;
shadowColor = new Color(140, 140, 140);
}
private void createFont() {
font = new Font(fontFamily, fontStyle, fontSize);
}
private void calculateSize() {
FontMetrics fontMetrics = new Canvas().getFontMetrics(font);
size = new Size(
fontMetrics.stringWidth(text) + padding.getHorizontal(),
fontMetrics.getHeight() + padding.getVertical()
);
}
@Override
public BufferedImage getSprite() {
BufferedImage image = (BufferedImage)ImageUtil.createCompatibleImage(size, Transparency.BITMASK);
Graphics2D g = image.createGraphics();
g.setFont(font);
if (dropShadow) {
g.setColor(shadowColor);
g.drawString(text, padding.getLeft() + dropShadowOffset, fontSize + padding.getTop() + dropShadowOffset);
}
g.setColor(color);
g.drawString(text, padding.getLeft(), fontSize + padding.getTop());
g.dispose();
return image;
}
@Override
public void update(State state) {
createFont();
calculateSize();
}
public void setText(String text) {
this.text = text;
}
}

View File

@@ -0,0 +1,35 @@
package ui;
import core.Position;
import core.Size;
public class VerticalContainer extends UIContainer {
public VerticalContainer(Size windowSize) {
super(windowSize);
}
@Override
protected void calculateChildrenPosition() {
int currentY = padding.getTop();
for (UIComponent child : children) {
currentY += child.getMargin().getTop();
child.setRelativePosition(new Position(padding.getLeft(), currentY));
child.setAbsolutePosition(new Position(padding.getLeft() + (int)absolutePosition.getX(), currentY + (int)absolutePosition.getY()));
currentY += child.getSize().getHeight();
currentY += child.getMargin().getBottom();
}
}
@Override
protected Size calculateChildrenSize() {
int combinedChildH = 0;
int widestChildW = 0;
for (UIComponent child : children) {
combinedChildH += child.getSize().getHeight() + child.getMargin().getVertical();
if (child.getSize().getWidth() > widestChildW) {
widestChildW = child.getSize().getWidth();
}
}
return new Size(widestChildW, combinedChildH);
}
}

View File

@@ -0,0 +1,49 @@
package ui.clickable;
import java.awt.Color;
import java.awt.image.BufferedImage;
import core.Size;
import ui.UIContainer;
import ui.UIText;
import ui.VerticalContainer;
import state.State;
public class UIButton extends UIClickable {
private UIContainer container;
private UIText label;
private Runnable clickEvent;
public UIButton(String label, Runnable clickEvent) {
this.label = new UIText(label);
this.clickEvent = clickEvent;
container = new VerticalContainer(new Size(0, 0));
container.addUIComponent(this.label);
container.setFixedSize(new Size(150, 30));
}
@Override
public BufferedImage getSprite() {
return container.getSprite();
}
@Override
public void onClick() {
clickEvent.run();
}
@Override
public void update(State state) {
super.update(state);
container.update(state);
size = container.getSize();
Color color = Color.GRAY;
if (hasFocus) {
color = Color.LIGHT_GRAY;
}
if (isPressed) {
color = Color.DARK_GRAY;
}
container.setBackgroundColor(color);
}
}

View File

@@ -0,0 +1,33 @@
package ui.clickable;
import java.awt.Rectangle;
import core.Position;
import state.State;
import ui.UIComponent;
public abstract class UIClickable extends UIComponent {
protected boolean hasFocus;
protected boolean isPressed;
@Override
public void update(State state) {
Position mousePosition = state.getMouse().getMousePosition();
hasFocus = getBounds().contains((int)mousePosition.getX(), (int)mousePosition.getY());
isPressed = hasFocus && state.getMouse().isMousePressed();
if (hasFocus && state.getMouse().isMouseClicked()) {
onClick();
}
}
private Rectangle getBounds() {
return new Rectangle(
(int)absolutePosition.getX(),
(int)absolutePosition.getY(),
size.getWidth(),
size.getHeight()
);
}
protected abstract void onClick();
}

View File

@@ -0,0 +1,48 @@
package ui.debug;
import java.awt.Color;
import core.Size;
import entity.creature.Player;
import game.GameLoop;
import map.GameMap;
import state.PlayState;
import state.State;
import ui.VerticalContainer;
import ui.UIText;
public class UIDebugInfo extends VerticalContainer {
private UIText ram = new UIText("RAM");
private UIText fps = new UIText("FPS");
private UIText ups = new UIText("UPS");
private UIText loop = new UIText("LOOP");
private UIText pos = new UIText("POS");
private UIText grid = new UIText("GRID");
private UIText dir = new UIText("DIR");
public UIDebugInfo(Size windowSize) {
super(windowSize);
setBackgroundColor(new Color(0, 0, 0, 0));
addUIComponentAll(ram, fps, ups, loop, pos, grid, dir);
}
public void update(State state) {
super.update(state);
ram.setText(String.format("RAM: %.3fMB",
(((double)(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1000) / 1000)));
fps.setText(String.format("FPS: %d", GameLoop.fpsCount));
ups.setText(String.format("UPS: %d", GameLoop.upsCount));
loop.setText(String.format("1S : %d", GameLoop.loopCount));
if (state instanceof PlayState pState) {
Player p = pState.getPlayer();
GameMap gm = state.getGameMap();
Size tileSize = gm.getTileSize();
int scale = gm.getScale();
pos.setText(String.format("%s", "Player: " + p.getPosition()));
int gridX = ((int) p.getCollisionBox().getX() / (tileSize.getWidth() * scale));
int gridY = ((int) p.getCollisionBox().getY() / (tileSize.getHeight() * scale));
grid.setText(String.format("%s", "(X: " + gridX + " Y: " + gridY + ")"));
dir.setText(String.format("%s", p.getDirection()));
}
}
}

View File

@@ -0,0 +1,28 @@
package ui.state.start;
import core.Size;
import game.Game;
import state.PlayState;
import ui.Alignment;
import ui.UIText;
import ui.VerticalContainer;
import ui.clickable.UIButton;
public class UIStartStateMenu extends VerticalContainer {
private Game game;
private UIText title;
private UIButton start;
private UIButton setting;
private UIButton exit;
public UIStartStateMenu(Size windowSize, Game game) {
super(windowSize);
this.game = game;
setAlignment(Alignment.Position.CENTER, Alignment.Position.CENTER);
title = new UIText("2D Game Engine Example");
start = new UIButton("開始", () -> game.setState(new PlayState(game.getDisplay().getCanvasSize(), game)));
setting = new UIButton("設定", () -> {});
exit = new UIButton("終了", () -> game.stop());
addUIComponentAll(title, start, setting, exit);
}
}

36
src/util/ImageUtil.java Normal file
View File

@@ -0,0 +1,36 @@
package util;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import core.Size;
public class ImageUtil {
public static BufferedImage createCompatibleImage(Size size, int transparency) {
GraphicsConfiguration config = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
return config.createCompatibleImage(size.getWidth(), size.getHeight(), transparency);
}
public static BufferedImage loadImage(String path) throws IOException, IllegalArgumentException {
try {
Image imageFromDisk = ImageIO.read(ImageUtil.class.getResource(path));
BufferedImage compatibleImage = createCompatibleImage(
new Size(imageFromDisk.getWidth(null), imageFromDisk.getHeight(null)), Transparency.BITMASK);
Graphics2D graphics = compatibleImage.createGraphics();
graphics.drawImage(imageFromDisk, 0, 0, null);
graphics.dispose();
return compatibleImage;
} catch (IOException e) {
throw new IOException("指定されたパスからイメージを読み込む事が出来ませんでした。 path: " + path, e);
} catch (IllegalArgumentException e) {
throw new IOException("指定されたパスが存在しませんでした。 path: " + path, e);
}
}
}

98
src/util/MapUtil.java Normal file
View File

@@ -0,0 +1,98 @@
package util;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class MapUtil {
public static final int NUMBER_OF_TILE_SIZE = 2;
public static final String MAP_KEY_DATA = "data";
public static final String MAP_KEY_HEIGHT = "height";
public static final String MAP_KEY_ID = "id";
public static final String MAP_KEY_WIDTH = "width";
public static final String MAP_KEY_SOURCE = "source";
public static final String MAP_KEY_TILE_HEIGHT = "tileheight";
public static final String MAP_KEY_TILE_WIDTH = "tilewidth";
public static final String TILE_SET_PATH = "/tileset/";
private static void loadMapInfo(Map<String, String> mapData, String fileName)
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = builder.parse(MapUtil.class.getResourceAsStream(TILE_SET_PATH + fileName));
document.getDocumentElement().normalize();
Element elementList = document.getDocumentElement();
NodeList nodeList = elementList.getElementsByTagName("tileset");
Element element = (Element) nodeList.item(0);
mapData.put(MAP_KEY_SOURCE, element.getAttribute(MAP_KEY_SOURCE).replace("tsx", "png"));
nodeList = elementList.getElementsByTagName("layer");
for (int i = 0; i < nodeList.getLength(); i++) {
element = (Element) nodeList.item(i);
int id = Integer.parseInt(element.getAttribute(MAP_KEY_ID));
mapData.put(MAP_KEY_ID + id, element.getAttribute(MAP_KEY_ID));
mapData.put(MAP_KEY_WIDTH + id, element.getAttribute(MAP_KEY_WIDTH));
mapData.put(MAP_KEY_HEIGHT + id, element.getAttribute(MAP_KEY_HEIGHT));
mapData.put(MAP_KEY_DATA + id, element.getTextContent());
}
}
// FIXME TileSizeの値を取得する方法を考える必要がある。
private static void loadTileSizeInfo(Map<String, String> mapData, String fileName) throws IOException, URISyntaxException {
try (BufferedReader br = new BufferedReader(
new FileReader(new File(MapUtil.class.getResource(TILE_SET_PATH + fileName).toURI())))) {
String line = "";
int idx = 0;
while ((line = br.readLine()) != null) {
idx++;
if (idx == NUMBER_OF_TILE_SIZE) {
String beginStr = "tilewidth=";
String endStr = "tileheight=";
int beginIndex = line.indexOf(beginStr);
int endIndex = line.indexOf(endStr);
String tileWidth = line.substring(beginIndex, endIndex).replace(beginStr, "").replace("\"", "").replace(" ", "");
beginStr = "tileheight=";
endStr = "infinite=";
beginIndex = line.indexOf(beginStr);
endIndex = line.indexOf(endStr);
String tileHeight = line.substring(beginIndex, endIndex).replace(beginStr, "").replace("\"", "").replace(" ", "");
mapData.put(MAP_KEY_TILE_WIDTH, tileWidth);
mapData.put(MAP_KEY_TILE_HEIGHT, tileHeight);
}
}
} catch (IOException e) {
throw new IOException("タイルサイズの読み込みに失敗しました。 fileName: " + fileName, e);
} catch (URISyntaxException e) {
System.err.println("不正なURIが設定されたため例外が発生しました。 fileName: " + fileName);
throw e;
}
}
public static Map<String, String> loadMapFromFileName(String fileName)
throws ParserConfigurationException, SAXException, IOException, URISyntaxException {
try {
Map<String, String> mapData = new HashMap<>();
loadMapInfo(mapData, fileName);
loadTileSizeInfo(mapData, fileName);
return mapData;
} catch (ParserConfigurationException e) {
System.err.println("XMLの構成に致命的なミスがあります。 fileName: " + fileName);
throw e;
} catch (SAXException e) {
throw new SAXException("SAXでの処理で例外が発生しました。fileName: " + fileName, e);
} catch (IOException e) {
throw new IOException("入出力処理で例外が発生しました。 fileName: " + fileName, e);
}
}
}