调整弹药总量显示位置,添加弹药盒弹药数量显示,添加更多动画

This commit is contained in:
Light_Quanta 2025-03-20 14:06:29 +08:00
parent 1aeb3d65fc
commit 3255c0754a
No known key found for this signature in database
GPG key ID: 11A39A1B8C890959
6 changed files with 204 additions and 115 deletions

View file

@ -14,14 +14,14 @@ import com.atsuishio.superbwarfare.tools.GunsTool;
import com.atsuishio.superbwarfare.tools.InventoryTool; import com.atsuishio.superbwarfare.tools.InventoryTool;
import com.atsuishio.superbwarfare.tools.animation.AnimationCurves; import com.atsuishio.superbwarfare.tools.animation.AnimationCurves;
import com.atsuishio.superbwarfare.tools.animation.AnimationTimer; import com.atsuishio.superbwarfare.tools.animation.AnimationTimer;
import com.atsuishio.superbwarfare.tools.animation.DualValueHolder; import com.atsuishio.superbwarfare.tools.animation.ValueAnimator;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth; import net.minecraft.util.FastColor;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
@ -215,10 +215,17 @@ public class AmmoBarOverlay {
private static final AnimationTimer ammoInfoTimer = new AnimationTimer(1500) private static final AnimationTimer ammoInfoTimer = new AnimationTimer(1500)
.forwardAnimation(AnimationCurves.EASE_OUT_EXPO) .forwardAnimation(AnimationCurves.EASE_OUT_EXPO)
.backwardAnimation(AnimationCurves.EASE_IN_EXPO); .backwardAnimation(AnimationCurves.EASE_IN_EXPO);
private static final AnimationTimer ammoBoxTimer = new AnimationTimer(1500)
.forwardAnimation(AnimationCurves.EASE_OUT_EXPO)
.backwardAnimation(AnimationCurves.EASE_IN_EXPO);
private static final DualValueHolder<Integer>[] ammoCountHolders = DualValueHolder.create(AmmoType.values().length, 0); private static final ValueAnimator<Integer>[] ammoCountAnimators = ValueAnimator.create(
AmmoType.values().length, 800, 0
);
private static final ValueAnimator<Integer>[] ammoBoxAnimators = ValueAnimator.create(
AmmoType.values().length, 800, 0
);
private static final AnimationTimer[] ammoCountTimers = AnimationTimer.createTimers(AmmoType.values().length, 800, AnimationCurves.EASE_OUT_EXPO);
/** /**
* 在手持弹药或弹药盒时渲染玩家弹药总量信息 * 在手持弹药或弹药盒时渲染玩家弹药总量信息
@ -229,80 +236,100 @@ public class AmmoBarOverlay {
Player player = Minecraft.getInstance().player; Player player = Minecraft.getInstance().player;
if (player == null || player.isSpectator()) return; if (player == null || player.isSpectator()) return;
boolean isAmmoBox = false;
// 动画计算 // 动画计算
var currentTime = System.currentTimeMillis(); var currentTime = System.currentTimeMillis();
ItemStack stack = player.getMainHandItem(); ItemStack stack = player.getMainHandItem();
if ((stack.getItem() instanceof AmmoSupplierItem || stack.getItem() == ModItems.AMMO_BOX.get()) && !(player.getVehicle() instanceof ArmedVehicleEntity vehicle && vehicle.banHand(player))) { if ((stack.getItem() instanceof AmmoSupplierItem || stack.getItem() == ModItems.AMMO_BOX.get())
&& !(player.getVehicle() instanceof ArmedVehicleEntity vehicle && vehicle.banHand(player))
) {
// 刚拿出弹药物品时视为开始弹药信息渲染 // 刚拿出弹药物品时视为开始弹药信息渲染
startRenderingAmmoInfo = ammoInfoTimer.getProgress(currentTime) == 0; startRenderingAmmoInfo = ammoInfoTimer.getProgress(currentTime) == 0;
ammoInfoTimer.forward(currentTime); ammoInfoTimer.forward(currentTime);
if (stack.getItem() == ModItems.AMMO_BOX.get()) {
isAmmoBox = true;
ammoBoxTimer.forward(currentTime);
} else {
ammoBoxTimer.backward(currentTime);
}
} else { } else {
ammoInfoTimer.backward(currentTime); ammoInfoTimer.backward(currentTime);
ammoBoxTimer.backward(currentTime);
} }
if (!ammoInfoTimer.isForward() && ammoInfoTimer.finished(currentTime)) return; if (!ammoInfoTimer.isForward() && ammoInfoTimer.finished(currentTime)) return;
var poseStack = event.getGuiGraphics().pose(); var poseStack = event.getGuiGraphics().pose();
poseStack.pushPose(); poseStack.pushPose();
var xOffset = -Mth.lerp(ammoInfoTimer.getProgress(currentTime), 0, 120);
final int fontHeight = 15;
var yOffset = -AmmoType.values().length * fontHeight;
// 渲染总弹药数量
var cap = player.getCapability(ModVariables.PLAYER_VARIABLES_CAPABILITY, null).orElse(new ModVariables.PlayerVariables());
int w = event.getWindow().getGuiScaledWidth(); int w = event.getWindow().getGuiScaledWidth();
int h = event.getWindow().getGuiScaledHeight(); int h = event.getWindow().getGuiScaledHeight();
// 总体透明度设置 var ammoX = ammoInfoTimer.lerp(w + 120, (float) w / 2 + 40, currentTime);
RenderSystem.setShaderColor(1, 1, 1, Mth.lerp(ammoInfoTimer.getProgress(currentTime), 0, 1)); final int fontHeight = 15;
var yOffset = (-h - AmmoType.values().length * fontHeight) / 2f;
// 渲染总弹药数量
var cap = player.getCapability(ModVariables.PLAYER_VARIABLES_CAPABILITY, null).orElse(new ModVariables.PlayerVariables());
var font = Minecraft.getInstance().font; var font = Minecraft.getInstance().font;
for (var type : AmmoType.values()) { for (var type : AmmoType.values()) {
var index = type.ordinal(); var index = type.ordinal();
var ammoCount = type.get(cap); var ammoCount = type.get(cap);
var holder = ammoCountHolders[index]; var animator = ammoCountAnimators[index];
var timer = ammoCountTimers[index];
var boxAnimator = ammoBoxAnimators[index];
var boxAmmoCount = boxAnimator.newValue();
boolean boxAmmoSelected = false;
if (isAmmoBox) {
var ammoBoxType = stack.getOrCreateTag().getString("Type");
boxAmmoCount = type.get(stack);
if (ammoBoxType.equals("All") || ammoBoxType.equals(type.name)) {
boxAnimator.forward(currentTime);
boxAmmoSelected = true;
} else {
boxAnimator.reset(boxAmmoCount);
}
}
// 首次开始渲染弹药信息时记录弹药数量便于后续播放动画 // 首次开始渲染弹药信息时记录弹药数量便于后续播放动画
if (startRenderingAmmoInfo) { if (startRenderingAmmoInfo) {
holder.reset(ammoCount); animator.reset(ammoCount);
timer.endForward(currentTime); animator.endForward(currentTime);
if (isAmmoBox) {
boxAnimator.reset(type.get(stack));
boxAnimator.endForward(currentTime);
}
} }
int isAdd = ammoCount == holder.oldValue() ? 0 : (ammoCount > holder.oldValue() ? 1 : -1); int ammoAdd = Integer.compare(ammoCount, animator.oldValue());
// 弹药数量变化时更新并开始播放弹药数量更改动画
animator.compareAndUpdate(ammoCount, () -> {
// 弹药数量变化时开始播放弹药数量更改动画 // 弹药数量变化时开始播放弹药数量更改动画
if (holder.newValue() != ammoCount) { animator.beginForward(currentTime);
// 更新初始和当前弹药数量播放由 初始弹药数量 -> 当前弹药数量 的动画 });
holder.update(ammoCount);
// 开始播放弹药数量更改动画
timer.beginForward(currentTime);
}
var progress = timer.getProgress(currentTime); var progress = animator.getProgress(currentTime);
var ammoCountStr = Integer.toString( var ammoCountStr = Integer.toString(
Math.round(Mth.lerp(progress, holder.oldValue(), ammoCount)) Math.round(animator.lerp(animator.oldValue(), ammoCount, currentTime))
); );
// 弹药增加时颜色由绿变白否则由红变白 // 弹药增加时颜色由绿变白否则由红变白
var fontColor = switch (isAdd) { var fontColor = FastColor.ARGB32.lerp(progress, switch (ammoAdd) {
case 1 -> Mth.color( case 1 -> 0xFF00FF00;
Mth.lerp(progress, 0, 1), case -1 -> 0xFFFF0000;
1, default -> 0xFFFFFFFF;
Mth.lerp(progress, 0, 1) }, 0xFFFFFFFF);
);
case -1 -> Mth.color( RenderSystem.setShaderColor(1, 1, 1, ammoInfoTimer.lerp(0, 1, currentTime));
1,
Mth.lerp(progress, 0, 1),
Mth.lerp(progress, 0, 1)
);
default -> 0xFFFFFF;
};
// 弹药数量 // 弹药数量
event.getGuiGraphics().drawString( event.getGuiGraphics().drawString(
Minecraft.getInstance().font, font,
ammoCountStr, ammoCountStr,
w + xOffset + (30 - font.width(ammoCountStr)), ammoX + (30 - font.width(ammoCountStr)),
h + yOffset, h + yOffset,
fontColor, fontColor,
true true
@ -310,14 +337,45 @@ public class AmmoBarOverlay {
// 弹药类型 // 弹药类型
event.getGuiGraphics().drawString( event.getGuiGraphics().drawString(
Minecraft.getInstance().font, font,
Component.translatable(type.translatableKey).getString(), Component.translatable(type.translatableKey).getString(),
w + xOffset + 35, ammoX + 35,
h + yOffset, h + yOffset,
fontColor, fontColor,
true true
); );
// 弹药盒信息渲染
RenderSystem.setShaderColor(1, 1, 1, ammoBoxTimer.lerp(0, 1, currentTime));
var ammoBoxX = ammoBoxTimer.lerp(-30, (float) w / 2, currentTime);
int ammoBoxAdd = Integer.compare(boxAmmoCount, boxAnimator.oldValue());
boxAnimator.compareAndUpdate(boxAmmoCount, () -> boxAnimator.beginForward(currentTime));
// 选中时显示为黄色否则为白色
var targetColor = boxAmmoSelected ? 0xFFFFFF00 : 0xFFFFFFFF;
var boxFontColor = FastColor.ARGB32.lerp(boxAnimator.getProgress(currentTime),
switch (ammoBoxAdd) {
case 1 -> 0xFF00FF00;
case -1 -> 0xFFFF0000;
default -> targetColor;
},
targetColor
);
// 弹药盒内弹药数量
event.getGuiGraphics().drawString(
Minecraft.getInstance().font,
Integer.toString(
Math.round(boxAnimator.lerp(boxAnimator.oldValue(), boxAmmoCount, currentTime))
),
ammoBoxX - 70,
h + yOffset,
boxFontColor,
true
);
yOffset += fontHeight; yOffset += fontHeight;
} }

View file

@ -68,9 +68,9 @@ public class VehicleHudOverlay {
public static final int ANIMATION_TIME = 300; public static final int ANIMATION_TIME = 300;
private static final AnimationTimer[] weaponSlotsTimer = AnimationTimer.createTimers(9, ANIMATION_TIME, AnimationCurves.EASE_OUT_CIRC); private static final AnimationTimer[] weaponSlotsTimer = AnimationTimer.createTimers(9, ANIMATION_TIME, AnimationCurves.EASE_OUT_CIRC);
private static boolean lastTimeRenderingWeapons = false; private static boolean wasRenderingWeapons = false;
private static int lastTimeWeaponIndex = 0; private static int oldWeaponIndex = 0;
private static int lastTimeRenderWeaponIndex = 0; private static int oldRenderWeaponIndex = 0;
private static final AnimationTimer weaponIndexUpdateTimer = new AnimationTimer(ANIMATION_TIME).animation(AnimationCurves.EASE_OUT_CIRC); private static final AnimationTimer weaponIndexUpdateTimer = new AnimationTimer(ANIMATION_TIME).animation(AnimationCurves.EASE_OUT_CIRC);
@ -81,7 +81,7 @@ public class VehicleHudOverlay {
Player player = Minecraft.getInstance().player; Player player = Minecraft.getInstance().player;
if (!shouldRenderHud(player)) { if (!shouldRenderHud(player)) {
lastTimeRenderingWeapons = false; wasRenderingWeapons = false;
return; return;
} }
@ -456,8 +456,8 @@ public class VehicleHudOverlay {
private static void renderWeaponInfo(GuiGraphics guiGraphics, VehicleEntity vehicle, int w, int h) { private static void renderWeaponInfo(GuiGraphics guiGraphics, VehicleEntity vehicle, int w, int h) {
if (!(vehicle instanceof WeaponVehicleEntity weaponVehicle)) return; if (!(vehicle instanceof WeaponVehicleEntity weaponVehicle)) return;
var temp = lastTimeRenderingWeapons; var temp = wasRenderingWeapons;
lastTimeRenderingWeapons = false; wasRenderingWeapons = false;
Player player = Minecraft.getInstance().player; Player player = Minecraft.getInstance().player;
assert player != null; assert player != null;
@ -471,37 +471,33 @@ public class VehicleHudOverlay {
int weaponIndex = weaponVehicle.getWeaponIndex(index); int weaponIndex = weaponVehicle.getWeaponIndex(index);
if (weaponIndex == -1) return; if (weaponIndex == -1) return;
lastTimeRenderingWeapons = temp; wasRenderingWeapons = temp;
var currentTime = System.currentTimeMillis(); var currentTime = System.currentTimeMillis();
// 若上一帧未在渲染武器信息则初始化动画相关变量 // 若上一帧未在渲染武器信息则初始化动画相关变量
if (!lastTimeRenderingWeapons) { if (!wasRenderingWeapons) {
weaponSlotsTimer[weaponIndex].begin(); weaponSlotsTimer[weaponIndex].beginForward(currentTime);
weaponSlotsTimer[weaponIndex].forward(currentTime);
if (lastTimeWeaponIndex != weaponIndex) { if (oldWeaponIndex != weaponIndex) {
weaponSlotsTimer[lastTimeWeaponIndex].backward(currentTime); weaponSlotsTimer[oldWeaponIndex].endBackward(currentTime);
weaponSlotsTimer[lastTimeWeaponIndex].end();
lastTimeWeaponIndex = weaponIndex; oldWeaponIndex = weaponIndex;
lastTimeRenderWeaponIndex = weaponIndex; oldRenderWeaponIndex = weaponIndex;
} }
weaponIndexUpdateTimer.begin(); weaponIndexUpdateTimer.beginForward(currentTime);
weaponIndexUpdateTimer.forward(currentTime);
} }
// 切换武器时更新上一个武器槽位和当前武器槽位的动画信息 // 切换武器时更新上一个武器槽位和当前武器槽位的动画信息
if (weaponIndex != lastTimeWeaponIndex) { if (weaponIndex != oldWeaponIndex) {
weaponSlotsTimer[weaponIndex].forward(currentTime); weaponSlotsTimer[weaponIndex].forward(currentTime);
weaponSlotsTimer[lastTimeWeaponIndex].backward(currentTime); weaponSlotsTimer[oldWeaponIndex].backward(currentTime);
lastTimeRenderWeaponIndex = lastTimeWeaponIndex; oldRenderWeaponIndex = oldWeaponIndex;
lastTimeWeaponIndex = weaponIndex; oldWeaponIndex = weaponIndex;
weaponIndexUpdateTimer.begin(); weaponIndexUpdateTimer.beginForward(currentTime);
weaponIndexUpdateTimer.forward(currentTime);
} }
var pose = guiGraphics.pose(); var pose = guiGraphics.pose();
@ -537,7 +533,7 @@ public class VehicleHudOverlay {
// 当前选中武器 // 当前选中武器
if (weaponIndex == i) { if (weaponIndex == i) {
var startY = Mth.lerp(progress, var startY = Mth.lerp(progress,
h - (weapons.size() - 1 - lastTimeRenderWeaponIndex) * 18 - 16, h - (weapons.size() - 1 - oldRenderWeaponIndex) * 18 - 16,
h - (weapons.size() - 1 - weaponIndex) * 18 - 16 h - (weapons.size() - 1 - weaponIndex) * 18 - 16
); );
@ -562,10 +558,10 @@ public class VehicleHudOverlay {
pose.popPose(); pose.popPose();
// 切换武器光标动画播放结束后更新上次选择槽位 // 切换武器光标动画播放结束后更新上次选择槽位
if (lastTimeWeaponIndex != lastTimeRenderWeaponIndex && weaponIndexUpdateTimer.finished(currentTime)) { if (oldWeaponIndex != oldRenderWeaponIndex && weaponIndexUpdateTimer.finished(currentTime)) {
lastTimeRenderWeaponIndex = lastTimeWeaponIndex; oldRenderWeaponIndex = oldWeaponIndex;
} }
lastTimeRenderingWeapons = true; wasRenderingWeapons = true;
} }
private static void renderNumber(GuiGraphics guiGraphics, int number, boolean percent, float x, float y, float scale) { private static void renderNumber(GuiGraphics guiGraphics, int number, boolean percent, float x, float y, float scale) {

View file

@ -55,6 +55,6 @@ public class AmmoSupplierItem extends Item {
player.displayClientMessage(Component.translatable("item.superbwarfare.ammo_supplier.supply", Component.translatable(this.type.translatableKey), ammoToAdd * count), true); player.displayClientMessage(Component.translatable("item.superbwarfare.ammo_supplier.supply", Component.translatable(this.type.translatableKey), ammoToAdd * count), true);
level.playSound(null, player.blockPosition(), ModSounds.BULLET_SUPPLY.get(), SoundSource.PLAYERS, 1, 1); level.playSound(null, player.blockPosition(), ModSounds.BULLET_SUPPLY.get(), SoundSource.PLAYERS, 1, 1);
} }
return InteractionResultHolder.consume(stack); return InteractionResultHolder.success(stack);
} }
} }

View file

@ -151,7 +151,7 @@ public class AnimationTimer {
if (!initialized) { if (!initialized) {
initialized = true; initialized = true;
startTime = currentTime + (isStart ? 0 : duration); startTime = currentTime + (isStart ? 0 : duration);
} else { } else if (reversed) {
startTime = currentTime - getElapsedTime(currentTime); startTime = currentTime - getElapsedTime(currentTime);
} }
reversed = false; reversed = false;
@ -181,7 +181,7 @@ public class AnimationTimer {
if (!initialized) { if (!initialized) {
initialized = true; initialized = true;
startTime = currentTime + (isStart ? duration : 0); startTime = currentTime + (isStart ? duration : 0);
} else { } else if (!reversed) {
startTime = currentTime + getElapsedTime(currentTime); startTime = currentTime + getElapsedTime(currentTime);
} }
reversed = true; reversed = true;
@ -203,4 +203,8 @@ public class AnimationTimer {
backward(currentTime); backward(currentTime);
} }
public float lerp(float start, float end, long currentTime) {
return Mth.lerp(getProgress(currentTime), start, end);
}
} }

View file

@ -1,41 +0,0 @@
package com.atsuishio.superbwarfare.tools.animation;
public class DualValueHolder<T> {
private T oldValue;
private T newValue;
public static <T> DualValueHolder<T>[] create(int size, T defaultValue) {
// 傻逼Java
@SuppressWarnings("unchecked")
DualValueHolder<T>[] holders = new DualValueHolder[size];
for (int i = 0; i < size; i++) {
holders[i] = new DualValueHolder<>();
holders[i].reset(defaultValue);
}
return holders;
}
public void update(T value) {
this.oldValue = this.newValue;
this.newValue = value;
}
public void reset(T value) {
this.oldValue = value;
this.newValue = value;
}
public T oldValue() {
return this.oldValue;
}
public T newValue() {
return this.newValue;
}
public boolean changed() {
return this.oldValue.equals(this.newValue);
}
}

View file

@ -0,0 +1,72 @@
package com.atsuishio.superbwarfare.tools.animation;
import javax.annotation.Nullable;
/**
* 可以存储额外的新旧数值的AnimationTimer
*/
public class ValueAnimator<T> extends AnimationTimer {
private T oldValue;
private T newValue;
public ValueAnimator(long duration, T defaultValue) {
super(duration);
reset(defaultValue);
}
public static <T> ValueAnimator<T>[] create(int size, long duration, T defaultValue) {
// 傻逼Java
@SuppressWarnings("unchecked")
ValueAnimator<T>[] animators = (ValueAnimator<T>[]) new ValueAnimator[size];
for (int i = 0; i < size; i++) {
animators[i] = new ValueAnimator<>(duration, defaultValue);
}
return animators;
}
public void update(T value) {
this.oldValue = this.newValue;
this.newValue = value;
}
/**
* 比较当前值和新值如果不同则更新
*
* @param value 当前值
*/
public void compareAndUpdate(T value) {
compareAndUpdate(value, null);
}
/**
* 比较当前值和新值如果不同则更新
*
* @param value 当前值
* @param callback 更新成功后的回调函数
*/
public void compareAndUpdate(T value, @Nullable Runnable callback) {
if (!this.newValue.equals(value)) {
update(value);
if (callback != null) {
callback.run();
}
}
}
public void reset(T value) {
this.oldValue = value;
this.newValue = value;
}
public T oldValue() {
return this.oldValue;
}
public T newValue() {
return this.newValue;
}
}