- 205
- 12
- 103
Всем привет. За последнее время у меня всё чаще начинает назревать вопрос о правильной структуризации своего проекта. Расскажу небольшую предысторию на примере реальной проблемы:
Я занимаюсь портированием контента игры Terraria на кубач версии 1.15.2. Цель довольно абстрактная и глупая, согласен, но я занимаюсь этим не для чего-либо/кого-либо, а просто для практики и понимания. Сейчас у меня возникает такая проблема: наплодилось достаточно много классов с примерно похожим содержанием, и, ладно, если бы они были по 40 строк, но тут их обычно около 200. Допустим, недавно я портировал все виды бумерангов в игру. На каждый такой бумеранг нужно по 3 класса: предмет, энтить, рендер. Предмет строк на 50, в нем контент зачастую одинаковый, просто указывается какая энтить будет бросаться и какой размер стака броска. Энтить обычно на ~200 строк, и тут уже все сложнее. Отличия, в основном, заключаются в следующем: какие партиклы, какой звук, есть ли шанс на доп.дроп или какие-либо бонусы, какой урон и эффекты, реже - особая физика и всякое такое. И я не знаю, правильно ли, что у меня так много классов с примерно похожим контентом под это дело. Можно и нужно ли это как-то стандартизировать. если нужно, то как лучше?
В качестве примера прилагаю один такой класс:
Очень надеюсь на советы по этому поводу. Спасибо.
Я занимаюсь портированием контента игры Terraria на кубач версии 1.15.2. Цель довольно абстрактная и глупая, согласен, но я занимаюсь этим не для чего-либо/кого-либо, а просто для практики и понимания. Сейчас у меня возникает такая проблема: наплодилось достаточно много классов с примерно похожим содержанием, и, ладно, если бы они были по 40 строк, но тут их обычно около 200. Допустим, недавно я портировал все виды бумерангов в игру. На каждый такой бумеранг нужно по 3 класса: предмет, энтить, рендер. Предмет строк на 50, в нем контент зачастую одинаковый, просто указывается какая энтить будет бросаться и какой размер стака броска. Энтить обычно на ~200 строк, и тут уже все сложнее. Отличия, в основном, заключаются в следующем: какие партиклы, какой звук, есть ли шанс на доп.дроп или какие-либо бонусы, какой урон и эффекты, реже - особая физика и всякое такое. И я не знаю, правильно ли, что у меня так много классов с примерно похожим контентом под это дело. Можно и нужно ли это как-то стандартизировать. если нужно, то как лучше?
В качестве примера прилагаю один такой класс:
Java:
public class LightDiscEntity extends ProjectileItemEntity {
private int TICKS_UNTIL_RETURN = 25;
private static final int TICKS_UNTIL_DEATH = 300;
private static final double SPEED = 1.0;
private static final int MAX_BOUNCES = 2;
private static final DataParameter<Byte> IS_RETURNING = EntityDataManager.createKey(LightDiscEntity.class, DataSerializers.BYTE);
private static DataParameter<Integer> BOUNCES = EntityDataManager.createKey(LightDiscEntity.class, DataSerializers.VARINT);
private static final DataParameter<String> OWNER = EntityDataManager.createKey(LightDiscEntity.class, DataSerializers.STRING);
private static boolean SUCCESSFULLY_BOUNCED = false;
private ItemStack lightDiscThrown = ItemStack.EMPTY;
private PlayerEntity targetEntity;
public LightDiscEntity(EntityType<? extends ProjectileItemEntity> type, World worldIn) {
super(type, worldIn);
}
public LightDiscEntity(World worldIn, LivingEntity throwerIn) {
super(ProjectileEntities.Entities.light_disk, throwerIn, worldIn);
}
@Override
protected void registerData() {
super.registerData();
this.dataManager.register(IS_RETURNING, (byte) 0);
this.dataManager.register(BOUNCES, 0);
this.dataManager.register(OWNER, "");
}
public void setLightDiscEntityThrown(ItemStack lightDiscThrown) {
this.lightDiscThrown = lightDiscThrown;
}
@Override
public void writeAdditional(CompoundNBT tag) {
tag.putString("owner", dataManager.get(OWNER));
tag.putByte("returning", dataManager.get(IS_RETURNING));
tag.putInt("bounces", dataManager.get(BOUNCES));
lightDiscThrown.write(tag);
super.writeAdditional(tag);
}
@Override
public void readAdditional(CompoundNBT tag) {
dataManager.set(OWNER, tag.getString("owner"));
dataManager.set(IS_RETURNING, tag.getByte("returning"));
dataManager.set(BOUNCES, tag.getInt("bounces"));
lightDiscThrown = ItemStack.read(tag);
super.readAdditional(tag);
}
public void setOwner(PlayerEntity playerIn) {
this.targetEntity = playerIn;
if (playerIn != null && playerIn.getUniqueID() != null)
dataManager.set(OWNER, playerIn.getUniqueID().toString());
}
private void movementReturnCheck() {
boolean returning = dataManager.get(IS_RETURNING) == 1;
if (returning) {
if (this.targetEntity != null) {
this.moveTowardsTarget();
}
}
}
public static float yawDegreesBetweenPoints(double posX, double posY, double posZ, double posX2, double posY2, double posZ2) {
float f = (float) ((180.0f * Math.atan2(posX2 - posX, posZ2 - posZ)) / (float) Math.PI);
return f;
}
public static float pitchDegreesBetweenPoints(double posX, double posY, double posZ, double posX2, double posY2, double posZ2) {
return (float) Math.toDegrees(Math.atan2(posY2 - posY, Math.sqrt((posX2 - posX) * (posX2 - posX) + (posZ2 - posZ) * (posZ2 - posZ))));
}
public static Vec3d lookVector(float rotYaw, float rotPitch) {
return new Vec3d(
Math.sin(rotYaw) * Math.cos(rotPitch),
Math.sin(rotPitch),
Math.cos(rotYaw) * Math.cos(rotPitch));
}
private void moveTowardsTarget() {
rotationYaw = (float) Math.toRadians(yawDegreesBetweenPoints(getPosX(), getPosY(), getPosZ(), targetEntity.getPosX(), targetEntity.getPosY(), targetEntity.getPosZ()));
rotationPitch = (float) Math.toRadians(pitchDegreesBetweenPoints(getPosX(), getPosY(), getPosZ(), targetEntity.getPosX(), targetEntity.getPosY(), targetEntity.getPosZ()));
Vec3d moveVec = lookVector(this.rotationYaw, this.rotationPitch).scale(SPEED);
this.setMotion(
0.5f * this.getMotion().getX() + 0.5F * moveVec.x,
0.5f * this.getMotion().getY() + 0.5F * moveVec.y,
0.5f * this.getMotion().getZ() + 0.5F * moveVec.z);
}
private void setIsReturning() {
dataManager.set(IS_RETURNING, (byte) 1);
}
@Override
public void tick() {
Vec3d defaultMotion = getMotion();
super.tick();
if (!SUCCESSFULLY_BOUNCED) {
setMotion(defaultMotion);
}
SUCCESSFULLY_BOUNCED = false;
if (this.ticksExisted > TICKS_UNTIL_DEATH) {
this.remove();
return;
}
if (this.ticksExisted > TICKS_UNTIL_RETURN) {
setIsReturning();
}
final BlockPos pos = this.getPosition();
if (owner != null && TerraUtils.distanceBetweenHorizontal(pos, this.owner.getPosition()) < 1.0F && this.ticksExisted > 10) {
this.remove();
return;
}
movementReturnCheck();
}
@Override
protected void onImpact(@Nonnull RayTraceResult rayTraceResult) {
if (!world.isRemote) {
switch (rayTraceResult.getType()) {
case BLOCK: {
BlockRayTraceResult blockRayTraceResult = (BlockRayTraceResult) rayTraceResult;
if (world.getBlockState(blockRayTraceResult.getPos()).isSolid()) {
Vec3d currentMovementVec = new Vec3d(getMotion().x, getMotion().y, getMotion().z);
int bounces = getTimesBounced();
if (bounces < MAX_BOUNCES) {
Direction dir = blockRayTraceResult.getFace();
Vec3d normalVector = new Vec3d(-2 * dir.getXOffset(), -2 * dir.getYOffset(), -2 * dir.getZOffset()).normalize();
Vec3d movementVec = normalVector.mul( //vec.w = vec.w * -abs(offs.w), w - component
-2 * currentMovementVec.dotProduct(normalVector),
-2 * currentMovementVec.dotProduct(normalVector),
-2 * currentMovementVec.dotProduct(normalVector))
.add(currentMovementVec);
setMotion(movementVec);
world.playSound((PlayerEntity) null, this.getPosX(), this.getPosY(), this.getPosZ(), ModSounds.DIG, SoundCategory.NEUTRAL, 1.0F, 1.0F / (new Random().nextFloat() * 0.4F + 0.8F));
setTimesBounced(getTimesBounced() + 1);
TICKS_UNTIL_RETURN = 20 + new Random().nextInt(5);
SUCCESSFULLY_BOUNCED = true;
} else {
TICKS_UNTIL_RETURN = 10 + new Random().nextInt(5);
}
} else {
world.destroyBlock(blockRayTraceResult.getPos(), true); //replace with a passable block array instance
}
break;
}
case ENTITY: {
EntityRayTraceResult entityRayTraceResult = (EntityRayTraceResult) rayTraceResult;
if (entityRayTraceResult.getEntity() instanceof LivingEntity && entityRayTraceResult.getEntity() != getThrower()) {
LivingEntity thrower = getThrower();
entityRayTraceResult.getEntity().attackEntityFrom(thrower != null
? thrower instanceof PlayerEntity
? DamageSource.causeThrownDamage(this, thrower)
: DamageSource.causeMobDamage(thrower)
: DamageSource.GENERIC, 57);
}
break;
}
default:
break;
}
}
}
private int getTimesBounced() {
return dataManager.get(BOUNCES);
}
private void setTimesBounced(int times) {
dataManager.set(BOUNCES, times);
}
@Override
protected float getGravityVelocity() {
return 0;
}
@Nonnull
@Override
protected Item getDefaultItem() {
return ThrowableItems.Items.light_disk;
}
@Nonnull
@Override
public IPacket<?> createSpawnPacket() {
return NetworkHooks.getEntitySpawningPacket(this);
}
}
Очень надеюсь на советы по этому поводу. Спасибо.