diff --git a/docs/perf_hotspots.md b/docs/perf_hotspots.md new file mode 100644 index 0000000..984dffb --- /dev/null +++ b/docs/perf_hotspots.md @@ -0,0 +1,12 @@ +# 性能热点初稿 + +首轮标记: + +| 区域 | 说明 | 优先级 | 说明 | +|------|------|--------|------| +GameScreen.render | 多次集合遍历 + 远程玩家循环内更新 | 高 | 后续拆分逻辑/渲染阶段 | +SimpleFighter.update | 物理与攻击状态混杂 | 中 | 拆分到组件式(动作/物理) | +网络同步(待补) | sendPosition 每帧发送 | 中 | 引入位置压缩/频率限制 | +清屏/批处理 | 现已静态化 | 已改善 | 继续合并渲染批次 | + +后续收集: 帧时间分布 / GC 次数。 diff --git a/pom.xml b/pom.xml index aab8140..4036ec1 100644 --- a/pom.xml +++ b/pom.xml @@ -14,71 +14,87 @@ 21 21 + 21 UTF-8 1.12.1 - - com.badlogicgames.gdx - gdx - 1.12.1 + + + com.badlogicgames.gdx + gdx + ${gdx.version} + + + + + com.badlogicgames.gdx + gdx-backend-lwjgl + ${gdx.version} + + + + + com.badlogicgames.gdx + gdx-platform + ${gdx.version} + natives-desktop - - com.badlogicgames.gdx - gdx-freetype - 1.12.1 - - - - - com.badlogicgames.gdx - gdx-freetype-platform - 1.12.1 - natives-desktop - - - com.badlogicgames.gdx - gdx-backend-lwjgl - 1.12.1 - - - com.badlogicgames.gdx - gdx-platform - 1.12.1 - natives-desktop - com.badlogicgames.gdx - gdx - 1.12.1 + gdx-freetype + ${gdx.version} com.badlogicgames.gdx - gdx-backend-lwjgl3 - 1.12.1 - - - com.badlogicgames.gdx - gdx-platform - 1.12.1 + gdx-freetype-platform + ${gdx.version} natives-desktop + + + org.junit.jupiter + junit-jupiter + 5.10.2 + test + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.4.1 + + + enforce + enforce + + + + [21,) + + + + false + + + + org.apache.maven.plugins maven-compiler-plugin 3.13.0 - - 17 - 17 - + + ${maven.compiler.source} + ${maven.compiler.target} + ${maven.compiler.release} @@ -87,8 +103,6 @@ exec-maven-plugin 3.1.0 - - uno.mloluyu.desktop.DesktopLauncher @@ -107,6 +121,31 @@ + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + false + + + + + org.jacoco + jacoco-maven-plugin + 0.8.11 + + + prepare-agent + + + report + test + report + + + diff --git a/src/main/java/uno/mloluyu/characters/ActionStateGuard.java b/src/main/java/uno/mloluyu/characters/ActionStateGuard.java new file mode 100644 index 0000000..13737c0 --- /dev/null +++ b/src/main/java/uno/mloluyu/characters/ActionStateGuard.java @@ -0,0 +1,20 @@ +package uno.mloluyu.characters; + +/** 提供状态切换验证。 */ +public final class ActionStateGuard { + private ActionStateGuard() { + } + + public static Action transition(FighterBase fighter, Action next) { + Action current = fighter.getCurrentAction(); + if (current == next) + return current; + if (!ActionTransitionMap.can(current, next)) { + return current; + } + if (fighter instanceof uno.mloluyu.characters.SimpleFighter sf) { + sf.directSetAction(next); + } + return next; + } +} diff --git a/src/main/java/uno/mloluyu/characters/ActionTransitionMap.java b/src/main/java/uno/mloluyu/characters/ActionTransitionMap.java new file mode 100644 index 0000000..45dc5d5 --- /dev/null +++ b/src/main/java/uno/mloluyu/characters/ActionTransitionMap.java @@ -0,0 +1,25 @@ +package uno.mloluyu.characters; + +import java.util.*; + +/** 定义合法动作迁移,供校验/日志使用。 */ +public final class ActionTransitionMap { + private static final Map> ALLOWED = new EnumMap<>(Action.class); + static { + allow(Action.IDLE, Action.MOVE, Action.JUMP, Action.ATTACK, Action.DEFEND, Action.HIT, Action.DEAD); + allow(Action.MOVE, Action.IDLE, Action.JUMP, Action.ATTACK, Action.HIT, Action.DEAD); + allow(Action.JUMP, Action.HIT, Action.ATTACK, Action.DEAD, Action.IDLE, Action.MOVE); + allow(Action.ATTACK, Action.HIT, Action.DEAD, Action.IDLE, Action.MOVE); + allow(Action.DEFEND, Action.HIT, Action.IDLE, Action.MOVE, Action.DEAD); + allow(Action.HIT, Action.IDLE, Action.MOVE, Action.DEAD); + allow(Action.DEAD); // 终止状态 + } + + private static void allow(Action from, Action... tos) { + ALLOWED.put(from, new HashSet<>(Arrays.asList(tos))); + } + + public static boolean can(Action from, Action to) { + return ALLOWED.getOrDefault(from, Collections.emptySet()).contains(to); + } +} diff --git a/src/main/java/uno/mloluyu/characters/AdvancedFighter.java b/src/main/java/uno/mloluyu/characters/AdvancedFighter.java index 3382dca..da5a912 100644 --- a/src/main/java/uno/mloluyu/characters/AdvancedFighter.java +++ b/src/main/java/uno/mloluyu/characters/AdvancedFighter.java @@ -3,14 +3,12 @@ package uno.mloluyu.characters; public class AdvancedFighter extends SimpleFighter { public AdvancedFighter(String name) { - super(name); // 调用父类构造函数 + super(name); } @Override public void attack(String attackType) { - // 先使用父类的攻击逻辑来保证 isAttacking/attackTimer/attackbox 等状态被正确设置 super.attack(attackType); - // 在这里可以添加 AdvancedFighter 特有的扩展行为(攻击力、特效等) - // 例如:根据 attackType 调整伤害或触发粒子/声音,但不要忘记保留父类的状态设置 + } } diff --git a/src/main/java/uno/mloluyu/characters/FighterBase.java b/src/main/java/uno/mloluyu/characters/FighterBase.java new file mode 100644 index 0000000..831673a --- /dev/null +++ b/src/main/java/uno/mloluyu/characters/FighterBase.java @@ -0,0 +1,47 @@ +package uno.mloluyu.characters; + +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.graphics.Color; +import java.util.concurrent.ThreadLocalRandom; + +/** 公共角色基类:后续 Simple/Advanced 统一继承。 */ +public abstract class FighterBase { + protected String name; + protected Action currentAction = Action.IDLE; + protected Rectangle hitbox = new Rectangle(0, 0, 64, 128); + protected Rectangle attackbox = new Rectangle(0, 0, 80, 80); + protected final Color debugColor; + + public FighterBase(String name) { + this.name = name; + ThreadLocalRandom r = ThreadLocalRandom.current(); + float rr = 0.35f + r.nextFloat() * 0.65f; + float gg = 0.35f + r.nextFloat() * 0.65f; + float bb = 0.35f + r.nextFloat() * 0.65f; + this.debugColor = new Color(rr, gg, bb, 1f); + } + + public Action getCurrentAction() { + return currentAction; + } + + public Rectangle getHitbox() { + return hitbox; + } + + public Rectangle getAttackbox() { + return attackbox; + } + + public String getName() { + return name; + } + + public Color getDebugColor() { + return debugColor; + } + + protected void setAction(Action next) { + this.currentAction = next; + } +} diff --git a/src/main/java/uno/mloluyu/characters/SimpleFighter.java b/src/main/java/uno/mloluyu/characters/SimpleFighter.java index afb72ef..d010d6a 100644 --- a/src/main/java/uno/mloluyu/characters/SimpleFighter.java +++ b/src/main/java/uno/mloluyu/characters/SimpleFighter.java @@ -10,21 +10,21 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.math.Rectangle; import uno.mloluyu.network.NetworkManager; +import uno.mloluyu.util.GameConstants; /** * 简化版角色类,仅包含移动、攻击、受击等基础功能。 */ -public class SimpleFighter { +public class SimpleFighter extends FighterBase { - private String name; // 角色名称 - private Action currentAction = Action.IDLE; // 当前动作状态 + // 继承: name, currentAction, hitbox, attackbox private float verticalSpeed = 0f; // 垂直速度(跳跃/下落) private boolean isGrounded = true; // 是否着地 private Rectangle hitbox = new Rectangle(0, 0, 64, 128); // 碰撞盒 private Rectangle attackbox = new Rectangle(0, 0, 80, 80); // 攻击判定盒 private boolean isFacingRight = true; // 朝向(右/左) - private float speed = 300f; // 水平移动速度 - private int health = 100; // 生命值 + private float speed = GameConstants.MOVE_SPEED; // 水平移动速度 + private int health = 200; // 生命值 private boolean isAttacking = false; // 是否正在攻击 private boolean attackJustStarted = false; // 攻击是否刚开始 private float attackTimer = 0f; // 攻击计时器 @@ -41,7 +41,7 @@ public class SimpleFighter { private static final float KNOCKBACK_DURATION = 0.12f; public SimpleFighter(String name) { - this.name = name; + super(name); } public void update(float deltaTime) { @@ -76,10 +76,10 @@ public class SimpleFighter { } if (!isGrounded) { - verticalSpeed -= 2500 * deltaTime; + verticalSpeed -= GameConstants.GRAVITY * deltaTime; hitbox.y += verticalSpeed * deltaTime; - if (hitbox.y <= 0) { - hitbox.y = 0; + if (hitbox.y <= GameConstants.GROUND_Y) { + hitbox.y = GameConstants.GROUND_Y; verticalSpeed = 0; isGrounded = true; changeAction(Action.IDLE); @@ -103,7 +103,7 @@ public class SimpleFighter { hitbox.y + hitbox.height * 0.7f); } - public void handleInput(int keycode, boolean isPressed) { + public void handleInput(int keycode, boolean isPressed, float duration) { if (isPressed) { if (keycode == Input.Keys.LEFT || keycode == Input.Keys.A) { move(-1, Gdx.graphics.getDeltaTime()); @@ -138,12 +138,16 @@ public class SimpleFighter { } public void changeAction(Action newAction) { - this.currentAction = newAction; + this.currentAction = ActionStateGuard.transition(this, newAction); + } + + void directSetAction(Action a) { + this.currentAction = a; } public void jump() { if (isGrounded) { - verticalSpeed = 1000f; + verticalSpeed = GameConstants.JUMP_SPEED; isGrounded = false; changeAction(Action.JUMP); } @@ -181,28 +185,32 @@ public class SimpleFighter { } private void updateAttackbox(String attackType) { - float offsetX, offsetY = 20, width = 80, height = 80; + float baseOffsetY = 20f; + float width = 80f, height = 80f; + float offsetX; + float offsetY = baseOffsetY; + // 先决定尺寸,再根据朝向计算 offset(不再用旧 attackbox.width 避免漂移) switch (attackType) { case "heavy": - offsetX = isFacingRight ? hitbox.width : -100; - offsetY = 40; - width = 100; - height = 100; - break; - case "light": - offsetX = isFacingRight ? hitbox.width - 10 : -attackbox.width + 10; + width = 100f; + height = 100f; + offsetY = 40f; + offsetX = isFacingRight ? hitbox.width : -width; // 重击靠近身体或覆盖前方 break; case "special": - offsetX = isFacingRight ? hitbox.width + 20 : -attackbox.width - 20; - offsetY = 50; - width = 120; - height = 60; + width = 120f; + height = 60f; + offsetY = 50f; + offsetX = isFacingRight ? hitbox.width + 20f : -width - 20f; break; + case "light": default: - offsetX = isFacingRight ? hitbox.width - 10 : -attackbox.width + 10; + // 轻击稍微往前,不再参考旧 attackbox.width + offsetX = isFacingRight ? hitbox.width - 10f : -80f + 10f; + break; } - attackbox.setPosition(hitbox.x + offsetX, hitbox.y + offsetY); attackbox.setSize(width, height); + attackbox.setPosition(hitbox.x + offsetX, hitbox.y + offsetY); } public void attack(String attackType) { @@ -304,6 +312,13 @@ public class SimpleFighter { hitbox.setPosition(x, y); } + /** 若当前 Y 低于地面则贴到地面。 */ + public void alignToGround() { + if (hitbox.y < GameConstants.GROUND_Y) { + hitbox.y = GameConstants.GROUND_Y; + } + } + public float getAttackTimer() { return attackTimer; } diff --git a/src/main/java/uno/mloluyu/desktop/BaseScreen.java b/src/main/java/uno/mloluyu/desktop/BaseScreen.java new file mode 100644 index 0000000..18f709c --- /dev/null +++ b/src/main/java/uno/mloluyu/desktop/BaseScreen.java @@ -0,0 +1,16 @@ +package uno.mloluyu.desktop; + +import com.badlogic.gdx.ScreenAdapter; + +/** 所有 Screen 的基础类,集中生命周期钩子扩展。 */ +public abstract class BaseScreen extends ScreenAdapter { + protected final MainGame game; + + protected BaseScreen(MainGame game) { + this.game = game; + } + + /** 提供可选的资源预加载完成回调 */ + public void onAssetsReady() { + } +} diff --git a/src/main/java/uno/mloluyu/desktop/CharacterSelectScreen.java b/src/main/java/uno/mloluyu/desktop/CharacterSelectScreen.java index 274a7d6..9ca4967 100644 --- a/src/main/java/uno/mloluyu/desktop/CharacterSelectScreen.java +++ b/src/main/java/uno/mloluyu/desktop/CharacterSelectScreen.java @@ -33,14 +33,12 @@ public class CharacterSelectScreen extends ScreenAdapter implements InputProcess private SimpleFighter selectedFighter2; private final List bgs = Arrays.asList( - new Texture(Gdx.files.internal("src/main/resources/selectpage/10b_back_blue2p.png")), - //new Texture(Gdx.files.internal("src/main/resources/selectpage/back_door.png")), - new Texture(Gdx.files.internal("src/main/resources/selectpage/11b_back_red1p.png")) - ); + new Texture(Gdx.files.internal("selectpage/10b_back_blue2p.png")), + // new Texture(Gdx.files.internal("selectpage/back_door.png")), + new Texture(Gdx.files.internal("selectpage/11b_back_red1p.png"))); private final List charsTexts = Arrays.asList( - new Texture(Gdx.files.internal("src/main/resources/selectpage/character_03.png")), - new Texture(Gdx.files.internal("src/main/resources/selectpage/character_00.png")) - ); + new Texture(Gdx.files.internal("selectpage/character_03.png")), + new Texture(Gdx.files.internal("selectpage/character_00.png"))); private final List characters = Arrays.asList("Alice", "Reimu", "暂定"); private Texture profile1p = charsTexts.get(0); private Texture profile2p = charsTexts.get(1); @@ -49,11 +47,13 @@ public class CharacterSelectScreen extends ScreenAdapter implements InputProcess private static int selectedIndex = 0; private static boolean is1P = true; - private static final int BUTTON_WIDTH = 300; - private static final int BUTTON_HEIGHT = 80; - private static final int BUTTON_X = 800; - private static final int CONFIRM_Y = 200; - private static final int BACK_Y = 100; + // 下面这些按钮常量原本用于文本/按钮绘制,当前 UI 逻辑已注释。 + // 如果后续恢复 renderTexts() 可重新启用;为减少无用警告暂时注释。 + // private static final int BUTTON_WIDTH = 300; + // private static final int BUTTON_HEIGHT = 80; + // private static final int BUTTON_X = 800; + // private static final int CONFIRM_Y = 200; + // private static final int BACK_Y = 100; public CharacterSelectScreen(MainGame game) { this.game = game; @@ -75,13 +75,14 @@ public class CharacterSelectScreen extends ScreenAdapter implements InputProcess @Override public void render(float delta) { - new ClearScreen(); + // 清屏:使用工具静态方法,避免误用私有构造器 + ClearScreen.clear(); int mouseX = Gdx.input.getX(); int mouseY = Gdx.graphics.getHeight() - Gdx.input.getY(); renderBackground(); renderCharacters(multiplayerMode); -// renderTexts(); + // renderTexts(); handleInput(mouseX, mouseY); if (multiplayerMode) { @@ -99,7 +100,7 @@ public class CharacterSelectScreen extends ScreenAdapter implements InputProcess private void renderBackground() { batch.begin(); - for (int i = 0; i < bgs.size(); i ++) { + for (int i = 0; i < bgs.size(); i++) { batch.draw(bgs.get(i), 0, 528 * i, 1920, 528); } batch.end(); @@ -107,45 +108,47 @@ public class CharacterSelectScreen extends ScreenAdapter implements InputProcess private void renderCharacters(boolean multiplayerMode) { batch.begin(); - batch.draw(profile1p, 0, 0, profile1p.getWidth()*3, profile1p.getHeight()*3); - batch.draw(profile2p, 0, 528, profile2p.getWidth()*3, profile2p.getHeight()*3); + batch.draw(profile1p, 0, 0, profile1p.getWidth() * 3, profile1p.getHeight() * 3); + batch.draw(profile2p, 0, 528, profile2p.getWidth() * 3, profile2p.getHeight() * 3); batch.end(); } -// -// private void renderTexts() { -// batch.begin(); -// font.draw(batch, "选择你的角色", 200, 650); -// for (int i = 0; i < characters.size(); i++) { -// int x = 200 + 30; -// int y = 500 - i * 120 + 50; -// font.draw(batch, characters.get(i), x, y); -// -// } -// if (selectedIndex != -1) { -// font.draw(batch, "已选择: " + characters.get(selectedIndex), 200, 100); -// } -// drawButtonText(CONFIRM_Y, "确认"); -// drawButtonText(BACK_Y, "返回"); -// batch.end(); -// } -// + // + // private void renderTexts() { + // batch.begin(); + // font.draw(batch, "选择你的角色", 200, 650); + // for (int i = 0; i < characters.size(); i++) { + // int x = 200 + 30; + // int y = 500 - i * 120 + 50; + // font.draw(batch, characters.get(i), x, y); + // + // } + // if (selectedIndex != -1) { + // font.draw(batch, "已选择: " + characters.get(selectedIndex), 200, 100); + // } + // drawButtonText(CONFIRM_Y, "确认"); + // drawButtonText(BACK_Y, "返回"); + // batch.end(); + // } + // private void handleInput(int mouseX, int mouseY) { - if (selectedFighter1 != null && selectedFighter2 != null) { - if (multiplayerMode) { - // 设置唯一玩家 ID 并发送角色选择 - if (NetworkManager.getInstance().getLocalPlayerId() == null) { - String playerId = UUID.randomUUID().toString(); - NetworkManager.getInstance().setLocalPlayerId(playerId); - Gdx.app.log("Network", "设置玩家ID: " + playerId); - } - NetworkManager.getInstance().sendCharacterSelection(selectedFighter1.getName()); + // 单人模式:只要1P选择了角色就进入游戏 + if (!multiplayerMode && selectedFighter1 != null) { + game.setScreen(new GameScreen(game, selectedFighter1)); + return; + } + // 联机模式:等待双方选择(当前逻辑仍采用2人都选才进入) + if (multiplayerMode && selectedFighter1 != null && selectedFighter2 != null) { + if (NetworkManager.getInstance().getLocalPlayerId() == null) { + String playerId = UUID.randomUUID().toString(); + NetworkManager.getInstance().setLocalPlayerId(playerId); + Gdx.app.log("Network", "设置玩家ID: " + playerId); } + NetworkManager.getInstance().sendCharacterSelection(selectedFighter1.getName()); game.setScreen(new GameScreen(game, selectedFighter1)); } } - @Override public void dispose() { batch.dispose(); @@ -168,7 +171,7 @@ public class CharacterSelectScreen extends ScreenAdapter implements InputProcess } value = true; } else { - //占坑说是 + // 占坑说是 } if (i == Input.Keys.Z) { if (is1P) { diff --git a/src/main/java/uno/mloluyu/desktop/DesktopLauncher.java b/src/main/java/uno/mloluyu/desktop/DesktopLauncher.java index 64b8942..65f45f9 100644 --- a/src/main/java/uno/mloluyu/desktop/DesktopLauncher.java +++ b/src/main/java/uno/mloluyu/desktop/DesktopLauncher.java @@ -3,6 +3,8 @@ package uno.mloluyu.desktop; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; +// 日志功能已移除 + /** * Desktop 平台启动器 */ @@ -10,6 +12,8 @@ public class DesktopLauncher { public static void main(String[] args) { + // 若需要异常抓取,可在此处添加简单的 Thread.setDefaultUncaughtExceptionHandler + LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); float scale = 1.0F; diff --git a/src/main/java/uno/mloluyu/desktop/GameScreen.java b/src/main/java/uno/mloluyu/desktop/GameScreen.java index 6672e50..417a340 100644 --- a/src/main/java/uno/mloluyu/desktop/GameScreen.java +++ b/src/main/java/uno/mloluyu/desktop/GameScreen.java @@ -5,6 +5,7 @@ import com.badlogic.gdx.ScreenAdapter; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.GL20; import uno.mloluyu.characters.Action; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; @@ -18,6 +19,9 @@ import uno.mloluyu.characters.SimpleFighter; import uno.mloluyu.characters.AdvancedFighter; import uno.mloluyu.network.NetworkManager; import uno.mloluyu.util.ClearScreen; +import uno.mloluyu.perf.PerfMetrics; +import uno.mloluyu.util.TimeStepLimiter; +import uno.mloluyu.util.GameConstants; import uno.mloluyu.versatile.FighterController; public class GameScreen extends ScreenAdapter { @@ -30,6 +34,37 @@ public class GameScreen extends ScreenAdapter { private SpriteBatch batch; private ShapeRenderer shapeRenderer; private OrthographicCamera camera; + private Texture background; + // 世界尺寸(基于背景原始尺寸 * 缩放)。如果需要可读取 Texture 宽高后动态设。 + private float worldWidth; + private float worldHeight; + // 背景整体缩放倍数:>1 表示背景比屏幕大,可只显示局部 + private static final float BACKGROUND_SCALE = 1.5f; + // 摄像机竖直偏移(正值=镜头上移,让玩家更靠下;这里改为较小的正值防止看不到下方) + private static final float CAMERA_Y_OFFSET = 60f; + // 允许相机向下多看到的底部扩展(不被 clamp 过早挡住),解决放大 hitbox 下半部分出框 + private static final float CAMERA_BOTTOM_MARGIN = -30f; + // 平滑跟随的垂直插值系数(独立控制 y,防止瞬间跳) + private static final float CAMERA_LERP_ALPHA = 0.12f; + // ========== 摄像机缩放配置 ========== + // 是否使用动态缩放(多人时根据距离自动拉远 / 靠近) + private static final boolean CAMERA_DYNAMIC_ZOOM = true; + // (保留)固定缩放模式开关 & 数值;若需要强制固定视角可把 CAMERA_DYNAMIC_ZOOM 设 false + private static final boolean CAMERA_USE_FIXED_ZOOM = false; + private static final float CAMERA_FIXED_ZOOM = 0.80f; + // 动态缩放参数:最小与最大(OrthographicCamera: <1 视角更近,>1 更远) + private static final float CAMERA_MIN_ZOOM = 0.55f; // 角色很近时 + private static final float CAMERA_MAX_ZOOM = 1.25f; // 距离很远时 + // 达到最大缩放所对应的“玩家距离”基准(屏幕世界单位,按你的角色移动范围调) + private static final float CAMERA_MAX_DISTANCE = 1800f; + // 缩放插值速度(越大越快贴近目标) + private static final float CAMERA_ZOOM_LERP = 0.10f; + // 显示地面参考线 + private static final boolean SHOW_GROUND_LINE = true; + // 半透明地面条带显示 + private static final boolean SHOW_GROUND_STRIP = true; + private static final float GROUND_STRIP_HEIGHT = 14f; // 条带厚度 + private static final float GROUND_STRIP_ALPHA = 0.20f; // 透明度 (0~1) public GameScreen(MainGame game, SimpleFighter player) { this.player = player; @@ -38,18 +73,33 @@ public class GameScreen extends ScreenAdapter { @Override public void show() { + // 确保角色初始贴地(地面抬高后老存档/默认 0 需要调整) + player.alignToGround(); camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); camera.position.set(player.getHitbox().x, player.getHitbox().y, 0); // 初始化摄像机位置 + // 初始化缩放:动态优先,否则固定 + if (CAMERA_DYNAMIC_ZOOM) { + camera.zoom = CAMERA_MIN_ZOOM; + } else if (CAMERA_USE_FIXED_ZOOM) { + camera.zoom = CAMERA_FIXED_ZOOM; + } camera.update(); batch = new SpriteBatch(); shapeRenderer = new ShapeRenderer(); Gdx.input.setInputProcessor(controller); + // 背景图(与主菜单共用 bg.png),可后续扩展成多关卡背景 + background = new Texture(Gdx.files.internal("innerbg.png")); + worldWidth = background.getWidth() * BACKGROUND_SCALE; + worldHeight = background.getHeight() * BACKGROUND_SCALE; } @Override public void render(float delta) { - new ClearScreen(); + delta = TimeStepLimiter.clamp(delta); + ClearScreen.clear(); + PerfMetrics.frame(delta); + // (原先背景在摄像机更新前绘制,会出现一帧“滞后”错位;已移到摄像机更新后) // 输入 / 逻辑 player.update(delta); @@ -150,7 +200,7 @@ public class GameScreen extends ScreenAdapter { String pid = dt.getKey(); // 重生位置简单放原点附近随机 float rx = (float) (Math.random() * 200 - 100); - float ry = 0; + float ry = GameConstants.GROUND_Y; NetworkManager.getInstance().sendRespawn(pid, rx, ry); deathTimers.remove(pid); } @@ -197,6 +247,9 @@ public class GameScreen extends ScreenAdapter { } } + // 本地与远程玩家之间简单碰撞(防穿人)——放在摄像机更新前 + resolvePlayerCollisions(); + // 摄像机跟随 // 摄像头跟随:若有一个远程玩家,则居中于本地和远程玩家中点 Vector3 targetPos; @@ -204,32 +257,87 @@ public class GameScreen extends ScreenAdapter { SimpleFighter remote = otherPlayers.values().iterator().next(); float midX = (player.getHitbox().x + remote.getHitbox().x) * 0.5f; float midY = (player.getHitbox().y + remote.getHitbox().y) * 0.5f; - targetPos = new Vector3(midX, midY, 0); + targetPos = new Vector3(midX, midY + CAMERA_Y_OFFSET, 0); } else { // 默认为跟随本地玩家 - targetPos = new Vector3(player.getHitbox().x, player.getHitbox().y, 0); + targetPos = new Vector3(player.getHitbox().x, player.getHitbox().y + CAMERA_Y_OFFSET, 0); } - camera.position.lerp(targetPos, 0.1f); + // 仅对 x,y 做分量插值,可单独调节速度 + camera.position.x += (targetPos.x - camera.position.x) * CAMERA_LERP_ALPHA; + camera.position.y += (targetPos.y - camera.position.y) * (CAMERA_LERP_ALPHA * 1.1f); + // 计算动态缩放目标 + if (CAMERA_DYNAMIC_ZOOM && otherPlayers.size() >= 1) { + // 取所有玩家(本地 + 远程)x 坐标的最大跨度作为距离依据,可扩展为对角线距离 + float minX = player.getHitbox().x; + float maxX = player.getHitbox().x; + float minY = player.getHitbox().y; + float maxY = player.getHitbox().y; + for (SimpleFighter r : otherPlayers.values()) { + Rectangle hb = r.getHitbox(); + if (hb.x < minX) + minX = hb.x; + if (hb.x > maxX) + maxX = hb.x; + if (hb.y < minY) + minY = hb.y; + if (hb.y > maxY) + maxY = hb.y; + } + float dx = maxX - minX; + float dy = maxY - minY; + // 这里主要横向对战,优先 dx;若想考虑纵向可用距离 = max(dx, dy*系数) + float dist = Math.max(dx, dy * 0.6f); + float t = Math.min(1f, dist / CAMERA_MAX_DISTANCE); // 0~1 + float targetZoom = CAMERA_MIN_ZOOM + (CAMERA_MAX_ZOOM - CAMERA_MIN_ZOOM) * t; + camera.zoom += (targetZoom - camera.zoom) * CAMERA_ZOOM_LERP; + } else if (CAMERA_USE_FIXED_ZOOM) { + // 固定缩放平滑 + if (Math.abs(camera.zoom - CAMERA_FIXED_ZOOM) > 0.0001f) { + camera.zoom += (CAMERA_FIXED_ZOOM - camera.zoom) * 0.25f; + } + } + // 约束摄像机在世界边界内(视口以中心为基准) + float halfW = (camera.viewportWidth * camera.zoom) / 2f; + float halfH = (camera.viewportHeight * camera.zoom) / 2f; + camera.position.x = Math.max(halfW, Math.min(worldWidth - halfW, camera.position.x)); + float minY = halfH - CAMERA_BOTTOM_MARGIN; // 允许比世界底部再低一些显示底部区域 + camera.position.y = Math.max(minY, Math.min(worldHeight - halfH, camera.position.y)); camera.update(); batch.setProjectionMatrix(camera.combined); shapeRenderer.setProjectionMatrix(camera.combined); + // -------- Background pass -------- + batch.begin(); + // 仅绘制背景的局部:通过在更大的缩放空间中直接拉伸整张图并限制摄像机 + // 若想真正裁剪一部分,可改用纹理区域;此处使用整图放大后让摄像机在其内游走 + batch.draw(background, + 0, 0, + worldWidth, worldHeight); + batch.end(); + // 混合 Gdx.gl.glEnable(GL20.GL_BLEND); Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); // -------- Filled pass -------- shapeRenderer.begin(ShapeRenderer.ShapeType.Filled); - drawHitbox(player, Color.BLUE); + // 半透明地面条带(先于 hitbox 绘制,这样人物在其上方) + if (SHOW_GROUND_STRIP) { + float y = GameConstants.GROUND_Y - GROUND_STRIP_HEIGHT * 0.5f; // 居中穿过地面线 + shapeRenderer.setColor(0.9f, 0.9f, 0.9f, GROUND_STRIP_ALPHA); // 近白轻薄层 + shapeRenderer.rect(0f, y, worldWidth, GROUND_STRIP_HEIGHT); + } + drawHitbox(player, player.getDebugColor()); boolean showPlayerAttack = player.isAttacking() || (player.getCurrentAction() == Action.ATTACK && player.getAttackTimer() > 0); if (showPlayerAttack) - drawAttackBox(player, 1f, 0f, 0f, 0.35f); + // 攻击框颜色改为蓝色(原红色) + drawAttackBox(player, 0.0f, 0.45f, 1f, 0.35f); for (SimpleFighter remote : otherPlayers.values()) { - drawHitbox(remote, Color.GREEN); + drawHitbox(remote, remote.getDebugColor()); if (remote.isAttacking()) - drawAttackBox(remote, 1f, 0f, 0f, 0.25f); + drawAttackBox(remote, 0.0f, 0.45f, 1f, 0.25f); } shapeRenderer.end(); @@ -240,11 +348,13 @@ public class GameScreen extends ScreenAdapter { // -------- Debug line pass -------- shapeRenderer.begin(ShapeRenderer.ShapeType.Line); + if (SHOW_GROUND_LINE) { + shapeRenderer.setColor(0.65f, 0.65f, 0.65f, 1f); // 浅灰色参考线 + shapeRenderer.line(0f, GameConstants.GROUND_Y, worldWidth, GameConstants.GROUND_Y); + } player.renderDebug(shapeRenderer); for (SimpleFighter remote : otherPlayers.values()) remote.renderDebug(shapeRenderer); - shapeRenderer.setColor(Color.WHITE); - shapeRenderer.rect(0, 0, 1000, 1000); shapeRenderer.end(); // -------- UI health bar pass -------- // 使用屏幕坐标绘制血条 @@ -274,18 +384,45 @@ public class GameScreen extends ScreenAdapter { idx++; } shapeRenderer.end(); + // 原定期性能日志已移除(日志系统删除) } private void drawHitbox(SimpleFighter fighter, Color color) { shapeRenderer.setColor(color); Rectangle r = fighter.getHitbox(); - shapeRenderer.rect(r.x, r.y, r.width, r.height); + float scale = GameConstants.DEBUG_BOX_SCALE; + if (scale <= 1.0001f && scale >= 0.9999f) { // 视为 1 + shapeRenderer.rect(r.x, r.y, r.width, r.height); + return; + } + float cx = r.x + r.width / 2f; + float cy = r.y + r.height / 2f; + float w = r.width * scale; + float h = r.height * scale; + if (GameConstants.DEBUG_SCALE_FROM_CENTER) { + shapeRenderer.rect(cx - w / 2f, cy - h / 2f, w, h); + } else { + shapeRenderer.rect(r.x, r.y, w, h); + } } private void drawAttackBox(SimpleFighter fighter, float r, float g, float b, float a) { shapeRenderer.setColor(r, g, b, a); Rectangle box = fighter.getAttackbox(); - shapeRenderer.rect(box.x, box.y, box.width, box.height); + float scale = GameConstants.DEBUG_BOX_SCALE; + if (scale <= 1.0001f && scale >= 0.9999f) { + shapeRenderer.rect(box.x, box.y, box.width, box.height); + return; + } + float cx = box.x + box.width / 2f; + float cy = box.y + box.height / 2f; + float w = box.width * scale; + float h = box.height * scale; + if (GameConstants.DEBUG_SCALE_FROM_CENTER) { + shapeRenderer.rect(cx - w / 2f, cy - h / 2f, w, h); + } else { + shapeRenderer.rect(box.x, box.y, w, h); + } } // private void checkPlayerAttacks() { @@ -303,6 +440,41 @@ public class GameScreen extends ScreenAdapter { public void dispose() { batch.dispose(); shapeRenderer.dispose(); + if (background != null) + background.dispose(); NetworkManager.getInstance().disconnect(); } + + /** + * 解决本地玩家与每个远程玩家的水平重叠,避免“穿人”视觉效果。 + * 当前策略:仅移动本地玩家(不修改远程玩家坐标,避免产生需要网络回传的状态)。 + * 若需要更严格的对等碰撞,可在主机端双向分离并广播,但此处先满足基本需求。 + */ + private void resolvePlayerCollisions() { + if (otherPlayers.isEmpty()) + return; + Rectangle a = player.getHitbox(); + for (SimpleFighter remote : otherPlayers.values()) { + Rectangle b = remote.getHitbox(); + if (!a.overlaps(b)) + continue; + // 仅考虑水平最小位移分离(2D 侧向格斗常见做法) + float axCenter = a.x + a.width / 2f; + float bxCenter = b.x + b.width / 2f; + float dx = axCenter - bxCenter; // 正值:本地在右侧 + float overlapX = (a.width + b.width) / 2f - Math.abs(dx); + if (overlapX > 0) { + if (dx >= 0) { + a.x += overlapX; // 本地向右推 + } else { + a.x -= overlapX; // 本地向左推 + } + // 世界边界限制(假设世界从 0 开始到 worldWidth) + if (a.x < 0) + a.x = 0; + if (a.x + a.width > worldWidth) + a.x = worldWidth - a.width; + } + } + } } diff --git a/src/main/java/uno/mloluyu/desktop/MainGame.java b/src/main/java/uno/mloluyu/desktop/MainGame.java index 699842e..1cdc690 100644 --- a/src/main/java/uno/mloluyu/desktop/MainGame.java +++ b/src/main/java/uno/mloluyu/desktop/MainGame.java @@ -1,8 +1,6 @@ package uno.mloluyu.desktop; import com.badlogic.gdx.Game; -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.graphics.Texture; public class MainGame extends Game { public static final float WORLD_WIDTH = 1920; diff --git a/src/main/java/uno/mloluyu/desktop/MainMenuScreen.java b/src/main/java/uno/mloluyu/desktop/MainMenuScreen.java index 8206052..e19c657 100644 --- a/src/main/java/uno/mloluyu/desktop/MainMenuScreen.java +++ b/src/main/java/uno/mloluyu/desktop/MainMenuScreen.java @@ -9,8 +9,6 @@ import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; -import uno.mloluyu.versatile.FighterController; - import static uno.mloluyu.util.Font.loadChineseFont; public class MainMenuScreen extends ScreenAdapter { @@ -32,7 +30,7 @@ public class MainMenuScreen extends ScreenAdapter { public MainMenuScreen(MainGame game) { this.game = game; - texture = new Texture(Gdx.files.internal("src\\main\\resources\\bg.png")); + texture = new Texture(Gdx.files.internal("bg.png")); } @Override @@ -46,7 +44,7 @@ public class MainMenuScreen extends ScreenAdapter { @Override public void render(float delta) { - Gdx.gl.glClearColor(0,0,0,0); + Gdx.gl.glClearColor(0, 0, 0, 0); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); int mouseX = Gdx.input.getX(); @@ -76,6 +74,7 @@ public class MainMenuScreen extends ScreenAdapter { game.setScreen(new CharacterSelectScreen(game)); } else if (isTouched(mouseX, mouseY, buttonX, settingsY)) { Gdx.app.log("Button", "设置按钮被点击!"); + game.setScreen(new SettingsScreen(game)); } else if (isTouched(mouseX, mouseY, buttonX, networkY)) { Gdx.app.log("Button", "联网设置按钮被点击!"); game.setScreen(new NetworkSettingsScreen(game)); @@ -92,8 +91,7 @@ public class MainMenuScreen extends ScreenAdapter { } private void drawButtonText(int y, String text) { - float textWidth = font.getRegion().getRegionWidth(); // 粗略估算 - float textX = buttonX + buttonWidth / 2f - text.length() * 20; // 居中估算 + float textX = buttonX + buttonWidth / 2f - text.length() * 20; // 简单估算居中 float textY = y + buttonHeight / 2f + 20; font.draw(batch, text, textX, textY); } @@ -104,8 +102,11 @@ public class MainMenuScreen extends ScreenAdapter { @Override public void dispose() { - if (batch != null) batch.dispose(); - if (font != null) font.dispose(); - if (shapeRenderer != null) shapeRenderer.dispose(); + if (batch != null) + batch.dispose(); + if (font != null) + font.dispose(); + if (shapeRenderer != null) + shapeRenderer.dispose(); } } diff --git a/src/main/java/uno/mloluyu/desktop/NetworkSettingsScreen.java b/src/main/java/uno/mloluyu/desktop/NetworkSettingsScreen.java index 5428946..f47cca0 100644 --- a/src/main/java/uno/mloluyu/desktop/NetworkSettingsScreen.java +++ b/src/main/java/uno/mloluyu/desktop/NetworkSettingsScreen.java @@ -3,7 +3,6 @@ package uno.mloluyu.desktop; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.ScreenAdapter; import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; @@ -40,7 +39,8 @@ public class NetworkSettingsScreen extends ScreenAdapter { @Override public void render(float delta) { - new ClearScreen(); + // 使用静态工具方法清屏 + ClearScreen.clear(); int mouseX = Gdx.input.getX(); int mouseY = Gdx.graphics.getHeight() - Gdx.input.getY(); @@ -64,6 +64,12 @@ public class NetworkSettingsScreen extends ScreenAdapter { drawButtonText(CREATE_ROOM_Y, "创建房间"); drawButtonText(JOIN_ROOM_Y, "加入房间"); drawButtonText(EXIT_Y, "返回"); + // 状态信息 + NetworkManager nm = NetworkManager.getInstance(); + String id = nm.getLocalPlayerId(); + font.draw(batch, "本机ID: " + (id == null ? "(未分配)" : id.substring(0, Math.min(8, id.length()))), 50, 200); + font.draw(batch, nm.isHost() ? "当前: 房主" : (nm.isConnected() ? "当前: 已连接客户端" : "当前: 未连接"), 50, 160); + font.draw(batch, "在线玩家: " + (nm.getPlayerPositions() == null ? 0 : nm.getPlayerPositions().size()), 50, 120); batch.end(); } diff --git a/src/main/java/uno/mloluyu/desktop/ScreenManager.java b/src/main/java/uno/mloluyu/desktop/ScreenManager.java new file mode 100644 index 0000000..ff1e7c2 --- /dev/null +++ b/src/main/java/uno/mloluyu/desktop/ScreenManager.java @@ -0,0 +1,25 @@ +package uno.mloluyu.desktop; + +/** 简易屏幕管理器,负责切换与异常保护。 */ +public class ScreenManager { + private final MainGame game; + private BaseScreen current; + + public ScreenManager(MainGame game) { + this.game = game; + } + + public void set(BaseScreen next) { + try { + if (current != null) { + current.hide(); + current.dispose(); + } + current = next; + game.setScreen(next); + } catch (Throwable t) { + // 忽略或可加简单 System.err + t.printStackTrace(); + } + } +} diff --git a/src/main/java/uno/mloluyu/desktop/SettingsScreen.java b/src/main/java/uno/mloluyu/desktop/SettingsScreen.java new file mode 100644 index 0000000..46d6142 --- /dev/null +++ b/src/main/java/uno/mloluyu/desktop/SettingsScreen.java @@ -0,0 +1,75 @@ +package uno.mloluyu.desktop; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.ScreenAdapter; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import uno.mloluyu.util.ClearScreen; +import static uno.mloluyu.util.Font.loadChineseFont; + +/** + * 简单设置界面占位:未来可扩展(音量/按键设置)。 + */ +public class SettingsScreen extends ScreenAdapter { + private final MainGame game; + private SpriteBatch batch; + private BitmapFont font; + private ShapeRenderer shapeRenderer; + + private static final int BACK_X = 100; // 返回按钮区域 + private static final int BACK_Y = 100; + private static final int BACK_W = 220; + private static final int BACK_H = 70; + + public SettingsScreen(MainGame game) { + this.game = game; + } + + @Override + public void show() { + batch = new SpriteBatch(); + shapeRenderer = new ShapeRenderer(); + font = loadChineseFont(); + font.setColor(Color.WHITE); + font.getData().setScale(2f); + } + + @Override + public void render(float delta) { + ClearScreen.clear(); + int mouseX = Gdx.input.getX(); + int mouseY = Gdx.graphics.getHeight() - Gdx.input.getY(); + + // 绘制背景与返回按钮 + shapeRenderer.begin(ShapeRenderer.ShapeType.Filled); + shapeRenderer.setColor(Color.DARK_GRAY); + shapeRenderer.rect(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + boolean backHover = isHovered(mouseX, mouseY, BACK_X, BACK_Y, BACK_W, BACK_H); + shapeRenderer.setColor(backHover ? Color.LIGHT_GRAY : Color.GRAY); + shapeRenderer.rect(BACK_X, BACK_Y, BACK_W, BACK_H); + shapeRenderer.end(); + + batch.begin(); + font.draw(batch, "设置 (占位界面)", 100, Gdx.graphics.getHeight() - 120); + font.draw(batch, "此处可添加: 音量 / 按键 / 分辨率 / 语言 等", 100, Gdx.graphics.getHeight() - 180); + font.draw(batch, "返回", BACK_X + 50, BACK_Y + 45); + batch.end(); + + if (Gdx.input.justTouched() && backHover) { + game.setScreen(new MainMenuScreen(game)); + } + } + + private boolean isHovered(int x, int y, int bx, int by, int bw, int bh) { + return x >= bx && x <= bx + bw && y >= by && y <= by + bh; + } + + @Override + public void dispose() { + batch.dispose(); + font.dispose(); + shapeRenderer.dispose(); + } +} diff --git a/src/main/java/uno/mloluyu/desktop/StartScreen.java b/src/main/java/uno/mloluyu/desktop/StartScreen.java index 3217a97..9d278ef 100644 --- a/src/main/java/uno/mloluyu/desktop/StartScreen.java +++ b/src/main/java/uno/mloluyu/desktop/StartScreen.java @@ -1,17 +1,17 @@ package uno.mloluyu.desktop; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.Screen; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; +import uno.mloluyu.util.ResourcePaths; /** * 启动屏幕类 * 显示游戏Logo并在3秒后切换到主菜单界面 */ -public class StartScreen implements Screen { +public class StartScreen extends BaseScreen { - private MainGame mainGame; + private MainGame mainGame; // TODO: 后续可直接用BaseScreen.game private Texture logoTexture; private com.badlogic.gdx.graphics.g2d.SpriteBatch batch; @@ -19,8 +19,9 @@ public class StartScreen implements Screen { private float deltaSum; public StartScreen(MainGame mainGame) { + super(mainGame); this.mainGame = mainGame; - logoTexture = new Texture(Gdx.files.internal("logo.png")); + logoTexture = new Texture(Gdx.files.internal(ResourcePaths.LOGO)); batch = new com.badlogic.gdx.graphics.g2d.SpriteBatch(); } @@ -36,7 +37,6 @@ public class StartScreen implements Screen { if (deltaSum >= .01F) { if (mainGame != null) { mainGame.showGameScreen(); - System.out.println("已经切换到主菜单"); return; } } diff --git a/src/main/java/uno/mloluyu/network/NetworkManager.java b/src/main/java/uno/mloluyu/network/NetworkManager.java index f5b8a10..826206e 100644 --- a/src/main/java/uno/mloluyu/network/NetworkManager.java +++ b/src/main/java/uno/mloluyu/network/NetworkManager.java @@ -73,6 +73,14 @@ public class NetworkManager { } } + /** + * 本地玩家所选角色(仅本地缓存,远程映射存于 playerCharacters)。 + * 供界面或后续同步逻辑查询。 + */ + public String getLocalCharacter() { + return localCharacter; + } + public void receiveMessage(String message) {// 解析消息 if (message.startsWith("POS:")) { diff --git a/src/main/java/uno/mloluyu/perf/PerfMetrics.java b/src/main/java/uno/mloluyu/perf/PerfMetrics.java new file mode 100644 index 0000000..35a6151 --- /dev/null +++ b/src/main/java/uno/mloluyu/perf/PerfMetrics.java @@ -0,0 +1,20 @@ +package uno.mloluyu.perf; + +/** 简单性能指标收集(首版)。 */ +public final class PerfMetrics { + private static long frameCount; + private static double accumTime; + private static double maxFrame = 0; + + public static void frame(double delta) { + frameCount++; + accumTime += delta; + if (delta > maxFrame) + maxFrame = delta; + } + + public static String summary() { + double avg = frameCount == 0 ? 0 : accumTime / frameCount; + return String.format("frames=%d avg=%.4f max=%.4f", frameCount, avg, maxFrame); + } +} diff --git a/src/main/java/uno/mloluyu/util/ClearScreen.java b/src/main/java/uno/mloluyu/util/ClearScreen.java index 55d3fcd..12fd426 100644 --- a/src/main/java/uno/mloluyu/util/ClearScreen.java +++ b/src/main/java/uno/mloluyu/util/ClearScreen.java @@ -3,9 +3,13 @@ package uno.mloluyu.util; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; -public class ClearScreen { - public ClearScreen() { +/** 清屏工具:改为静态方法避免每帧 new 对象。 */ +public final class ClearScreen { + private ClearScreen() { + } + + public static void clear() { Gdx.gl.glClearColor(0.3F, 0.3F, 0.5F, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); } -} \ No newline at end of file +} diff --git a/src/main/java/uno/mloluyu/util/GameConstants.java b/src/main/java/uno/mloluyu/util/GameConstants.java new file mode 100644 index 0000000..3a17828 --- /dev/null +++ b/src/main/java/uno/mloluyu/util/GameConstants.java @@ -0,0 +1,32 @@ +package uno.mloluyu.util; + +/** + * 全局游戏常量集中放置。 + */ +public final class GameConstants { + /** + * 地面(空气墙)Y 坐标。抬高后便于更好构图。 + */ + public static final float GROUND_Y = 180f; // 可按需要再调 + + // 角色移动/物理参数(集中配置便于统一手感调节) + // 用户要求:速度 *2,跳跃 *3(基于当前 520 / 1250) + public static final float MOVE_SPEED = 1040f; // 520 *2 (最初 ~300) + // 调低跳跃:更低高度 + 更短滞空:v0 ↓,同时重力 ↑ + // 说明:上升时间 t_up = v0 / g,本次取 v0=1500, g=3200 => + // t_up≈0.47s,总滞空≈0.94s,高度≈(v0^2)/(2g)≈351 + // 若想再更低:JUMP_SPEED 1400 + GRAVITY 3400;再更高一点:JUMP_SPEED 1600 + GRAVITY 3000。 + public static final float JUMP_SPEED = 1500f; + public static final float GRAVITY = 3200f; // 加大重力让落地更快 + + // 调试命中盒渲染缩放(=1 表示真实大小;之前放大 3.6 现在回归可控) + // 调试盒缩放:1 = 实际大小;若想放大显示结构,可调大。 + public static final float DEBUG_BOX_SCALE = 1.0f; + // 是否按中心放大(true 则保持角色中心位置,不会视觉漂移) + public static final boolean DEBUG_SCALE_FROM_CENTER = true; + + // (可选)相机或后续平衡参数也可集中放这里 + + private GameConstants() { + } +} diff --git a/src/main/java/uno/mloluyu/util/ResourcePaths.java b/src/main/java/uno/mloluyu/util/ResourcePaths.java new file mode 100644 index 0000000..a62800b --- /dev/null +++ b/src/main/java/uno/mloluyu/util/ResourcePaths.java @@ -0,0 +1,12 @@ +package uno.mloluyu.util; + +/** 统一资源常量,避免魔法字符串散落。 */ +public final class ResourcePaths { + private ResourcePaths() { + } + + public static final String LOGO = "logo.png"; + public static final String FONT_MAIN = "FLyouzichati-Regular-2.ttf"; + public static final String CHARACTER_ROOT = "character/"; + public static final String UI_SKIN_JSON = "ui/uiskin.json"; +} diff --git a/src/main/java/uno/mloluyu/util/TimeStepLimiter.java b/src/main/java/uno/mloluyu/util/TimeStepLimiter.java new file mode 100644 index 0000000..f8f8fef --- /dev/null +++ b/src/main/java/uno/mloluyu/util/TimeStepLimiter.java @@ -0,0 +1,13 @@ +package uno.mloluyu.util; + +/** 限制delta时间,防止窗口拖拽/卡顿后出现物理跳跃。 */ +public final class TimeStepLimiter { + private TimeStepLimiter() { + } + + private static final float MAX_DELTA = 1f / 30f; // 上限: 相当于最低30FPS + + public static float clamp(float delta) { + return delta > MAX_DELTA ? MAX_DELTA : Math.max(delta, 0f); + } +} diff --git a/src/main/resources/innerbg.png b/src/main/resources/innerbg.png new file mode 100644 index 0000000..7503a26 Binary files /dev/null and b/src/main/resources/innerbg.png differ diff --git a/src/test/java/uno/mloluyu/assets/AssetsExistenceTest.java b/src/test/java/uno/mloluyu/assets/AssetsExistenceTest.java new file mode 100644 index 0000000..71b535f --- /dev/null +++ b/src/test/java/uno/mloluyu/assets/AssetsExistenceTest.java @@ -0,0 +1,22 @@ +package uno.mloluyu.assets; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +public class AssetsExistenceTest { + private static final Path RES = Path.of("src", "main", "resources"); + + @Test + void testCoreAssetsPresent() { + List required = List.of("logo.png", "character/alice/alice.png", "character/reimu/reimu-0.png", + "ui/uiskin.json"); + for (String r : required) { + Path p = RES.resolve(r); + Assertions.assertTrue(Files.exists(p), "缺失资源: " + r); + } + } +} diff --git a/src/test/java/uno/mloluyu/characters/ActionStateTest.java b/src/test/java/uno/mloluyu/characters/ActionStateTest.java new file mode 100644 index 0000000..211434b --- /dev/null +++ b/src/test/java/uno/mloluyu/characters/ActionStateTest.java @@ -0,0 +1,21 @@ +package uno.mloluyu.characters; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ActionStateTest { + @Test + void testLegalTransitionIdleToMove() { + SimpleFighter f = new SimpleFighter("Test"); + f.changeAction(Action.MOVE); + Assertions.assertEquals(Action.MOVE, f.getCurrentAction()); + } + + @Test + void testIllegalTransitionDeadToMove() { + SimpleFighter f = new SimpleFighter("Test"); + f.changeAction(Action.DEAD); + f.changeAction(Action.MOVE); // 应被拒绝 + Assertions.assertEquals(Action.DEAD, f.getCurrentAction()); + } +} diff --git a/target/classes/innerbg.png b/target/classes/innerbg.png new file mode 100644 index 0000000..7503a26 Binary files /dev/null and b/target/classes/innerbg.png differ diff --git a/target/classes/uno/mloluyu/characters/Action.class b/target/classes/uno/mloluyu/characters/Action.class index 92cd063..7ea2ac5 100644 Binary files a/target/classes/uno/mloluyu/characters/Action.class and b/target/classes/uno/mloluyu/characters/Action.class differ diff --git a/target/classes/uno/mloluyu/characters/ActionStateGuard.class b/target/classes/uno/mloluyu/characters/ActionStateGuard.class new file mode 100644 index 0000000..dcc21b6 Binary files /dev/null and b/target/classes/uno/mloluyu/characters/ActionStateGuard.class differ diff --git a/target/classes/uno/mloluyu/characters/ActionTransitionMap.class b/target/classes/uno/mloluyu/characters/ActionTransitionMap.class new file mode 100644 index 0000000..da867dc Binary files /dev/null and b/target/classes/uno/mloluyu/characters/ActionTransitionMap.class differ diff --git a/target/classes/uno/mloluyu/characters/AdvancedFighter.class b/target/classes/uno/mloluyu/characters/AdvancedFighter.class index 102df56..6b749bf 100644 Binary files a/target/classes/uno/mloluyu/characters/AdvancedFighter.class and b/target/classes/uno/mloluyu/characters/AdvancedFighter.class differ diff --git a/target/classes/uno/mloluyu/characters/FighterAnimationManager.class b/target/classes/uno/mloluyu/characters/FighterAnimationManager.class index eb5f5a6..cf0ed32 100644 Binary files a/target/classes/uno/mloluyu/characters/FighterAnimationManager.class and b/target/classes/uno/mloluyu/characters/FighterAnimationManager.class differ diff --git a/target/classes/uno/mloluyu/characters/FighterBase.class b/target/classes/uno/mloluyu/characters/FighterBase.class new file mode 100644 index 0000000..126da1a Binary files /dev/null and b/target/classes/uno/mloluyu/characters/FighterBase.class differ diff --git a/target/classes/uno/mloluyu/characters/SimpleFighter.class b/target/classes/uno/mloluyu/characters/SimpleFighter.class index dd65ca8..0e8605e 100644 Binary files a/target/classes/uno/mloluyu/characters/SimpleFighter.class and b/target/classes/uno/mloluyu/characters/SimpleFighter.class differ diff --git a/target/classes/uno/mloluyu/desktop/BaseScreen.class b/target/classes/uno/mloluyu/desktop/BaseScreen.class new file mode 100644 index 0000000..a6a8708 Binary files /dev/null and b/target/classes/uno/mloluyu/desktop/BaseScreen.class differ diff --git a/target/classes/uno/mloluyu/desktop/CharacterSelectScreen.class b/target/classes/uno/mloluyu/desktop/CharacterSelectScreen.class index 97f3dae..b4abd5d 100644 Binary files a/target/classes/uno/mloluyu/desktop/CharacterSelectScreen.class and b/target/classes/uno/mloluyu/desktop/CharacterSelectScreen.class differ diff --git a/target/classes/uno/mloluyu/desktop/DesktopLauncher.class b/target/classes/uno/mloluyu/desktop/DesktopLauncher.class index d258f8a..14c04bc 100644 Binary files a/target/classes/uno/mloluyu/desktop/DesktopLauncher.class and b/target/classes/uno/mloluyu/desktop/DesktopLauncher.class differ diff --git a/target/classes/uno/mloluyu/desktop/GameScreen.class b/target/classes/uno/mloluyu/desktop/GameScreen.class index 4f63201..4b3f60d 100644 Binary files a/target/classes/uno/mloluyu/desktop/GameScreen.class and b/target/classes/uno/mloluyu/desktop/GameScreen.class differ diff --git a/target/classes/uno/mloluyu/desktop/MainGame.class b/target/classes/uno/mloluyu/desktop/MainGame.class index 56c5d61..91fbf91 100644 Binary files a/target/classes/uno/mloluyu/desktop/MainGame.class and b/target/classes/uno/mloluyu/desktop/MainGame.class differ diff --git a/target/classes/uno/mloluyu/desktop/MainMenuScreen.class b/target/classes/uno/mloluyu/desktop/MainMenuScreen.class index 0c537df..55edc9f 100644 Binary files a/target/classes/uno/mloluyu/desktop/MainMenuScreen.class and b/target/classes/uno/mloluyu/desktop/MainMenuScreen.class differ diff --git a/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen$1.class b/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen$1.class index 283b1c0..32054a2 100644 Binary files a/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen$1.class and b/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen$1.class differ diff --git a/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen.class b/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen.class index b845803..14617b8 100644 Binary files a/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen.class and b/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen.class differ diff --git a/target/classes/uno/mloluyu/desktop/ScreenManager.class b/target/classes/uno/mloluyu/desktop/ScreenManager.class new file mode 100644 index 0000000..d833a70 Binary files /dev/null and b/target/classes/uno/mloluyu/desktop/ScreenManager.class differ diff --git a/target/classes/uno/mloluyu/desktop/SettingsScreen.class b/target/classes/uno/mloluyu/desktop/SettingsScreen.class new file mode 100644 index 0000000..5f8abb7 Binary files /dev/null and b/target/classes/uno/mloluyu/desktop/SettingsScreen.class differ diff --git a/target/classes/uno/mloluyu/desktop/StartScreen.class b/target/classes/uno/mloluyu/desktop/StartScreen.class index f60838b..bca2b1d 100644 Binary files a/target/classes/uno/mloluyu/desktop/StartScreen.class and b/target/classes/uno/mloluyu/desktop/StartScreen.class differ diff --git a/target/classes/uno/mloluyu/network/ConnectClient.class b/target/classes/uno/mloluyu/network/ConnectClient.class index 3e28e9f..a050295 100644 Binary files a/target/classes/uno/mloluyu/network/ConnectClient.class and b/target/classes/uno/mloluyu/network/ConnectClient.class differ diff --git a/target/classes/uno/mloluyu/network/ConnectServer.class b/target/classes/uno/mloluyu/network/ConnectServer.class index 5c99138..c56c7a3 100644 Binary files a/target/classes/uno/mloluyu/network/ConnectServer.class and b/target/classes/uno/mloluyu/network/ConnectServer.class differ diff --git a/target/classes/uno/mloluyu/network/NetworkManager.class b/target/classes/uno/mloluyu/network/NetworkManager.class index f836db3..cdd9abd 100644 Binary files a/target/classes/uno/mloluyu/network/NetworkManager.class and b/target/classes/uno/mloluyu/network/NetworkManager.class differ diff --git a/target/classes/uno/mloluyu/perf/PerfMetrics.class b/target/classes/uno/mloluyu/perf/PerfMetrics.class new file mode 100644 index 0000000..803cb01 Binary files /dev/null and b/target/classes/uno/mloluyu/perf/PerfMetrics.class differ diff --git a/target/classes/uno/mloluyu/util/ClearScreen.class b/target/classes/uno/mloluyu/util/ClearScreen.class index b747ce3..7d173c8 100644 Binary files a/target/classes/uno/mloluyu/util/ClearScreen.class and b/target/classes/uno/mloluyu/util/ClearScreen.class differ diff --git a/target/classes/uno/mloluyu/util/Font.class b/target/classes/uno/mloluyu/util/Font.class index d0e69d2..f3d8782 100644 Binary files a/target/classes/uno/mloluyu/util/Font.class and b/target/classes/uno/mloluyu/util/Font.class differ diff --git a/target/classes/uno/mloluyu/util/GameConstants.class b/target/classes/uno/mloluyu/util/GameConstants.class new file mode 100644 index 0000000..e35d632 Binary files /dev/null and b/target/classes/uno/mloluyu/util/GameConstants.class differ diff --git a/target/classes/uno/mloluyu/util/ResourcePaths.class b/target/classes/uno/mloluyu/util/ResourcePaths.class new file mode 100644 index 0000000..758f2ae Binary files /dev/null and b/target/classes/uno/mloluyu/util/ResourcePaths.class differ diff --git a/target/classes/uno/mloluyu/util/SimpleFormatter.class b/target/classes/uno/mloluyu/util/SimpleFormatter.class index 8c9bab2..84af275 100644 Binary files a/target/classes/uno/mloluyu/util/SimpleFormatter.class and b/target/classes/uno/mloluyu/util/SimpleFormatter.class differ diff --git a/target/classes/uno/mloluyu/util/TimeStepLimiter.class b/target/classes/uno/mloluyu/util/TimeStepLimiter.class new file mode 100644 index 0000000..0700958 Binary files /dev/null and b/target/classes/uno/mloluyu/util/TimeStepLimiter.class differ diff --git a/target/classes/uno/mloluyu/versatile/FighterController.class b/target/classes/uno/mloluyu/versatile/FighterController.class index f375ecf..9f2fe30 100644 Binary files a/target/classes/uno/mloluyu/versatile/FighterController.class and b/target/classes/uno/mloluyu/versatile/FighterController.class differ diff --git a/target/game-1.0-SNAPSHOT.jar b/target/game-1.0-SNAPSHOT.jar new file mode 100644 index 0000000..8f63653 Binary files /dev/null and b/target/game-1.0-SNAPSHOT.jar differ diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..a1c2e23 --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=game +groupId=uno.mloluyu +version=1.0-SNAPSHOT diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 59271ad..c994b96 100644 --- a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,6 +1,7 @@ uno\mloluyu\desktop\StartScreen.class uno\mloluyu\desktop\NetworkSettingsScreen$1.class uno\mloluyu\network\NetworkManager.class +uno\mloluyu\characters\FighterAnimationManager.class uno\mloluyu\desktop\CharacterSelectScreen.class uno\mloluyu\versatile\FighterController.class uno\mloluyu\desktop\MainGame.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index d01a604..46662ee 100644 --- a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -1,17 +1,28 @@ C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\characters\Action.java +C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\characters\ActionStateGuard.java +C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\characters\ActionTransitionMap.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\characters\AdvancedFighter.java +C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\characters\FighterAnimationManager.java +C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\characters\FighterBase.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\characters\SimpleFighter.java +C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\desktop\BaseScreen.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\desktop\CharacterSelectScreen.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\desktop\DesktopLauncher.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\desktop\GameScreen.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\desktop\MainGame.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\desktop\MainMenuScreen.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\desktop\NetworkSettingsScreen.java +C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\desktop\ScreenManager.java +C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\desktop\SettingsScreen.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\desktop\StartScreen.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\network\ConnectClient.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\network\ConnectServer.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\network\NetworkManager.java +C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\perf\PerfMetrics.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\util\ClearScreen.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\util\Font.java +C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\util\GameConstants.java +C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\util\ResourcePaths.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\util\SimpleFormatter.java +C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\util\TimeStepLimiter.java C:\Users\www\Documents\Game\格斗游戏\Game\src\main\java\uno\mloluyu\versatile\FighterController.java diff --git a/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..e0019a5 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1,2 @@ +C:\Users\www\Documents\Game\格斗游戏\Game\src\test\java\uno\mloluyu\assets\AssetsExistenceTest.java +C:\Users\www\Documents\Game\格斗游戏\Game\src\test\java\uno\mloluyu\characters\ActionStateTest.java diff --git a/target/test-classes/uno/mloluyu/assets/AssetsExistenceTest.class b/target/test-classes/uno/mloluyu/assets/AssetsExistenceTest.class new file mode 100644 index 0000000..7e5bba1 Binary files /dev/null and b/target/test-classes/uno/mloluyu/assets/AssetsExistenceTest.class differ diff --git a/target/test-classes/uno/mloluyu/characters/ActionStateTest.class b/target/test-classes/uno/mloluyu/characters/ActionStateTest.class new file mode 100644 index 0000000..7d5a884 Binary files /dev/null and b/target/test-classes/uno/mloluyu/characters/ActionStateTest.class differ